kwin/src/composite.cpp
Vlad Zahorodnii 467a1b3b49 Move cursor delegates to scene/
They've been overlooked.
2023-05-12 07:01:35 +00:00

1135 lines
36 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "composite.h"
#include <config-kwin.h>
#include "core/output.h"
#include "core/outputbackend.h"
#include "core/outputlayer.h"
#include "core/overlaywindow.h"
#include "core/renderlayer.h"
#include "core/renderloop.h"
#include "dbusinterface.h"
#include "decorations/decoratedclient.h"
#include "effects.h"
#include "ftrace.h"
#include "internalwindow.h"
#include "platformsupport/scenes/opengl/openglbackend.h"
#include "platformsupport/scenes/qpainter/qpainterbackend.h"
#include "scene/cursordelegate_opengl.h"
#include "scene/cursordelegate_qpainter.h"
#include "scene/cursorscene.h"
#include "scene/itemrenderer_opengl.h"
#include "scene/itemrenderer_qpainter.h"
#include "scene/surfaceitem_x11.h"
#include "scene/workspacescene_opengl.h"
#include "scene/workspacescene_qpainter.h"
#include "shadow.h"
#include "useractions.h"
#include "utils/common.h"
#include "utils/xcbutils.h"
#include "wayland/surface_interface.h"
#include "wayland_server.h"
#include "workspace.h"
#include "x11syncmanager.h"
#include "x11window.h"
#include "libkwineffects/kwinglplatform.h"
#include "libkwineffects/kwingltexture.h"
#include <KCrash>
#include <KGlobalAccel>
#include <KLocalizedString>
#if KWIN_BUILD_NOTIFICATIONS
#include <KNotification>
#endif
#include <KSelectionOwner>
#include <QDateTime>
#include <QFutureWatcher>
#include <QMenu>
#include <QOpenGLContext>
#include <QQuickWindow>
#include <QTextStream>
#include <QTimerEvent>
#include <QtConcurrentRun>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <cstdio>
Q_DECLARE_METATYPE(KWin::X11Compositor::SuspendReason)
namespace KWin
{
Compositor *Compositor::s_compositor = nullptr;
Compositor *Compositor::self()
{
return s_compositor;
}
WaylandCompositor *WaylandCompositor::create(QObject *parent)
{
Q_ASSERT(!s_compositor);
auto *compositor = new WaylandCompositor(parent);
s_compositor = compositor;
return compositor;
}
X11Compositor *X11Compositor::create(QObject *parent)
{
Q_ASSERT(!s_compositor);
auto *compositor = new X11Compositor(parent);
s_compositor = compositor;
return compositor;
}
class CompositorSelectionOwner : public KSelectionOwner
{
Q_OBJECT
public:
CompositorSelectionOwner(const char *selection)
: KSelectionOwner(selection, kwinApp()->x11Connection(), kwinApp()->x11RootWindow())
, m_owning(false)
{
connect(this, &CompositorSelectionOwner::lostOwnership,
this, [this]() {
m_owning = false;
});
}
bool owning() const
{
return m_owning;
}
void setOwning(bool own)
{
m_owning = own;
}
private:
bool m_owning;
};
Compositor::Compositor(QObject *workspace)
: QObject(workspace)
{
connect(options, &Options::configChanged, this, &Compositor::configChanged);
connect(options, &Options::animationSpeedChanged, this, &Compositor::configChanged);
// 2 sec which should be enough to restart the compositor.
static const int compositorLostMessageDelay = 2000;
m_releaseSelectionTimer.setSingleShot(true);
m_releaseSelectionTimer.setInterval(compositorLostMessageDelay);
connect(&m_releaseSelectionTimer, &QTimer::timeout,
this, &Compositor::releaseCompositorSelection);
m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay);
m_unusedSupportPropertyTimer.setSingleShot(true);
connect(&m_unusedSupportPropertyTimer, &QTimer::timeout,
this, &Compositor::deleteUnusedSupportProperties);
// Delay the call to start by one event cycle.
// The ctor of this class is invoked from the Workspace ctor, that means before
// Workspace is completely constructed, so calling Workspace::self() would result
// in undefined behavior. This is fixed by using a delayed invocation.
QTimer::singleShot(0, this, &Compositor::start);
connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::initializeX11);
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &Compositor::cleanupX11);
// register DBus
new CompositorDBusInterface(this);
FTraceLogger::create();
}
Compositor::~Compositor()
{
deleteUnusedSupportProperties();
destroyCompositorSelection();
s_compositor = nullptr;
}
bool Compositor::attemptOpenGLCompositing()
{
// Some broken drivers crash on glXQuery() so to prevent constant KWin crashes:
if (openGLCompositingIsBroken()) {
qCWarning(KWIN_CORE) << "KWin has detected that your OpenGL library is unsafe to use";
return false;
}
createOpenGLSafePoint(OpenGLSafePoint::PreInit);
auto safePointScope = qScopeGuard([this]() {
createOpenGLSafePoint(OpenGLSafePoint::PostInit);
});
std::unique_ptr<OpenGLBackend> backend = kwinApp()->outputBackend()->createOpenGLBackend();
if (!backend) {
return false;
}
if (!backend->isFailed()) {
backend->init();
}
if (backend->isFailed()) {
return false;
}
const QByteArray forceEnv = qgetenv("KWIN_COMPOSE");
if (!forceEnv.isEmpty()) {
if (qstrcmp(forceEnv, "O2") == 0 || qstrcmp(forceEnv, "O2ES") == 0) {
qCDebug(KWIN_CORE) << "OpenGL 2 compositing enforced by environment variable";
} else {
// OpenGL 2 disabled by environment variable
return false;
}
} else {
if (!backend->isDirectRendering()) {
return false;
}
if (GLPlatform::instance()->recommendedCompositor() < OpenGLCompositing) {
qCDebug(KWIN_CORE) << "Driver does not recommend OpenGL compositing";
return false;
}
}
// We only support the OpenGL 2+ shader API, not GL_ARB_shader_objects
if (!hasGLVersion(2, 0)) {
qCDebug(KWIN_CORE) << "OpenGL 2.0 is not supported";
return false;
}
m_scene = std::make_unique<WorkspaceSceneOpenGL>(backend.get());
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererOpenGL>());
m_backend = std::move(backend);
// set strict binding
if (options->isGlStrictBindingFollowsDriver()) {
options->setGlStrictBinding(!GLPlatform::instance()->supports(LooseBinding));
}
qCDebug(KWIN_CORE) << "OpenGL compositing has been successfully initialized";
return true;
}
bool Compositor::attemptQPainterCompositing()
{
std::unique_ptr<QPainterBackend> backend(kwinApp()->outputBackend()->createQPainterBackend());
if (!backend || backend->isFailed()) {
return false;
}
m_scene = std::make_unique<WorkspaceSceneQPainter>(backend.get());
m_cursorScene = std::make_unique<CursorScene>(std::make_unique<ItemRendererQPainter>());
m_backend = std::move(backend);
qCDebug(KWIN_CORE) << "QPainter compositing has been successfully initialized";
return true;
}
bool Compositor::setupStart()
{
if (kwinApp()->isTerminating()) {
// Don't start while KWin is terminating. An event to restart might be lingering
// in the event queue due to graphics reset.
return false;
}
if (m_state != State::Off) {
return false;
}
m_state = State::Starting;
options->reloadCompositingSettings(true);
initializeX11();
Q_EMIT aboutToToggleCompositing();
const QVector<CompositingType> availableCompositors = kwinApp()->outputBackend()->supportedCompositors();
QVector<CompositingType> candidateCompositors;
// If compositing has been restarted, try to use the last used compositing type.
if (m_selectedCompositor != NoCompositing) {
candidateCompositors.append(m_selectedCompositor);
} else {
candidateCompositors = availableCompositors;
const auto userConfigIt = std::find(candidateCompositors.begin(), candidateCompositors.end(), options->compositingMode());
if (userConfigIt != candidateCompositors.end()) {
candidateCompositors.erase(userConfigIt);
candidateCompositors.prepend(options->compositingMode());
} else {
qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults";
}
}
for (auto type : std::as_const(candidateCompositors)) {
bool stop = false;
switch (type) {
case OpenGLCompositing:
qCDebug(KWIN_CORE) << "Attempting to load the OpenGL scene";
stop = attemptOpenGLCompositing();
break;
case QPainterCompositing:
qCDebug(KWIN_CORE) << "Attempting to load the QPainter scene";
stop = attemptQPainterCompositing();
break;
case NoCompositing:
qCDebug(KWIN_CORE) << "Starting without compositing...";
stop = true;
break;
}
if (stop) {
break;
}
}
if (!m_backend) {
m_state = State::Off;
if (auto *con = kwinApp()->x11Connection()) {
xcb_composite_unredirect_subwindows(con, kwinApp()->x11RootWindow(),
XCB_COMPOSITE_REDIRECT_MANUAL);
}
if (m_selectionOwner) {
m_selectionOwner->setOwning(false);
m_selectionOwner->release();
}
if (!availableCompositors.contains(NoCompositing)) {
qCCritical(KWIN_CORE) << "The used windowing system requires compositing";
qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken";
qApp->quit();
}
return false;
}
m_selectedCompositor = m_backend->compositingType();
if (!Workspace::self() && m_backend && m_backend->compositingType() == QPainterCompositing) {
// Force Software QtQuick on first startup with QPainter.
QQuickWindow::setGraphicsApi(QSGRendererInterface::Software);
}
Q_EMIT sceneCreated();
return true;
}
void Compositor::initializeX11()
{
xcb_connection_t *connection = kwinApp()->x11Connection();
if (!connection) {
return;
}
if (!m_selectionOwner) {
m_selectionOwner = std::make_unique<CompositorSelectionOwner>("_NET_WM_CM_S0");
connect(m_selectionOwner.get(), &CompositorSelectionOwner::lostOwnership, this, &Compositor::stop);
}
if (!m_selectionOwner->owning()) {
// Force claim ownership.
m_selectionOwner->claim(true);
m_selectionOwner->setOwning(true);
}
xcb_composite_redirect_subwindows(connection, kwinApp()->x11RootWindow(),
XCB_COMPOSITE_REDIRECT_MANUAL);
}
void Compositor::cleanupX11()
{
m_selectionOwner.reset();
}
void Compositor::startupWithWorkspace()
{
Q_ASSERT(m_scene);
m_scene->initialize();
m_cursorScene->initialize();
const QList<Output *> outputs = workspace()->outputs();
if (kwinApp()->operationMode() == Application::OperationModeX11) {
auto workspaceLayer = new RenderLayer(outputs.constFirst()->renderLoop());
workspaceLayer->setDelegate(std::make_unique<SceneDelegate>(m_scene.get(), nullptr));
workspaceLayer->setGeometry(workspace()->geometry());
connect(workspace(), &Workspace::geometryChanged, workspaceLayer, [workspaceLayer]() {
workspaceLayer->setGeometry(workspace()->geometry());
});
addSuperLayer(workspaceLayer);
} else {
for (Output *output : outputs) {
addOutput(output);
}
connect(workspace(), &Workspace::outputAdded, this, &Compositor::addOutput);
connect(workspace(), &Workspace::outputRemoved, this, &Compositor::removeOutput);
}
m_state = State::On;
const auto windows = workspace()->windows();
for (Window *window : windows) {
window->setupCompositing();
}
// Sets also the 'effects' pointer.
kwinApp()->createEffectsHandler(this, m_scene.get());
Q_EMIT compositingToggled(true);
if (m_releaseSelectionTimer.isActive()) {
m_releaseSelectionTimer.stop();
}
}
Output *Compositor::findOutput(RenderLoop *loop) const
{
const auto outputs = workspace()->outputs();
for (Output *output : outputs) {
if (output->renderLoop() == loop) {
return output;
}
}
return nullptr;
}
void Compositor::addOutput(Output *output)
{
Q_ASSERT(kwinApp()->operationMode() != Application::OperationModeX11);
auto workspaceLayer = new RenderLayer(output->renderLoop());
workspaceLayer->setDelegate(std::make_unique<SceneDelegate>(m_scene.get(), output));
workspaceLayer->setGeometry(output->rect());
connect(output, &Output::geometryChanged, workspaceLayer, [output, workspaceLayer]() {
workspaceLayer->setGeometry(output->rect());
});
auto cursorLayer = new RenderLayer(output->renderLoop());
cursorLayer->setVisible(false);
if (m_backend->compositingType() == OpenGLCompositing) {
cursorLayer->setDelegate(std::make_unique<CursorDelegateOpenGL>(output));
} else {
cursorLayer->setDelegate(std::make_unique<CursorDelegateQPainter>(output));
}
cursorLayer->setParent(workspaceLayer);
cursorLayer->setSuperlayer(workspaceLayer);
auto updateCursorLayer = [output, cursorLayer]() {
const Cursor *cursor = Cursors::self()->currentCursor();
const QRectF layerRect = output->mapFromGlobal(cursor->geometry());
bool usesHardwareCursor = false;
if (!Cursors::self()->isCursorHidden()) {
usesHardwareCursor = output->setCursor(cursor->source()) && output->moveCursor(layerRect.topLeft());
} else {
usesHardwareCursor = output->setCursor(nullptr);
}
cursorLayer->setVisible(cursor->isOnOutput(output) && !usesHardwareCursor);
cursorLayer->setGeometry(layerRect);
cursorLayer->addRepaintFull();
};
auto moveCursorLayer = [output, cursorLayer]() {
const Cursor *cursor = Cursors::self()->currentCursor();
const QRectF layerRect = output->mapFromGlobal(cursor->geometry());
const bool usesHardwareCursor = output->moveCursor(layerRect.topLeft());
cursorLayer->setVisible(cursor->isOnOutput(output) && !usesHardwareCursor);
cursorLayer->setGeometry(layerRect);
cursorLayer->addRepaintFull();
};
updateCursorLayer();
connect(output, &Output::geometryChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::currentCursorChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::hiddenChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::positionChanged, cursorLayer, moveCursorLayer);
addSuperLayer(workspaceLayer);
}
void Compositor::removeOutput(Output *output)
{
removeSuperLayer(m_superlayers[output->renderLoop()]);
}
void Compositor::addSuperLayer(RenderLayer *layer)
{
m_superlayers.insert(layer->loop(), layer);
connect(layer->loop(), &RenderLoop::frameRequested, this, &Compositor::handleFrameRequested);
}
void Compositor::removeSuperLayer(RenderLayer *layer)
{
m_superlayers.remove(layer->loop());
disconnect(layer->loop(), &RenderLoop::frameRequested, this, &Compositor::handleFrameRequested);
delete layer;
}
void Compositor::scheduleRepaint()
{
for (auto it = m_superlayers.constBegin(); it != m_superlayers.constEnd(); ++it) {
it.key()->scheduleRepaint();
}
}
void Compositor::stop()
{
if (m_state == State::Off || m_state == State::Stopping) {
return;
}
m_state = State::Stopping;
Q_EMIT aboutToToggleCompositing();
m_releaseSelectionTimer.start();
// Some effects might need access to effect windows when they are about to
// be destroyed, for example to unreference deleted windows, so we have to
// make sure that effect windows outlive effects.
delete effects;
effects = nullptr;
if (Workspace::self()) {
const auto windows = workspace()->windows();
for (Window *window : windows) {
window->finishCompositing();
}
if (auto *con = kwinApp()->x11Connection()) {
xcb_composite_unredirect_subwindows(con, kwinApp()->x11RootWindow(),
XCB_COMPOSITE_REDIRECT_MANUAL);
}
disconnect(workspace(), &Workspace::outputAdded, this, &Compositor::addOutput);
disconnect(workspace(), &Workspace::outputRemoved, this, &Compositor::removeOutput);
}
if (m_backend->compositingType() == OpenGLCompositing) {
// some layers need a context current for destruction
static_cast<OpenGLBackend *>(m_backend.get())->makeCurrent();
}
const auto superlayers = m_superlayers;
for (auto it = superlayers.begin(); it != superlayers.end(); ++it) {
removeSuperLayer(*it);
}
m_scene.reset();
m_cursorScene.reset();
m_backend.reset();
m_state = State::Off;
Q_EMIT compositingToggled(false);
}
void Compositor::destroyCompositorSelection()
{
m_selectionOwner.reset();
}
void Compositor::releaseCompositorSelection()
{
switch (m_state) {
case State::On:
// We are compositing at the moment. Don't release.
break;
case State::Off:
if (m_selectionOwner) {
qCDebug(KWIN_CORE) << "Releasing compositor selection";
m_selectionOwner->setOwning(false);
m_selectionOwner->release();
}
break;
case State::Starting:
case State::Stopping:
// Still starting or shutting down the compositor. Starting might fail
// or after stopping a restart might follow. So test again later on.
m_releaseSelectionTimer.start();
break;
}
}
void Compositor::keepSupportProperty(xcb_atom_t atom)
{
m_unusedSupportProperties.removeAll(atom);
}
void Compositor::removeSupportProperty(xcb_atom_t atom)
{
m_unusedSupportProperties << atom;
m_unusedSupportPropertyTimer.start();
}
void Compositor::deleteUnusedSupportProperties()
{
if (m_state == State::Starting || m_state == State::Stopping) {
// Currently still maybe restarting the compositor.
m_unusedSupportPropertyTimer.start();
return;
}
if (auto *con = kwinApp()->x11Connection()) {
for (const xcb_atom_t &atom : std::as_const(m_unusedSupportProperties)) {
// remove property from root window
xcb_delete_property(con, kwinApp()->x11RootWindow(), atom);
}
m_unusedSupportProperties.clear();
}
}
void Compositor::configChanged()
{
reinitialize();
}
void Compositor::reinitialize()
{
// Reparse config. Config options will be reloaded by start()
kwinApp()->config()->reparseConfiguration();
// Restart compositing
stop();
start();
if (effects) { // start() may fail
effects->reconfigure();
}
}
void Compositor::handleFrameRequested(RenderLoop *renderLoop)
{
composite(renderLoop);
}
uint Compositor::outputFormat(Output *output)
{
OutputLayer *primaryLayer = m_backend->primaryLayer(output);
return primaryLayer->format();
}
void Compositor::composite(RenderLoop *renderLoop)
{
if (m_backend->checkGraphicsReset()) {
qCDebug(KWIN_CORE) << "Graphics reset occurred";
#if KWIN_BUILD_NOTIFICATIONS
KNotification::event(QStringLiteral("graphicsreset"), i18n("Desktop effects were restarted due to a graphics reset"));
#endif
reinitialize();
return;
}
Output *output = findOutput(renderLoop);
OutputLayer *primaryLayer = m_backend->primaryLayer(output);
fTraceDuration("Paint (", output->name(), ")");
RenderLayer *superLayer = m_superlayers[renderLoop];
prePaintPass(superLayer);
superLayer->setOutputLayer(primaryLayer);
SurfaceItem *scanoutCandidate = superLayer->delegate()->scanoutCandidate();
renderLoop->setFullscreenSurface(scanoutCandidate);
output->setContentType(scanoutCandidate ? scanoutCandidate->contentType() : ContentType::None);
renderLoop->beginFrame();
bool directScanout = false;
if (scanoutCandidate) {
const auto sublayers = superLayer->sublayers();
const bool scanoutPossible = std::none_of(sublayers.begin(), sublayers.end(), [](RenderLayer *sublayer) {
return sublayer->isVisible();
});
if (scanoutPossible && !output->directScanoutInhibited()) {
directScanout = primaryLayer->scanout(scanoutCandidate);
}
}
if (!directScanout) {
QRegion surfaceDamage = primaryLayer->repaints();
primaryLayer->resetRepaints();
preparePaintPass(superLayer, &surfaceDamage);
if (auto beginInfo = primaryLayer->beginFrame()) {
auto &[renderTarget, repaint] = beginInfo.value();
const QRegion bufferDamage = surfaceDamage.united(repaint).intersected(superLayer->rect().toAlignedRect());
paintPass(superLayer, renderTarget, bufferDamage);
primaryLayer->endFrame(bufferDamage, surfaceDamage);
}
}
postPaintPass(superLayer);
renderLoop->endFrame();
m_backend->present(output);
// TODO: Put it inside the cursor layer once the cursor layer can be backed by a real output layer.
if (waylandServer()) {
const std::chrono::milliseconds frameTime =
std::chrono::duration_cast<std::chrono::milliseconds>(output->renderLoop()->lastPresentationTimestamp());
if (!Cursors::self()->isCursorHidden()) {
Cursor *cursor = Cursors::self()->currentCursor();
if (cursor->geometry().intersects(output->geometry())) {
cursor->markAsRendered(frameTime);
}
}
}
}
void Compositor::prePaintPass(RenderLayer *layer)
{
layer->delegate()->prePaint();
const auto sublayers = layer->sublayers();
for (RenderLayer *sublayer : sublayers) {
prePaintPass(sublayer);
}
}
void Compositor::postPaintPass(RenderLayer *layer)
{
layer->delegate()->postPaint();
const auto sublayers = layer->sublayers();
for (RenderLayer *sublayer : sublayers) {
postPaintPass(sublayer);
}
}
void Compositor::preparePaintPass(RenderLayer *layer, QRegion *repaint)
{
// TODO: Cull opaque region.
*repaint += layer->mapToGlobal(layer->repaints() + layer->delegate()->repaints());
layer->resetRepaints();
const auto sublayers = layer->sublayers();
for (RenderLayer *sublayer : sublayers) {
if (sublayer->isVisible()) {
preparePaintPass(sublayer, repaint);
}
}
}
void Compositor::paintPass(RenderLayer *layer, const RenderTarget &renderTarget, const QRegion &region)
{
layer->delegate()->paint(renderTarget, region);
const auto sublayers = layer->sublayers();
for (RenderLayer *sublayer : sublayers) {
if (sublayer->isVisible()) {
paintPass(sublayer, renderTarget, region);
}
}
}
bool Compositor::isActive()
{
return m_state == State::On;
}
bool Compositor::compositingPossible() const
{
return true;
}
QString Compositor::compositingNotPossibleReason() const
{
return QString();
}
bool Compositor::openGLCompositingIsBroken() const
{
return false;
}
void Compositor::createOpenGLSafePoint(OpenGLSafePoint safePoint)
{
}
WaylandCompositor::WaylandCompositor(QObject *parent)
: Compositor(parent)
{
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed,
this, &WaylandCompositor::destroyCompositorSelection);
}
WaylandCompositor::~WaylandCompositor()
{
Q_EMIT aboutToDestroy();
stop(); // this can't be called in the destructor of Compositor
}
void WaylandCompositor::toggleCompositing()
{
// For the shortcut. Not possible on Wayland because we always composite.
}
void WaylandCompositor::start()
{
if (!Compositor::setupStart()) {
// Internal setup failed, abort.
return;
}
if (Workspace::self()) {
startupWithWorkspace();
} else {
connect(kwinApp(), &Application::workspaceCreated,
this, &WaylandCompositor::startupWithWorkspace);
}
}
X11Compositor::X11Compositor(QObject *parent)
: Compositor(parent)
, m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend)
{
if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) {
m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED");
}
}
X11Compositor::~X11Compositor()
{
Q_EMIT aboutToDestroy();
if (m_openGLFreezeProtectionThread) {
m_openGLFreezeProtectionThread->quit();
m_openGLFreezeProtectionThread->wait();
}
stop(); // this can't be called in the destructor of Compositor
}
X11SyncManager *X11Compositor::syncManager() const
{
return m_syncManager.get();
}
void X11Compositor::toggleCompositing()
{
if (m_suspended) {
// Direct user call; clear all bits.
resume(AllReasonSuspend);
} else {
// But only set the user one (sufficient to suspend).
suspend(UserSuspend);
}
}
void X11Compositor::reinitialize()
{
// Resume compositing if suspended.
m_suspended = NoReasonSuspend;
Compositor::reinitialize();
}
void X11Compositor::configChanged()
{
if (m_suspended) {
stop();
return;
}
Compositor::configChanged();
}
void X11Compositor::suspend(X11Compositor::SuspendReason reason)
{
Q_ASSERT(reason != NoReasonSuspend);
m_suspended |= reason;
if (reason & ScriptSuspend) {
// When disabled show a shortcut how the user can get back compositing.
const auto shortcuts = KGlobalAccel::self()->shortcut(
workspace()->findChild<QAction *>(QStringLiteral("Suspend Compositing")));
if (!shortcuts.isEmpty()) {
// Display notification only if there is the shortcut.
const QString message =
i18n("Desktop effects have been suspended by another application.<br/>"
"You can resume using the '%1' shortcut.",
shortcuts.first().toString(QKeySequence::NativeText));
#if KWIN_BUILD_NOTIFICATIONS
KNotification::event(QStringLiteral("compositingsuspendeddbus"), message);
#endif
}
}
stop();
}
void X11Compositor::resume(X11Compositor::SuspendReason reason)
{
Q_ASSERT(reason != NoReasonSuspend);
m_suspended &= ~reason;
start();
}
void X11Compositor::start()
{
if (m_suspended) {
QStringList reasons;
if (m_suspended & UserSuspend) {
reasons << QStringLiteral("Disabled by User");
}
if (m_suspended & BlockRuleSuspend) {
reasons << QStringLiteral("Disabled by Window");
}
if (m_suspended & ScriptSuspend) {
reasons << QStringLiteral("Disabled by Script");
}
qCInfo(KWIN_CORE) << "Compositing is suspended, reason:" << reasons;
return;
} else if (!compositingPossible()) {
qCWarning(KWIN_CORE) << "Compositing is not possible";
return;
}
if (!Compositor::setupStart()) {
// Internal setup failed, abort.
return;
}
startupWithWorkspace();
m_syncManager.reset(X11SyncManager::create());
}
void X11Compositor::stop()
{
m_syncManager.reset();
Compositor::stop();
}
void X11Compositor::composite(RenderLoop *renderLoop)
{
if (backend()->overlayWindow() && !isOverlayWindowVisible()) {
// Return since nothing is visible.
return;
}
QList<Window *> windows = workspace()->stackingOrder();
QList<SurfaceItemX11 *> dirtyItems;
// Reset the damage state of each window and fetch the damage region
// without waiting for a reply
for (Window *window : std::as_const(windows)) {
SurfaceItemX11 *surfaceItem = static_cast<SurfaceItemX11 *>(window->surfaceItem());
if (surfaceItem->fetchDamage()) {
dirtyItems.append(surfaceItem);
}
}
if (dirtyItems.count() > 0) {
if (m_syncManager) {
m_syncManager->triggerFence();
}
xcb_flush(kwinApp()->x11Connection());
}
// Get the replies
for (SurfaceItemX11 *item : std::as_const(dirtyItems)) {
item->waitForDamage();
}
if (m_framesToTestForSafety > 0 && (backend()->compositingType() & OpenGLCompositing)) {
createOpenGLSafePoint(OpenGLSafePoint::PreFrame);
}
Compositor::composite(renderLoop);
if (m_syncManager) {
if (!m_syncManager->endFrame()) {
qCDebug(KWIN_CORE) << "Aborting explicit synchronization with the X command stream.";
qCDebug(KWIN_CORE) << "Future frames will be rendered unsynchronized.";
m_syncManager.reset();
}
}
if (m_framesToTestForSafety > 0) {
if (backend()->compositingType() & OpenGLCompositing) {
createOpenGLSafePoint(OpenGLSafePoint::PostFrame);
}
m_framesToTestForSafety--;
if (m_framesToTestForSafety == 0 && (backend()->compositingType() & OpenGLCompositing)) {
createOpenGLSafePoint(OpenGLSafePoint::PostLastGuardedFrame);
}
}
}
bool X11Compositor::checkForOverlayWindow(WId w) const
{
if (!backend()) {
// No backend, so it cannot be the overlay window.
return false;
}
if (!backend()->overlayWindow()) {
// No overlay window, it cannot be the overlay.
return false;
}
// Compare the window ID's.
return w == backend()->overlayWindow()->window();
}
bool X11Compositor::isOverlayWindowVisible() const
{
if (!backend()) {
return false;
}
if (!backend()->overlayWindow()) {
return false;
}
return backend()->overlayWindow()->isVisible();
}
void X11Compositor::updateClientCompositeBlocking(X11Window *c)
{
if (c) {
if (c->isBlockingCompositing()) {
// Do NOT attempt to call suspend(true) from within the eventchain!
if (!(m_suspended & BlockRuleSuspend)) {
QMetaObject::invokeMethod(
this, [this]() {
suspend(BlockRuleSuspend);
},
Qt::QueuedConnection);
}
}
} else if (m_suspended & BlockRuleSuspend) {
// If !c we just check if we can resume in case a blocking client was lost.
bool shouldResume = true;
const auto windows = workspace()->windows();
for (Window *window : windows) {
X11Window *x11Window = qobject_cast<X11Window *>(window);
if (x11Window && x11Window->isBlockingCompositing()) {
shouldResume = false;
break;
}
}
if (shouldResume) {
// Do NOT attempt to call suspend(false) from within the eventchain!
QMetaObject::invokeMethod(
this, [this]() {
resume(BlockRuleSuspend);
},
Qt::QueuedConnection);
}
}
}
X11Compositor *X11Compositor::self()
{
return qobject_cast<X11Compositor *>(Compositor::self());
}
bool X11Compositor::openGLCompositingIsBroken() const
{
auto timestamp = KConfigGroup(kwinApp()->config(), "Compositing").readEntry(QLatin1String("LastFailureTimestamp"), 0);
if (timestamp > 0) {
if (QDateTime::currentSecsSinceEpoch() - timestamp < 60) {
return true;
}
}
return false;
}
QString X11Compositor::compositingNotPossibleReason() const
{
// first off, check whether we figured that we'll crash on detection because of a buggy driver
KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing");
if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && openGLCompositingIsBroken()) {
return i18n("<b>OpenGL compositing (the default) has crashed KWin in the past.</b><br>"
"This was most likely due to a driver bug."
"<p>If you think that you have meanwhile upgraded to a stable driver,<br>"
"you can reset this protection but <b>be aware that this might result in an immediate crash!</b></p>");
}
if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) {
return i18n("Required X extensions (XComposite and XDamage) are not available.");
}
if (!Xcb::Extensions::self()->hasGlx()) {
return i18n("GLX/OpenGL is not available.");
}
return QString();
}
bool X11Compositor::compositingPossible() const
{
// first off, check whether we figured that we'll crash on detection because of a buggy driver
KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing");
if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") && openGLCompositingIsBroken()) {
qCWarning(KWIN_CORE) << "Compositing disabled: video driver seems unstable. If you think it's a false positive, please try again in a few minutes.";
return false;
}
if (!Xcb::Extensions::self()->isCompositeAvailable()) {
qCWarning(KWIN_CORE) << "Compositing disabled: no composite extension available";
return false;
}
if (!Xcb::Extensions::self()->isDamageAvailable()) {
qCWarning(KWIN_CORE) << "Compositing disabled: no damage extension available";
return false;
}
if (Xcb::Extensions::self()->hasGlx()) {
return true;
}
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
return true;
} else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
return true;
}
qCWarning(KWIN_CORE) << "Compositing disabled: no OpenGL support";
return false;
}
void X11Compositor::createOpenGLSafePoint(OpenGLSafePoint safePoint)
{
auto group = KConfigGroup(kwinApp()->config(), "Compositing");
switch (safePoint) {
case OpenGLSafePoint::PreInit:
// Explicitly write the failure timestamp so that if we crash during
// OpenGL init, we know we should not try again.
group.writeEntry(QLatin1String("LastFailureTimestamp"), QDateTime::currentSecsSinceEpoch());
group.sync();
// Deliberately continue with PreFrame
Q_FALLTHROUGH();
case OpenGLSafePoint::PreFrame:
if (m_openGLFreezeProtectionThread == nullptr) {
Q_ASSERT(m_openGLFreezeProtection == nullptr);
m_openGLFreezeProtectionThread = std::make_unique<QThread>();
m_openGLFreezeProtectionThread->setObjectName("FreezeDetector");
m_openGLFreezeProtectionThread->start();
m_openGLFreezeProtection = std::make_unique<QTimer>();
m_openGLFreezeProtection->setInterval(15000);
m_openGLFreezeProtection->setSingleShot(true);
m_openGLFreezeProtection->start();
const QString configName = kwinApp()->config()->name();
m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread.get());
connect(
m_openGLFreezeProtection.get(), &QTimer::timeout, m_openGLFreezeProtection.get(),
[configName] {
auto group = KConfigGroup(KSharedConfig::openConfig(configName), "Compositing");
group.writeEntry(QLatin1String("LastFailureTimestamp"), QDateTime::currentSecsSinceEpoch());
group.sync();
KCrash::setDrKonqiEnabled(false);
qFatal("Freeze in OpenGL initialization detected");
},
Qt::DirectConnection);
} else {
Q_ASSERT(m_openGLFreezeProtection);
QMetaObject::invokeMethod(m_openGLFreezeProtection.get(), QOverload<>::of(&QTimer::start), Qt::QueuedConnection);
}
break;
case OpenGLSafePoint::PostInit:
group.deleteEntry(QLatin1String("LastFailureTimestamp"));
group.sync();
// Deliberately continue with PostFrame
Q_FALLTHROUGH();
case OpenGLSafePoint::PostFrame:
QMetaObject::invokeMethod(m_openGLFreezeProtection.get(), &QTimer::stop, Qt::QueuedConnection);
break;
case OpenGLSafePoint::PostLastGuardedFrame:
m_openGLFreezeProtectionThread->quit();
m_openGLFreezeProtectionThread->wait();
m_openGLFreezeProtectionThread.reset();
m_openGLFreezeProtection.reset();
break;
}
}
}
// included for CompositorSelectionOwner
#include "composite.moc"