You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
579 lines
17 KiB
579 lines
17 KiB
/*****************************************************************************
|
|
* Copyright (C) 2021 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 <math.h>
|
|
|
|
#include <QtEvents>
|
|
#include <QWindow>
|
|
#include <QX11Info>
|
|
#include <QMainWindow>
|
|
#include <QThread>
|
|
#include <QSocketNotifier>
|
|
|
|
#include <xcb/composite.h>
|
|
|
|
#include "compositor_x11_renderwindow.hpp"
|
|
#include "compositor_x11_renderclient.hpp"
|
|
#include "compositor_x11_uisurface.hpp"
|
|
#include "compositor_x11_utils.hpp"
|
|
|
|
#include <vlc_cxx_helpers.hpp>
|
|
|
|
#include "qt.hpp"
|
|
|
|
//blur behind for KDE
|
|
#define _KDE_NET_WM_BLUR_BEHIND_REGION_NAME "_KDE_NET_WM_BLUR_BEHIND_REGION"
|
|
|
|
using namespace vlc;
|
|
|
|
RenderTask::RenderTask(qt_intf_t *intf, xcb_connection_t* conn, xcb_drawable_t wid,
|
|
QMutex& pictureLock, QObject *parent)
|
|
: QObject(parent)
|
|
, m_intf(intf)
|
|
, m_conn(conn)
|
|
, m_pictureLock(pictureLock)
|
|
, m_drawingarea(m_conn)
|
|
, m_wid(wid)
|
|
{
|
|
assert(conn);
|
|
assert(m_intf);
|
|
assert(m_wid);
|
|
connect(this, &RenderTask::requestRefreshInternal, this, &RenderTask::render);
|
|
}
|
|
|
|
RenderTask::~RenderTask()
|
|
{
|
|
}
|
|
|
|
void RenderTask::render(unsigned int requestId)
|
|
{
|
|
if (requestId != m_refreshRequestId)
|
|
return;
|
|
if (!m_visible)
|
|
return;
|
|
|
|
assert(m_interfaceClient != nullptr);
|
|
|
|
xcb_flush(m_conn);
|
|
xcb_render_picture_t drawingarea = getBackTexture();
|
|
|
|
if (m_hasAcrylic)
|
|
{
|
|
//clear screen
|
|
xcb_render_color_t clear = { 0x0000, 0x0000, 0x0000, 0x0000 };
|
|
xcb_rectangle_t rect = {0, 0, 0xFFFF, 0xFFFF};
|
|
xcb_render_fill_rectangles(m_conn, XCB_RENDER_PICT_OP_SRC, drawingarea,
|
|
clear, 1, &rect);
|
|
}
|
|
|
|
|
|
{
|
|
QMutexLocker lock(&m_pictureLock);
|
|
if (m_videoEmbed)
|
|
{
|
|
assert(m_videoClient);
|
|
xcb_render_picture_t pic = m_videoClient->getPicture();
|
|
if (pic)
|
|
{
|
|
xcb_render_composite(m_conn, XCB_RENDER_PICT_OP_SRC,
|
|
pic, 0, drawingarea,
|
|
0,0,0,0,
|
|
m_videoPosition.x(),m_videoPosition.y(),
|
|
m_videoPosition.width(), m_videoPosition.height());
|
|
}
|
|
}
|
|
|
|
xcb_render_picture_t pic = m_interfaceClient->getPicture();
|
|
if (pic)
|
|
{
|
|
xcb_render_composite(m_conn, XCB_RENDER_PICT_OP_OVER,
|
|
pic, 0, drawingarea,
|
|
0,0,0,0,
|
|
0, 0, m_interfaceSize.width(), m_interfaceSize.height());
|
|
}
|
|
|
|
} //picture lock scope
|
|
|
|
xcb_clear_area(m_conn, 0, m_wid,
|
|
0, 0, 0, 0);
|
|
|
|
m_refreshRequestId++;
|
|
}
|
|
|
|
void RenderTask::onWindowSizeChanged(const QSize& newSize)
|
|
{
|
|
if (m_renderSize.isValid()
|
|
&& newSize.width() <= m_renderSize.width()
|
|
&& newSize.height() <= m_renderSize.height())
|
|
return;
|
|
|
|
if (!m_renderSize.isValid())
|
|
{
|
|
m_renderSize = newSize;
|
|
}
|
|
else
|
|
{
|
|
m_renderSize.setWidth(vlc_align(newSize.width(), 128));
|
|
m_renderSize.setHeight(vlc_align(newSize.height(), 128));
|
|
}
|
|
m_resizeRequested = true;
|
|
}
|
|
|
|
void RenderTask::requestRefresh()
|
|
{
|
|
emit requestRefreshInternal(m_refreshRequestId, {});
|
|
}
|
|
|
|
void RenderTask::onInterfaceSurfaceChanged(CompositorX11RenderClient* surface)
|
|
{
|
|
m_interfaceClient = surface;
|
|
}
|
|
|
|
void RenderTask::onVideoSurfaceChanged(CompositorX11RenderClient* surface)
|
|
{
|
|
m_videoClient = surface;
|
|
}
|
|
|
|
void RenderTask::onRegisterVideoWindow(unsigned int surface)
|
|
{
|
|
m_videoEmbed = (surface != 0);
|
|
}
|
|
|
|
void RenderTask::onVideoPositionChanged(const QRect& position)
|
|
{
|
|
if (m_videoPosition == position)
|
|
return;
|
|
m_videoPosition = position;
|
|
emit requestRefreshInternal(m_refreshRequestId, {});
|
|
}
|
|
|
|
void RenderTask::onInterfaceSizeChanged(const QSize& size)
|
|
{
|
|
if (m_interfaceSize == size)
|
|
return;
|
|
m_interfaceSize = size;
|
|
}
|
|
|
|
void RenderTask::onVisibilityChanged(bool visible)
|
|
{
|
|
m_visible = visible;
|
|
}
|
|
|
|
void RenderTask::onAcrylicChanged(bool enabled)
|
|
{
|
|
m_hasAcrylic = enabled;
|
|
}
|
|
|
|
xcb_render_picture_t RenderTask::getBackTexture()
|
|
{
|
|
if (m_drawingarea && !m_resizeRequested)
|
|
return m_drawingarea.get();
|
|
|
|
xcb_void_cookie_t voidCookie;
|
|
auto err = wrap_cptr<xcb_generic_error_t>(nullptr);
|
|
xcb_generic_error_t* rawerror = NULL;
|
|
|
|
xcb_get_window_attributes_cookie_t attrCookie = xcb_get_window_attributes(m_conn, m_wid);
|
|
|
|
auto attrReply = wrap_cptr(xcb_get_window_attributes_reply(m_conn, attrCookie, &rawerror));
|
|
if (rawerror)
|
|
{
|
|
msg_Warn(m_intf, " error: getting window attributes xcb_get_window_attributes_reply %u", rawerror->error_code);
|
|
free(rawerror);
|
|
return 0;
|
|
}
|
|
|
|
xcb_visualid_t visual = attrReply->visual;
|
|
|
|
uint8_t depth;
|
|
xcb_render_pictformat_t fmt;
|
|
findVisualFormat(m_conn, visual, &fmt, &depth);
|
|
|
|
PixmapPtr background{ m_conn};
|
|
background.generateId();
|
|
voidCookie = xcb_create_pixmap_checked(m_conn, depth, background.get(), m_wid, m_renderSize.width(), m_renderSize.height());
|
|
err.reset(xcb_request_check(m_conn, voidCookie));
|
|
if (err)
|
|
{
|
|
msg_Warn(m_intf, " error: creating xcb_create_pixmap %u", err->error_code);
|
|
return 0;
|
|
}
|
|
|
|
uint32_t attributeList[] = {background.get()};
|
|
voidCookie = xcb_change_window_attributes_checked(m_conn, m_wid, XCB_CW_BACK_PIXMAP, attributeList);
|
|
err.reset(xcb_request_check(m_conn, voidCookie));
|
|
if (err)
|
|
{
|
|
msg_Warn(m_intf, " error: xcb_change_window_attributes_checked %u", err->error_code);
|
|
return 0;
|
|
}
|
|
|
|
m_drawingarea.generateId();
|
|
xcb_render_create_picture_checked(m_conn, m_drawingarea.get(), background.get(), fmt, 0, nullptr);
|
|
err.reset(xcb_request_check(m_conn, voidCookie));
|
|
if (err)
|
|
{
|
|
msg_Warn(m_intf, " error: xcb_change_window_attributes_checked %u", err->error_code);
|
|
return 0;
|
|
}
|
|
|
|
m_resizeRequested = false;
|
|
return m_drawingarea.get();
|
|
}
|
|
|
|
//X11 damage listenner
|
|
|
|
X11DamageObserver::X11DamageObserver(qt_intf_t* intf, xcb_connection_t* conn, QObject* parent)
|
|
: QObject(parent)
|
|
, m_intf(intf)
|
|
, m_conn(conn)
|
|
{
|
|
}
|
|
|
|
bool X11DamageObserver::init()
|
|
{
|
|
const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_conn, &xcb_damage_id);
|
|
if (!reply || !reply->present)
|
|
return false;
|
|
m_xdamageBaseEvent = reply->first_event;
|
|
m_connFd = xcb_get_file_descriptor(m_conn);
|
|
return true;
|
|
}
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
//can't use QOverload with private signals
|
|
template<class T>
|
|
static auto privateOverload(void (QSocketNotifier::* s)( QSocketDescriptor,QSocketNotifier::Type, T) )
|
|
{
|
|
return s;
|
|
}
|
|
#endif
|
|
|
|
void X11DamageObserver::start()
|
|
{
|
|
//listen to the x11 socket instead of blocking
|
|
m_socketNotifier = new QSocketNotifier(m_connFd, QSocketNotifier::Read, this);
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
connect(m_socketNotifier, privateOverload(&QSocketNotifier::activated),
|
|
this, &X11DamageObserver::onEvent);
|
|
#else
|
|
connect(m_socketNotifier, &QSocketNotifier::activated, this, &X11DamageObserver::onEvent);
|
|
#endif
|
|
}
|
|
|
|
bool X11DamageObserver::onRegisterSurfaceDamage(unsigned int wid)
|
|
{
|
|
if (m_dammage != 0)
|
|
{
|
|
xcb_damage_destroy(m_conn, m_dammage);
|
|
m_dammage = 0;
|
|
}
|
|
if (wid != 0)
|
|
{
|
|
m_dammage = xcb_generate_id(m_conn);
|
|
xcb_void_cookie_t cookie = xcb_damage_create_checked(m_conn, m_dammage, wid, XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES);
|
|
auto err = wrap_cptr(xcb_request_check(m_conn, cookie));
|
|
if (err)
|
|
{
|
|
msg_Warn(m_intf, "error while registering damage on surface");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void X11DamageObserver::onEvent()
|
|
{
|
|
bool isRefreshNeeded = false;
|
|
auto event = wrap_cptr<xcb_generic_event_t>(nullptr);
|
|
while ((event = wrap_cptr(xcb_poll_for_event(m_conn))) != nullptr)
|
|
{
|
|
if (event->response_type == m_xdamageBaseEvent + XCB_DAMAGE_NOTIFY)
|
|
{
|
|
xcb_damage_notify_event_t* damageEvent = reinterpret_cast<xcb_damage_notify_event_t*>(event.get());
|
|
if (damageEvent->damage != m_dammage)
|
|
continue;
|
|
isRefreshNeeded = true;
|
|
}
|
|
}
|
|
|
|
if (isRefreshNeeded)
|
|
emit needRefresh();
|
|
}
|
|
|
|
//// CompositorX11RenderWindow
|
|
|
|
CompositorX11RenderWindow::CompositorX11RenderWindow(qt_intf_t* p_intf, xcb_connection_t* conn, bool useCDS, QObject* parent)
|
|
: QObject(parent)
|
|
, m_intf(p_intf)
|
|
, m_conn(conn)
|
|
{
|
|
m_rootWidget = new QMainWindow();
|
|
m_rootWidget->setAttribute(Qt::WA_NativeWindow);
|
|
m_rootWidget->setAttribute(Qt::WA_OpaquePaintEvent);
|
|
m_rootWidget->setAttribute(Qt::WA_NoSystemBackground);
|
|
m_rootWidget->setAttribute(Qt::WA_TranslucentBackground);
|
|
m_rootWidget->setAttribute(Qt::WA_MouseTracking);
|
|
|
|
if (useCDS)
|
|
m_rootWidget->setWindowFlag(Qt::FramelessWindowHint);
|
|
|
|
m_stable = new DummyNativeWidget(m_rootWidget);
|
|
m_stable->winId();
|
|
|
|
m_rootWidget->setCentralWidget(m_stable);
|
|
|
|
m_rootWidget->winId();
|
|
m_rootWidget->show();
|
|
|
|
m_window = m_rootWidget->window()->windowHandle();
|
|
m_wid = m_window->winId();
|
|
}
|
|
|
|
CompositorX11RenderWindow::~CompositorX11RenderWindow()
|
|
{
|
|
stopRendering();
|
|
|
|
if (m_rootWidget)
|
|
{
|
|
m_rootWidget->removeEventFilter(this);
|
|
m_window->removeEventFilter(this);
|
|
|
|
if (m_rootWidget)
|
|
delete m_rootWidget;
|
|
}
|
|
}
|
|
|
|
bool CompositorX11RenderWindow::init()
|
|
{
|
|
m_damageObserver = new X11DamageObserver(m_intf, m_conn);
|
|
bool ret = m_damageObserver->init();
|
|
if (!ret)
|
|
{
|
|
delete m_damageObserver;
|
|
m_damageObserver = nullptr;
|
|
msg_Warn(m_intf, "can't initialize X11 damage");
|
|
return false;
|
|
}
|
|
|
|
xcb_connection_t* qtConn = QX11Info::connection();
|
|
|
|
//check if KDE "acrylic" effect is available
|
|
xcb_atom_t blurBehindAtom = getInternAtom(qtConn, _KDE_NET_WM_BLUR_BEHIND_REGION_NAME);
|
|
if (blurBehindAtom != XCB_ATOM_NONE)
|
|
{
|
|
uint32_t val = 0;
|
|
xcb_change_property(qtConn, XCB_PROP_MODE_REPLACE, m_wid,
|
|
blurBehindAtom, XCB_ATOM_CARDINAL, 32, 1, &val);
|
|
m_hasAcrylic = true;
|
|
}
|
|
|
|
//install event filters
|
|
m_rootWidget->installEventFilter(this);
|
|
m_window->installEventFilter(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CompositorX11RenderWindow::startRendering()
|
|
{
|
|
assert(m_interfaceWindow);
|
|
|
|
//Rendering thread
|
|
m_renderTask = new RenderTask(m_intf, m_conn, m_stable->effectiveWinId(), m_pictureLock);
|
|
m_renderThread = new QThread(this);
|
|
|
|
m_renderTask->moveToThread(m_renderThread);
|
|
connect(m_renderThread, &QThread::finished, m_renderTask, &QObject::deleteLater);
|
|
|
|
connect(m_interfaceWindow, &CompositorX11UISurface::afterRendering, m_renderTask, &RenderTask::requestRefresh);
|
|
connect(m_interfaceWindow, &CompositorX11UISurface::sizeChanged, m_renderTask, &RenderTask::onInterfaceSizeChanged);
|
|
|
|
connect(this, &CompositorX11RenderWindow::windowSizeChanged, m_renderTask, &RenderTask::onWindowSizeChanged);
|
|
connect(this, &CompositorX11RenderWindow::requestUIRefresh, m_renderTask, &RenderTask::requestRefresh);
|
|
connect(this, &CompositorX11RenderWindow::visiblityChanged, m_renderTask, &RenderTask::onVisibilityChanged);
|
|
connect(this, &CompositorX11RenderWindow::videoPositionChanged, m_renderTask, &RenderTask::onVideoPositionChanged);
|
|
connect(this, &CompositorX11RenderWindow::registerVideoWindow, m_renderTask, &RenderTask::onRegisterVideoWindow);
|
|
connect(this, &CompositorX11RenderWindow::videoSurfaceChanged, m_renderTask, &RenderTask::onVideoSurfaceChanged, Qt::BlockingQueuedConnection);
|
|
|
|
//pass initial values
|
|
m_renderTask->onInterfaceSurfaceChanged(m_interfaceClient.get());
|
|
m_renderTask->onVideoSurfaceChanged(m_videoClient.get());
|
|
m_renderTask->onWindowSizeChanged(m_rootWidget->size() * m_rootWidget->devicePixelRatioF());
|
|
m_renderTask->onAcrylicChanged(m_hasAcrylic);
|
|
|
|
//use the same thread as the rendering thread, neither tasks are blocking.
|
|
m_damageObserver->moveToThread(m_renderThread);
|
|
connect(m_renderThread, &QThread::started, m_damageObserver, &X11DamageObserver::start);
|
|
connect(this, &CompositorX11RenderWindow::registerVideoWindow, m_damageObserver, &X11DamageObserver::onRegisterSurfaceDamage);
|
|
connect(m_damageObserver, &X11DamageObserver::needRefresh, m_renderTask, &RenderTask::requestRefresh);
|
|
connect(m_renderThread, &QThread::finished, m_damageObserver, &QObject::deleteLater);
|
|
|
|
//start the rendering thread
|
|
m_renderThread->start();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CompositorX11RenderWindow::stopRendering()
|
|
{
|
|
if (m_renderThread)
|
|
{
|
|
m_renderThread->quit();
|
|
m_renderThread->wait();
|
|
delete m_renderThread;
|
|
m_renderThread = nullptr;
|
|
}
|
|
m_videoClient.reset();
|
|
m_videoWindow = nullptr;
|
|
m_interfaceClient.reset();
|
|
m_interfaceWindow = nullptr;
|
|
}
|
|
|
|
void CompositorX11RenderWindow::resetClientPixmaps()
|
|
{
|
|
QMutexLocker lock(&m_pictureLock);
|
|
xcb_flush(QX11Info::connection());
|
|
//reset and recreate the clients surfaces
|
|
if (m_interfaceClient)
|
|
{
|
|
m_interfaceClient->resetPixmap();
|
|
m_interfaceClient->getPicture();
|
|
}
|
|
if (m_videoClient)
|
|
{
|
|
m_videoClient->resetPixmap();
|
|
m_interfaceClient->getPicture();
|
|
}
|
|
}
|
|
|
|
bool CompositorX11RenderWindow::eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
bool ret = false;
|
|
bool needRefresh = false;
|
|
|
|
//event on the window
|
|
if (obj == m_window)
|
|
{
|
|
//window may get resized without the widget knowing about it
|
|
if (event->type() == QEvent::Resize)
|
|
{
|
|
auto resizeEvent = static_cast<QResizeEvent*>(event);
|
|
if (m_interfaceWindow)
|
|
m_interfaceWindow->handleWindowEvent(event);
|
|
resetClientPixmaps();
|
|
emit windowSizeChanged(resizeEvent->size() * m_rootWidget->devicePixelRatioF());
|
|
needRefresh = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(obj == m_rootWidget);
|
|
if (event->type() == QEvent::Resize)
|
|
return false;
|
|
|
|
if (m_interfaceWindow)
|
|
ret = m_interfaceWindow->handleWindowEvent(event);
|
|
|
|
switch (event->type())
|
|
{
|
|
case QEvent::Expose:
|
|
{
|
|
resetClientPixmaps();
|
|
needRefresh = true;
|
|
break;
|
|
}
|
|
case QEvent::Show:
|
|
{
|
|
resetClientPixmaps();
|
|
needRefresh = true;
|
|
emit visiblityChanged(true);
|
|
break;
|
|
}
|
|
case QEvent::Hide:
|
|
emit visiblityChanged(false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (needRefresh)
|
|
emit requestUIRefresh();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void CompositorX11RenderWindow::setVideoPosition(const QPoint& position)
|
|
{
|
|
if (m_videoWindow && m_videoClient)
|
|
{
|
|
m_videoPosition.moveTopLeft(position);
|
|
emit videoPositionChanged(m_videoPosition);
|
|
}
|
|
}
|
|
|
|
void CompositorX11RenderWindow::setVideoSize(const QSize& size)
|
|
{
|
|
if (m_videoWindow && m_videoClient)
|
|
{
|
|
m_videoWindow->resize(size);
|
|
{
|
|
QMutexLocker lock(&m_pictureLock);
|
|
xcb_flush(QX11Info::connection());
|
|
//reset and recreate the clients surfaces
|
|
m_videoClient->resetPixmap();
|
|
m_videoClient->getPicture();
|
|
}
|
|
m_videoPosition.setSize(size * m_rootWidget->devicePixelRatioF());
|
|
emit videoPositionChanged(m_videoPosition);
|
|
}
|
|
}
|
|
|
|
void CompositorX11RenderWindow::setVideoWindow( QWindow* window)
|
|
{
|
|
window->setParent(m_window);
|
|
//ensure Qt x11 pending operation have been forwarded to the server
|
|
xcb_flush(QX11Info::connection());
|
|
m_videoClient = std::make_unique<CompositorX11RenderClient>(m_intf, m_conn, window);
|
|
m_videoPosition = QRect(0,0,0,0);
|
|
setTransparentForMouseEvent(QX11Info::connection(), window->winId());
|
|
m_videoWindow = window;
|
|
emit videoSurfaceChanged(m_videoClient.get());
|
|
}
|
|
|
|
void CompositorX11RenderWindow::enableVideoWindow()
|
|
{
|
|
emit registerVideoWindow(m_videoWindow->winId());
|
|
}
|
|
|
|
void CompositorX11RenderWindow::disableVideoWindow()
|
|
{
|
|
emit registerVideoWindow(0);
|
|
}
|
|
|
|
void CompositorX11RenderWindow::setInterfaceWindow(CompositorX11UISurface* window)
|
|
{
|
|
assert(m_window);
|
|
window->setParent(m_window);
|
|
//ensure Qt x11 pending operation have been forwarded to the server
|
|
xcb_flush(QX11Info::connection());
|
|
m_interfaceClient = std::make_unique<CompositorX11RenderClient>(m_intf, m_conn, window);
|
|
m_interfaceWindow = window;
|
|
|
|
}
|
|
|