diff --git a/autotests/integration/generic_scene_opengl_test.cpp b/autotests/integration/generic_scene_opengl_test.cpp index ff13007b8..33a9cf48d 100644 --- a/autotests/integration/generic_scene_opengl_test.cpp +++ b/autotests/integration/generic_scene_opengl_test.cpp @@ -1,118 +1,120 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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, see . *********************************************************************/ #include "generic_scene_opengl_test.h" #include "composite.h" #include "effectloader.h" #include "cursor.h" #include "platform.h" #include "scene.h" #include "shell_client.h" #include "wayland_server.h" #include "effect_builtins.h" #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_opengl-0"); GenericSceneOpenGLTest::GenericSceneOpenGLTest(const QByteArray &envVariable) : QObject() , m_envVariable(envVariable) { } GenericSceneOpenGLTest::~GenericSceneOpenGLTest() { } void GenericSceneOpenGLTest::cleanup() { Test::destroyWaylandConnection(); } void GenericSceneOpenGLTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); ScriptedEffectLoader loader; const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); qputenv("KWIN_COMPOSE", m_envVariable); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(Compositor::self()); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); QCOMPARE(scene->compositingType(), KWin::OpenGL2Compositing); + QCOMPARE(kwinApp()->platform()->selectedCompositor(), KWin::OpenGLCompositing); } void GenericSceneOpenGLTest::testRestart_data() { QTest::addColumn("core"); QTest::newRow("GLCore") << true; QTest::newRow("Legacy") << false; } void GenericSceneOpenGLTest::testRestart() { // simple restart of the OpenGL compositor without any windows being shown // setup opengl compositing options auto compositingGroup = kwinApp()->config()->group("Compositing"); QFETCH(bool, core); compositingGroup.writeEntry("GLCore", core); compositingGroup.sync(); QSignalSpy sceneCreatedSpy(KWin::Compositor::self(), &Compositor::sceneCreated); QVERIFY(sceneCreatedSpy.isValid()); KWin::Compositor::self()->slotReinitialize(); if (sceneCreatedSpy.isEmpty()) { QVERIFY(sceneCreatedSpy.wait()); } QCOMPARE(sceneCreatedSpy.count(), 1); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); QCOMPARE(scene->compositingType(), KWin::OpenGL2Compositing); + QCOMPARE(kwinApp()->platform()->selectedCompositor(), KWin::OpenGLCompositing); // trigger a repaint KWin::Compositor::self()->addRepaintFull(); // and wait 100 msec to ensure it's rendered // TODO: introduce frameRendered signal in SceneOpenGL QTest::qWait(100); } diff --git a/autotests/integration/scene_qpainter_test.cpp b/autotests/integration/scene_qpainter_test.cpp index a887fe1ca..61ff40e0c 100644 --- a/autotests/integration/scene_qpainter_test.cpp +++ b/autotests/integration/scene_qpainter_test.cpp @@ -1,396 +1,397 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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, see . *********************************************************************/ #include "kwin_wayland_test.h" #include "composite.h" #include "effectloader.h" #include "client.h" #include "cursor.h" #include "effects.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "effect_builtins.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include using namespace KWin; static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_qpainter-0"); class SceneQPainterTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanup(); void testStartFrame(); void testCursorMoving(); void testWindow_data(); void testWindow(); void testWindowScaled(); void testCompositorRestart_data(); void testCompositorRestart(); void testX11Window(); }; void SceneQPainterTest::cleanup() { Test::destroyWaylandConnection(); } void SceneQPainterTest::initTestCase() { qRegisterMetaType(); qRegisterMetaType(); QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); QVERIFY(workspaceCreatedSpy.isValid()); kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); // disable all effects - we don't want to have it interact with the rendering auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); KConfigGroup plugins(config, QStringLiteral("Plugins")); ScriptedEffectLoader loader; const auto builtinNames = BuiltInEffects::availableEffectNames() << loader.listOfKnownEffects(); for (QString name : builtinNames) { plugins.writeEntry(name + QStringLiteral("Enabled"), false); } config->sync(); kwinApp()->setConfig(config); if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); } else { // might be vanilla-dmz (e.g. Arch, FreeBSD) qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); } qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); kwinApp()->start(); QVERIFY(workspaceCreatedSpy.wait()); QVERIFY(Compositor::self()); } void SceneQPainterTest::testStartFrame() { // this test verifies that the initial rendering is correct Compositor::self()->addRepaintFull(); auto scene = Compositor::self()->scene(); QVERIFY(scene); + QCOMPARE(kwinApp()->platform()->selectedCompositor(), QPainterCompositing); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); QVERIFY(frameRenderedSpy.wait()); // now let's render a reference image for comparison QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter p(&referenceImage); const QImage cursorImage = kwinApp()->platform()->softwareCursor(); QVERIFY(!cursorImage.isNull()); p.drawImage(KWin::Cursor::pos() - kwinApp()->platform()->softwareCursorHotspot(), cursorImage); QCOMPARE(referenceImage, *scene->qpainterRenderBuffer()); } void SceneQPainterTest::testCursorMoving() { // this test verifies that rendering is correct also after moving the cursor a few times auto scene = Compositor::self()->scene(); QVERIFY(scene); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); KWin::Cursor::setPos(0, 0); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(10, 0); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(10, 12); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(12, 14); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(50, 60); QVERIFY(frameRenderedSpy.wait()); KWin::Cursor::setPos(45, 45); QVERIFY(frameRenderedSpy.wait()); // now let's render a reference image for comparison QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter p(&referenceImage); const QImage cursorImage = kwinApp()->platform()->softwareCursor(); QVERIFY(!cursorImage.isNull()); p.drawImage(QPoint(45, 45) - kwinApp()->platform()->softwareCursorHotspot(), cursorImage); QCOMPARE(referenceImage, *scene->qpainterRenderBuffer()); } void SceneQPainterTest::testWindow_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; } void SceneQPainterTest::testWindow() { KWin::Cursor::setPos(45, 45); // this test verifies that a window is rendered correctly using namespace KWayland::Client; QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); QScopedPointer s(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer ss(Test::createShellSurface(type, s.data())); QScopedPointer p(Test::waylandSeat()->createPointer()); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); // now let's map the window QVERIFY(Test::renderAndWaitForShown(s.data(), QSize(200, 300), Qt::blue)); // which should trigger a frame if (frameRenderedSpy.isEmpty()) { QVERIFY(frameRenderedSpy.wait()); } // we didn't set a cursor image on the surface yet, so it should be just black + window and previous cursor QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter painter(&referenceImage); painter.fillRect(0, 0, 200, 300, Qt::blue); // now let's set a cursor image QScopedPointer cs(Test::createSurface()); QVERIFY(!cs.isNull()); Test::render(cs.data(), QSize(10, 10), Qt::red); p->setCursor(cs.data(), QPoint(5, 5)); QVERIFY(frameRenderedSpy.wait()); painter.fillRect(KWin::Cursor::pos().x() - 5, KWin::Cursor::pos().y() - 5, 10, 10, Qt::red); QCOMPARE(referenceImage, *scene->qpainterRenderBuffer()); // let's move the cursor again KWin::Cursor::setPos(10, 10); QVERIFY(frameRenderedSpy.wait()); painter.fillRect(0, 0, 200, 300, Qt::blue); painter.fillRect(5, 5, 10, 10, Qt::red); QCOMPARE(referenceImage, *scene->qpainterRenderBuffer()); } void SceneQPainterTest::testWindowScaled() { KWin::Cursor::setPos(10, 10); // this test verifies that a window is rendered correctly using namespace KWayland::Client; QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); QScopedPointer s(Test::createSurface()); QScopedPointer ss(Test::createShellSurface(s.data())); QScopedPointer p(Test::waylandSeat()->createPointer()); QSignalSpy pointerEnteredSpy(p.data(), &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); // now let's set a cursor image QScopedPointer cs(Test::createSurface()); QVERIFY(!cs.isNull()); Test::render(cs.data(), QSize(10, 10), Qt::red); // now let's map the window s->setScale(2); //draw a blue square@400x600 with red rectangle@200x200 in the middle const QSize size(400,600); QImage img(size, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::blue); QPainter surfacePainter(&img); surfacePainter.fillRect(200,300,200,200, Qt::red); //add buffer Test::render(s.data(), img); QVERIFY(pointerEnteredSpy.wait()); p->setCursor(cs.data(), QPoint(5, 5)); // which should trigger a frame QVERIFY(frameRenderedSpy.wait()); QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter painter(&referenceImage); painter.fillRect(0, 0, 200, 300, Qt::blue); painter.fillRect(100, 150, 100, 100, Qt::red); painter.fillRect(5, 5, 10, 10, Qt::red); //cursor QCOMPARE(referenceImage, *scene->qpainterRenderBuffer()); } void SceneQPainterTest::testCompositorRestart_data() { QTest::addColumn("type"); QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; } void SceneQPainterTest::testCompositorRestart() { // this test verifies that the compositor/SceneQPainter survive a restart of the compositor and still render correctly KWin::Cursor::setPos(400, 400); // first create a window using namespace KWayland::Client; QVERIFY(Test::setupWaylandConnection()); QScopedPointer s(Test::createSurface()); QFETCH(Test::ShellSurfaceType, type); QScopedPointer ss(Test::createShellSurface(type, s.data())); QVERIFY(Test::renderAndWaitForShown(s.data(), QSize(200, 300), Qt::blue)); // now let's try to reinitialize the compositing scene auto oldScene = KWin::Compositor::self()->scene(); QVERIFY(oldScene); QSignalSpy sceneCreatedSpy(KWin::Compositor::self(), &KWin::Compositor::sceneCreated); QVERIFY(sceneCreatedSpy.isValid()); KWin::Compositor::self()->slotReinitialize(); if (sceneCreatedSpy.isEmpty()) { QVERIFY(sceneCreatedSpy.wait()); } QCOMPARE(sceneCreatedSpy.count(), 1); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); // this should directly trigger a frame KWin::Compositor::self()->addRepaintFull(); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); QVERIFY(frameRenderedSpy.wait()); // render reference image QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); referenceImage.fill(Qt::black); QPainter painter(&referenceImage); painter.fillRect(0, 0, 200, 300, Qt::blue); const QImage cursorImage = kwinApp()->platform()->softwareCursor(); QVERIFY(!cursorImage.isNull()); painter.drawImage(QPoint(400, 400) - kwinApp()->platform()->softwareCursorHotspot(), cursorImage); QCOMPARE(referenceImage, *scene->qpainterRenderBuffer()); } struct XcbConnectionDeleter { static inline void cleanup(xcb_connection_t *pointer) { xcb_disconnect(pointer); } }; void SceneQPainterTest::testX11Window() { // this test verifies the condition of BUG: 382748 // create X11 window QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); QVERIFY(windowAddedSpy.isValid()); // create an xcb window QScopedPointer c(xcb_connect(nullptr, nullptr)); QVERIFY(!xcb_connection_has_error(c.data())); const QRect windowGeometry(0, 0, 100, 200); xcb_window_t w = xcb_generate_id(c.data()); uint32_t value = defaultScreen()->white_pixel; xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), windowGeometry.x(), windowGeometry.y(), windowGeometry.width(), windowGeometry.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL, &value); xcb_size_hints_t hints; memset(&hints, 0, sizeof(hints)); xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); xcb_map_window(c.data(), w); xcb_flush(c.data()); // we should get a client for it QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); QVERIFY(windowCreatedSpy.isValid()); QVERIFY(windowCreatedSpy.wait()); Client *client = windowCreatedSpy.first().first().value(); QVERIFY(client); QCOMPARE(client->window(), w); QCOMPARE(client->clientSize(), QSize(100, 200)); if (!client->surface()) { // wait for surface QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); QVERIFY(surfaceChangedSpy.isValid()); QVERIFY(surfaceChangedSpy.wait()); } QVERIFY(client->surface()); QTRY_VERIFY(client->surface()->buffer()); QTRY_COMPARE(client->surface()->buffer()->data().size(), client->geometry().size()); QImage compareImage(client->clientSize(), QImage::Format_RGB32); compareImage.fill(Qt::white); QCOMPARE(client->surface()->buffer()->data().copy(QRect(client->clientPos(), client->clientSize())), compareImage); // enough time for rendering the window QTest::qWait(100); auto scene = KWin::Compositor::self()->scene(); QVERIFY(scene); // this should directly trigger a frame KWin::Compositor::self()->addRepaintFull(); QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); QVERIFY(frameRenderedSpy.isValid()); QVERIFY(frameRenderedSpy.wait()); const QPoint startPos = client->pos() + client->clientPos(); auto image = scene->qpainterRenderBuffer(); QCOMPARE(image->copy(QRect(startPos, client->clientSize())), compareImage); // and destroy the window again xcb_unmap_window(c.data(), w); xcb_flush(c.data()); QSignalSpy windowClosedSpy(client, &Client::windowClosed); QVERIFY(windowClosedSpy.isValid()); QVERIFY(windowClosedSpy.wait()); xcb_destroy_window(c.data(), w); c.reset(); } WAYLANDTEST_MAIN(SceneQPainterTest) #include "scene_qpainter_test.moc" diff --git a/composite.cpp b/composite.cpp index 5fd3a3106..ab096a4ed 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1213 +1,1215 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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, see . *********************************************************************/ #include "composite.h" #include "dbusinterface.h" #include "utils.h" #include #include "workspace.h" #include "client.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "scene.h" #include "screens.h" #include "shadow.h" #include "useractions.h" #include "xcbutils.h" #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include "decorations/decoratedclient.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::Compositor::SuspendReason) namespace KWin { extern int currentRefreshRate(); CompositorSelectionOwner::CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()), owning(false) { connect (this, SIGNAL(lostOwnership()), SLOT(looseOwnership())); } void CompositorSelectionOwner::looseOwnership() { owning = false; } KWIN_SINGLETON_FACTORY_VARIABLE(Compositor, s_compositor) static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , cm_selection(NULL) , vBlankInterval(0) , fpsInterval(0) , m_xrrRefreshRate(0) , m_finishing(false) , m_starting(false) , m_timeSinceLastVBlank(0) , m_scene(NULL) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { qRegisterMetaType("Compositor::SuspendReason"); connect(&compositeResetTimer, SIGNAL(timeout()), SLOT(restart())); connect(options, &Options::configChanged, this, &Compositor::slotConfigChanged); compositeResetTimer.setSingleShot(true); nextPaintReference.invalidate(); // Initialize the timer // 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, SIGNAL(timeout()), SLOT(releaseCompositorSelection())); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, SIGNAL(timeout()), SLOT(deleteUnusedSupportProperties())); // delay the call to setup 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. if (kwinApp()->platform()->isReady()) { QMetaObject::invokeMethod(this, "setup", Qt::QueuedConnection); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { setup(); } else { finish(); } }, Qt::QueuedConnection ); connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, [this] { delete cm_selection; cm_selection = nullptr; } ); if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); finish(); deleteUnusedSupportProperties(); delete cm_selection; s_compositor = NULL; } void Compositor::setup() { if (kwinApp()->isTerminating()) { // don't setup while KWin is terminating. An event to restart might be lingering in the event queue due to graphics reset return; } if (hasScene()) return; 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"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!kwinApp()->platform()->compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } m_starting = true; if (!options->isCompositingInitialized()) { options->reloadCompositingSettings(true); } slotCompositingOptionsInitialized(); } extern int screen_number; // main.cpp extern bool is_multihead; void Compositor::slotCompositingOptionsInitialized() { setupX11Support(); // There might still be a deleted around, needs to be cleared before creating the scene (BUG 333275) if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } emit aboutToToggleCompositing(); auto supportedCompositors = kwinApp()->platform()->supportedCompositors(); const auto userConfigIt = std::find(supportedCompositors.begin(), supportedCompositors.end(), options->compositingMode()); if (userConfigIt != supportedCompositors.end()) { supportedCompositors.erase(userConfigIt); supportedCompositors.prepend(options->compositingMode()); } else { qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; } const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.scenes")); for (auto type : qAsConst(supportedCompositors)) { const auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [type] (const auto &plugin) { const auto &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("CompositingType")); if (it != metaData.end()) { if ((*it).toInt() == int{type}) { return true; } } return false; }); if (pluginIt != availablePlugins.end()) { std::unique_ptr factory{qobject_cast(pluginIt->instantiate())}; if (factory) { m_scene = factory->create(this); if (m_scene) { if (!m_scene->initFailed()) { qCDebug(KWIN_CORE) << "Instantiated compositing plugin:" << pluginIt->name(); break; } else { delete m_scene; m_scene = nullptr; } } } } } if (m_scene == NULL || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; delete m_scene; m_scene = NULL; m_starting = false; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } if (!supportedCompositors.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; } + kwinApp()->platform()->setSelectedCompositor(m_scene->compositingType() & OpenGLCompositing ? OpenGLCompositing : m_scene->compositingType()); + if (!Workspace::self() && m_scene && m_scene->compositingType() == QPainterCompositing) { // Force Software QtQuick on first startup with QPainter QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); } connect(m_scene, &Scene::resetCompositing, this, &Compositor::restart); emit sceneCreated(); if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &Compositor::startupWithWorkspace); } } void Compositor::claimCompositorSelection() { if (!cm_selection) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); cm_selection = new CompositorSelectionOwner(selection_name); connect(cm_selection, SIGNAL(lostOwnership()), SLOT(finish())); } if (!cm_selection) // no X11 yet return; if (!cm_selection->owning) { cm_selection->claim(true); // force claiming cm_selection->owning = true; } } void Compositor::setupX11Support() { auto c = kwinApp()->x11Connection(); if (!c) { delete cm_selection; cm_selection = nullptr; return; } claimCompositorSelection(); xcb_composite_redirect_subwindows(c, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } void Compositor::startupWithWorkspace() { if (!m_starting) { return; } connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::setupX11Support, Qt::UniqueConnection); Workspace::self()->markXStackingOrderAsDirty(); Q_ASSERT(m_scene); connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); }); setupX11Support(); m_xrrRefreshRate = KWin::currentRefreshRate(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // if we do vsync, set the fps to the next multiple of the vblank rate vBlankInterval = milliToNano(1000) / m_xrrRefreshRate; fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else vBlankInterval = milliToNano(1); // no sync - DO NOT set "0", would cause div-by-zero segfaults. m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" - we don't have even a slight idea when the first vsync will occur scheduleRepaint(); kwinApp()->platform()->createEffectsHandler(this, m_scene); // sets also the 'effects' pointer connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::removeToplevel); connect(effects, SIGNAL(screenGeometryChanged(QSize)), SLOT(addRepaintFull())); addRepaintFull(); foreach (Client * c, Workspace::self()->clientList()) { c->setupCompositing(); c->getShadow(); } foreach (Client * c, Workspace::self()->desktopList()) c->setupCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) { c->setupCompositing(); c->getShadow(); } if (auto w = waylandServer()) { const auto clients = w->clients(); for (auto c : clients) { c->setupCompositing(); c->getShadow(); } const auto internalClients = w->internalClients(); for (auto c : internalClients) { c->setupCompositing(); c->getShadow(); } } emit compositingToggled(true); m_starting = false; if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // render at least once performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::finish() { if (!hasScene()) return; m_finishing = true; m_releaseSelectionTimer.start(); emit aboutToToggleCompositing(); // 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()) { foreach (Client * c, Workspace::self()->clientList()) m_scene->removeToplevel(c); foreach (Client * c, Workspace::self()->desktopList()) m_scene->removeToplevel(c); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) m_scene->removeToplevel(c); foreach (Deleted * c, Workspace::self()->deletedList()) m_scene->removeToplevel(c); foreach (Client * c, Workspace::self()->clientList()) c->finishCompositing(); foreach (Client * c, Workspace::self()->desktopList()) c->finishCompositing(); foreach (Unmanaged * c, Workspace::self()->unmanagedList()) c->finishCompositing(); foreach (Deleted * c, Workspace::self()->deletedList()) c->finishCompositing(); if (auto c = kwinApp()->x11Connection()) { xcb_composite_unredirect_subwindows(c, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } } if (waylandServer()) { foreach (ShellClient *c, waylandServer()->clients()) { m_scene->removeToplevel(c); } foreach (ShellClient *c, waylandServer()->internalClients()) { m_scene->removeToplevel(c); } foreach (ShellClient *c, waylandServer()->clients()) { c->finishCompositing(); } foreach (ShellClient *c, waylandServer()->internalClients()) { c->finishCompositing(); } } delete m_scene; m_scene = NULL; compositeTimer.stop(); repaints_region = QRegion(); if (Workspace::self()) { for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { // forward all opacity values to the frame in case there'll be other CM running if ((*it)->opacity() != 1.0) { NETWinInfo i(connection(), (*it)->frameId(), rootWindow(), 0, 0); i.setOpacity(static_cast< unsigned long >((*it)->opacity() * 0xffffffff)); } } // discard all Deleted windows (#152914) while (!Workspace::self()->deletedList().isEmpty()) Workspace::self()->deletedList().first()->discard(); } m_finishing = false; emit compositingToggled(false); } void Compositor::releaseCompositorSelection() { if (hasScene() && !m_finishing) { // compositor is up and running again, no need to release the selection return; } if (m_starting) { // currently still starting the compositor, it might fail, so restart the timer to test again m_releaseSelectionTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow, so restart the timer to test again m_releaseSelectionTimer.start(); return; } qCDebug(KWIN_CORE) << "Releasing compositor selection"; if (cm_selection) { cm_selection->owning = false; cm_selection->release(); } } 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_starting) { // currently still starting the compositor m_unusedSupportPropertyTimer.start(); return; } if (m_finishing) { // still shutting down, a restart might follow m_unusedSupportPropertyTimer.start(); return; } if (const auto c = kwinApp()->x11Connection()) { foreach (const xcb_atom_t &atom, m_unusedSupportProperties) { // remove property from root window xcb_delete_property(c, kwinApp()->x11RootWindow(), atom); } } } void Compositor::slotConfigChanged() { if (!m_suspended) { setup(); if (effects) // setupCompositing() may fail effects->reconfigure(); addRepaintFull(); } else finish(); } void Compositor::slotReinitialize() { // Reparse config. Config options will be reloaded by setup() kwinApp()->config()->reparseConfiguration(); // Restart compositing finish(); // resume compositing if suspended m_suspended = NoReasonSuspend; options->setCompositingInitialized(false); setup(); if (effects) { // setup() may fail effects->reconfigure(); } } // for the shortcut void Compositor::slotToggleCompositing() { if (kwinApp()->platform()->requiresCompositing()) { // we are not allowed to turn on/off compositing return; } if (m_suspended) { // direct user call; clear all bits resume(AllReasonSuspend); } else { // but only set the user one (sufficient to suspend) suspend(UserSuspend); } } void Compositor::updateCompositeBlocking() { updateCompositeBlocking(NULL); } void Compositor::updateCompositeBlocking(Client *c) { if (kwinApp()->platform()->requiresCompositing()) { return; } if (c) { // if c == 0 we just check if we can resume if (c->isBlockingCompositing()) { if (!(m_suspended & BlockRuleSuspend)) // do NOT attempt to call suspend(true); from within the eventchain! QMetaObject::invokeMethod(this, "suspend", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } else if (m_suspended & BlockRuleSuspend) { // lost a client and we're blocked - can we resume? bool resume = true; for (ClientList::ConstIterator it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { resume = false; break; } } if (resume) { // do NOT attempt to call suspend(false); from within the eventchain! QMetaObject::invokeMethod(this, "resume", Qt::QueuedConnection, Q_ARG(Compositor::SuspendReason, BlockRuleSuspend)); } } } void Compositor::suspend(Compositor::SuspendReason reason) { if (kwinApp()->platform()->requiresCompositing()) { return; } Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & KWin::Compositor::ScriptSuspend) { // when disabled show a shortcut how the user can get back compositing const auto shortcuts = KGlobalAccel::self()->shortcut(workspace()->findChild(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.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } finish(); } void Compositor::resume(Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; setup(); // signal "toggled" is eventually emitted from within setup } void Compositor::restart() { if (hasScene()) { finish(); QTimer::singleShot(0, this, SLOT(setup())); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (!hasScene()) return; repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (!hasScene()) return; repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (!hasScene()) return; const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { assert(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { assert(m_bufferSwapPending); m_bufferSwapPending = false; emit bufferSwapCompleted(); if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { if (m_scene->usesOverlayWindow() && !isOverlayWindowVisible()) return; // nothing is visible anyway // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (!kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order ToplevelList windows = Workspace::self()->xStackingOrder(); ToplevelList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply foreach (Toplevel *win, windows) { if (win->resetAndFetchDamage()) damaged << win; } if (damaged.count() > 0) { m_scene->triggerFence(); if (auto c = kwinApp()->x11Connection()) { xcb_flush(c); } } // Move elevated windows to the top of the stacking order foreach (EffectWindow *c, static_cast(effects)->elevatedWindows()) { Toplevel* t = static_cast< EffectWindowImpl* >(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies foreach (Toplevel *win, damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" m_timeSinceStart += m_timeSinceLastVBlank; // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. compositeTimer.stop(); return; } // skip windows that are not yet ready for being painted and if screen is locked skip windows that are // neither lockscreen nor inputmethod windows // TODO ? // this cannot be used so carelessly - needs protections against broken clients, the window // should not get focus before it's displayed, handle unredirected windows properly and so on. foreach (Toplevel *t, windows) { if (!t->readyForPainting()) { windows.removeAll(t); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!t->isLockScreen() && !t->isInputMethod()) { windows.removeAll(t); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); if (m_framesToTestForSafety > 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame); } m_timeSinceLastVBlank = m_scene->paint(repaints, windows); if (m_framesToTestForSafety > 0) { if (m_scene->compositingType() & OpenGLCompositing) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame); } m_framesToTestForSafety--; if (m_framesToTestForSafety == 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostLastGuardedFrame); } } m_timeSinceStart += m_timeSinceLastVBlank; if (waylandServer()) { for (Toplevel *win : qAsConst(windows)) { if (auto surface = win->surface()) { surface->frameRendered(m_timeSinceStart); } } } compositeTimer.stop(); // stop here to ensure *we* cause the next repaint schedule - not some effect through m_scene->paint() // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } template static bool repaintsPending(const QList &windows) { return std::any_of(windows.begin(), windows.end(), [] (T *t) { return !t->repaints().isEmpty(); }); } bool Compositor::windowRepaintsPending() const { if (repaintsPending(Workspace::self()->clientList())) { return true; } if (repaintsPending(Workspace::self()->desktopList())) { return true; } if (repaintsPending(Workspace::self()->unmanagedList())) { return true; } if (repaintsPending(Workspace::self()->deletedList())) { return true; } if (auto w = waylandServer()) { const auto &clients = w->clients(); auto test = [] (ShellClient *c) { return c->readyForPainting() && !c->repaints().isEmpty(); }; if (std::any_of(clients.begin(), clients.end(), test)) { return true; } const auto &internalClients = w->internalClients(); auto internalTest = [] (ShellClient *c) { return c->isShown(true) && !c->repaints().isEmpty(); }; if (std::any_of(internalClients.begin(), internalClients.end(), internalTest)) { return true; } } return false; } void Compositor::setCompositeResetTimer(int msecs) { compositeResetTimer.start(msecs); } void Compositor::setCompositeTimer() { if (!hasScene()) // should not really happen, but there may be e.g. some damage events still pending return; if (m_starting || !Workspace::self()) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (!kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // we're at low repaints or spent more time in painting than the user wanted to wait for that frame padding = vBlankInterval - (padding%vBlankInterval); // -> align to next vblank } else { // -> align to the next maxFps tick padding = ((vBlankInterval - padding%vBlankInterval) + (fpsInterval/vBlankInterval-1)*vBlankInterval); // "remaining time of the first vsync" + "time for the other vsyncs of the frame" } if (padding < options->vBlankTime()) { // we'll likely miss this frame waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); // so we add one } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { waitTime = 1; // will ensure we don't block out the eventloop - the system's just not faster ... } }/* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { waitTime = 1; // ... "0" would be sufficient, but the compositor isn't the WMs only task } } compositeTimer.start(qMin(waitTime, 250u), this); // force 4fps minimum } bool Compositor::isActive() { return !m_finishing && hasScene(); } bool Compositor::checkForOverlayWindow(WId w) const { if (!hasScene()) { // no scene, so it cannot be the overlay window return false; } if (!m_scene->overlayWindow()) { // no overlay window, it cannot be the overlay return false; } // and compare the window ID's return w == m_scene->overlayWindow()->window(); } bool Compositor::isOverlayWindowVisible() const { if (!hasScene()) { return false; } if (!m_scene->overlayWindow()) { return false; } return m_scene->overlayWindow()->isVisible(); } /***************************************************** * Workspace ****************************************************/ bool Workspace::compositing() const { return m_compositor && m_compositor->hasScene(); } //**************************************** // Toplevel //**************************************** bool Toplevel::setupCompositing() { if (!compositing()) return false; if (damage_handle != XCB_NONE) return false; if (kwinApp()->operationMode() == Application::OperationModeX11 && !surface()) { damage_handle = xcb_generate_id(connection()); xcb_damage_create(connection(), damage_handle, frameId(), XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); } damage_region = QRegion(0, 0, width(), height()); effect_window = new EffectWindowImpl(this); Compositor::self()->scene()->addToplevel(this); // With unmanaged windows there is a race condition between the client painting the window // and us setting up damage tracking. If the client wins we won't get a damage event even // though the window has been painted. To avoid this we mark the whole window as damaged // and schedule a repaint immediately after creating the damage object. if (dynamic_cast(this)) addDamageFull(); return true; } void Toplevel::finishCompositing(ReleaseReason releaseReason) { if (kwinApp()->operationMode() == Application::OperationModeX11 && damage_handle == XCB_NONE) return; if (effect_window->window() == this) { // otherwise it's already passed to Deleted, don't free data discardWindowPixmap(); delete effect_window; } if (damage_handle != XCB_NONE && releaseReason != ReleaseReason::Destroyed) { xcb_damage_destroy(connection(), damage_handle); } damage_handle = XCB_NONE; damage_region = QRegion(); repaints_region = QRegion(); effect_window = NULL; } void Toplevel::discardWindowPixmap() { addDamageFull(); if (effectWindow() != NULL && effectWindow()->sceneWindow() != NULL) effectWindow()->sceneWindow()->pixmapDiscarded(); } void Toplevel::damageNotifyEvent() { m_isDamaged = true; // Note: The rect is supposed to specify the damage extents, // but we don't know it at this point. No one who connects // to this signal uses the rect however. emit damaged(this, QRect()); } bool Toplevel::compositing() const { if (!Workspace::self()) { return false; } return Workspace::self()->compositing(); } void Client::damageNotifyEvent() { if (syncRequest.isPending && isResize()) { emit damaged(this, QRect()); m_isDamaged = true; return; } if (!ready_for_painting) { // avoid "setReadyForPainting()" function calling overhead if (syncRequest.counter == XCB_NONE) { // cannot detect complete redraw, consider done now setReadyForPainting(); setupWindowManagementInterface(); } } Toplevel::damageNotifyEvent(); } bool Toplevel::resetAndFetchDamage() { if (!m_isDamaged) return false; if (damage_handle == XCB_NONE) { m_isDamaged = false; return true; } xcb_connection_t *conn = connection(); // Create a new region and copy the damage region to it, // resetting the damaged state. xcb_xfixes_region_t region = xcb_generate_id(conn); xcb_xfixes_create_region(conn, region, 0, 0); xcb_damage_subtract(conn, damage_handle, 0, region); // Send a fetch-region request and destroy the region m_regionCookie = xcb_xfixes_fetch_region_unchecked(conn, region); xcb_xfixes_destroy_region(conn, region); m_isDamaged = false; m_damageReplyPending = true; return m_damageReplyPending; } void Toplevel::getDamageRegionReply() { if (!m_damageReplyPending) return; m_damageReplyPending = false; // Get the fetch-region reply xcb_xfixes_fetch_region_reply_t *reply = xcb_xfixes_fetch_region_reply(connection(), m_regionCookie, 0); if (!reply) return; // Convert the reply to a QRegion int count = xcb_xfixes_fetch_region_rectangles_length(reply); QRegion region; if (count > 1 && count < 16) { xcb_rectangle_t *rects = xcb_xfixes_fetch_region_rectangles(reply); QVector qrects; qrects.reserve(count); for (int i = 0; i < count; i++) qrects << QRect(rects[i].x, rects[i].y, rects[i].width, rects[i].height); region.setRects(qrects.constData(), count); } else region += QRect(reply->extents.x, reply->extents.y, reply->extents.width, reply->extents.height); damage_region += region; repaints_region += region; free(reply); } void Toplevel::addDamageFull() { if (!compositing()) return; damage_region = rect(); repaints_region |= rect(); emit damaged(this, rect()); } void Toplevel::resetDamage() { damage_region = QRegion(); } void Toplevel::addRepaint(const QRect& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addRepaint(r); } void Toplevel::addRepaint(const QRegion& r) { if (!compositing()) { return; } repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(const QRect& r) { if (!compositing()) { return; } layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addLayerRepaint(int x, int y, int w, int h) { QRect r(x, y, w, h); addLayerRepaint(r); } void Toplevel::addLayerRepaint(const QRegion& r) { if (!compositing()) return; layer_repaints_region += r; emit needsRepaint(); } void Toplevel::addRepaintFull() { repaints_region = visibleRect().translated(-pos()); emit needsRepaint(); } void Toplevel::resetRepaints() { repaints_region = QRegion(); layer_repaints_region = QRegion(); } void Toplevel::addWorkspaceRepaint(int x, int y, int w, int h) { addWorkspaceRepaint(QRect(x, y, w, h)); } void Toplevel::addWorkspaceRepaint(const QRect& r2) { if (!compositing()) return; Compositor::self()->addRepaint(r2); } //**************************************** // Client //**************************************** bool Client::setupCompositing() { if (!Toplevel::setupCompositing()){ return false; } updateVisibility(); // for internalKeep() return true; } void Client::finishCompositing(ReleaseReason releaseReason) { Toplevel::finishCompositing(releaseReason); updateVisibility(); // for safety in case KWin is just resizing the window resetHaveResizeEffect(); } } // namespace diff --git a/platform.h b/platform.h index 765d16794..e2fdc00cb 100644 --- a/platform.h +++ b/platform.h @@ -1,540 +1,560 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #ifndef KWIN_PLATFORM_H #define KWIN_PLATFORM_H #include #include #include #include "fixqopengl.h" #include #include #include #include class QAction; namespace KWayland { namespace Server { class OutputConfigurationInterface; } } namespace KWin { namespace ColorCorrect { class Manager; } class AbstractOutput; class Edge; class Compositor; class OverlayWindow; class OpenGLBackend; class Outline; class OutlineVisual; class QPainterBackend; class Scene; class Screens; class ScreenEdges; class Toplevel; class WaylandCursorTheme; namespace Decoration { class Renderer; class DecoratedClientImpl; } class KWIN_EXPORT Outputs : public QVector { public: Outputs(){}; template Outputs(const QVector &other) { resize(other.size()); std::copy(other.constBegin(), other.constEnd(), begin()); } }; class KWIN_EXPORT Platform : public QObject { Q_OBJECT public: virtual ~Platform(); virtual void init() = 0; virtual Screens *createScreens(QObject *parent = nullptr); virtual OpenGLBackend *createOpenGLBackend(); virtual QPainterBackend *createQPainterBackend(); /** * Allows the platform to create a platform specific screen edge. * The default implementation creates a Edge. **/ virtual Edge *createScreenEdge(ScreenEdges *parent); /** * Allows the platform to create a platform specific Cursor. * The default implementation creates an InputRedirectionCursor. **/ virtual void createPlatformCursor(QObject *parent = nullptr); virtual void warpPointer(const QPointF &globalPos); /** * Whether our Compositing EGL display allows a surface less context * so that a sharing context could be created. **/ virtual bool supportsQpaContext() const; /** * The EGLDisplay used by the compositing scene. **/ EGLDisplay sceneEglDisplay() const; void setSceneEglDisplay(EGLDisplay display); /** * The EGLContext used by the compositing scene. **/ virtual EGLContext sceneEglContext() const { return m_context; } /** * Sets the @p context used by the compositing scene. **/ void setSceneEglContext(EGLContext context) { m_context = context; } /** * The first (in case of multiple) EGLSurface used by the compositing scene. **/ EGLSurface sceneEglSurface() const { return m_surface; } /** * Sets the first @p surface used by the compositing scene. * @see sceneEglSurface **/ void setSceneEglSurface(EGLSurface surface) { m_surface = surface; } /** * The EglConfig used by the compositing scene. **/ EGLConfig sceneEglConfig() const { return m_eglConfig; } /** * Sets the @p config used by the compositing scene. * @see sceneEglConfig **/ void setSceneEglConfig(EGLConfig config) { m_eglConfig = config; } /** * Implementing subclasses should provide a size in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns an invalid size. **/ virtual QSize screenSize() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns one QRect positioned at 0/0 with screenSize() as size. **/ virtual QVector screenGeometries() const; /** * Implementing subclasses should provide all geometries in case the backend represents * a basic screen and uses the BasicScreens. * * Base implementation returns a screen with a scale of 1. **/ virtual QVector screenScales() const; /** * Implement this method to receive configuration change requests through KWayland's * OutputManagement interface. * * Base implementation warns that the current backend does not implement this * functionality. **/ virtual void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); /** * Whether the Platform requires compositing for rendering. * Default implementation returns @c true. If the implementing Platform allows to be used * without compositing (e.g. rendering is done by the windowing system), re-implement this method. **/ virtual bool requiresCompositing() const; /** * Whether Compositing is possible in the Platform. * Returning @c false in this method makes only sense if requiresCompositing returns @c false. * * The default implementation returns @c true. * @see requiresCompositing **/ virtual bool compositingPossible() const; /** * Returns a user facing text explaining why compositing is not possible in case * compositingPossible returns @c false. * * The default implementation returns an empty string. * @see compositingPossible **/ virtual QString compositingNotPossibleReason() const; /** * Whether OpenGL compositing is broken. * The Platform can implement this method if it is able to detect whether OpenGL compositing * broke (e.g. triggered a crash in a previous run). * * Default implementation returns @c false. * @see createOpenGLSafePoint **/ virtual bool openGLCompositingIsBroken() const; enum class OpenGLSafePoint { PreInit, PostInit, PreFrame, PostFrame, PostLastGuardedFrame }; /** * This method is invoked before and after creating the OpenGL rendering Scene. * An implementing Platform can use it to detect crashes triggered by the OpenGL implementation. * This can be used for openGLCompositingIsBroken. * * The default implementation does nothing. * @see openGLCompositingIsBroken. **/ virtual void createOpenGLSafePoint(OpenGLSafePoint safePoint); /** * Starts an interactive window selection process. * * Once the user selected a window the @p callback is invoked with the selected Toplevel as * argument. In case the user cancels the interactive window selection or selecting a window is currently * not possible (e.g. screen locked) the @p callback is invoked with a @c nullptr argument. * * During the interactive window selection the cursor is turned into a crosshair cursor unless * @p cursorName is provided. The argument @p cursorName is a QByteArray instead of Qt::CursorShape * to support the "pirate" cursor for kill window which is not wrapped by Qt::CursorShape. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive window selection ends * @param cursorName The optional name of the cursor shape to use, default is crosshair **/ virtual void startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName = QByteArray()); /** * Starts an interactive position selection process. * * Once the user selected a position on the screen the @p callback is invoked with * the selected point as argument. In case the user cancels the interactive position selection * or selecting a position is currently not possible (e.g. screen locked) the @p callback * is invoked with a point at @c -1 as x and y argument. * * During the interactive window selection the cursor is turned into a crosshair cursor. * * The default implementation forwards to InputRedirection. * * @param callback The function to invoke once the interactive position selection ends **/ virtual void startInteractivePositionSelection(std::function callback); /** * Platform specific preparation for an @p action which is used for KGlobalAccel. * * A platform might need to do preparation for an @p action before * it can be used with KGlobalAccel. * * Code using KGlobalAccel should invoke this method for the @p action * prior to setting up any shortcuts and connections. * * The default implementation does nothing. * * @param action The action which will be used with KGlobalAccel. * @since 5.10 **/ virtual void setupActionForGlobalAccel(QAction *action); bool usesSoftwareCursor() const { return m_softWareCursor; } QImage softwareCursor() const; QPoint softwareCursorHotspot() const; void markCursorAsRendered(); /** * Returns a PlatformCursorImage. By default this is created by softwareCursor and * softwareCursorHotspot. An implementing subclass can use this to provide a better * suited PlatformCursorImage. * * @see softwareCursor * @see softwareCursorHotspot * @since 5.9 **/ virtual PlatformCursorImage cursorImage() const; /** * The Platform cursor image should be hidden. * @see showCursor * @see doHideCursor * @see isCursorHidden * @since 5.9 **/ void hideCursor(); /** * The Platform cursor image should be shown again. * @see hideCursor * @see doShowCursor * @see isCursorHidden * @since 5.9 **/ void showCursor(); /** * Whether the cursor is currently hidden. * @see showCursor * @see hideCursor * @since 5.9 **/ bool isCursorHidden() const { return m_hideCursorCounter > 0; } bool handlesOutputs() const { return m_handlesOutputs; } bool isReady() const { return m_ready; } void setInitialWindowSize(const QSize &size) { m_initialWindowSize = size; } void setDeviceIdentifier(const QByteArray &identifier) { m_deviceIdentifier = identifier; } bool supportsPointerWarping() const { return m_pointerWarping; } bool areOutputsEnabled() const { return m_outputsEnabled; } void setOutputsEnabled(bool enabled) { m_outputsEnabled = enabled; } int initialOutputCount() const { return m_initialOutputCount; } void setInitialOutputCount(int count) { m_initialOutputCount = count; } qreal initialOutputScale() const { return m_initialOutputScale; } void setInitialOutputScale(qreal scale) { m_initialOutputScale = scale; } /** * Creates the OverlayWindow required for X11 based compositors. * Default implementation returns @c nullptr. **/ virtual OverlayWindow *createOverlayWindow(); /** * Allows a platform to update the X11 timestamp. * Mostly for the X11 standalone platform to interact with QX11Info. * * Default implementation does nothing. This means code relying on the X timestamp being up to date, * might not be working. E.g. synced X11 window resizing **/ virtual void updateXTime(); /** * Creates the OutlineVisual for the given @p outline. * Default implementation creates an OutlineVisual suited for composited usage. **/ virtual OutlineVisual *createOutline(Outline *outline); /** * Creates the Decoration::Renderer for the given @p client. * * The default implementation creates a Renderer suited for the Compositor, @c nullptr if there is no Compositor. **/ virtual Decoration::Renderer *createDecorationRenderer(Decoration::DecoratedClientImpl *client); /** * Platform specific way to invert the screen. * Default implementation invokes the invert effect **/ virtual void invertScreen(); /** * Default implementation creates an EffectsHandlerImp; **/ virtual void createEffectsHandler(Compositor *compositor, Scene *scene); /** * The CompositingTypes supported by the Platform. * The first item should be the most preferred one. * @since 5.11 **/ virtual QVector supportedCompositors() const = 0; /** * Whether gamma control is supported by the backend. * @since 5.12 **/ bool supportsGammaControl() const { return m_supportsGammaControl; } ColorCorrect::Manager *colorCorrectManager() { return m_colorCorrect; } // outputs with connections (org_kde_kwin_outputdevice) virtual Outputs outputs() const { return Outputs(); } // actively compositing outputs (wl_output) virtual Outputs enabledOutputs() const { return Outputs(); } /** * A string of information to include in kwin debug output * It should not be translated. * * The base implementation prints the name. * @since 5.12 **/ virtual QString supportInformation() const; + /** + * The compositor plugin which got selected from @link{supportedCompositors}. + * Prior to selecting a compositor this returns @c NoCompositing. + * + * This method allows the platforms to limit the offerings in @link{supportedCompositors} + * in case they do not support runtime compositor switching + **/ + CompositingType selectedCompositor() const + { + return m_selectedCompositor; + } + /** + * Used by Compositor to set the used compositor. + **/ + void setSelectedCompositor(CompositingType type) + { + m_selectedCompositor = type; + } + public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); void pointerButtonReleased(quint32 button, quint32 time); void pointerAxisHorizontal(qreal delta, quint32 time); void pointerAxisVertical(qreal delta, quint32 time); void keyboardKeyPressed(quint32 key, quint32 time); void keyboardKeyReleased(quint32 key, quint32 time); void keyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void keymapChange(int fd, uint32_t size); void touchDown(qint32 id, const QPointF &pos, quint32 time); void touchUp(qint32 id, quint32 time); void touchMotion(qint32 id, const QPointF &pos, quint32 time); void touchCancel(); void touchFrame(); void processSwipeGestureBegin(int fingerCount, quint32 time); void processSwipeGestureUpdate(const QSizeF &delta, quint32 time); void processSwipeGestureEnd(quint32 time); void processSwipeGestureCancelled(quint32 time); void processPinchGestureBegin(int fingerCount, quint32 time); void processPinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time); void processPinchGestureEnd(quint32 time); void processPinchGestureCancelled(quint32 time); Q_SIGNALS: void screensQueried(); void initFailed(); void cursorChanged(); void readyChanged(bool); /** * Emitted by backends using a one screen (nested window) approach and when the size of that changes. **/ void screenSizeChanged(); protected: explicit Platform(QObject *parent = nullptr); void setSoftWareCursor(bool set); void handleOutputs() { m_handlesOutputs = true; } void repaint(const QRect &rect); void setReady(bool ready); QSize initialWindowSize() const { return m_initialWindowSize; } QByteArray deviceIdentifier() const { return m_deviceIdentifier; } void setSupportsPointerWarping(bool set) { m_pointerWarping = set; } void setSupportsGammaControl(bool set) { m_supportsGammaControl = set; } /** * Actual platform specific way to hide the cursor. * Sub-classes need to implement if they support hiding the cursor. * * This method is invoked by hideCursor if the cursor needs to be hidden. * The default implementation does nothing. * * @see doShowCursor * @see hideCursor * @see showCursor **/ virtual void doHideCursor(); /** * Actual platform specific way to show the cursor. * Sub-classes need to implement if they support showing the cursor. * * This method is invoked by showCursor if the cursor needs to be shown again. * * @see doShowCursor * @see hideCursor * @see showCursor **/ virtual void doShowCursor(); private: void triggerCursorRepaint(); bool m_softWareCursor = false; struct { QRect lastRenderedGeometry; } m_cursor; bool m_handlesOutputs = false; bool m_ready = false; QSize m_initialWindowSize; QByteArray m_deviceIdentifier; bool m_pointerWarping = false; bool m_outputsEnabled = true; int m_initialOutputCount = 1; qreal m_initialOutputScale = 1; EGLDisplay m_eglDisplay; EGLConfig m_eglConfig = nullptr; EGLContext m_context = EGL_NO_CONTEXT; EGLSurface m_surface = EGL_NO_SURFACE; int m_hideCursorCounter = 0; ColorCorrect::Manager *m_colorCorrect = nullptr; bool m_supportsGammaControl = false; + CompositingType m_selectedCompositor = NoCompositing; }; } Q_DECLARE_INTERFACE(KWin::Platform, "org.kde.kwin.Platform") #endif diff --git a/plugins/platforms/drm/drm_backend.cpp b/plugins/platforms/drm/drm_backend.cpp index 0ae0a7069..34c7c386a 100644 --- a/plugins/platforms/drm/drm_backend.cpp +++ b/plugins/platforms/drm/drm_backend.cpp @@ -1,790 +1,793 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #include "drm_backend.h" #include "drm_output.h" #include "drm_object_connector.h" #include "drm_object_crtc.h" #include "drm_object_plane.h" #include "composite.h" #include "cursor.h" #include "logging.h" #include "logind.h" #include "main.h" #include "scene_qpainter_drm_backend.h" #include "screens_drm.h" #include "udev.h" #include "wayland_server.h" #if HAVE_GBM #include "egl_gbm_backend.h" #include #endif // KWayland #include #include // KF5 #include #include #include #include // Qt #include #include #include // system #include #include // drm #include #include #include #ifndef DRM_CAP_CURSOR_WIDTH #define DRM_CAP_CURSOR_WIDTH 0x8 #endif #ifndef DRM_CAP_CURSOR_HEIGHT #define DRM_CAP_CURSOR_HEIGHT 0x9 #endif #define KWIN_DRM_EVENT_CONTEXT_VERSION 2 namespace KWin { DrmBackend::DrmBackend(QObject *parent) : Platform(parent) , m_udev(new Udev) , m_udevMonitor(m_udev->monitor()) , m_dpmsFilter() { setSupportsGammaControl(true); handleOutputs(); } DrmBackend::~DrmBackend() { #if HAVE_GBM if (m_gbmDevice) { gbm_device_destroy(m_gbmDevice); } #endif if (m_fd >= 0) { // wait for pageflips while (m_pageFlipsPending != 0) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); } // we need to first remove all outputs qDeleteAll(m_outputs); m_outputs.clear(); m_enabledOutputs.clear(); qDeleteAll(m_planes); qDeleteAll(m_crtcs); qDeleteAll(m_connectors); close(m_fd); } } void DrmBackend::init() { LogindIntegration *logind = LogindIntegration::self(); auto takeControl = [logind, this]() { if (logind->hasSessionControl()) { openDrm(); } else { logind->takeControl(); connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm); } }; if (logind->isConnected()) { takeControl(); } else { connect(logind, &LogindIntegration::connectedChanged, this, takeControl); } } Outputs DrmBackend::outputs() const { return m_outputs; } Outputs DrmBackend::enabledOutputs() const { return m_enabledOutputs; } void DrmBackend::outputWentOff() { if (!m_dpmsFilter.isNull()) { // already another output is off return; } m_dpmsFilter.reset(new DpmsInputEventFilter(this)); input()->prependInputEventFilter(m_dpmsFilter.data()); } void DrmBackend::turnOutputsOn() { m_dpmsFilter.reset(); for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { (*it)->updateDpms(KWayland::Server::OutputInterface::DpmsMode::On); } } void DrmBackend::checkOutputsAreOn() { if (m_dpmsFilter.isNull()) { // already disabled, all outputs are on return; } for (auto it = m_enabledOutputs.constBegin(), end = m_enabledOutputs.constEnd(); it != end; it++) { if (!(*it)->isDpmsEnabled()) { // dpms still disabled, need to keep the filter return; } } // all outputs are on, disable the filter m_dpmsFilter.reset(); } void DrmBackend::activate(bool active) { if (active) { qCDebug(KWIN_DRM) << "Activating session."; reactivate(); } else { qCDebug(KWIN_DRM) << "Deactivating session."; deactivate(); } } void DrmBackend::reactivate() { if (m_active) { return; } m_active = true; if (!usesSoftwareCursor()) { const QPoint cp = Cursor::pos() - softwareCursorHotspot(); for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; // only relevant in atomic mode o->m_modesetRequested = true; o->pageFlipped(); // TODO: Do we really need this? o->m_crtc->blank(); o->showCursor(); o->moveCursor(cp); } } // restart compositor m_pageFlipsPending = 0; if (Compositor *compositor = Compositor::self()) { compositor->bufferSwapComplete(); compositor->addRepaintFull(); } } void DrmBackend::deactivate() { if (!m_active) { return; } // block compositor if (m_pageFlipsPending == 0 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } // hide cursor and disable for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { DrmOutput *o = *it; o->hideCursor(); } m_active = false; } void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { Q_UNUSED(fd) Q_UNUSED(frame) Q_UNUSED(sec) Q_UNUSED(usec) auto output = reinterpret_cast(data); output->pageFlipped(); output->m_backend->m_pageFlipsPending--; if (output->m_backend->m_pageFlipsPending == 0) { // TODO: improve, this currently means we wait for all page flips or all outputs. // It would be better to driver the repaint per output if (output->m_dpmsAtomicOffPending) { output->m_modesetRequested = true; output->dpmsAtomicOff(); } if (Compositor::self()) { Compositor::self()->bufferSwapComplete(); } } } void DrmBackend::openDrm() { connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, this, &DrmBackend::activate); UdevDevice::Ptr device = m_udev->primaryGpu(); if (!device) { qCWarning(KWIN_DRM) << "Did not find a GPU"; return; } int fd = LogindIntegration::self()->takeDevice(device->devNode()); if (fd < 0) { qCWarning(KWIN_DRM) << "failed to open drm device at" << device->devNode(); return; } m_fd = fd; m_active = true; QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { if (!LogindIntegration::self()->isActiveSession()) { return; } drmEventContext e; memset(&e, 0, sizeof e); e.version = KWIN_DRM_EVENT_CONTEXT_VERSION; e.page_flip_handler = pageFlipHandler; drmHandleEvent(m_fd, &e); } ); m_drmId = device->sysNum(); // trying to activate Atomic Mode Setting (this means also Universal Planes) if (!qEnvironmentVariableIsSet("KWIN_DRM_NO_AMS")) { if (drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1) == 0) { qCDebug(KWIN_DRM) << "Using Atomic Mode Setting."; m_atomicModeSetting = true; ScopedDrmPointer planeResources(drmModeGetPlaneResources(m_fd)); if (!planeResources) { qCWarning(KWIN_DRM) << "Failed to get plane resources. Falling back to legacy mode"; m_atomicModeSetting = false; } if (m_atomicModeSetting) { qCDebug(KWIN_DRM) << "Number of planes:" << planeResources->count_planes; // create the plane objects for (unsigned int i = 0; i < planeResources->count_planes; ++i) { drmModePlane *kplane = drmModeGetPlane(m_fd, planeResources->planes[i]); DrmPlane *p = new DrmPlane(kplane->plane_id, m_fd); if (p->atomicInit()) { m_planes << p; if (p->type() == DrmPlane::TypeIndex::Overlay) { m_overlayPlanes << p; } } else { delete p; } } if (m_planes.isEmpty()) { qCWarning(KWIN_DRM) << "Failed to create any plane. Falling back to legacy mode"; m_atomicModeSetting = false; } } } else { qCWarning(KWIN_DRM) << "drmSetClientCap for Atomic Mode Setting failed. Using legacy mode."; } } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); drmModeRes *res = resources.data(); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } for (int i = 0; i < res->count_connectors; ++i) { m_connectors << new DrmConnector(res->connectors[i], m_fd); } for (int i = 0; i < res->count_crtcs; ++i) { m_crtcs << new DrmCrtc(res->crtcs[i], this, i); } if (m_atomicModeSetting) { auto tryAtomicInit = [] (DrmObject *obj) -> bool { if (obj->atomicInit()) { return false; } else { delete obj; return true; } }; m_connectors.erase(std::remove_if(m_connectors.begin(), m_connectors.end(), tryAtomicInit), m_connectors.end()); m_crtcs.erase(std::remove_if(m_crtcs.begin(), m_crtcs.end(), tryAtomicInit), m_crtcs.end()); } initCursor(); updateOutputs(); if (m_outputs.isEmpty()) { qCWarning(KWIN_DRM) << "No outputs, cannot render, will terminate now"; emit initFailed(); return; } // setup udevMonitor if (m_udevMonitor) { m_udevMonitor->filterSubsystemDevType("drm"); const int fd = m_udevMonitor->fd(); if (fd != -1) { QSocketNotifier *notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this] { auto device = m_udevMonitor->getDevice(); if (!device) { return; } if (device->sysNum() != m_drmId) { return; } if (device->hasProperty("HOTPLUG", "1")) { qCDebug(KWIN_DRM) << "Received hot plug event for monitored drm device"; updateOutputs(); updateCursor(); } } ); m_udevMonitor->enable(); } } setReady(true); } void DrmBackend::updateOutputs() { if (m_fd < 0) { return; } ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd)); if (!resources) { qCWarning(KWIN_DRM) << "drmModeGetResources failed"; return; } QVector connectedOutputs; QVector pendingConnectors; // split up connected connectors in already or not yet assigned ones for (DrmConnector *con : qAsConst(m_connectors)) { if (!con->isConnected()) { continue; } if (DrmOutput *o = findOutput(con->id())) { connectedOutputs << o; } else { pendingConnectors << con; } } // check for outputs which got removed auto it = m_outputs.begin(); while (it != m_outputs.end()) { if (connectedOutputs.contains(*it)) { it++; continue; } DrmOutput *removed = *it; it = m_outputs.erase(it); m_enabledOutputs.removeOne(removed); emit outputRemoved(removed); removed->teardown(); } // now check new connections for (DrmConnector *con : qAsConst(pendingConnectors)) { ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_fd, con->id())); if (!connector) { continue; } if (connector->count_modes == 0) { continue; } bool outputDone = false; QVector encoders = con->encoders(); for (auto encId : qAsConst(encoders)) { ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, encId)); if (!encoder) { continue; } for (DrmCrtc *crtc : qAsConst(m_crtcs)) { if (!(encoder->possible_crtcs & (1 << crtc->resIndex()))) { continue; } // check if crtc isn't used yet -- currently we don't allow multiple outputs on one crtc (cloned mode) auto it = std::find_if(connectedOutputs.constBegin(), connectedOutputs.constEnd(), [crtc] (DrmOutput *o) { return o->m_crtc == crtc; } ); if (it != connectedOutputs.constEnd()) { continue; } // we found a suitable encoder+crtc // TODO: we could avoid these lib drm calls if we store all struct data in DrmCrtc and DrmConnector in the beginning ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> modeCrtc(drmModeGetCrtc(m_fd, crtc->id())); if (!modeCrtc) { continue; } DrmOutput *output = new DrmOutput(this); con->setOutput(output); output->m_conn = con; crtc->setOutput(output); output->m_crtc = crtc; connect(output, &DrmOutput::dpmsChanged, this, &DrmBackend::outputDpmsChanged); if (modeCrtc->mode_valid) { output->m_mode = modeCrtc->mode; } else { output->m_mode = connector->modes[0]; } qCDebug(KWIN_DRM) << "For new output use mode " << output->m_mode.name; if (!output->init(connector.data())) { qCWarning(KWIN_DRM) << "Failed to create output for connector " << con->id(); delete output; continue; } if (!output->initCursor(m_cursorSize)) { setSoftWareCursor(true); } qCDebug(KWIN_DRM) << "Found new output with uuid" << output->uuid(); connectedOutputs << output; emit outputAdded(output); outputDone = true; break; } if (outputDone) { break; } } } std::sort(connectedOutputs.begin(), connectedOutputs.end(), [] (DrmOutput *a, DrmOutput *b) { return a->m_conn->id() < b->m_conn->id(); }); m_outputs = connectedOutputs; m_enabledOutputs = connectedOutputs; readOutputsConfiguration(); if (!m_outputs.isEmpty()) { emit screensQueried(); } } void DrmBackend::readOutputsConfiguration() { if (m_outputs.isEmpty()) { return; } const QByteArray uuid = generateOutputConfigurationUuid(); const auto outputGroup = kwinApp()->config()->group("DrmOutputs"); const auto configGroup = outputGroup.group(uuid); // default position goes from left to right QPoint pos(0, 0); for (auto it = m_outputs.begin(); it != m_outputs.end(); ++it) { qCDebug(KWIN_DRM) << "Reading output configuration for [" << uuid << "] ["<< (*it)->uuid() << "]"; const auto outputConfig = configGroup.group((*it)->uuid()); (*it)->setGlobalPos(outputConfig.readEntry("Position", pos)); // TODO: add mode (*it)->setScale(outputConfig.readEntry("Scale", 1.0)); pos.setX(pos.x() + (*it)->geometry().width()); } } QByteArray DrmBackend::generateOutputConfigurationUuid() const { auto it = m_outputs.constBegin(); if (m_outputs.size() == 1) { // special case: one output return (*it)->uuid(); } QCryptographicHash hash(QCryptographicHash::Md5); for (; it != m_outputs.constEnd(); ++it) { hash.addData((*it)->uuid()); } return hash.result().toHex().left(10); } void DrmBackend::configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config) { const auto changes = config->changes(); bool countChanged = false; //process all non-disabling changes for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); continue; } if (changeset->enabledChanged() && changeset->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Enabled) { drmoutput->setEnabled(true); m_enabledOutputs << drmoutput; emit outputAdded(drmoutput); countChanged = true; } drmoutput->setChanges(changeset); } //process any disable requests for (auto it = changes.begin(); it != changes.end(); it++) { KWayland::Server::OutputChangeSet *changeset = it.value(); if (changeset->enabledChanged() && changeset->enabled() == KWayland::Server::OutputDeviceInterface::Enablement::Disabled) { if (m_enabledOutputs.count() == 1) { qCWarning(KWIN_DRM) << "Not disabling final screen" << it.key()->uuid(); continue; } auto drmoutput = findOutput(it.key()->uuid()); if (drmoutput == nullptr) { qCWarning(KWIN_DRM) << "Could NOT find DrmOutput matching " << it.key()->uuid(); continue; } drmoutput->setEnabled(false); m_enabledOutputs.removeOne(drmoutput); emit outputRemoved(drmoutput); countChanged = true; } } if (countChanged) { emit screensQueried(); } else { emit screens()->changed(); } config->setApplied(); } DrmOutput *DrmBackend::findOutput(quint32 connector) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [connector] (DrmOutput *o) { return o->m_conn->id() == connector; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } DrmOutput *DrmBackend::findOutput(const QByteArray &uuid) { auto it = std::find_if(m_outputs.constBegin(), m_outputs.constEnd(), [uuid] (DrmOutput *o) { return o->m_uuid == uuid; }); if (it != m_outputs.constEnd()) { return *it; } return nullptr; } void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output) { if (!buffer || buffer->bufferId() == 0) { if (m_deleteBufferAfterPageFlip) { delete buffer; } return; } if (output->present(buffer)) { m_pageFlipsPending++; if (m_pageFlipsPending == 1 && Compositor::self()) { Compositor::self()->aboutToSwapBuffers(); } } else if (m_deleteBufferAfterPageFlip) { delete buffer; } } void DrmBackend::initCursor() { m_cursorEnabled = waylandServer()->seat()->hasPointer(); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasPointerChanged, this, [this] { m_cursorEnabled = waylandServer()->seat()->hasPointer(); if (usesSoftwareCursor()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (m_cursorEnabled) { if (!(*it)->showCursor()) { setSoftWareCursor(true); } } else { (*it)->hideCursor(); } } } ); uint64_t capability = 0; QSize cursorSize; if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) { cursorSize.setWidth(capability); } else { cursorSize.setWidth(64); } if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) { cursorSize.setHeight(capability); } else { cursorSize.setHeight(64); } m_cursorSize = cursorSize; // now we have screens and can set cursors, so start tracking connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); } void DrmBackend::setCursor() { if (m_cursorEnabled) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { if (!(*it)->showCursor()) { setSoftWareCursor(true); } } } markCursorAsRendered(); } void DrmBackend::updateCursor() { if (usesSoftwareCursor()) { return; } if (isCursorHidden()) { return; } const QImage &cursorImage = softwareCursor(); if (cursorImage.isNull()) { doHideCursor(); return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->updateCursor(); } setCursor(); moveCursor(); } void DrmBackend::doShowCursor() { updateCursor(); } void DrmBackend::doHideCursor() { if (!m_cursorEnabled) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->hideCursor(); } } void DrmBackend::moveCursor() { if (!m_cursorEnabled || isCursorHidden()) { return; } for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { (*it)->moveCursor(Cursor::pos()); } } Screens *DrmBackend::createScreens(QObject *parent) { return new DrmScreens(this, parent); } QPainterBackend *DrmBackend::createQPainterBackend() { m_deleteBufferAfterPageFlip = false; return new DrmQPainterBackend(this); } OpenGLBackend *DrmBackend::createOpenGLBackend() { #if HAVE_GBM m_deleteBufferAfterPageFlip = true; return new EglGbmBackend(this); #else return Platform::createOpenGLBackend(); #endif } DrmDumbBuffer *DrmBackend::createBuffer(const QSize &size) { DrmDumbBuffer *b = new DrmDumbBuffer(m_fd, size); return b; } #if HAVE_GBM DrmSurfaceBuffer *DrmBackend::createBuffer(const std::shared_ptr &surface) { DrmSurfaceBuffer *b = new DrmSurfaceBuffer(m_fd, surface); return b; } #endif void DrmBackend::outputDpmsChanged() { if (m_enabledOutputs.isEmpty()) { return; } bool enabled = false; for (auto it = m_enabledOutputs.constBegin(); it != m_enabledOutputs.constEnd(); ++it) { enabled = enabled || (*it)->isDpmsEnabled(); } setOutputsEnabled(enabled); } QVector DrmBackend::supportedCompositors() const { + if (selectedCompositor() != NoCompositing) { + return {selectedCompositor()}; + } #if HAVE_GBM return QVector{OpenGLCompositing, QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } QString DrmBackend::supportInformation() const { QString supportInfo; QDebug s(&supportInfo); s.nospace(); s << "Name: " << "DRM" << endl; s << "Active: " << m_active << endl; s << "Atomic Mode Setting: " << m_atomicModeSetting << endl; return supportInfo; } } diff --git a/plugins/platforms/virtual/virtual_backend.h b/plugins/platforms/virtual/virtual_backend.h index b22f4b3fa..458dee57d 100644 --- a/plugins/platforms/virtual/virtual_backend.h +++ b/plugins/platforms/virtual/virtual_backend.h @@ -1,76 +1,79 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #ifndef KWIN_VIRTUAL_BACKEND_H #define KWIN_VIRTUAL_BACKEND_H #include "platform.h" #include #include #include class QTemporaryDir; namespace KWin { class VirtualOutput; class KWIN_EXPORT VirtualBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "virtual.json") public: VirtualBackend(QObject *parent = nullptr); virtual ~VirtualBackend(); void init() override; bool saveFrames() const { return !m_screenshotDir.isNull(); } QString screenshotDirPath() const; Screens *createScreens(QObject *parent = nullptr) override; QPainterBackend* createQPainterBackend() override; OpenGLBackend *createOpenGLBackend() override; Q_INVOKABLE void setVirtualOutputs(int count, QVector geometries = QVector(), QVector scales = QVector()); Outputs outputs() const override; Outputs enabledOutputs() const override; QVector supportedCompositors() const override { + if (selectedCompositor() != NoCompositing) { + return {selectedCompositor()}; + } return QVector{OpenGLCompositing, QPainterCompositing}; } Q_SIGNALS: void virtualOutputsSet(bool countChanged); private: QVector m_outputs; QVector m_enabledOutputs; QScopedPointer m_screenshotDir; }; } #endif diff --git a/plugins/platforms/wayland/wayland_backend.cpp b/plugins/platforms/wayland/wayland_backend.cpp index e06881f09..71e95e96c 100644 --- a/plugins/platforms/wayland/wayland_backend.cpp +++ b/plugins/platforms/wayland/wayland_backend.cpp @@ -1,668 +1,671 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin 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, see . *********************************************************************/ // own #include "wayland_backend.h" #include // KWin #include "cursor.h" #include "logging.h" #include "main.h" #include "scene_qpainter_wayland_backend.h" #include "screens.h" #include "wayland_server.h" #include "wayland_cursor_theme.h" #if HAVE_WAYLAND_EGL #include "egl_wayland_backend.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include // Wayland #include #include namespace KWin { namespace Wayland { using namespace KWayland::Client; WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend) : QObject(NULL) , m_seat(new Seat(this)) , m_pointer(NULL) , m_keyboard(NULL) , m_touch(nullptr) , m_cursor(NULL) , m_enteredSerial(0) , m_backend(backend) , m_installCursor(false) { m_seat->setup(seat); connect(m_seat, &Seat::hasKeyboardChanged, this, [this](bool hasKeyboard) { if (hasKeyboard) { m_keyboard = m_seat->createKeyboard(this); connect(m_keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState state, quint32 time) { switch (state) { case Keyboard::KeyState::Pressed: if (key == KEY_RIGHTCTRL) { m_backend->togglePointerConfinement(); } m_backend->keyboardKeyPressed(key, time); break; case Keyboard::KeyState::Released: m_backend->keyboardKeyReleased(key, time); break; default: Q_UNREACHABLE(); } } ); connect(m_keyboard, &Keyboard::modifiersChanged, this, [this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) { m_backend->keyboardModifiers(depressed, latched, locked, group); } ); connect(m_keyboard, &Keyboard::keymapChanged, this, [this](int fd, quint32 size) { m_backend->keymapChange(fd, size); } ); } else { destroyKeyboard(); } } ); connect(m_seat, &Seat::hasPointerChanged, this, [this](bool hasPointer) { if (hasPointer && !m_pointer) { m_pointer = m_seat->createPointer(this); setupPointerGestures(); connect(m_pointer, &Pointer::entered, this, [this](quint32 serial) { m_enteredSerial = serial; if (!m_installCursor) { // explicitly hide cursor m_pointer->hideCursor(); } } ); connect(m_pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) { m_backend->pointerMotion(relativeToSurface, time); } ); connect(m_pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState state) { Q_UNUSED(serial) switch (state) { case Pointer::ButtonState::Pressed: m_backend->pointerButtonPressed(button, time); break; case Pointer::ButtonState::Released: m_backend->pointerButtonReleased(button, time); break; default: Q_UNREACHABLE(); } } ); connect(m_pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis axis, qreal delta) { switch (axis) { case Pointer::Axis::Horizontal: m_backend->pointerAxisHorizontal(delta, time); break; case Pointer::Axis::Vertical: m_backend->pointerAxisVertical(delta, time); break; default: Q_UNREACHABLE(); } } ); } else { destroyPointer(); } } ); connect(m_seat, &Seat::hasTouchChanged, [this] (bool hasTouch) { if (hasTouch && !m_touch) { m_touch = m_seat->createTouch(this); connect(m_touch, &Touch::sequenceCanceled, m_backend, &Platform::touchCancel); connect(m_touch, &Touch::frameEnded, m_backend, &Platform::touchFrame); connect(m_touch, &Touch::sequenceStarted, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointAdded, this, [this] (TouchPoint *tp) { m_backend->touchDown(tp->id(), tp->position(), tp->time()); } ); connect(m_touch, &Touch::pointRemoved, this, [this] (TouchPoint *tp) { m_backend->touchUp(tp->id(), tp->time()); } ); connect(m_touch, &Touch::pointMoved, this, [this] (TouchPoint *tp) { m_backend->touchMotion(tp->id(), tp->position(), tp->time()); } ); } else { destroyTouch(); } } ); WaylandServer *server = waylandServer(); if (server) { using namespace KWayland::Server; SeatInterface *si = server->seat(); connect(m_seat, &Seat::hasKeyboardChanged, si, &SeatInterface::setHasKeyboard); connect(m_seat, &Seat::hasPointerChanged, si, &SeatInterface::setHasPointer); connect(m_seat, &Seat::hasTouchChanged, si, &SeatInterface::setHasTouch); connect(m_seat, &Seat::nameChanged, si, &SeatInterface::setName); } } WaylandSeat::~WaylandSeat() { destroyPointer(); destroyKeyboard(); destroyTouch(); } void WaylandSeat::destroyPointer() { delete m_pinchGesture; m_pinchGesture = nullptr; delete m_swipeGesture; m_swipeGesture = nullptr; delete m_pointer; m_pointer = nullptr; } void WaylandSeat::destroyKeyboard() { delete m_keyboard; m_keyboard = nullptr; } void WaylandSeat::destroyTouch() { delete m_touch; m_touch = nullptr; } void WaylandSeat::installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotSpot) { if (!m_installCursor) { return; } if (!m_pointer || !m_pointer->isValid()) { return; } if (!m_cursor) { m_cursor = m_backend->compositor()->createSurface(this); } if (!m_cursor || !m_cursor->isValid()) { return; } m_pointer->setCursor(m_cursor, hotSpot); m_cursor->attachBuffer(image); m_cursor->damage(QRect(QPoint(0,0), size)); m_cursor->commit(Surface::CommitFlag::None); m_backend->flush(); } void WaylandSeat::installCursorImage(const QImage &image, const QPoint &hotSpot) { if (image.isNull()) { installCursorImage(nullptr, QSize(), QPoint()); return; } installCursorImage(*(m_backend->shmPool()->createBuffer(image).data()), image.size(), hotSpot); } void WaylandSeat::setInstallCursor(bool install) { // TODO: remove, add? m_installCursor = install; } void WaylandSeat::setupPointerGestures() { if (!m_pointer || !m_gesturesInterface) { return; } if (m_pinchGesture || m_swipeGesture) { return; } m_pinchGesture = m_gesturesInterface->createPinchGesture(m_pointer, this); m_swipeGesture = m_gesturesInterface->createSwipeGesture(m_pointer, this); connect(m_pinchGesture, &PointerPinchGesture::started, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial); m_backend->processPinchGestureBegin(m_pinchGesture->fingerCount(), time); } ); connect(m_pinchGesture, &PointerPinchGesture::updated, m_backend, [this] (const QSizeF &delta, qreal scale, qreal rotation, quint32 time) { m_backend->processPinchGestureUpdate(scale, rotation, delta, time); } ); connect(m_pinchGesture, &PointerPinchGesture::ended, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processPinchGestureEnd(time); } ); connect(m_pinchGesture, &PointerPinchGesture::cancelled, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processPinchGestureCancelled(time); } ); connect(m_swipeGesture, &PointerSwipeGesture::started, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureBegin(m_swipeGesture->fingerCount(), time); } ); connect(m_swipeGesture, &PointerSwipeGesture::updated, m_backend, &Platform::processSwipeGestureUpdate); connect(m_swipeGesture, &PointerSwipeGesture::ended, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureEnd(time); } ); connect(m_swipeGesture, &PointerSwipeGesture::cancelled, m_backend, [this] (quint32 serial, quint32 time) { Q_UNUSED(serial) m_backend->processSwipeGestureCancelled(time); } ); } WaylandBackend::WaylandBackend(QObject *parent) : Platform(parent) , m_display(nullptr) , m_eventQueue(new EventQueue(this)) , m_registry(new Registry(this)) , m_compositor(new KWayland::Client::Compositor(this)) , m_shell(new Shell(this)) , m_surface(nullptr) , m_shellSurface(NULL) , m_seat() , m_shm(new ShmPool(this)) , m_connectionThreadObject(new ConnectionThread(nullptr)) , m_connectionThread(nullptr) { connect(this, &WaylandBackend::connectionFailed, this, &WaylandBackend::initFailed); connect(this, &WaylandBackend::shellSurfaceSizeChanged, this, &WaylandBackend::screenSizeChanged); } WaylandBackend::~WaylandBackend() { if (m_pointerConstraints) { m_pointerConstraints->release(); } if (m_xdgShellSurface) { m_xdgShellSurface->release(); } if (m_shellSurface) { m_shellSurface->release(); } if (m_surface) { m_surface->release(); } if (m_xdgShell) { m_xdgShell->release(); } m_shell->release(); m_compositor->release(); m_registry->release(); m_seat.reset(); m_shm->release(); m_eventQueue->release(); m_connectionThreadObject->deleteLater(); m_connectionThread->quit(); m_connectionThread->wait(); qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display"; } void WaylandBackend::init() { connect(m_registry, &Registry::compositorAnnounced, this, [this](quint32 name) { m_compositor->setup(m_registry->bindCompositor(name, 1)); } ); connect(m_registry, &Registry::shellAnnounced, this, [this](quint32 name) { m_shell->setup(m_registry->bindShell(name, 1)); } ); connect(m_registry, &Registry::seatAnnounced, this, [this](quint32 name) { if (Application::usesLibinput()) { return; } m_seat.reset(new WaylandSeat(m_registry->bindSeat(name, 2), this)); } ); connect(m_registry, &Registry::shmAnnounced, this, [this](quint32 name) { m_shm->setup(m_registry->bindShm(name, 1)); } ); connect(m_registry, &Registry::pointerConstraintsUnstableV1Announced, this, [this](quint32 name, quint32 version) { if (m_pointerConstraints) { return; } m_pointerConstraints = m_registry->createPointerConstraints(name, version, this); updateWindowTitle(); } ); connect(m_registry, &Registry::interfacesAnnounced, this, &WaylandBackend::createSurface); connect(m_registry, &Registry::interfacesAnnounced, this, [this] { if (!m_seat) { return; } const auto gi = m_registry->interface(Registry::Interface::PointerGesturesUnstableV1); if (gi.name == 0) { return; } auto gesturesInterface = m_registry->createPointerGestures(gi.name, gi.version, m_seat.data()); m_seat->installGesturesInterface(gesturesInterface); } ); if (!deviceIdentifier().isEmpty()) { m_connectionThreadObject->setSocketName(deviceIdentifier()); } connect(this, &WaylandBackend::cursorChanged, this, [this] { if (m_seat.isNull() || !m_seat->isInstallCursor()) { return; } m_seat->installCursorImage(softwareCursor(), softwareCursorHotspot()); markCursorAsRendered(); } ); initConnection(); } void WaylandBackend::initConnection() { connect(m_connectionThreadObject, &ConnectionThread::connected, this, [this]() { // create the event queue for the main gui thread m_display = m_connectionThreadObject->display(); m_eventQueue->setup(m_connectionThreadObject); m_registry->setEventQueue(m_eventQueue); // setup registry m_registry->create(m_display); m_registry->setup(); }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::connectionDied, this, [this]() { setReady(false); emit systemCompositorDied(); m_seat.reset(); m_shm->destroy(); if (m_xdgShellSurface) { m_xdgShellSurface->destroy(); delete m_xdgShellSurface; m_xdgShellSurface = nullptr; } if (m_shellSurface) { m_shellSurface->destroy(); delete m_shellSurface; m_shellSurface = nullptr; } if (m_surface) { m_surface->destroy(); delete m_surface; m_surface = nullptr; } if (m_shell) { m_shell->destroy(); } if (m_xdgShell) { m_xdgShell->destroy(); } m_compositor->destroy(); m_registry->destroy(); m_eventQueue->destroy(); if (m_display) { m_display = nullptr; } }, Qt::QueuedConnection); connect(m_connectionThreadObject, &ConnectionThread::failed, this, &WaylandBackend::connectionFailed, Qt::QueuedConnection); m_connectionThread = new QThread(this); m_connectionThreadObject->moveToThread(m_connectionThread); m_connectionThread->start(); m_connectionThreadObject->initConnection(); } void WaylandBackend::createSurface() { m_surface = m_compositor->createSurface(this); if (!m_surface || !m_surface->isValid()) { qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; return; } using namespace KWayland::Client; auto iface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); if (iface.name != 0) { auto manager = m_registry->createServerSideDecorationManager(iface.name, iface.version, this); auto decoration = manager->create(m_surface, this); connect(decoration, &ServerSideDecoration::modeChanged, this, [this, decoration] { if (decoration->mode() != ServerSideDecoration::Mode::Server) { decoration->requestMode(ServerSideDecoration::Mode::Server); } } ); } if (m_seat) { m_seat->setInstallCursor(true); } // check for xdg shell auto xdgIface = m_registry->interface(Registry::Interface::XdgShellUnstableV6); if (xdgIface.name != 0) { m_xdgShell = m_registry->createXdgShell(xdgIface.name, xdgIface.version, this); if (m_xdgShell && m_xdgShell->isValid()) { m_xdgShellSurface = m_xdgShell->createSurface(m_surface, this); connect(m_xdgShellSurface, &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit); setupSurface(m_xdgShellSurface); return; } } if (m_shell->isValid()) { m_shellSurface = m_shell->createSurface(m_surface, this); setupSurface(m_shellSurface); m_shellSurface->setToplevel(); } } template void WaylandBackend::setupSurface(T *surface) { connect(surface, &T::sizeChanged, this, &WaylandBackend::shellSurfaceSizeChanged); surface->setSize(initialWindowSize()); updateWindowTitle(); setReady(true); emit screensQueried(); } QSize WaylandBackend::shellSurfaceSize() const { if (m_shellSurface) { return m_shellSurface->size(); } if (m_xdgShellSurface) { return m_xdgShellSurface->size(); } return QSize(); } Screens *WaylandBackend::createScreens(QObject *parent) { return new BasicScreens(this, parent); } OpenGLBackend *WaylandBackend::createOpenGLBackend() { #if HAVE_WAYLAND_EGL return new EglWaylandBackend(this); #else return nullptr; #endif } QPainterBackend *WaylandBackend::createQPainterBackend() { return new WaylandQPainterBackend(this); } void WaylandBackend::flush() { if (m_connectionThreadObject) { m_connectionThreadObject->flush(); } } void WaylandBackend::togglePointerConfinement() { if (!m_pointerConstraints) { return; } if (!m_seat) { return; } auto p = m_seat->pointer(); if (!p) { return; } if (!m_surface) { return; } if (m_pointerConfinement && m_isPointerConfined) { delete m_pointerConfinement; m_pointerConfinement = nullptr; m_isPointerConfined = false; updateWindowTitle(); flush(); return; } else if (m_pointerConfinement) { return; } m_pointerConfinement = m_pointerConstraints->confinePointer(m_surface, p, nullptr, PointerConstraints::LifeTime::Persistent, this); connect(m_pointerConfinement, &ConfinedPointer::confined, this, [this] { m_isPointerConfined = true; updateWindowTitle(); } ); connect(m_pointerConfinement, &ConfinedPointer::unconfined, this, [this] { m_isPointerConfined = false; updateWindowTitle(); } ); updateWindowTitle(); flush(); } void WaylandBackend::updateWindowTitle() { if (!m_xdgShellSurface) { return; } QString grab; if (m_isPointerConfined) { grab = i18n("Press right control to ungrab pointer"); } else { if (!m_pointerConfinement && m_pointerConstraints) { grab = i18n("Press right control key to grab pointer"); } } const QString title = i18nc("Title of nested KWin Wayland with Wayland socket identifier as argument", "KDE Wayland Compositor (%1)", waylandServer()->display()->socketName()); if (grab.isEmpty()) { m_xdgShellSurface->setTitle(title); } else { m_xdgShellSurface->setTitle(title + QStringLiteral(" - ") + grab); } } QVector WaylandBackend::supportedCompositors() const { + if (selectedCompositor() != NoCompositing) { + return {selectedCompositor()}; + } #if HAVE_WAYLAND_EGL return QVector{OpenGLCompositing, QPainterCompositing}; #else return QVector{QPainterCompositing}; #endif } } } // KWin diff --git a/plugins/platforms/x11/windowed/x11windowed_backend.h b/plugins/platforms/x11/windowed/x11windowed_backend.h index b34392cf9..b1df54573 100644 --- a/plugins/platforms/x11/windowed/x11windowed_backend.h +++ b/plugins/platforms/x11/windowed/x11windowed_backend.h @@ -1,120 +1,123 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 Martin Gräßlin 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, see . *********************************************************************/ #ifndef KWIN_X11WINDOWED_BACKEND_H #define KWIN_X11WINDOWED_BACKEND_H #include "platform.h" #include #include #include #include struct _XDisplay; typedef struct _XDisplay Display; typedef struct _XCBKeySymbols xcb_key_symbols_t; class NETWinInfo; namespace KWin { class KWIN_EXPORT X11WindowedBackend : public Platform { Q_OBJECT Q_INTERFACES(KWin::Platform) Q_PLUGIN_METADATA(IID "org.kde.kwin.Platform" FILE "x11.json") Q_PROPERTY(QSize size READ screenSize NOTIFY sizeChanged) public: X11WindowedBackend(QObject *parent = nullptr); virtual ~X11WindowedBackend(); void init() override; QVector screenGeometries() const override; QVector screenScales() const override; xcb_connection_t *connection() const { return m_connection; } int screenNumer() const { return m_screenNumber; } xcb_window_t window() const { return windowForScreen(0); } xcb_window_t windowForScreen(int screen) const; Display *display() const { return m_display; } xcb_window_t rootWindow() const; Screens *createScreens(QObject *parent = nullptr) override; OpenGLBackend *createOpenGLBackend() override; QPainterBackend* createQPainterBackend() override; void warpPointer(const QPointF &globalPos) override; QVector supportedCompositors() const override { + if (selectedCompositor() != NoCompositing) { + return {selectedCompositor()}; + } return QVector{OpenGLCompositing, QPainterCompositing}; } Q_SIGNALS: void sizeChanged(); private: void createWindow(); void startEventReading(); void grabKeyboard(xcb_timestamp_t time); void updateWindowTitle(); void handleEvent(xcb_generic_event_t *event); void handleClientMessage(xcb_client_message_event_t *event); void handleButtonPress(xcb_button_press_event_t *event); void handleExpose(xcb_expose_event_t *event); void updateSize(xcb_configure_notify_event_t *event); void createCursor(const QImage &img, const QPoint &hotspot); void initXInputForWindow(xcb_window_t window); void initXInput(); xcb_connection_t *m_connection = nullptr; xcb_screen_t *m_screen = nullptr; xcb_key_symbols_t *m_keySymbols = nullptr; int m_screenNumber = 0; struct Output { xcb_window_t window = XCB_WINDOW_NONE; QSize size; qreal scale = 1; QPoint xPosition; QPoint internalPosition; NETWinInfo *winInfo = nullptr; }; QVector m_windows; xcb_atom_t m_protocols = XCB_ATOM_NONE; xcb_atom_t m_deleteWindowProtocol = XCB_ATOM_NONE; xcb_cursor_t m_cursor = XCB_CURSOR_NONE; Display *m_display = nullptr; bool m_keyboardGrabbed = false; bool m_hasXInput = false; int m_xiOpcode = 0; int m_majorVersion = 0; int m_minorVersion = 0; }; } #endif