diff --git a/autotests/integration/debug_console_test.cpp b/autotests/integration/debug_console_test.cpp --- a/autotests/integration/debug_console_test.cpp +++ b/autotests/integration/debug_console_test.cpp @@ -49,6 +49,7 @@ void topLevelTest(); void testX11Client(); void testX11Unmanaged(); + void testWaylandClient_data(); void testWaylandClient(); void testInternalWindow(); }; @@ -292,6 +293,14 @@ QVERIFY(!model2.hasChildren(model2.index(1, 0, QModelIndex()))); } +void DebugConsoleTest::testWaylandClient_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; +} + void DebugConsoleTest::testWaylandClient() { DebugConsoleModel model; @@ -318,8 +327,9 @@ using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(surface->isValid()); - QScopedPointer shellSurface(Test::createShellSurface(surface.data())); - QVERIFY(shellSurface->isValid()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + QVERIFY(!shellSurface.isNull()); Test::render(surface.data(), QSize(10, 10), Qt::red); // now we have the window, it should be added to our model @@ -380,11 +390,13 @@ shellSurface.reset(); Test::flushWaylandConnection(); qDebug() << rowsRemovedSpy.count(); - QEXPECT_FAIL("", "Deleting a ShellSurface does not result in the server removing the ShellClient", Continue); + QEXPECT_FAIL("wlShell", "Deleting a ShellSurface does not result in the server removing the ShellClient", Continue); QVERIFY(rowsRemovedSpy.wait()); surface.reset(); - QVERIFY(rowsRemovedSpy.wait()); + if (rowsRemovedSpy.isEmpty()) { + QVERIFY(rowsRemovedSpy.wait()); + } QCOMPARE(rowsRemovedSpy.count(), 1); QCOMPARE(rowsRemovedSpy.first().first().value(), waylandTopLevelIndex); QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp --- a/autotests/integration/decoration_input_test.cpp +++ b/autotests/integration/decoration_input_test.cpp @@ -64,14 +64,15 @@ void testDoubleClick(); void testDoubleTap_data(); void testDoubleTap(); + void testHover_data(); void testHover(); void testPressToMove_data(); void testPressToMove(); void testTapToMove_data(); void testTapToMove(); private: - AbstractClient *showWindow(); + AbstractClient *showWindow(Test::ShellSurfaceType type); }; #define MOTION(target) \ @@ -83,7 +84,7 @@ #define RELEASE \ kwinApp()->platform()->pointerButtonReleased(BTN_LEFT, timestamp++) -AbstractClient *DecorationInputTest::showWindow() +AbstractClient *DecorationInputTest::showWindow(Test::ShellSurfaceType type) { using namespace KWayland::Client; #define VERIFY(statement) \ @@ -95,7 +96,7 @@ Surface *surface = Test::createSurface(Test::waylandCompositor()); VERIFY(surface); - ShellSurface *shellSurface = Test::createShellSurface(surface, surface); + auto shellSurface = Test::createShellSurface(type, surface, surface); VERIFY(shellSurface); auto deco = Test::waylandServerSideDecoration()->create(surface, surface); QSignalSpy decoSpy(deco, &ServerSideDecoration::modeChanged); @@ -162,15 +163,20 @@ { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); - - QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; - QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; - QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; + QTest::addColumn("type"); + + QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("topLeft|xdg") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("top|xdg") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("topRight|xdg") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testAxis() { - AbstractClient *c = showWindow(); + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); @@ -211,15 +217,20 @@ { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); - - QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; - QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; - QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; + QTest::addColumn("type"); + + QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("topLeft|xdg") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("top|xdg") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("topRight|xdg") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; } void KWin::DecorationInputTest::testDoubleClick() { - AbstractClient *c = showWindow(); + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); @@ -261,15 +272,20 @@ { QTest::addColumn("decoPoint"); QTest::addColumn("expectedSection"); - - QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; - QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; - QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; + QTest::addColumn("type"); + + QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::WlShell; + QTest::newRow("topLeft|xdg") << QPoint(0, 0) << Qt::TopLeftSection << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("top|xdg") << QPoint(250, 0) << Qt::TopSection << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("topRight|xdg") << QPoint(499, 0) << Qt::TopRightSection << Test::ShellSurfaceType::XdgShellV5; } void KWin::DecorationInputTest::testDoubleTap() { - AbstractClient *c = showWindow(); + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); @@ -306,9 +322,18 @@ QVERIFY(c->isOnAllDesktops()); } +void DecorationInputTest::testHover_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; +} + void DecorationInputTest::testHover() { - AbstractClient *c = showWindow(); + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); @@ -347,16 +372,22 @@ QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); - - QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); - QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); - QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); - QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); + QTest::addColumn("type"); + + QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To right|xdg") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("To left|xdg") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("To bottom|xdg") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("To top|xdg") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testPressToMove() { - AbstractClient *c = showWindow(); + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); @@ -406,16 +437,22 @@ QTest::addColumn("offset"); QTest::addColumn("offset2"); QTest::addColumn("offset3"); - - QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); - QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); - QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); - QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); + QTest::addColumn("type"); + + QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::WlShell; + QTest::newRow("To right|xdg") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0) << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("To left|xdg") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0) << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("To bottom|xdg") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30) << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("To top|xdg") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30) << Test::ShellSurfaceType::XdgShellV5; } void DecorationInputTest::testTapToMove() { - AbstractClient *c = showWindow(); + QFETCH(Test::ShellSurfaceType, type); + AbstractClient *c = showWindow(type); QVERIFY(c); QVERIFY(c->isDecorated()); QVERIFY(!c->noBorder()); diff --git a/autotests/integration/dont_crash_no_border.cpp b/autotests/integration/dont_crash_no_border.cpp --- a/autotests/integration/dont_crash_no_border.cpp +++ b/autotests/integration/dont_crash_no_border.cpp @@ -49,6 +49,7 @@ void initTestCase(); void init(); void cleanup(); + void testCreateWindow_data(); void testCreateWindow(); }; @@ -94,14 +95,23 @@ Test::destroyWaylandConnection(); } +void DontCrashNoBorder::testCreateWindow_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; +} + void DontCrashNoBorder::testCreateWindow() { // create a window and ensure that this doesn't crash using namespace KWayland::Client; QScopedPointer surface(Test::createSurface()); QVERIFY(!surface.isNull()); - QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); QVERIFY(shellSurface); QScopedPointer deco(Test::waylandServerSideDecoration()->create(surface.data())); QSignalSpy decoSpy(deco.data(), &ServerSideDecoration::modeChanged); diff --git a/autotests/integration/input_stacking_order.cpp b/autotests/integration/input_stacking_order.cpp --- a/autotests/integration/input_stacking_order.cpp +++ b/autotests/integration/input_stacking_order.cpp @@ -53,6 +53,7 @@ void initTestCase(); void init(); void cleanup(); + void testPointerFocusUpdatesOnStackingOrderChange_data(); void testPointerFocusUpdatesOnStackingOrderChange(); private: @@ -100,6 +101,14 @@ Test::flushWaylandConnection(); } +void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; +} + void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange() { // this test creates two windows which overlap @@ -121,16 +130,17 @@ QVERIFY(clientAddedSpy.isValid()); Surface *surface1 = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface1); - ShellSurface *shellSurface1 = Test::createShellSurface(surface1, surface1); + QFETCH(Test::ShellSurfaceType, type); + auto shellSurface1 = Test::createShellSurface(type, surface1, surface1); QVERIFY(shellSurface1); render(surface1); QVERIFY(clientAddedSpy.wait()); AbstractClient *window1 = workspace()->activeClient(); QVERIFY(window1); Surface *surface2 = Test::createSurface(Test::waylandCompositor()); QVERIFY(surface2); - ShellSurface *shellSurface2 = Test::createShellSurface(surface2, surface2); + auto shellSurface2 = Test::createShellSurface(type, surface2, surface2); QVERIFY(shellSurface2); render(surface2); QVERIFY(clientAddedSpy.wait()); diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -39,6 +39,7 @@ class ShellSurface; class ShmPool; class Surface; +class XdgShellSurface; } } @@ -112,7 +113,13 @@ void flushWaylandConnection(); KWayland::Client::Surface *createSurface(QObject *parent = nullptr); +enum class ShellSurfaceType { + WlShell, + XdgShellV5 +}; +QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent = nullptr); KWayland::Client::ShellSurface *createShellSurface(KWayland::Client::Surface *surface, QObject *parent = nullptr); +KWayland::Client::XdgShellSurface *createXdgShellV5Surface(KWayland::Client::Surface *surface, QObject *parent = nullptr); /** * Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface. @@ -140,6 +147,7 @@ } Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Test::AdditionalWaylandInterfaces) +Q_DECLARE_METATYPE(KWin::Test::ShellSurfaceType) #define WAYLANDTEST_MAIN_HELPER(TestObject, DPI) \ int main(int argc, char *argv[]) \ diff --git a/autotests/integration/scene_qpainter_test.cpp b/autotests/integration/scene_qpainter_test.cpp --- a/autotests/integration/scene_qpainter_test.cpp +++ b/autotests/integration/scene_qpainter_test.cpp @@ -47,6 +47,7 @@ void cleanup(); void testStartFrame(); void testCursorMoving(); + void testWindow_data(); void testWindow(); }; @@ -133,14 +134,24 @@ QCOMPARE(referenceImage, *scene->backend()->buffer()); } +void SceneQPainterTest::testWindow_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; +} + void SceneQPainterTest::testWindow() { + KWin::Cursor::setPos(45, 45); // this test verifies that a window is rendered correctly using namespace KWayland::Client; QVERIFY(Test::setupWaylandConnection(s_socketName, Test::AdditionalWaylandInterface::Seat)); QVERIFY(Test::waitForWaylandPointer()); QScopedPointer s(Test::createSurface()); - QScopedPointer ss(Test::createShellSurface(s.data())); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer ss(Test::createShellSurface(type, s.data())); QScopedPointer p(Test::waylandSeat()->createPointer()); auto scene = qobject_cast(KWin::Compositor::self()->scene()); diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -46,6 +46,7 @@ void init(); void cleanup(); + void testMapUnmapMap_data(); void testMapUnmapMap(); void testDesktopPresenceChanged(); }; @@ -81,6 +82,14 @@ Test::destroyWaylandConnection(); } +void TestShellClient::testMapUnmapMap_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; +} + void TestShellClient::testMapUnmapMap() { // this test verifies that mapping a previously mapped window works correctly @@ -92,7 +101,8 @@ QVERIFY(effectsWindowHiddenSpy.isValid()); QScopedPointer surface(Test::createSurface()); - QScopedPointer shellSurface(Test::createShellSurface(surface.data())); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); // now let's render Test::render(surface.data(), QSize(100, 50), Qt::blue); diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -48,6 +49,7 @@ Compositor *compositor = nullptr; ServerSideDecorationManager *decoration = nullptr; Shell *shell = nullptr; + XdgShell *xdgShellV5 = nullptr; ShmPool *shm = nullptr; Seat *seat = nullptr; PlasmaShell *plasmaShell = nullptr; @@ -110,6 +112,10 @@ if (!s_waylandConnection.shell->isValid()) { return false; } + s_waylandConnection.xdgShellV5 = registry.createXdgShell(registry.interface(Registry::Interface::XdgShellUnstableV5).name, registry.interface(Registry::Interface::XdgShellUnstableV5).version); + if (!s_waylandConnection.xdgShellV5->isValid()) { + return false; + } if (flags.testFlag(AdditionalWaylandInterface::Seat)) { s_waylandConnection.seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version); if (!s_waylandConnection.seat->isValid()) { @@ -155,6 +161,8 @@ s_waylandConnection.decoration = nullptr; delete s_waylandConnection.seat; s_waylandConnection.seat = nullptr; + delete s_waylandConnection.xdgShellV5; + s_waylandConnection.xdgShellV5 = nullptr; delete s_waylandConnection.shell; s_waylandConnection.shell = nullptr; delete s_waylandConnection.shm; @@ -305,6 +313,32 @@ return s; } +XdgShellSurface *createXdgShellV5Surface(Surface *surface, QObject *parent) +{ + if (!s_waylandConnection.xdgShellV5) { + return nullptr; + } + auto s = s_waylandConnection.xdgShellV5->createSurface(surface, parent); + if (!s->isValid()) { + delete s; + return nullptr; + } + return s; +} + +QObject *createShellSurface(ShellSurfaceType type, KWayland::Client::Surface *surface, QObject *parent) +{ + switch (type) { + case ShellSurfaceType::WlShell: + return createShellSurface(surface, parent); + case ShellSurfaceType::XdgShellV5: + return createXdgShellV5Surface(surface, parent); + default: + Q_UNREACHABLE(); + return nullptr; + } +} + bool waitForWindowDestroyed(AbstractClient *client) { QSignalSpy destroyedSpy(client, &QObject::destroyed); diff --git a/shell_client.h b/shell_client.h --- a/shell_client.h +++ b/shell_client.h @@ -21,6 +21,7 @@ #define KWIN_SHELL_CLIENT_H #include "abstract_client.h" +#include namespace KWayland { @@ -41,6 +42,8 @@ Q_OBJECT public: ShellClient(KWayland::Server::ShellSurfaceInterface *surface); + ShellClient(KWayland::Server::XdgShellSurfaceInterface *surface); + ShellClient(KWayland::Server::XdgShellPopupInterface *surface); virtual ~ShellClient(); QStringList activities() const override; @@ -141,6 +144,8 @@ private: void init(); + template + void initSurface(T *shellSurface); void requestGeometry(const QRect &rect); void doSetGeometry(const QRect &rect); void createDecoration(const QRect &oldgeom); @@ -153,9 +158,12 @@ void markAsMapped(); void setTransient(); bool shouldExposeToWindowManagement(); + KWayland::Server::XdgShellSurfaceInterface::States xdgSurfaceStates() const; static void deleteClient(ShellClient *c); KWayland::Server::ShellSurfaceInterface *m_shellSurface; + KWayland::Server::XdgShellSurfaceInterface *m_xdgShellSurface; + KWayland::Server::XdgShellPopupInterface *m_xdgShellPopup; QSize m_clientSize; ClearablePoint m_positionAfterResize; // co-ordinates saved from a requestGeometry call, real geometry will be updated after the next damage event when the client has resized diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -41,7 +41,6 @@ #include #include #include - #include #include @@ -55,14 +54,110 @@ ShellClient::ShellClient(ShellSurfaceInterface *surface) : AbstractClient() , m_shellSurface(surface) + , m_xdgShellSurface(nullptr) + , m_xdgShellPopup(nullptr) + , m_internal(surface->client() == waylandServer()->internalConnection()) +{ + setSurface(surface->surface()); + init(); +} + +ShellClient::ShellClient(XdgShellSurfaceInterface *surface) + : AbstractClient() + , m_shellSurface(nullptr) + , m_xdgShellSurface(surface) + , m_xdgShellPopup(nullptr) + , m_internal(surface->client() == waylandServer()->internalConnection()) +{ + setSurface(surface->surface()); + init(); +} + +ShellClient::ShellClient(XdgShellPopupInterface *surface) + : AbstractClient() + , m_shellSurface(nullptr) + , m_xdgShellSurface(nullptr) + , m_xdgShellPopup(surface) , m_internal(surface->client() == waylandServer()->internalConnection()) { setSurface(surface->surface()); init(); } ShellClient::~ShellClient() = default; +template +void ShellClient::initSurface(T *shellSurface) +{ + m_caption = shellSurface->title(); + connect(shellSurface, &T::titleChanged, this, &ShellClient::captionChanged); + connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); + connect(shellSurface, &T::titleChanged, this, + [this] (const QString &s) { + m_caption = s; + emit captionChanged(); + } + ); + connect(shellSurface, &T::moveRequested, this, + [this] { + // TODO: check the seat and serial + performMouseCommand(Options::MouseMove, Cursor::pos()); + } + ); + connect(shellSurface, &T::windowClassChanged, this, &ShellClient::updateIcon); + + setResourceClass(shellSurface->windowClass()); + connect(shellSurface, &T::windowClassChanged, this, + [this] (const QByteArray &windowClass) { + setResourceClass(windowClass); + } + ); + connect(shellSurface, &T::resizeRequested, this, + [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { + // TODO: check the seat and serial + Q_UNUSED(seat) + Q_UNUSED(serial) + if (!isResizable() || isShade()) { + return; + } + if (isMoveResize()) { + finishMoveResize(false); + } + setMoveResizePointerButtonDown(true); + setMoveOffset(Cursor::pos() - pos()); // map from global + setInvertedMoveOffset(rect().bottomRight() - moveOffset()); + setUnrestrictedMoveResize(false); + auto toPosition = [edges] { + Position pos = PositionCenter; + if (edges.testFlag(Qt::TopEdge)) { + pos = PositionTop; + } else if (edges.testFlag(Qt::BottomEdge)) { + pos = PositionBottom; + } + if (edges.testFlag(Qt::LeftEdge)) { + pos = Position(pos | PositionLeft); + } else if (edges.testFlag(Qt::RightEdge)) { + pos = Position(pos | PositionRight); + } + return pos; + }; + setMoveResizePointerMode(toPosition()); + if (!startMoveResize()) + setMoveResizePointerButtonDown(false); + updateCursor(); + } + ); + connect(shellSurface, &T::maximizedChanged, this, + [this] (bool maximized) { + maximize(maximized ? MaximizeFull : MaximizeRestore); + } + ); + // TODO: consider output! + connect(shellSurface, &T::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); + + connect(shellSurface, &T::transientForChanged, this, &ShellClient::setTransient); +} + void ShellClient::init() { findInternalWindow(); @@ -101,71 +196,33 @@ connect(s, &SurfaceInterface::unmapped, this, &ShellClient::unmap); connect(s, &SurfaceInterface::destroyed, this, &ShellClient::destroyClient); if (m_shellSurface) { - m_caption = m_shellSurface->title(); - connect(m_shellSurface, &ShellSurfaceInterface::destroyed, this, &ShellClient::destroyClient); - connect(m_shellSurface, &ShellSurfaceInterface::titleChanged, this, - [this] { - m_caption = m_shellSurface->title(); - emit captionChanged(); - } - ); - - connect(m_shellSurface, &ShellSurfaceInterface::fullscreenChanged, this, &ShellClient::clientFullScreenChanged); - connect(m_shellSurface, &ShellSurfaceInterface::maximizedChanged, this, - [this] (bool maximized) { - maximize(maximized ? MaximizeFull : MaximizeRestore); - } - ); - connect(m_shellSurface, &ShellSurfaceInterface::windowClassChanged, this, &ShellClient::updateIcon); - - setResourceClass(m_shellSurface->windowClass()); - connect(m_shellSurface, &ShellSurfaceInterface::windowClassChanged, this, - [this] { - setResourceClass(m_shellSurface->windowClass()); + initSurface(m_shellSurface); + } else if (m_xdgShellSurface) { + initSurface(m_xdgShellSurface); + connect(m_xdgShellSurface, &XdgShellSurfaceInterface::windowMenuRequested, this, + [this] (SeatInterface *seat, quint32 serial, const QPoint &surfacePos) { + // TODO: check serial on seat + Q_UNUSED(seat) + Q_UNUSED(serial) + performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); } ); - connect(m_shellSurface, &ShellSurfaceInterface::transientForChanged, this, &ShellClient::setTransient); - connect(m_shellSurface, &ShellSurfaceInterface::moveRequested, this, + connect(m_xdgShellSurface, &XdgShellSurfaceInterface::minimizeRequested, this, [this] { - // TODO: check the seat and serial - performMouseCommand(Options::MouseMove, Cursor::pos()); + performMouseCommand(Options::MouseMinimize, Cursor::pos()); } ); - connect(m_shellSurface, &ShellSurfaceInterface::resizeRequested, this, - [this] (SeatInterface *seat, quint32 serial, Qt::Edges edges) { - // TODO: check the seat and serial - Q_UNUSED(seat) - Q_UNUSED(serial) - if (!isResizable() || isShade()) { - return; - } - if (isMoveResize()) { - finishMoveResize(false); - } - setMoveResizePointerButtonDown(true); - setMoveOffset(Cursor::pos() - pos()); // map from global - setInvertedMoveOffset(rect().bottomRight() - moveOffset()); - setUnrestrictedMoveResize(false); - auto toPosition = [edges] { - Position pos = PositionCenter; - if (edges.testFlag(Qt::TopEdge)) { - pos = PositionTop; - } else if (edges.testFlag(Qt::BottomEdge)) { - pos = PositionBottom; - } - if (edges.testFlag(Qt::LeftEdge)) { - pos = Position(pos | PositionLeft); - } else if (edges.testFlag(Qt::RightEdge)) { - pos = Position(pos | PositionRight); - } - return pos; - }; - setMoveResizePointerMode(toPosition()); - if (!startMoveResize()) - setMoveResizePointerButtonDown(false); - updateCursor(); + auto configure = [this] { + if (m_closing) { + return; } - ); + m_xdgShellSurface->configure(xdgSurfaceStates()); + }; + connect(this, &AbstractClient::activeChanged, this, configure); + connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); + connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); + } else if (m_xdgShellPopup) { + connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &ShellClient::destroyClient); } updateIcon(); @@ -213,6 +270,8 @@ del->unrefWindow(); } m_shellSurface = nullptr; + m_xdgShellSurface = nullptr; + m_xdgShellPopup = nullptr; deleteClient(this); } @@ -466,6 +525,10 @@ void ShellClient::closeWindow() { + if (m_xdgShellSurface && isCloseable()) { + m_xdgShellSurface->close(); + return; + } if (m_qtExtendedSurface && isCloseable()) { m_qtExtendedSurface->close(); } @@ -482,6 +545,9 @@ if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { return false; } + if (m_xdgShellSurface) { + return true; + } return m_qtExtendedSurface ? true : false; } @@ -539,22 +605,31 @@ if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } + if (m_xdgShellPopup) { + return false; + } return true; } bool ShellClient::isMovableAcrossScreens() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } + if (m_xdgShellPopup) { + return false; + } return true; } bool ShellClient::isResizable() const { if (m_plasmaShellSurface) { return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; } + if (m_xdgShellPopup) { + return false; + } return true; } @@ -752,6 +827,10 @@ // if the window is not visible it doesn't get input return m_shellSurface->acceptsKeyboardFocus() && isShown(true); } + if (m_xdgShellSurface) { + // TODO: proper + return true; + } return false; } @@ -826,8 +905,12 @@ return; } m_positionAfterResize.setPoint(rect.topLeft()); + const QSize size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); if (m_shellSurface) { - m_shellSurface->requestSize(rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom())); + m_shellSurface->requestSize(size); + } + if (m_xdgShellSurface) { + m_xdgShellSurface->configure(xdgSurfaceStates(), size); } m_blockedRequestGeometry = QRect(); } @@ -837,6 +920,9 @@ StackingUpdatesBlocker blocker(workspace()); workspace()->updateClientLayer(this); // active fullscreens get different layer + const bool emitSignal = m_fullScreen != fullScreen; + m_fullScreen = fullScreen; + if (fullScreen) { m_geomFsRestore = geometry(); requestGeometry(workspace()->clientArea(FullScreenArea, this)); @@ -848,8 +934,7 @@ requestGeometry(workspace()->clientArea(MaximizeArea, this)); } } - if (m_fullScreen != fullScreen) { - m_fullScreen = fullScreen; + if (emitSignal) { emit fullScreenChanged(); } } @@ -868,6 +953,9 @@ if (m_shellSurface) { m_shellSurface->requestSize(QSize(w, h)); } + if (m_xdgShellSurface) { + m_xdgShellSurface->configure(xdgSurfaceStates(), QSize(w, h)); + } } void ShellClient::unmap() @@ -995,6 +1083,14 @@ if (m_shellSurface) { s = m_shellSurface->transientFor().data(); } + if (m_xdgShellSurface) { + if (auto transient = m_xdgShellSurface->transientFor().data()) { + s = transient->surface(); + } + } + if (m_xdgShellPopup) { + s = m_xdgShellPopup->transientFor().data(); + } auto t = waylandServer()->findClient(s); if (t != transientFor()) { // remove from main client @@ -1018,6 +1114,9 @@ if (m_shellSurface) { return m_shellSurface->transientOffset(); } + if (m_xdgShellPopup) { + return m_xdgShellPopup->transientOffset(); + } return QPoint(); } @@ -1087,4 +1186,22 @@ return true; } +KWayland::Server::XdgShellSurfaceInterface::States ShellClient::xdgSurfaceStates() const +{ + XdgShellSurfaceInterface::States states; + if (isActive()) { + states |= XdgShellSurfaceInterface::State::Activated; + } + if (isFullScreen()) { + states |= XdgShellSurfaceInterface::State::Fullscreen; + } + if (maximizeMode() == MaximizeMode::MaximizeFull) { + states |= XdgShellSurfaceInterface::State::Maximized; + } + if (isResize()) { + states |= XdgShellSurfaceInterface::State::Resizing; + } + return states; +} + } diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -54,6 +54,7 @@ class QtSurfaceExtensionInterface; class OutputManagementInterface; class OutputConfigurationInterface; +class XdgShellInterface; } } @@ -174,10 +175,13 @@ quint16 createClientId(KWayland::Server::ClientConnection *c); void destroyInternalConnection(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); + template + void createSurface(T *surface); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositor = nullptr; KWayland::Server::SeatInterface *m_seat = nullptr; KWayland::Server::ShellInterface *m_shell = nullptr; + KWayland::Server::XdgShellInterface *m_xdgShell = nullptr; KWayland::Server::PlasmaShellInterface *m_plasmaShell = nullptr; KWayland::Server::PlasmaWindowManagementInterface *m_windowManagement = nullptr; KWayland::Server::QtSurfaceExtensionInterface *m_qtExtendedSurface = nullptr; diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -49,6 +49,7 @@ #include #include #include +#include // Qt #include @@ -113,6 +114,33 @@ } } +template +void WaylandServer::createSurface(T *surface) +{ + if (!Workspace::self()) { + // it's possible that a Surface gets created before Workspace is created + return; + } + if (surface->client() == m_xwayland.client) { + // skip Xwayland clients, those are created using standard X11 way + return; + } + if (surface->client() == m_screenLockerClientConnection) { + ScreenLocker::KSldApp::self()->lockScreenShown(); + } + auto client = new ShellClient(surface); + if (client->isInternal()) { + m_internalClients << client; + } else { + m_clients << client; + } + if (client->readyForPainting()) { + emit shellClientAdded(client); + } else { + connect(client, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); + } +} + bool WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; @@ -148,32 +176,12 @@ ); m_shell = m_display->createShell(m_display); m_shell->create(); - connect(m_shell, &ShellInterface::surfaceCreated, this, - [this] (ShellSurfaceInterface *surface) { - if (!Workspace::self()) { - // it's possible that a Surface gets created before Workspace is created - return; - } - if (surface->client() == m_xwayland.client) { - // skip Xwayland clients, those are created using standard X11 way - return; - } - if (surface->client() == m_screenLockerClientConnection) { - ScreenLocker::KSldApp::self()->lockScreenShown(); - } - auto client = new ShellClient(surface); - if (client->isInternal()) { - m_internalClients << client; - } else { - m_clients << client; - } - if (client->readyForPainting()) { - emit shellClientAdded(client); - } else { - connect(client, &ShellClient::windowShown, this, &WaylandServer::shellClientShown); - } - } - ); + connect(m_shell, &ShellInterface::surfaceCreated, this, &WaylandServer::createSurface); + m_xdgShell = m_display->createXdgShell(XdgShellInterfaceVersion::UnstableV5, m_display); + m_xdgShell->create(); + connect(m_xdgShell, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); + // TODO: verify seat and serial + connect(m_xdgShell, &XdgShellInterface::popupCreated, this, &WaylandServer::createSurface); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create();