From ac45977e9e641b97c10ccacbc3004657e61e9866 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Tue, 1 Jan 2019 17:37:18 +0000 Subject: [PATCH] [wayland] XdgDecoration Support Summary: Does something similar to our existing ServerDecoration, but based around XDG Shell patterns and with a few subtle differneces. We'll probably still need both in kwin for the forseeable future as GTK3 won't ever change from using the KDE Server Decoration. Test Plan: Relevant unit test. It's a bit simpler as spec states toolkits must follow what the compositor configures if they bind the interface. Modified plasma-integration to remove ServerIntegration (as Qt5.12 has native support) all my windows look and act the same. Reviewers: #kwin, zzag Reviewed By: #kwin, zzag Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D17756 --- autotests/integration/kwin_wayland_test.h | 6 ++- autotests/integration/shell_client_test.cpp | 45 ++++++++++++++++++++- autotests/integration/test_helpers.cpp | 16 ++++++++ shell_client.cpp | 39 +++++++++++++++++- shell_client.h | 3 ++ wayland_server.cpp | 9 +++++ wayland_server.h | 2 + 7 files changed, 115 insertions(+), 5 deletions(-) diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index b5b3763348..4c7b183c3d 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -46,6 +46,7 @@ class Shell; class ShellSurface; class ShmPool; class Surface; +class XdgDecorationManager; } } @@ -89,7 +90,8 @@ enum class AdditionalWaylandInterface { PointerConstraints = 1 << 4, IdleInhibition = 1 << 5, AppMenu = 1 << 6, - ShadowManager = 1 << 7 + ShadowManager = 1 << 7, + XdgDecoration = 1 << 8, }; Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) /** @@ -120,7 +122,7 @@ KWayland::Client::PlasmaWindowManagement *waylandWindowManagement(); KWayland::Client::PointerConstraints *waylandPointerConstraints(); KWayland::Client::IdleInhibitManager *waylandIdleInhibitManager(); KWayland::Client::AppMenuManager *waylandAppMenuManager(); - +KWayland::Client::XdgDecorationManager *xdgDecorationManager(); bool waitForWaylandPointer(); bool waitForWaylandTouch(); diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index 59d0202b9a..186d566cb5 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -36,12 +36,13 @@ along with this program. If not, see . #include #include #include +#include #include #include #include #include - +#include // system #include @@ -96,6 +97,8 @@ private Q_SLOTS: void testSendClientWithTransientToDesktop(); void testMinimizeWindowWithTransients_data(); void testMinimizeWindowWithTransients(); + void testXdgDecoration_data(); + void testXdgDecoration(); }; void TestShellClient::initTestCase() @@ -122,6 +125,7 @@ void TestShellClient::initTestCase() void TestShellClient::init() { QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | + Test::AdditionalWaylandInterface::XdgDecoration | Test::AdditionalWaylandInterface::AppMenu)); screens()->setCurrent(0); @@ -1196,5 +1200,44 @@ void TestShellClient::testMinimizeWindowWithTransients() QVERIFY(!transient->isMinimized()); } +void TestShellClient::testXdgDecoration_data() +{ + QTest::addColumn("requestedMode"); + QTest::addColumn("expectedMode"); + + QTest::newRow("client side requested") << XdgDecoration::Mode::ClientSide << XdgDecoration::Mode::ClientSide; + QTest::newRow("server side requested") << XdgDecoration::Mode::ServerSide << XdgDecoration::Mode::ServerSide; +} + +void TestShellClient::testXdgDecoration() +{ + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(Test::createXdgShellStableSurface(surface.data())); + QScopedPointer deco(Test::xdgDecorationManager()->getToplevelDecoration(shellSurface.data())); + + QSignalSpy decorationConfiguredSpy(deco.data(), &XdgDecoration::modeChanged); + QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); + + QFETCH(KWayland::Client::XdgDecoration::Mode, requestedMode); + QFETCH(KWayland::Client::XdgDecoration::Mode, expectedMode); + + //request a mode + deco->setMode(requestedMode); + + //kwin will send a configure + decorationConfiguredSpy.wait(); + configureRequestedSpy.wait(); + + QCOMPARE(decorationConfiguredSpy.count(), 1); + QCOMPARE(decorationConfiguredSpy.first()[0].value(), expectedMode); + QVERIFY(configureRequestedSpy.count() > 0); + + shellSurface->ackConfigure(configureRequestedSpy.last()[2].toInt()); + + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QCOMPARE(c->userCanSetNoBorder(), expectedMode == XdgDecoration::Mode::ServerSide); + QCOMPARE(c->isDecorated(), expectedMode == XdgDecoration::Mode::ServerSide); +} + WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index a949455f0f..72405a56cd 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -39,6 +39,7 @@ along with this program. If not, see . #include #include #include +#include #include //screenlocker @@ -78,6 +79,7 @@ static struct { QVector outputs; IdleInhibitManager *idleInhibit = nullptr; AppMenuManager *appMenu = nullptr; + XdgDecorationManager *xdgDecoration = nullptr; } s_waylandConnection; bool setupWaylandConnection(AdditionalWaylandInterfaces flags) @@ -218,6 +220,12 @@ bool setupWaylandConnection(AdditionalWaylandInterfaces flags) return false; } } + if (flags.testFlag(AdditionalWaylandInterface::XdgDecoration)) { + s_waylandConnection.xdgDecoration = registry->createXdgDecorationManager(registry->interface(Registry::Interface::XdgDecorationUnstableV1).name, registry->interface(Registry::Interface::XdgDecorationUnstableV1).version); + if (!s_waylandConnection.xdgDecoration->isValid()) { + return false; + } + } return true; } @@ -258,6 +266,8 @@ void destroyWaylandConnection() s_waylandConnection.registry = nullptr; delete s_waylandConnection.appMenu; s_waylandConnection.appMenu = nullptr; + delete s_waylandConnection.xdgDecoration; + s_waylandConnection.xdgDecoration = nullptr; if (s_waylandConnection.thread) { QSignalSpy spy(s_waylandConnection.connection, &QObject::destroyed); s_waylandConnection.connection->deleteLater(); @@ -332,6 +342,12 @@ AppMenuManager* waylandAppMenuManager() return s_waylandConnection.appMenu; } +XdgDecorationManager *xdgDecorationManager() +{ + return s_waylandConnection.xdgDecoration; +} + + bool waitForWaylandPointer() { if (!s_waylandConnection.seat) { diff --git a/shell_client.cpp b/shell_client.cpp index 7ce764ec2d..143598584e 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -48,6 +48,7 @@ along with this program. If not, see . #include #include #include +#include #include @@ -579,6 +580,11 @@ void ShellClient::updateDecoration(bool check_workspace_pos, bool force) if (m_serverDecoration && isDecorated()) { m_serverDecoration->setMode(KWayland::Server::ServerSideDecorationManagerInterface::Mode::Server); } + if (m_xdgDecoration) { + auto mode = isDecorated() ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide; + m_xdgDecoration->configure(mode); + m_xdgShellSurface->configure(xdgSurfaceStates(), m_requestedClientSize); + } getShadow(); if (check_workspace_pos) checkWorkspacePosition(oldgeom, -2, oldClientGeom); @@ -932,6 +938,9 @@ bool ShellClient::noBorder() const return m_userNoBorder || isFullScreen(); } } + if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { + return m_userNoBorder || isFullScreen(); + } return true; } @@ -1056,6 +1065,9 @@ bool ShellClient::userCanSetNoBorder() const if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { return !isFullScreen() && !isShade() && !tabGroup(); } + if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { + return !isFullScreen() && !isShade() && !tabGroup(); + } if (m_internal) { return !m_internalWindowFlags.testFlag(Qt::FramelessWindowHint) || m_internalWindowFlags.testFlag(Qt::Popup); } @@ -1194,7 +1206,7 @@ void ShellClient::requestGeometry(const QRect &rect) configureRequest.maximizeMode = m_requestedMaximizeMode; const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); - m_requestedClientSize = QSize(0, 0); + m_requestedClientSize = size; if (m_shellSurface) { m_shellSurface->requestSize(size); @@ -1274,7 +1286,7 @@ void ShellClient::resizeWithChecks(int w, int h, ForceGeometry_t force) void ShellClient::unmap() { m_unmapped = true; - m_requestedClientSize = QSize(); + m_requestedClientSize = QSize(0, 0); destroyWindowManagementInterface(); if (Workspace::self()) { addWorkspaceRepaint(visibleRect()); @@ -1806,6 +1818,29 @@ void ShellClient::installServerSideDecoration(KWayland::Server::ServerSideDecora ); } +void ShellClient::installXdgDecoration(XdgDecorationInterface *deco) +{ + Q_ASSERT(m_xdgShellSurface); + + m_xdgDecoration = deco; + + connect(m_xdgDecoration, &QObject::destroyed, this, + [this] { + m_xdgDecoration = nullptr; + if (m_closing || !Workspace::self()) { + return; + } + updateDecoration(true); + } + ); + + connect(m_xdgDecoration, &XdgDecorationInterface::modeRequested, this, + [this] () { + //force is true as we must send a new configure response + updateDecoration(false, true); + }); +} + bool ShellClient::shouldExposeToWindowManagement() { if (isInternal()) { diff --git a/shell_client.h b/shell_client.h index f5557f4e6d..04155ae0ba 100644 --- a/shell_client.h +++ b/shell_client.h @@ -34,6 +34,7 @@ class ServerSideDecorationPaletteInterface; class AppMenuInterface; class PlasmaShellSurfaceInterface; class QtExtendedSurfaceInterface; +class XdgDecorationInterface; } } @@ -142,6 +143,7 @@ public: void installServerSideDecoration(KWayland::Server::ServerSideDecorationInterface *decoration); void installAppMenu(KWayland::Server::AppMenuInterface *appmenu); void installPalette(KWayland::Server::ServerSideDecorationPaletteInterface *palette); + void installXdgDecoration(KWayland::Server::XdgDecorationInterface *decoration); bool isInitialPositionSet() const override; @@ -257,6 +259,7 @@ private: QPointer m_appMenuInterface; QPointer m_paletteInterface; KWayland::Server::ServerSideDecorationInterface *m_serverDecoration = nullptr; + KWayland::Server::XdgDecorationInterface *m_xdgDecoration = nullptr; bool m_userNoBorder = false; bool m_fullScreen = false; bool m_transient = false; diff --git a/wayland_server.cpp b/wayland_server.cpp index afd86fa3e0..c0e92e80b8 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -57,6 +57,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -235,6 +236,14 @@ bool WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) connect(m_xdgShell, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); connect(m_xdgShell, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); + m_xdgDecorationManager = m_display->createXdgDecorationManager(m_xdgShell, m_display); + m_xdgDecorationManager->create(); + connect(m_xdgDecorationManager, &XdgDecorationManagerInterface::xdgDecorationInterfaceCreated, this, [this] (XdgDecorationInterface *deco) { + if (ShellClient *client = findClient(deco->surface()->surface())) { + client->installXdgDecoration(deco); + } + }); + m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); diff --git a/wayland_server.h b/wayland_server.h index f5f3805c66..3fc943ee47 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -59,6 +59,7 @@ class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; class OutputConfigurationInterface; +class XdgDecorationManagerInterface; class XdgShellInterface; class XdgForeignInterface; class XdgOutputManagerInterface; @@ -246,6 +247,7 @@ private: KWayland::Server::ServerSideDecorationPaletteManagerInterface *m_paletteManager = nullptr; KWayland::Server::IdleInterface *m_idle = nullptr; KWayland::Server::XdgOutputManagerInterface *m_xdgOutputManager = nullptr; + KWayland::Server::XdgDecorationManagerInterface *m_xdgDecorationManager = nullptr; struct { KWayland::Server::ClientConnection *client = nullptr; QMetaObject::Connection destroyConnection;