/*****************************************************************************
* 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 "compositor.hpp"
#include "compositor_dummy.hpp"
#include "mainctx.hpp"
#include "video_window_handler.hpp"
#include "videosurface.hpp"
#include "interface_window_handler.hpp"
#include "mainui.hpp"
#ifdef _WIN32
#include "mainctx_win32.hpp"
#include "compositor_dcomp.hpp"
#include "compositor_win7.hpp"
#endif
#ifdef QT_HAS_WAYLAND_COMPOSITOR
# include "compositor_wayland.hpp"
#endif
#ifdef QT_HAS_X11_COMPOSITOR
# include "compositor_x11.hpp"
#endif
#include "maininterface/windoweffects_module.hpp"
#include "compositor_platform.hpp"
#include <vlc_window.h>
#include <vlc_modules.h>
using namespace vlc;
template<typename T>
static Compositor* instanciateCompositor(qt_intf_t *p_intf) {
return new T(p_intf);
}
struct {
const char* name;
Compositor* (*instantiate)(qt_intf_t *p_intf);
} static compositorList[] = {
#if defined(_WIN32)
{"dcomp", &instanciateCompositor<CompositorDirectComposition> },
#endif
#if defined(_WIN32) || defined(__APPLE__)
{"platform", &instanciateCompositor<CompositorPlatform> },
#endif
#if defined(_WIN32)
{"win7", &instanciateCompositor<CompositorWin7> },
#endif
#ifdef QT_HAS_WAYLAND_COMPOSITOR
{"wayland", &instanciateCompositor<CompositorWayland> },
#endif
#ifdef QT_HAS_X11_COMPOSITOR
{"x11", &instanciateCompositor<CompositorX11> },
#endif
{"dummy", &instanciateCompositor<CompositorDummy> }
};
CompositorFactory::CompositorFactory(qt_intf_t *p_intf, const char* compositor)
: m_intf(p_intf)
, m_compositorName(compositor)
{
}
Compositor* CompositorFactory::createCompositor()
{
for (; m_compositorIndex < ARRAY_SIZE(compositorList); m_compositorIndex++)
{
if (m_compositorName == "auto" || m_compositorName == compositorList[m_compositorIndex].name)
{
std::unique_ptr<Compositor> compositor {
compositorList[m_compositorIndex].instantiate(m_intf)
};
if (compositor->init())
{
//avoid looping over the same compositor if the current ones fails further initialisation steps
m_compositorIndex++;
return compositor.release();
}
}
}
msg_Err(m_intf, "no suitable compositor found");
return nullptr;
}
namespace {
struct CompositorWindow {
/* The CompositorVideo instance that created the window */
vlc::CompositorVideo *compositor;
/* Callback setup from commonSetupVoutWindow. It usually comes from the main qt.cpp
* file so as to handle the reference counting of Qt's resources. */
void (*destroy_cb)(struct vlc_window *wnd);
};
}
extern "C"
{
static int windowEnableCb(vlc_window_t* p_wnd, const vlc_window_cfg_t * cfg)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
int ret = VLC_EGENERIC;
QMetaObject::invokeMethod(that->compositor, [&](){
ret = that->compositor->windowEnable(cfg);
}, Qt::BlockingQueuedConnection);
return ret;
}
static void windowDisableCb(vlc_window_t* p_wnd)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
QMetaObject::invokeMethod(that->compositor, [that](){
that->compositor->windowDisable();
}, Qt::BlockingQueuedConnection);
}
static void windowResizeCb(vlc_window_t* p_wnd, unsigned width, unsigned height)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
that->compositor->windowResize(width, height);
}
static void windowDestroyCb(struct vlc_window * p_wnd)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
that->compositor->windowDestroy();
that->compositor = nullptr;
if (that->destroy_cb != nullptr)
that->destroy_cb(p_wnd);
/* Destroy p_wnd->sys */
delete that;
p_wnd->sys = nullptr;
}
static void windowSetStateCb(vlc_window_t* p_wnd, unsigned state)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
that->compositor->windowSetState(state);
}
static void windowUnsetFullscreenCb(vlc_window_t* p_wnd)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
that->compositor->windowUnsetFullscreen();
}
static void windowSetFullscreenCb(vlc_window_t* p_wnd, const char *id)
{
assert(p_wnd->sys);
auto that = static_cast<CompositorWindow*>(p_wnd->sys);
that->compositor->windowSetFullscreen(id);
}
}
CompositorVideo::CompositorVideo(qt_intf_t *p_intf, QObject* parent)
: QObject(parent)
, m_intf(p_intf)
{
}
CompositorVideo::~CompositorVideo()
{
if (m_windowEffectsModule)
{
if (m_windowEffectsModule->p_module)
module_unneed(m_windowEffectsModule, m_windowEffectsModule->p_module);
vlc_object_delete(m_windowEffectsModule);
}
}
void CompositorVideo::commonSetupVoutWindow(vlc_window_t* p_wnd, VoutDestroyCb destroyCb)
{
static const struct vlc_window_operations ops = {
windowEnableCb,
windowDisableCb,
windowResizeCb,
windowDestroyCb,
windowSetStateCb,
windowUnsetFullscreenCb,
windowSetFullscreenCb,
nullptr, //window_set_title
};
/* Allocate state that will be tracked by the window through vlc_window::sys */
// NOTE: no std::nothrow here since no error code, forward the exception
auto *compositor_window = new CompositorWindow;
compositor_window->compositor = this;
compositor_window->destroy_cb = destroyCb;
/* We currently support only a single window in the compositor. */
assert(m_wnd == nullptr);
m_wnd = p_wnd;
p_wnd->sys = compositor_window;
p_wnd->ops = &ops;
p_wnd->info.has_double_click = true;
// These need to be connected here, since the compositor might not be ready when
// these signals are emitted. VOut window might not be set, or worse, compositor's
// internal preparations might not be completed yet:
constexpr auto connType = static_cast<Qt::ConnectionType>(Qt::UniqueConnection | Qt::DirectConnection);
connect(m_videoSurfaceProvider.get(), &VideoSurfaceProvider::surfacePropertiesChanged,
this, &CompositorVideo::onSurfacePropertiesChanged, connType);
}
void CompositorVideo::windowDestroy()
{
// Current thread may not be the thread where
// m_videoSurfaceProvider belongs to, so do not delete
// it here:
disconnect(m_videoSurfaceProvider.get(), &VideoSurfaceProvider::surfacePropertiesChanged,
this, &CompositorVideo::onSurfacePropertiesChanged);
m_wnd = nullptr;
}
void CompositorVideo::windowResize(unsigned width, unsigned height)
{
m_videoWindowHandler->requestResizeVideo(width, height);
}
void CompositorVideo::windowSetState(unsigned state)
{
m_videoWindowHandler->requestVideoState(static_cast<vlc_window_state>(state));
}
void CompositorVideo::windowUnsetFullscreen()
{
m_videoWindowHandler->requestVideoWindowed();
}
void CompositorVideo::windowSetFullscreen(const char *id)
{
m_videoWindowHandler->requestVideoFullScreen(id);
}
void CompositorVideo::commonWindowEnable()
{
m_videoSurfaceProvider->enable(m_wnd);
m_videoSurfaceProvider->setVideoEmbed(true);
}
void CompositorVideo::commonWindowDisable()
{
m_videoSurfaceProvider->setVideoEmbed(false);
m_videoSurfaceProvider->disable();
m_videoWindowHandler->disable();
}
bool CompositorVideo::commonGUICreateImpl(QWindow* window, CompositorVideo::Flags flags)
{
assert(m_mainCtx);
assert(window);
m_videoSurfaceProvider = std::make_unique<VideoSurfaceProvider>(canDoThreadedSurfaceUpdates());
m_mainCtx->setVideoSurfaceProvider(m_videoSurfaceProvider.get());
const QQuickWindow *const quickWindow = this->quickWindow();
assert(quickWindow);
assert(quickWindow->handle()); // Make sure `::create()` was called before
assert(quickWindow->rendererInterface());
// `ViewBlockingRectangle` only supports RHI with depth buffer (which is the default) or software mode:
const bool hasDepthBufferOrSoftwareMode = (quickWindow->format().depthBufferSize() > 0) ||
(quickWindow->rendererInterface()->graphicsApi() == QSGRendererInterface::Software);
if (!hasDepthBufferOrSoftwareMode)
qWarning() << this << ": interface window has no depth buffer, pip player and backdrop blur will not be functional!";
if (hasDepthBufferOrSoftwareMode && (flags & CompositorVideo::CAN_SHOW_PIP) && var_InheritBool(m_intf, "qt-pip-mode"))
{
m_mainCtx->setCanShowVideoPIP(true);
}
m_mainCtx->setWindowSuportExtendedFrame(flags & CompositorVideo::HAS_EXTENDED_FRAME);
if (hasDepthBufferOrSoftwareMode && (flags & CompositorVideo::HAS_ACRYLIC))
{
// FIXME: Normally window backdrop blur effect does not require depth buffer, provided that there is nothing between
// the window and the scene item that paints. Unfortunately this is not the case in multiple cases, due to
// various reasons. Because of that, we disable blur altogether when there is no depth buffer.
#ifndef _WIN32
assert(qGuiApp);
if (qGuiApp->platformName().startsWith(QLatin1String("wayland")) && Q_LIKELY(!window->isActive()))
{
connect(window, &QWindow::activeChanged, this, [this, window = QPointer(window)]() {
setBlurBehind(window, true);
}, Qt::SingleShotConnection);
}
else
#endif
{
setBlurBehind(window, true);
}
}
m_videoWindowHandler = std::make_unique<VideoWindowHandler>(m_intf);
m_videoWindowHandler->setWindow( window );
#ifdef _WIN32
m_interfaceWindowHandler = std::make_unique<InterfaceWindowHandlerWin32>(m_intf, m_mainCtx, window);
#else
m_interfaceWindowHandler = std::make_unique<InterfaceWindowHandler>(m_intf, m_mainCtx, window);
#endif
#ifdef _WIN32
m_taskbarWidget = std::make_unique<WinTaskbarWidget>(m_intf, window);
qApp->installNativeEventFilter(m_taskbarWidget.get());
#endif
m_ui = std::make_unique<MainUI>(m_intf, m_mainCtx, window);
return true;
}
void CompositorVideo::adjustBlurBehind()
{
if (m_mainCtx && m_windowEffectsModule)
{
if (QWindow *const window = interfaceMainWindow())
{
assert(m_windowEffectsModule->isEffectAvailable(window, WindowEffectsModule::BlurBehind));
const bool blurBehind = m_mainCtx->hasAcrylicSurface();
if (blurBehind && m_mainCtx->windowSuportExtendedFrame() && m_mainCtx->useClientSideDecoration())
{
QRegion blurRegion;
const QRegion maskRegion = window->mask();
if (maskRegion.isEmpty())
{
// If there is window mask, we assume that the content area is smaller than the
// window geometry. We also assume that in that case window has custom margins
// set (Wayland case). KWin 6.2 Wayland already considers window custom margins
// when applying the blur effect, but KWin X11 does not consider `_GTK_FRAME_EXTENTS`.
// Since the mask region represents the actual content area, and the outside region
// is included in the frame margins, we can set the blur region only if the mask is
// empty. This fixes blur backdrop applied to client shadows on X11:
const auto margin = m_mainCtx->windowExtendedMargin();
blurRegion = QRegion(margin, margin, window->width() - 2 * margin, window->height() - 2 * margin);
}
m_windowEffectsModule->setBlurBehind(window, true, blurRegion);
}
else
{
m_windowEffectsModule->setBlurBehind(window, blurBehind, {});
}
}
}
}
bool CompositorVideo::commonGUICreate(QWindow* window, QmlUISurface* qmlSurface, CompositorVideo::Flags flags)
{
bool ret = commonGUICreateImpl(window, flags);
if (!ret)
return false;
ret = m_ui->setup(qmlSurface->engine());
if (! ret)
return false;
const auto rootItem = m_ui->createRootItem();
if (Q_UNLIKELY(!rootItem))
return false;
qmlSurface->setContent(rootItem);
return true;
}
bool CompositorVideo::commonGUICreate(QWindow* window, QQuickView* qmlView, CompositorVideo::Flags flags)
{
bool ret = commonGUICreateImpl(window, flags);
if (!ret)
return false;
ret = m_ui->setup(qmlView->engine());
if (! ret)
return false;
const auto rootItem = m_ui->createRootItem();
if (Q_UNLIKELY(!rootItem))
return false;
qmlView->setContent(QUrl(), nullptr, rootItem);
return true;
}
void CompositorVideo::commonGUIDestroy()
{
m_ui.reset();
#ifdef _WIN32
qApp->removeNativeEventFilter(m_taskbarWidget.get());
m_taskbarWidget.reset();
#endif
m_interfaceWindowHandler.reset();
m_mainCtx = nullptr;
}
void CompositorVideo::commonIntfDestroy()
{
m_videoWindowHandler.reset();
m_videoSurfaceProvider.reset();
unloadGUI();
}
bool CompositorVideo::setBlurBehind(QWindow *window, const bool enable)
{
assert(window);
assert(m_intf);
if (enable)
{
if (!var_InheritBool(m_intf, "qt-backdrop-blur"))
{
return false;
}
}
if (m_failedToLoadWindowEffectsModule)
return false;
if (!m_windowEffectsModule)
{
m_windowEffectsModule = vlc_object_create<WindowEffectsModule>(m_intf);
if (!m_windowEffectsModule) // do not set m_failedToLoadWindowEffectsModule here
return false;
}
if (!m_windowEffectsModule->p_module)
{
m_windowEffectsModule->p_module = module_need(m_windowEffectsModule, "qtwindoweffects", nullptr, false);
if (!m_windowEffectsModule->p_module)
{
msg_Dbg(m_intf, "A module providing window effects capability could not be instantiated. " \
"Native background blur effect will not be available. " \
"The application may compensate this with a simulated effect on certain platform(s).");
m_failedToLoadWindowEffectsModule = true;
vlc_object_delete(m_windowEffectsModule);
m_windowEffectsModule = nullptr;
return false;
}
}
if (!m_windowEffectsModule->isEffectAvailable(window, WindowEffectsModule::BlurBehind))
return false;
m_mainCtx->setHasAcrylicSurface(enable);
if (m_mainCtx->windowSuportExtendedFrame())
{
assert(m_mainCtx);
if (enable)
{
connect(window, &QWindow::widthChanged, this, &CompositorVideo::adjustBlurBehind, Qt::UniqueConnection);
connect(window, &QWindow::heightChanged, this, &CompositorVideo::adjustBlurBehind, Qt::UniqueConnection);
connect(m_mainCtx, &MainCtx::windowExtendedMarginChanged, this, &CompositorVideo::adjustBlurBehind, Qt::UniqueConnection);
connect(m_mainCtx, &MainCtx::useClientSideDecorationChanged, this, &CompositorVideo::adjustBlurBehind, Qt::UniqueConnection);
}
else
{
disconnect(window, &QWindow::widthChanged, this, &CompositorVideo::adjustBlurBehind);
disconnect(window, &QWindow::heightChanged, this, &CompositorVideo::adjustBlurBehind);
disconnect(m_mainCtx, &MainCtx::windowExtendedMarginChanged, this, &CompositorVideo::adjustBlurBehind);
disconnect(m_mainCtx, &MainCtx::useClientSideDecorationChanged, this, &CompositorVideo::adjustBlurBehind);
}
adjustBlurBehind();
}
else
{
m_windowEffectsModule->setBlurBehind(window, enable, {});
}
return true;
}
QQuickWindow *Compositor::quickWindow() const
{
return qobject_cast<QQuickWindow*>(interfaceMainWindow());
}
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|