vlc/modules/gui/qt/maininterface/compositor_x11.cpp

234 lines
7.7 KiB
C++

/*****************************************************************************
* Copyright (C) 2020 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <QX11Info>
#include <QScreen>
#include "compositor_x11.hpp"
#include "compositor_x11_renderwindow.hpp"
#include "compositor_x11_uisurface.hpp"
#include "mainctx.hpp"
#include "interface_window_handler.hpp"
#include "video_window_handler.hpp"
#include "mainui.hpp"
using namespace vlc;
int CompositorX11::windowEnable(const vlc_window_cfg_t *)
{
commonWindowEnable();
m_renderWindow->enableVideoWindow();
return VLC_SUCCESS;
}
void CompositorX11::windowDisable()
{
m_renderWindow->disableVideoWindow();
commonWindowDisable();
}
CompositorX11::CompositorX11(qt_intf_t *p_intf, QObject *parent)
: CompositorVideo(p_intf, parent)
{
}
CompositorX11::~CompositorX11()
{
if (m_conn)
xcb_disconnect(m_conn);
}
bool CompositorX11::preInit(qt_intf_t*)
{
return true;
}
static bool checkExtensionPresent(qt_intf_t* intf, xcb_connection_t *conn, const char* extension)
{
bool ret = queryExtension(conn, extension, nullptr, nullptr);
if (! ret)
msg_Warn(intf, "X11 extension %s is missing", extension);
return ret;
}
#define REGISTER_XCB_EXTENSION(c, extension, minor, major) \
do { \
xcb_ ## extension ##_query_version_cookie_t cookie = xcb_## extension ##_query_version(c, minor, major); \
xcb_generic_error_t* error = nullptr; \
auto reply = wrap_cptr(xcb_## extension ##_query_version_reply(c, cookie, &error)); \
if (error) { \
msg_Warn(m_intf, "X11 extension %s is too old", #extension); \
free(error); \
return false; \
} \
} while(0)
bool CompositorX11::init()
{
if (!QX11Info::isPlatformX11())
{
msg_Info(m_intf, "not running an X11 platform");
return false;
}
//open a separated X11 connection from Qt to be able to receive
//damage events independently from Qt (even when Qt's main loop is stalled)
m_conn = xcb_connect(nullptr, nullptr);
if (xcb_connection_has_error(m_conn) != 0)
{
msg_Warn(m_intf, "can't open X11 connection");
return false;
}
//check X11 extensions
if (!checkExtensionPresent(m_intf, m_conn, "DAMAGE"))
return false;
REGISTER_XCB_EXTENSION(m_conn, damage, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
if (!checkExtensionPresent(m_intf, m_conn, "RENDER"))
return false;
//0.11 required for Blend mode operators
static_assert (XCB_RENDER_MAJOR_VERSION == 0 && XCB_RENDER_MINOR_VERSION >= 11,
"X11 Render version is too old, 0.11+ is required");
REGISTER_XCB_EXTENSION(m_conn, render, XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION);
if (!checkExtensionPresent(m_intf, m_conn, "Composite"))
return false;
//0.2 is required for NameWindowPixmap
static_assert (XCB_COMPOSITE_MAJOR_VERSION == 0 && XCB_COMPOSITE_MINOR_VERSION >= 2,
"X11 Composite version is too old, 0.2+ is required");
REGISTER_XCB_EXTENSION(m_conn, composite, XCB_COMPOSITE_MAJOR_VERSION, XCB_COMPOSITE_MINOR_VERSION);
if (!checkExtensionPresent(m_intf, m_conn, "XFIXES"))
return false;
//2.x is required for SetWindowShapeRegion
static_assert (XCB_XFIXES_MAJOR_VERSION >= 2,
"X11 Fixes version is too old, 2.0+ is required");
REGISTER_XCB_EXTENSION(m_conn, xfixes, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION);
// check whether we're running under "XWAYLAND"
auto screens = qApp->screens();
bool isXWayland = std::any_of(screens.begin(), screens.end(), [](QScreen* screen){
return screen->name().startsWith("XWAYLAND");
});
if (isXWayland)
{
// if the X11 server is XWayland, test if it is a broken version
const xcb_setup_t* setup = xcb_get_setup(m_conn);
if (setup == nullptr)
{
msg_Info(m_intf, "error checking for XWayland version");
return false;
}
if (setup->release_number < 12100000u)
{
msg_Info(m_intf, "running a broken XWayland version, disabling X11 composition");
return false;
}
}
return true;
}
bool CompositorX11::makeMainInterface(MainCtx* mainCtx)
{
m_mainCtx = mainCtx;
bool useCSD = m_mainCtx->useClientSideDecoration();
m_renderWindow = std::make_unique<vlc::CompositorX11RenderWindow>(m_intf, m_conn, useCSD);
if (!m_renderWindow->init())
return false;
m_videoWidget = std::make_unique<DummyNativeWidget>(m_renderWindow.get());
// widget would normally require WindowTransparentForInput, without this
// we end up with an invisible area within our window that grabs our mouse events.
// But using this this causes rendering issues with some VoutDisplay
// (xcb_render for instance) instead, we manually, set a null input region afterwards
// see setTransparentForMouseEvent
m_videoWidget->winId();
//update manually EventMask as we don't use WindowTransparentForInput
const uint32_t mask = XCB_CW_EVENT_MASK;
const uint32_t values = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_FOCUS_CHANGE;
xcb_change_window_attributes(QX11Info::connection(), m_videoWidget->winId(), mask, &values);
setTransparentForMouseEvent(QX11Info::connection(), m_videoWidget->winId());
m_videoWidget->show();
m_interfaceWindow = m_renderWindow->getWindow();
m_qmlView = std::make_unique<CompositorX11UISurface>(m_interfaceWindow);
m_qmlView->setFlag(Qt::WindowType::WindowTransparentForInput);
m_qmlView->setParent(m_interfaceWindow);
m_qmlView->winId();
m_qmlView->show();
CompositorVideo::Flags flags = CompositorVideo::CAN_SHOW_PIP;
if (m_renderWindow->hasAcrylic())
flags |= CompositorVideo::HAS_ACRYLIC;
commonGUICreate(m_interfaceWindow, m_qmlView.get(), flags);
m_renderWindow->setInterfaceWindow(m_qmlView.get());
m_renderWindow->setVideoWindow(m_videoWidget->windowHandle());
m_renderWindow->startRendering();
return true;
}
void CompositorX11::destroyMainInterface()
{
commonIntfDestroy();
m_videoWidget.reset();
m_renderWindow.reset();
}
void CompositorX11::unloadGUI()
{
m_renderWindow->stopRendering();
m_qmlView.reset();
commonGUIDestroy();
}
void CompositorX11::onSurfacePositionChanged(const QPointF& position)
{
m_renderWindow->setVideoPosition(position.toPoint());
}
void CompositorX11::onSurfaceSizeChanged(const QSizeF& size)
{
m_renderWindow->setVideoSize((size / m_videoWidget->window()->devicePixelRatioF()).toSize());
}
bool CompositorX11::setupVoutWindow(vlc_window_t* p_wnd, VoutDestroyCb destroyCb)
{
p_wnd->type = VLC_WINDOW_TYPE_XID;
p_wnd->handle.xid = m_videoWidget->winId();
commonSetupVoutWindow(p_wnd, destroyCb);
return true;
}
QQuickItem * CompositorX11::activeFocusItem() const /* override */
{
return m_qmlView->activeFocusItem();
}