diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -493,10 +493,10 @@ sm.cpp subsurfacemonitor.cpp syncalarmx11filter.cpp + tablet_input.cpp thumbnailitem.cpp toplevel.cpp touch_hide_cursor_spy.cpp - tablet_input.cpp touch_input.cpp udev.cpp unmanaged.cpp @@ -509,6 +509,7 @@ was_user_interaction_x11_filter.cpp wayland_cursor_theme.cpp wayland_server.cpp + waylandclient.cpp window_property_notify_x11_filter.cpp workspace.cpp x11client.cpp diff --git a/autotests/integration/maximize_test.cpp b/autotests/integration/maximize_test.cpp --- a/autotests/integration/maximize_test.cpp +++ b/autotests/integration/maximize_test.cpp @@ -309,6 +309,8 @@ // test case verifies that borderless maximized windows doesn't cause // clients to render client-side decorations instead (BUG 405385) + XdgShellSurface::States states; + // adjust config auto group = kwinApp()->config()->group("Windows"); group.writeEntry("BorderlessMaximizedWindows", true); @@ -324,29 +326,32 @@ QVERIFY(decorationConfiguredSpy.isValid()); auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client->isDecorated()); + QVERIFY(!client->noBorder()); QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); - QSignalSpy sizeChangeRequestedSpy(xdgShellSurface.data(), &XdgShellSurface::sizeChanged); - QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(xdgShellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); - QVERIFY(client->isDecorated()); - QVERIFY(!client->noBorder()); - configureRequestedSpy.wait(); + // Wait for the compositor to send a configure event with the Activated state. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Activated); + QCOMPARE(decorationConfiguredSpy.count(), 1); QCOMPARE(deco->mode(), XdgDecoration::Mode::ServerSide); // go to maximized xdgShellSurface->setMaximized(true); - QVERIFY(sizeChangeRequestedSpy.wait()); - QCOMPARE(sizeChangeRequestedSpy.count(), 1); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Maximized); - for (const auto &it: configureRequestedSpy) { - xdgShellSurface->ackConfigure(it[2].toInt()); - } - Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); + xdgShellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(frameGeometryChangedSpy.wait()); // no deco @@ -357,13 +362,13 @@ // go back to normal xdgShellSurface->setMaximized(false); - QVERIFY(sizeChangeRequestedSpy.wait()); - QCOMPARE(sizeChangeRequestedSpy.count(), 2); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(!(states & XdgShellSurface::State::Maximized)); - for (const auto &it: configureRequestedSpy) { - xdgShellSurface->ackConfigure(it[2].toInt()); - } - Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); + xdgShellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), configureRequestedSpy.last().first().toSize(), Qt::red); QVERIFY(frameGeometryChangedSpy.wait()); QVERIFY(client->isDecorated()); diff --git a/autotests/integration/move_resize_window_test.cpp b/autotests/integration/move_resize_window_test.cpp --- a/autotests/integration/move_resize_window_test.cpp +++ b/autotests/integration/move_resize_window_test.cpp @@ -316,15 +316,14 @@ QCOMPARE(moveResizedChangedSpy.count(), 2); QCOMPARE(c->isResize(), false); QCOMPARE(workspace()->moveResizeClient(), nullptr); - QEXPECT_FAIL("", "XdgShellClient currently doesn't send final configure event", Abort); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 6); states = configureRequestedSpy.last().at(1).value(); QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); QVERIFY(!states.testFlag(XdgShellSurface::State::Resizing)); // Destroy the client. - surface.reset(); + shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(c)); } diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp --- a/autotests/integration/quick_tiling_test.cpp +++ b/autotests/integration/quick_tiling_test.cpp @@ -192,7 +192,6 @@ // but we got requested a new geometry QVERIFY(configureRequestedSpy.wait()); - QEXPECT_FAIL("maximize", "Two configure events are sent for maximized", Continue); QCOMPARE(configureRequestedSpy.count(), 2); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), expectedGeometry.size()); @@ -272,7 +271,6 @@ // but we got requested a new geometry QVERIFY(configureRequestedSpy.wait()); - QEXPECT_FAIL("", "Two configure events are sent for maximized", Continue); QCOMPARE(configureRequestedSpy.count(), 2); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); @@ -305,7 +303,6 @@ QCOMPARE(c->geometryRestore(), QRect(0, 0, 100, 50)); // we got requested a new geometry QVERIFY(configureRequestedSpy.wait()); - QEXPECT_FAIL("", "Two configure events are sent for maximized", Continue); QCOMPARE(configureRequestedSpy.count(), 3); QCOMPARE(configureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); diff --git a/autotests/integration/xdgshellclient_rules_test.cpp b/autotests/integration/xdgshellclient_rules_test.cpp --- a/autotests/integration/xdgshellclient_rules_test.cpp +++ b/autotests/integration/xdgshellclient_rules_test.cpp @@ -840,9 +840,7 @@ QVERIFY(!client->isMove()); QVERIFY(!client->isResize()); - QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue); QVERIFY(configureRequestedSpy->wait(10)); - QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue); QCOMPARE(configureRequestedSpy->count(), 5); // The rule should be applied again if the client appears after it's been closed. @@ -978,9 +976,7 @@ QVERIFY(!client->isMove()); QVERIFY(!client->isResize()); - QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue); QVERIFY(configureRequestedSpy->wait(10)); - QEXPECT_FAIL("", "Interactive resize is not spec-compliant", Continue); QCOMPARE(configureRequestedSpy->count(), 5); // If the client appears again, it should have the last known size. @@ -1380,6 +1376,7 @@ workspace()->slotWindowMaximize(); QVERIFY(configureRequestedSpy->wait()); QCOMPARE(configureRequestedSpy->count(), 3); + QEXPECT_FAIL("", "Geometry restore is set to the first valid geometry", Continue); QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = configureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); @@ -1492,6 +1489,7 @@ workspace()->slotWindowMaximize(); QVERIFY(configureRequestedSpy->wait()); QCOMPARE(configureRequestedSpy->count(), 3); + QEXPECT_FAIL("", "Geometry restore is set to the first valid geometry", Continue); QCOMPARE(configureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); states = configureRequestedSpy->last().at(1).value(); QVERIFY(states.testFlag(XdgShellSurface::State::Activated)); diff --git a/autotests/integration/xdgshellclient_test.cpp b/autotests/integration/xdgshellclient_test.cpp --- a/autotests/integration/xdgshellclient_test.cpp +++ b/autotests/integration/xdgshellclient_test.cpp @@ -430,6 +430,9 @@ void TestXdgShellClient::testFullscreen() { // this test verifies that a window can be properly fullscreened + + XdgShellSurface::States states; + QScopedPointer surface(Test::createSurface()); QFETCH(Test::XdgShellSurfaceType, type); QScopedPointer shellSurface(Test::createXdgShellSurface(type, surface.data())); @@ -445,52 +448,77 @@ QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QVERIFY(c->isActive()); - QCOMPARE(c->layer(), NormalLayer); - QVERIFY(!c->isFullScreen()); - QCOMPARE(c->clientSize(), QSize(100, 50)); - QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); - QCOMPARE(c->clientSizeToFrameSize(c->clientSize()), c->frameGeometry().size()); - QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); - QVERIFY(fullscreenChangedSpy.isValid()); - QSignalSpy frameGeometryChangedSpy(c, &AbstractClient::frameGeometryChanged); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QCOMPARE(client->layer(), NormalLayer); + QVERIFY(!client->isFullScreen()); + QCOMPARE(client->clientSize(), QSize(100, 50)); + QCOMPARE(client->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); + QCOMPARE(client->clientSizeToFrameSize(client->clientSize()), client->size()); + + QSignalSpy fullScreenChangedSpy(client, &AbstractClient::fullScreenChanged); + QVERIFY(fullScreenChangedSpy.isValid()); + QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); - QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), &XdgShellSurface::sizeChanged); - QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); + // Wait for the compositor to send a configure event with the Activated state. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Activated); + + // Ask the compositor to show the window in full screen mode. shellSurface->setFullscreen(true); - QVERIFY(fullscreenChangedSpy.wait()); - QVERIFY(sizeChangeRequestedSpy.wait()); - QCOMPARE(sizeChangeRequestedSpy.count(), 1); - QCOMPARE(sizeChangeRequestedSpy.first().first().toSize(), QSize(screens()->size(0))); - // TODO: should switch to fullscreen once it's updated - QVERIFY(c->isFullScreen()); - QCOMPARE(c->clientSize(), QSize(100, 50)); - QVERIFY(frameGeometryChangedSpy.isEmpty()); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Fullscreen); + QCOMPARE(configureRequestedSpy.last().at(0).value(), screens()->size(0)); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); - Test::render(surface.data(), sizeChangeRequestedSpy.first().first().toSize(), Qt::red); + Test::render(surface.data(), configureRequestedSpy.last().at(0).value(), Qt::red); + +#if 0 // TODO: Uncomment when full screen state updates are truly asynchronous. + QVERIFY(fullScreenChangedSpy.wait()); + QCOMPARE(fullScreenChangedSpy.count(), 1); +#else QVERIFY(frameGeometryChangedSpy.wait()); - QCOMPARE(frameGeometryChangedSpy.count(), 1); - QVERIFY(c->isFullScreen()); - QVERIFY(!c->isDecorated()); - QCOMPARE(c->frameGeometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.first().first().toSize())); - QCOMPARE(c->layer(), ActiveLayer); + QCOMPARE(fullScreenChangedSpy.count(), 1); +#endif + QVERIFY(client->isFullScreen()); + QVERIFY(!client->isDecorated()); + QCOMPARE(client->layer(), ActiveLayer); + QCOMPARE(client->frameGeometry(), QRect(QPoint(0, 0), screens()->size(0))); - // swap back to normal + // Ask the compositor to show the window in normal mode. shellSurface->setFullscreen(false); - QVERIFY(fullscreenChangedSpy.wait()); - QVERIFY(sizeChangeRequestedSpy.wait()); - QCOMPARE(sizeChangeRequestedSpy.count(), 2); - QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); - // TODO: should switch to fullscreen once it's updated - QVERIFY(!c->isFullScreen()); - QCOMPARE(c->layer(), NormalLayer); - QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(!(states & XdgShellSurface::State::Fullscreen)); + QCOMPARE(configureRequestedSpy.last().at(0).value(), QSize(100, 50)); + + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), configureRequestedSpy.last().at(0).value(), Qt::blue); + +#if 0 // TODO: Uncomment when full screen state updates are truly asynchronous. + QVERIFY(fullScreenChangedSpy.wait()); + QCOMPARE(fullScreenChangedSpy.count(), 2); +#else + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(fullScreenChangedSpy.count(), 2); +#endif + QCOMPARE(client->clientSize(), QSize(100, 50)); + QVERIFY(!client->isFullScreen()); + QCOMPARE(client->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); + QCOMPARE(client->layer(), NormalLayer); + + // Destroy the client. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClient::testFullscreenRestore_data() @@ -646,6 +674,9 @@ void TestXdgShellClient::testMaximizedToFullscreen() { // this test verifies that a window can be properly fullscreened after maximizing + + XdgShellSurface::States states; + QScopedPointer surface(Test::createSurface()); QFETCH(Test::XdgShellSurfaceType, type); QScopedPointer shellSurface(Test::createXdgShellSurface(type, surface.data())); @@ -661,67 +692,81 @@ QVERIFY(decoSpy.wait()); QCOMPARE(deco->mode(), decoMode); - auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); - QVERIFY(c); - QVERIFY(c->isActive()); - QVERIFY(!c->isFullScreen()); - QCOMPARE(c->clientSize(), QSize(100, 50)); - QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); - QSignalSpy fullscreenChangedSpy(c, &AbstractClient::fullScreenChanged); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(client); + QVERIFY(client->isActive()); + QVERIFY(!client->isFullScreen()); + QCOMPARE(client->clientSize(), QSize(100, 50)); + QCOMPARE(client->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); + + QSignalSpy fullscreenChangedSpy(client, &AbstractClient::fullScreenChanged); QVERIFY(fullscreenChangedSpy.isValid()); - QSignalSpy frameGeometryChangedSpy(c, &AbstractClient::frameGeometryChanged); + QSignalSpy frameGeometryChangedSpy(client, &AbstractClient::frameGeometryChanged); QVERIFY(frameGeometryChangedSpy.isValid()); - QSignalSpy sizeChangeRequestedSpy(shellSurface.data(), &XdgShellSurface::sizeChanged); - QVERIFY(sizeChangeRequestedSpy.isValid()); QSignalSpy configureRequestedSpy(shellSurface.data(), &XdgShellSurface::configureRequested); QVERIFY(configureRequestedSpy.isValid()); + + // Wait for the compositor to send a configure event with the Activated state. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Activated); + + // Ask the compositor to maximize the window. shellSurface->setMaximized(true); - QVERIFY(sizeChangeRequestedSpy.wait()); - QCOMPARE(sizeChangeRequestedSpy.count(), 1); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Maximized); shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); - Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); + Test::render(surface.data(), configureRequestedSpy.last().at(0).value(), Qt::red); QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(client->maximizeMode(), MaximizeFull); - QCOMPARE(c->maximizeMode(), MaximizeFull); - QCOMPARE(frameGeometryChangedSpy.isEmpty(), false); - frameGeometryChangedSpy.clear(); - - // fullscreen the window + // Ask the compositor to show the window in full screen mode. shellSurface->setFullscreen(true); - QVERIFY(fullscreenChangedSpy.wait()); - if (decoMode == ServerSideDecoration::Mode::Server) { - QVERIFY(sizeChangeRequestedSpy.wait()); - QCOMPARE(sizeChangeRequestedSpy.count(), 2); - } - QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(screens()->size(0))); - // TODO: should switch to fullscreen once it's updated - QVERIFY(c->isFullScreen()); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + QCOMPARE(configureRequestedSpy.last().at(0).value(), screens()->size(0)); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(states & XdgShellSurface::State::Maximized); + QVERIFY(states & XdgShellSurface::State::Fullscreen); - // render at the new size shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); - Test::render(surface.data(), sizeChangeRequestedSpy.last().first().toSize(), Qt::red); + Test::render(surface.data(), configureRequestedSpy.last().at(0).value(), Qt::red); - QVERIFY(c->isFullScreen()); - QVERIFY(!c->isDecorated()); - QCOMPARE(c->frameGeometry(), QRect(QPoint(0, 0), sizeChangeRequestedSpy.last().first().toSize())); - sizeChangeRequestedSpy.clear(); +#if 0 // TODO: Uncomment when full screen changes are truly asynchronous. + QVERIFY(fullScreenChangedSpy.wait()); + QCOMPARE(fullScreenChangedSpy.count(), 1); +#else + QTRY_COMPARE(fullscreenChangedSpy.count(), 1); +#endif + QCOMPARE(client->maximizeMode(), MaximizeFull); + QVERIFY(client->isFullScreen()); + QVERIFY(!client->isDecorated()); - // swap back to normal + // Switch back to normal mode. shellSurface->setFullscreen(false); shellSurface->setMaximized(false); - QVERIFY(fullscreenChangedSpy.wait()); - if (decoMode == ServerSideDecoration::Mode::Server) { - QVERIFY(sizeChangeRequestedSpy.wait()); - // XDG will legitimately get two updates. They might be batched - if (shellSurface && sizeChangeRequestedSpy.count() == 1) { - QVERIFY(sizeChangeRequestedSpy.wait()); - } - QCOMPARE(sizeChangeRequestedSpy.last().first().toSize(), QSize(100, 50)); - } - // TODO: should switch to fullscreen once it's updated - QVERIFY(!c->isFullScreen()); - QCOMPARE(c->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 4); + QCOMPARE(configureRequestedSpy.last().at(0).value(), QSize(100, 50)); + states = configureRequestedSpy.last().at(1).value(); + QVERIFY(!(states & XdgShellSurface::State::Maximized)); + QVERIFY(!(states & XdgShellSurface::State::Fullscreen)); + + shellSurface->ackConfigure(configureRequestedSpy.last().at(2).value()); + Test::render(surface.data(), configureRequestedSpy.last().at(0).value(), Qt::red); + + QVERIFY(frameGeometryChangedSpy.wait()); + QVERIFY(!client->isFullScreen()); + QCOMPARE(client->isDecorated(), decoMode == ServerSideDecoration::Mode::Server); + QCOMPARE(client->maximizeMode(), MaximizeRestore); + + // Destroy the client. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(client)); } void TestXdgShellClient::testWindowOpensLargerThanScreen_data() @@ -753,10 +798,9 @@ auto c = Test::renderAndWaitForShown(surface.data(), screens()->size(0), Qt::blue); QVERIFY(c); QVERIFY(c->isActive()); - QCOMPARE(c->clientSize(), screens()->size(0)); QVERIFY(c->isDecorated()); QEXPECT_FAIL("", "BUG 366632", Continue); - QVERIFY(sizeChangeRequestedSpy.wait(10)); + QCOMPARE(c->frameGeometry(), QRect(QPoint(0, 0), screens()->size(0))); } void TestXdgShellClient::testHidden_data() @@ -1352,17 +1396,17 @@ QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(frameGeometryChangedSpy.count(), 2); QCOMPARE(client->frameGeometry().topLeft(), oldPosition); - QCOMPARE(client->frameGeometry().size(), QSize(100, 50)); + QCOMPARE(client->frameGeometry().size(), QSize(90, 40)); QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); QCOMPARE(client->bufferGeometry().size(), QSize(100, 50)); - shellSurface->setWindowGeometry(QRect(5, 5, 90, 40)); + shellSurface->setWindowGeometry(QRect(0, 0, 100, 50)); surface->commit(Surface::CommitFlag::None); QVERIFY(frameGeometryChangedSpy.wait()); QCOMPARE(frameGeometryChangedSpy.count(), 3); QCOMPARE(client->frameGeometry().topLeft(), oldPosition); - QCOMPARE(client->frameGeometry().size(), QSize(90, 40)); - QCOMPARE(client->bufferGeometry().topLeft(), oldPosition - QPoint(5, 5)); + QCOMPARE(client->frameGeometry().size(), QSize(100, 50)); + QCOMPARE(client->bufferGeometry().topLeft(), oldPosition); QCOMPARE(client->bufferGeometry().size(), QSize(100, 50)); shellSurface.reset(); @@ -1497,13 +1541,10 @@ client->keyPressEvent(Qt::Key_Enter); QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); QCOMPARE(workspace()->moveResizeClient(), nullptr); -#if 0 - QEXPECT_FAIL("", "XdgShellClient currently doesn't send final configure event", Abort); QVERIFY(configureRequestedSpy.wait()); QCOMPARE(configureRequestedSpy.count(), 5); states = configureRequestedSpy.last().at(1).value(); QVERIFY(!states.testFlag(XdgShellSurface::State::Resizing)); -#endif shellSurface.reset(); QVERIFY(Test::waitForWindowDestroyed(client)); diff --git a/deleted.cpp b/deleted.cpp --- a/deleted.cpp +++ b/deleted.cpp @@ -25,7 +25,7 @@ #include "group.h" #include "netinfo.h" #include "shadow.h" -#include "xdgshellclient.h" +#include "waylandclient.h" #include "decorations/decoratedclient.h" #include "decorations/decorationrenderer.h" @@ -149,7 +149,7 @@ }); } - m_wasWaylandClient = qobject_cast(c) != nullptr; + m_wasWaylandClient = qobject_cast(c) != nullptr; m_wasX11Client = qobject_cast(c) != nullptr; m_wasPopupWindow = c->isPopupWindow(); m_wasOutline = c->isOutline(); diff --git a/effects.cpp b/effects.cpp --- a/effects.cpp +++ b/effects.cpp @@ -55,7 +55,7 @@ #include "composite.h" #include "xcbutils.h" #include "platform.h" -#include "xdgshellclient.h" +#include "waylandclient.h" #include "wayland_server.h" #include "decorations/decorationbridge.h" @@ -1743,7 +1743,7 @@ // can still figure out whether it is/was a managed window. managed = toplevel->isClient(); - waylandClient = qobject_cast(toplevel) != nullptr; + waylandClient = qobject_cast(toplevel) != nullptr; x11Client = qobject_cast(toplevel) != nullptr || qobject_cast(toplevel) != nullptr; } diff --git a/placement.cpp b/placement.cpp --- a/placement.cpp +++ b/placement.cpp @@ -210,7 +210,7 @@ * with ideas from xfce. */ - if (!c->size().isValid()) { + if (!c->frameGeometry().isValid()) { return; } @@ -385,7 +385,7 @@ { Q_ASSERT(area.isValid()); - if (!c->size().isValid()) { + if (!c->frameGeometry().isValid()) { return; } diff --git a/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -62,23 +62,25 @@ class PlasmaWindowManagementInterface; class OutputManagementInterface; class OutputConfigurationInterface; -class XdgDecorationManagerInterface; -class XdgShellInterface; class XdgForeignInterface; class XdgOutputManagerInterface; class KeyStateInterface; class LinuxDmabufUnstableV1Interface; class LinuxDmabufUnstableV1Buffer; class TabletManagerInterface; +class XdgDecorationManagerV1Interface; +class XdgShellInterface; +class XdgToplevelInterface; +class XdgPopupInterface; } namespace KWin { -class XdgShellClient; class AbstractClient; class Toplevel; +class XdgToplevelClient; class KWIN_EXPORT WaylandServer : public QObject { @@ -133,7 +135,7 @@ void removeClient(AbstractClient *c); AbstractClient *findClient(quint32 id) const; AbstractClient *findClient(KWaylandServer::SurfaceInterface *surface) const; - XdgShellClient *findXdgShellClient(KWaylandServer::SurfaceInterface *surface) const; + XdgToplevelClient *findXdgToplevelClient(KWaylandServer::SurfaceInterface *surface) const; /** * @returns a transient parent of a surface imported with the foreign protocol, if any @@ -246,9 +248,10 @@ void shellClientShown(Toplevel *t); quint16 createClientId(KWaylandServer::ClientConnection *c); void destroyInternalConnection(); - template - void createSurface(T *surface); void initScreenLocker(); + void createXdgToplevelClient(KWaylandServer::XdgToplevelInterface *shellSurface); + void createXdgPopupClient(KWaylandServer::XdgPopupInterface *shellSurface); + void registerClient(AbstractClient *client); KWaylandServer::Display *m_display = nullptr; KWaylandServer::CompositorInterface *m_compositor = nullptr; KWaylandServer::SeatInterface *m_seat = nullptr; @@ -264,7 +267,7 @@ KWaylandServer::ServerSideDecorationPaletteManagerInterface *m_paletteManager = nullptr; KWaylandServer::IdleInterface *m_idle = nullptr; KWaylandServer::XdgOutputManagerInterface *m_xdgOutputManager = nullptr; - KWaylandServer::XdgDecorationManagerInterface *m_xdgDecorationManager = nullptr; + KWaylandServer::XdgDecorationManagerV1Interface *m_xdgDecorationManagerV1 = nullptr; KWaylandServer::LinuxDmabufUnstableV1Interface *m_linuxDmabuf = nullptr; QSet m_linuxDmabufBuffers; struct { diff --git a/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -23,8 +23,8 @@ #include "composite.h" #include "idle_inhibition.h" #include "screens.h" -#include "xdgshellclient.h" #include "workspace.h" +#include "xdgshellclient.h" // Client #include @@ -59,7 +59,7 @@ #include #include #include -#include +#include #include #include #include @@ -143,52 +143,67 @@ } } -template -void WaylandServer::createSurface(T *surface) +void WaylandServer::registerClient(AbstractClient *client) +{ + if (client->readyForPainting()) { + emit shellClientAdded(client); + } else { + connect(client, &AbstractClient::windowShown, this, &WaylandServer::shellClientShown); + } + m_clients << client; +} + +void WaylandServer::createXdgToplevelClient(XdgToplevelInterface *shellSurface) { - if (!Workspace::self()) { - // it's possible that a Surface gets created before Workspace is created + if (!workspace()) { return; } + + SurfaceInterface *surface = shellSurface->surface(); 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(); } - XdgShellClient *client = new XdgShellClient(surface); - if (ServerSideDecorationInterface *deco = ServerSideDecorationInterface::get(surface->surface())) { - client->installServerSideDecoration(deco); - } + + XdgToplevelClient *client = new XdgToplevelClient(shellSurface); + registerClient(client); + auto it = std::find_if(m_plasmaShellSurfaces.begin(), m_plasmaShellSurfaces.end(), - [client] (PlasmaShellSurfaceInterface *surface) { - return client->surface() == surface->surface(); + [surface] (PlasmaShellSurfaceInterface *plasmaSurface) { + return plasmaSurface->surface() == surface; } ); if (it != m_plasmaShellSurfaces.end()) { client->installPlasmaShellSurface(*it); m_plasmaShellSurfaces.erase(it); } - if (auto menu = m_appMenuManager->appMenuForSurface(surface->surface())) { + if (auto decoration = ServerSideDecorationInterface::get(surface)) { + client->installServerDecoration(decoration); + } + if (auto menu = m_appMenuManager->appMenuForSurface(surface)) { client->installAppMenu(menu); } - if (auto palette = m_paletteManager->paletteForSurface(surface->surface())) { + if (auto palette = m_paletteManager->paletteForSurface(surface)) { client->installPalette(palette); } - m_clients << client; - if (client->readyForPainting()) { - emit shellClientAdded(client); - } else { - connect(client, &XdgShellClient::windowShown, this, &WaylandServer::shellClientShown); - } - //not directly connected as the connection is tied to client instead of this - connect(m_XdgForeign, &KWaylandServer::XdgForeignInterface::transientChanged, client, [this](KWaylandServer::SurfaceInterface *child) { + connect(m_XdgForeign, &XdgForeignInterface::transientChanged, client, [this](SurfaceInterface *child) { emit foreignTransientChanged(child); }); } +void WaylandServer::createXdgPopupClient(XdgPopupInterface *shellSurface) +{ + if (!workspace()) { + return; + } + + XdgPopupClient *client = new XdgPopupClient(shellSurface); + registerClient(client); +} + class KWinDisplay : public KWaylandServer::FilteredDisplay { public: @@ -316,18 +331,18 @@ m_tabletManager = m_display->createTabletManagerInterface(m_display); - m_xdgShell = m_display->createXdgShell(XdgShellInterfaceVersion::Stable, m_display); - m_xdgShell->create(); - connect(m_xdgShell, &XdgShellInterface::surfaceCreated, this, &WaylandServer::createSurface); - connect(m_xdgShell, &XdgShellInterface::xdgPopupCreated, this, &WaylandServer::createSurface); + m_xdgShell = m_display->createXdgShell(m_display); + connect(m_xdgShell, &XdgShellInterface::toplevelCreated, this, &WaylandServer::createXdgToplevelClient); + connect(m_xdgShell, &XdgShellInterface::popupCreated, this, &WaylandServer::createXdgPopupClient); - m_xdgDecorationManager = m_display->createXdgDecorationManager(m_xdgShell, m_display); - m_xdgDecorationManager->create(); - connect(m_xdgDecorationManager, &XdgDecorationManagerInterface::xdgDecorationInterfaceCreated, this, [this] (XdgDecorationInterface *deco) { - if (XdgShellClient *client = findXdgShellClient(deco->surface()->surface())) { - client->installXdgDecoration(deco); + m_xdgDecorationManagerV1 = m_display->createXdgDecorationManagerV1(m_display); + connect(m_xdgDecorationManagerV1, &XdgDecorationManagerV1Interface::decorationCreated, this, + [this](XdgToplevelDecorationV1Interface *decoration) { + if (XdgToplevelClient *toplevel = findXdgToplevelClient(decoration->toplevel()->surface())) { + toplevel->installXdgDecoration(decoration); + } } - }); + ); m_display->createShm(); m_seat = m_display->createSeat(m_display); @@ -345,32 +360,30 @@ m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { - if (XdgShellClient *client = findXdgShellClient(surface->surface())) { + if (XdgToplevelClient *client = findXdgToplevelClient(surface->surface())) { client->installPlasmaShellSurface(surface); - } else { - m_plasmaShellSurfaces << surface; - connect(surface, &QObject::destroyed, this, - [this, surface] { - m_plasmaShellSurfaces.removeOne(surface); - } - ); + return; } + m_plasmaShellSurfaces.append(surface); + connect(surface, &QObject::destroyed, this, [this, surface] { + m_plasmaShellSurfaces.removeOne(surface); + }); } ); m_appMenuManager = m_display->createAppMenuManagerInterface(m_display); m_appMenuManager->create(); connect(m_appMenuManager, &AppMenuManagerInterface::appMenuCreated, [this] (AppMenuInterface *appMenu) { - if (XdgShellClient *client = findXdgShellClient(appMenu->surface())) { + if (XdgToplevelClient *client = findXdgToplevelClient(appMenu->surface())) { client->installAppMenu(appMenu); } } ); m_paletteManager = m_display->createServerSideDecorationPaletteManager(m_display); m_paletteManager->create(); connect(m_paletteManager, &ServerSideDecorationPaletteManagerInterface::paletteCreated, [this] (ServerSideDecorationPaletteInterface *palette) { - if (XdgShellClient *client = findXdgShellClient(palette->surface())) { + if (XdgToplevelClient *client = findXdgToplevelClient(palette->surface())) { client->installPalette(palette); } } @@ -414,14 +427,14 @@ m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, - [this] (ServerSideDecorationInterface *deco) { - if (XdgShellClient *c = findXdgShellClient(deco->surface())) { - c->installServerSideDecoration(deco); + [this] (ServerSideDecorationInterface *decoration) { + if (XdgToplevelClient *client = findXdgToplevelClient(decoration->surface())) { + client->installServerDecoration(decoration); } - connect(deco, &ServerSideDecorationInterface::modeRequested, this, - [deco] (ServerSideDecorationManagerInterface::Mode mode) { + connect(decoration, &ServerSideDecorationInterface::modeRequested, this, + [decoration] (ServerSideDecorationManagerInterface::Mode mode) { // always acknowledge the requested mode - deco->setMode(mode); + decoration->setMode(mode); } ); } @@ -463,15 +476,15 @@ return m_XdgForeign->transientFor(surface); } -void WaylandServer::shellClientShown(Toplevel *t) +void WaylandServer::shellClientShown(Toplevel *toplevel) { - XdgShellClient *c = dynamic_cast(t); - if (!c) { - qCWarning(KWIN_CORE) << "Failed to cast a Toplevel which is supposed to be a XdgShellClient to XdgShellClient"; + AbstractClient *client = qobject_cast(toplevel); + if (!client) { + qCWarning(KWIN_CORE) << "Failed to cast a Toplevel which is supposed to be an AbstractClient to AbstractClient"; return; } - disconnect(c, &XdgShellClient::windowShown, this, &WaylandServer::shellClientShown); - emit shellClientAdded(c); + disconnect(client, &AbstractClient::windowShown, this, &WaylandServer::shellClientShown); + emit shellClientAdded(client); } void WaylandServer::initWorkspace() @@ -749,9 +762,9 @@ return nullptr; } -XdgShellClient *WaylandServer::findXdgShellClient(SurfaceInterface *surface) const +XdgToplevelClient *WaylandServer::findXdgToplevelClient(SurfaceInterface *surface) const { - return qobject_cast(findClient(surface)); + return qobject_cast(findClient(surface)); } quint32 WaylandServer::createWindowId(SurfaceInterface *surface) diff --git a/waylandclient.h b/waylandclient.h new file mode 100644 --- /dev/null +++ b/waylandclient.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 Vlad Zahorodnii + * + * 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 . + */ + +#pragma once + +#include "abstract_client.h" + +namespace KWin +{ + +class WaylandClient : public AbstractClient +{ + Q_OBJECT + +public: + WaylandClient(KWaylandServer::SurfaceInterface *surface); + + QString captionNormal() const override; + QString captionSuffix() const override; + QStringList activities() const override; + void setOnAllActivities(bool set) override; + void blockActivityUpdates(bool b = true) override; + QPoint clientContentPos() const override; + QRect transparentRect() const override; + quint32 windowId() const override; + pid_t pid() const override; + bool isLockScreen() const override; + bool isInputMethod() const override; + bool isLocalhost() const override; + double opacity() const override; + void setOpacity(double opacity) override; + AbstractClient *findModal(bool allow_itself = false) override; + void resizeWithChecks(const QSize &size, ForceGeometry_t force = NormalGeometrySet) override; + void killWindow() override; + QByteArray windowRole() const override; + + void setCaption(const QString &caption); + +protected: + bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; + bool belongsToDesktop() const override; + void updateCaption() override; + +private: + void updateClientArea(); + void updateClientOutputs(); + void updateIcon(); + void updateResourceName(); + + QString m_captionNormal; + QString m_captionSuffix; + double m_opacity = 1.0; + quint32 m_windowId; +}; + +} // namespace KWin diff --git a/waylandclient.cpp b/waylandclient.cpp new file mode 100644 --- /dev/null +++ b/waylandclient.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2015 Martin Flöser + * Copyright (C) 2018 David Edmundson + * Copyright (C) 2020 Vlad Zahorodnii + * + * 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 "waylandclient.h" +#include "screens.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +#include + +#include + +#include +#include + +using namespace KWaylandServer; + +namespace KWin +{ + +WaylandClient::WaylandClient(SurfaceInterface *surface) +{ + // Note that we cannot setup compositing here because we may need to call visibleRect(), + // which in its turn will call bufferGeometry(), which is a pure virtual method. + + setSurface(surface); + + m_windowId = waylandServer()->createWindowId(surface); + + connect(this, &WaylandClient::frameGeometryChanged, + this, &WaylandClient::updateClientOutputs); + connect(this, &WaylandClient::frameGeometryChanged, + this, &WaylandClient::updateClientArea); + connect(this, &WaylandClient::desktopFileNameChanged, + this, &WaylandClient::updateIcon); + connect(screens(), &Screens::changed, this, + &WaylandClient::updateClientOutputs); + + updateResourceName(); + updateIcon(); +} + +QString WaylandClient::captionNormal() const +{ + return m_captionNormal; +} + +QString WaylandClient::captionSuffix() const +{ + return m_captionSuffix; +} + +QStringList WaylandClient::activities() const +{ + return QStringList(); +} + +void WaylandClient::setOnAllActivities(bool set) +{ + Q_UNUSED(set) +} + +void WaylandClient::blockActivityUpdates(bool b) +{ + Q_UNUSED(b) +} + +QPoint WaylandClient::clientContentPos() const +{ + return -clientPos(); +} + +QRect WaylandClient::transparentRect() const +{ + return QRect(); +} + +quint32 WaylandClient::windowId() const +{ + return m_windowId; +} + +pid_t WaylandClient::pid() const +{ + return surface()->client()->processId(); +} + +bool WaylandClient::isLockScreen() const +{ + return surface()->client() == waylandServer()->screenLockerClientConnection(); +} + +bool WaylandClient::isInputMethod() const +{ + return surface()->client() == waylandServer()->inputMethodConnection(); +} + +bool WaylandClient::isLocalhost() const +{ + return true; +} + +double WaylandClient::opacity() const +{ + return m_opacity; +} + +void WaylandClient::setOpacity(double opacity) +{ + const qreal newOpacity = qBound(0.0, opacity, 1.0); + if (newOpacity == m_opacity) { + return; + } + const qreal oldOpacity = m_opacity; + m_opacity = newOpacity; + addRepaintFull(); + emit opacityChanged(this, oldOpacity); +} + +AbstractClient *WaylandClient::findModal(bool allow_itself) +{ + Q_UNUSED(allow_itself) + return nullptr; +} + +void WaylandClient::resizeWithChecks(const QSize &size, ForceGeometry_t force) +{ + const QRect area = workspace()->clientArea(WorkArea, this); + + int width = size.width(); + int height = size.height(); + + // don't allow growing larger than workarea + if (width > area.width()) { + width = area.width(); + } + if (height > area.height()) { + height = area.height(); + } + setFrameGeometry(QRect(x(), y(), width, height), force); +} + +void WaylandClient::killWindow() +{ + if (!surface()) { + return; + } + auto c = surface()->client(); + if (c->processId() == getpid() || c->processId() == 0) { + c->destroy(); + return; + } + ::kill(c->processId(), SIGTERM); + // give it time to terminate and only if terminate fails, try destroy Wayland connection + QTimer::singleShot(5000, c, &ClientConnection::destroy); +} + +QByteArray WaylandClient::windowRole() const +{ + return QByteArray(); +} + +bool WaylandClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const +{ + if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { + if (other->desktopFileName() == desktopFileName()) { + return true; + } + } + if (auto s = other->surface()) { + return s->client() == surface()->client(); + } + return false; +} + +bool WaylandClient::belongsToDesktop() const +{ + const auto clients = waylandServer()->clients(); + + return std::any_of(clients.constBegin(), clients.constEnd(), + [this](const AbstractClient *client) { + if (belongsToSameApplication(client, SameApplicationChecks())) { + return client->isDesktop(); + } + return false; + } + ); +} + +void WaylandClient::updateClientArea() +{ + if (hasStrut()) { + workspace()->updateClientArea(); + } +} + +void WaylandClient::updateClientOutputs() +{ + const auto outputs = waylandServer()->display()->outputs(); + QVector clientOutputs; + clientOutputs.reserve(outputs.count()); + for (OutputInterface *output : outputs) { + const QRect outputGeometry(output->globalPosition(), output->pixelSize() / output->scale()); + if (frameGeometry().intersects(outputGeometry)) { + clientOutputs << output; + } + } + surface()->setOutputs(clientOutputs); +} + +void WaylandClient::updateIcon() +{ + const QString waylandIconName = QStringLiteral("wayland"); + const QString dfIconName = iconFromDesktopFile(); + const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName; + if (iconName == icon().name()) { + return; + } + setIcon(QIcon::fromTheme(iconName)); +} + +void WaylandClient::updateResourceName() +{ + const QFileInfo fileInfo(surface()->client()->executablePath()); + if (fileInfo.exists()) { + setResourceClass(fileInfo.fileName().toUtf8()); + } +} + +void WaylandClient::updateCaption() +{ + const QString oldSuffix = m_captionSuffix; + const auto shortcut = shortcutCaptionSuffix(); + m_captionSuffix = shortcut; + if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { + int i = 2; + do { + m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); + i++; + } while (findClientWithSameCaption()); + } + if (m_captionSuffix != oldSuffix) { + emit captionChanged(); + } +} + +void WaylandClient::setCaption(const QString &caption) +{ + const QString oldSuffix = m_captionSuffix; + m_captionNormal = caption.simplified(); + updateCaption(); + if (m_captionSuffix == oldSuffix) { + // Don't emit caption change twice it already got emitted by the changing suffix. + emit captionChanged(); + } +} + +} // namespace KWin diff --git a/xdgshellclient.h b/xdgshellclient.h --- a/xdgshellclient.h +++ b/xdgshellclient.h @@ -22,243 +22,273 @@ #pragma once -#include "abstract_client.h" +#include "waylandclient.h" #include +#include +#include + namespace KWaylandServer { -class ServerSideDecorationInterface; -class ServerSideDecorationPaletteInterface; class AppMenuInterface; class PlasmaShellSurfaceInterface; -class XdgDecorationInterface; +class ServerSideDecorationInterface; +class ServerSideDecorationPaletteInterface; +class XdgToplevelDecorationV1Interface; } namespace KWin { -/** - * @brief The reason for which the server pinged a client surface - */ -enum class PingReason { - CloseWindow = 0, - FocusWindow +class XdgSurfaceConfigure +{ +public: + enum ConfigureField { + PositionField = 0x1, + SizeField = 0x2, + }; + Q_DECLARE_FLAGS(ConfigureFields, ConfigureField) + + ConfigureFields presentFields; + QPoint position; + QSize size; + qreal serial; }; -class KWIN_EXPORT XdgShellClient : public AbstractClient +class XdgSurfaceClient : public WaylandClient { Q_OBJECT public: - XdgShellClient(KWaylandServer::XdgShellSurfaceInterface *surface); - XdgShellClient(KWaylandServer::XdgShellPopupInterface *surface); - ~XdgShellClient() override; + explicit XdgSurfaceClient(KWaylandServer::XdgSurfaceInterface *shellSurface); + ~XdgSurfaceClient() override; QRect inputGeometry() const override; QRect bufferGeometry() const override; - QStringList activities() const override; - QPoint clientContentPos() const override; QSize clientSize() const override; + QMatrix4x4 inputTransformation() const override; + void setFrameGeometry(const QRect &rect, ForceGeometry_t force = NormalGeometrySet) override; + using AbstractClient::move; + void move(int x, int y, ForceGeometry_t force = NormalGeometrySet) override; + bool isShown(bool shaded_is_shown) const override; + bool isHiddenInternal() const override; + void hideClient(bool hide) override; + void destroyClient() override; + + QRect frameRectToBufferRect(const QRect &rect) const; + QRect requestedFrameGeometry() const; + QPoint requestedPos() const; + QSize requestedSize() const; + QRect requestedClientGeometry() const; + QSize requestedClientSize() const; + QRect clientGeometry() const; + bool isClosing() const; + bool isHidden() const; + bool isUnmapped() const; + +Q_SIGNALS: + void windowMapped(); + void windowUnmapped(); + +protected: + void addDamage(const QRegion &damage) override; + + virtual XdgSurfaceConfigure *sendRoleConfigure() const = 0; + virtual void handleRoleCommit(); + + XdgSurfaceConfigure *lastAcknowledgedConfigure() const; + void scheduleConfigure(); + void sendConfigure(); + void requestGeometry(const QRect &rect); + void updateGeometry(const QRect &rect); + +private: + void handleConfigureAcknowledged(quint32 serial); + void handleCommit(); + void handleNextWindowGeometry(); + bool haveNextWindowGeometry() const; + void setHaveNextWindowGeometry(); + void resetHaveNextWindowGeometry(); + QRect adjustMoveResizeGeometry(const QRect &rect) const; + void updateGeometryRestoreHack(); + void updateDepth(); + void internalShow(); + void internalHide(); + void internalMap(); + void internalUnmap(); + void cleanGrouping(); + void cleanTabBox(); + + KWaylandServer::XdgSurfaceInterface *m_shellSurface; + QTimer *m_configureTimer; + QQueue m_configureEvents; + XdgSurfaceConfigure *m_lastAcknowledgedConfigure = nullptr; + QRect m_windowGeometry; + QRect m_requestedFrameGeometry; + QRect m_bufferGeometry; + QRect m_requestedClientGeometry; + QRect m_clientGeometry; + bool m_isClosing = false; + bool m_isHidden = false; + bool m_isUnmapped = true; + bool m_haveNextWindowGeometry = false; +}; + +class XdgToplevelConfigure final : public XdgSurfaceConfigure +{ +public: + KWaylandServer::XdgToplevelInterface::States states; +}; + +class XdgToplevelClient final : public XdgSurfaceClient +{ + Q_OBJECT + + enum class PingReason { + CloseWindow, + FocusWindow, + }; + +public: + explicit XdgToplevelClient(KWaylandServer::XdgToplevelInterface *shellSurface); + ~XdgToplevelClient() override; + + void debug(QDebug &stream) const override; + NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; + MaximizeMode maximizeMode() const override; + MaximizeMode requestedMaximizeMode() const override; QSize minSize() const override; QSize maxSize() const override; - QRect transparentRect() const override; - NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; - void debug(QDebug &stream) const override; - double opacity() const override; - void setOpacity(double opacity) override; - QByteArray windowRole() const override; - void blockActivityUpdates(bool b = true) override; - QString captionNormal() const override; - QString captionSuffix() const override; - void closeWindow() override; - AbstractClient *findModal(bool allow_itself = false) override; + bool isFullScreen() const override; + bool isMovableAcrossScreens() const override; + bool isMovable() const override; + bool isResizable() const override; bool isCloseable() const override; bool isFullScreenable() const override; - bool isFullScreen() const override; bool isMaximizable() const override; bool isMinimizable() const override; - bool isMovable() const override; - bool isMovableAcrossScreens() const override; - bool isResizable() const override; - bool isShown(bool shaded_is_shown) const override; - bool isHiddenInternal() const override; - void hideClient(bool hide) override; - MaximizeMode maximizeMode() const override; - MaximizeMode requestedMaximizeMode() const override; + bool isTransient() const override; + bool userCanSetFullScreen() const override; + bool userCanSetNoBorder() const override; bool noBorder() const override; - void setFullScreen(bool set, bool user = true) override; void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; - void setOnAllActivities(bool set) override; + void updateColorScheme() override; + bool supportsWindowRules() const override; void takeFocus() override; - bool userCanSetFullScreen() const override; - bool userCanSetNoBorder() const override; bool wantsInput() const override; bool dockWantsInput() const override; - void resizeWithChecks(const QSize &size, ForceGeometry_t force = NormalGeometrySet) override; - void setFrameGeometry(const QRect &rect, ForceGeometry_t force = NormalGeometrySet) override; bool hasStrut() const override; - quint32 windowId() const override; - pid_t pid() const override; - bool isLockScreen() const override; - bool isInputMethod() const override; - bool isInitialPositionSet() const override; - bool isTransient() const override; - bool hasTransientPlacementHint() const override; - QRect transientPlacement(const QRect &bounds) const override; - QMatrix4x4 inputTransformation() const override; void showOnScreenEdge() override; - bool hasPopupGrab() const override; - void popupDone() override; - void updateColorScheme() override; - bool isPopupWindow() const override; - void killWindow() override; - bool isLocalhost() const override; - bool supportsWindowRules() const override; - void destroyClient() override; + bool isInitialPositionSet() const override; + void setFullScreen(bool set, bool user) override; + void closeWindow() override; - void installPlasmaShellSurface(KWaylandServer::PlasmaShellSurfaceInterface *surface); - void installServerSideDecoration(KWaylandServer::ServerSideDecorationInterface *decoration); - void installAppMenu(KWaylandServer::AppMenuInterface *appmenu); + void installAppMenu(KWaylandServer::AppMenuInterface *appMenu); + void installServerDecoration(KWaylandServer::ServerSideDecorationInterface *decoration); void installPalette(KWaylandServer::ServerSideDecorationPaletteInterface *palette); - void installXdgDecoration(KWaylandServer::XdgDecorationInterface *decoration); + void installPlasmaShellSurface(KWaylandServer::PlasmaShellSurfaceInterface *shellSurface); + void installXdgDecoration(KWaylandServer::XdgToplevelDecorationV1Interface *decoration); protected: - void addDamage(const QRegion &damage) override; - bool belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const override; - void doSetActive() override; - bool belongsToDesktop() const override; - Layer layerForDock() const override; - void changeMaximize(bool horizontal, bool vertical, bool adjust) override; + XdgSurfaceConfigure *sendRoleConfigure() const override; + void handleRoleCommit() override; + void doMinimize() override; void doResizeSync() override; + void doSetActive() override; bool acceptsFocus() const override; - void doMinimize() override; - void updateCaption() override; - void doMove(int x, int y) override; + void changeMaximize(bool horizontal, bool vertical, bool adjust) override; + Layer layerForDock() const override; -private Q_SLOTS: - void handleConfigureAcknowledged(quint32 serial); - void handleTransientForChanged(); - void handleWindowClassChanged(const QByteArray &windowClass); - void handleWindowGeometryChanged(const QRect &windowGeometry); - void handleWindowTitleChanged(const QString &title); +private: + void handleWindowTitleChanged(); + void handleWindowClassChanged(); + void handleWindowMenuRequested(KWaylandServer::SeatInterface *seat, + const QPoint &surfacePos, quint32 serial); void handleMoveRequested(KWaylandServer::SeatInterface *seat, quint32 serial); - void handleResizeRequested(KWaylandServer::SeatInterface *seat, quint32 serial, Qt::Edges edges); + void handleResizeRequested(KWaylandServer::SeatInterface *seat, + Qt::Edges, quint32 serial); + void handleStatesAcknowledged(const KWaylandServer::XdgToplevelInterface::States &states); + void handleMaximizeRequested(); + void handleUnmaximizeRequested(); + void handleFullscreenRequested(KWaylandServer::OutputInterface *output); + void handleUnfullscreenRequested(); void handleMinimizeRequested(); - void handleMaximizeRequested(bool maximized); - void handleFullScreenRequested(bool fullScreen, KWaylandServer::OutputInterface *output); - void handleWindowMenuRequested(KWaylandServer::SeatInterface *seat, quint32 serial, const QPoint &surfacePos); - void handleGrabRequested(KWaylandServer::SeatInterface *seat, quint32 serial); - void handlePingDelayed(quint32 serial); + void handleTransientForChanged(); + void handleForeignTransientForChanged(KWaylandServer::SurfaceInterface *child); void handlePingTimeout(quint32 serial); + void handlePingDelayed(quint32 serial); void handlePongReceived(quint32 serial); - void handleCommitted(); - -private: - /** - * Called when the shell is created. - */ - void init(); - /** - * Called for the XDG case when the shell surface is committed to the surface. - * At this point all initial properties should have been set by the client. - */ - void finishInit(); - void createWindowId(); - void updateIcon(); - bool shouldExposeToWindowManagement(); - void updateClientOutputs(); - KWaylandServer::XdgShellSurfaceInterface::States xdgSurfaceStates() const; - void updateShowOnScreenEdge(); + void initialize(); void updateMaximizeMode(MaximizeMode maximizeMode); - // called on surface commit and processes all m_pendingConfigureRequests up to m_lastAckedConfigureReqest - void updatePendingGeometry(); - QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const; - void requestGeometry(const QRect &rect); - void doSetGeometry(const QRect &rect); - void unmap(); - void markAsMapped(); - QRect determineBufferGeometry() const; - void ping(PingReason reason); - static void deleteClient(XdgShellClient *c); - - QRect adjustMoveGeometry(const QRect &rect) const; - QRect adjustResizeGeometry(const QRect &rect) const; - - KWaylandServer::XdgShellSurfaceInterface *m_xdgShellToplevel; - KWaylandServer::XdgShellPopupInterface *m_xdgShellPopup; - - QRect m_bufferGeometry; - QRect m_windowGeometry; - bool m_hasWindowGeometry = false; - - // last size we requested or empty if we haven't sent an explicit request to the client - // if empty the client should choose their own default size - QSize m_requestedClientSize = QSize(0, 0); - - struct PendingConfigureRequest { - //note for wl_shell we have no serial, so serialId and m_lastAckedConfigureRequest will always be 0 - //meaning we treat a surface commit as having processed all requests - quint32 serialId = 0; - // position to apply after a resize operation has been completed - QPoint positionAfterResize; - MaximizeMode maximizeMode; - }; - QVector m_pendingConfigureRequests; - quint32 m_lastAckedConfigureRequest = 0; - - //mode in use by the current buffer - MaximizeMode m_maximizeMode = MaximizeRestore; - //mode we currently want to be, could be pending on client updating, could be not sent yet - MaximizeMode m_requestedMaximizeMode = MaximizeRestore; + void updateFullScreenMode(bool set); + void updateShowOnScreenEdge(); + void setupWindowManagementIntegration(); + void setupPlasmaShellIntegration(); + void sendPing(PingReason reason); - QRect m_geomFsRestore; //size and position of the window before it was set to fullscreen - bool m_closing = false; - quint32 m_windowId = 0; - bool m_unmapped = true; - NET::WindowType m_windowType = NET::Normal; QPointer m_plasmaShellSurface; QPointer m_appMenuInterface; QPointer m_paletteInterface; - KWaylandServer::ServerSideDecorationInterface *m_serverDecoration = nullptr; - KWaylandServer::XdgDecorationInterface *m_xdgDecoration = nullptr; + QPointer m_serverDecoration; + QPointer m_xdgDecoration; + KWaylandServer::XdgToplevelInterface *m_shellSurface; + KWaylandServer::XdgToplevelInterface::States m_lastAcknowledgedStates; + QMap m_pings; + QRect m_fullScreenGeometryRestore; + NET::WindowType m_windowType = NET::Normal; + MaximizeMode m_maximizeMode = MaximizeRestore; + MaximizeMode m_requestedMaximizeMode = MaximizeRestore; + bool m_isFullScreen = false; bool m_userNoBorder = false; - bool m_fullScreen = false; - bool m_transient = false; - bool m_hidden = false; - bool m_hasPopupGrab = false; - qreal m_opacity = 1.0; - - class RequestGeometryBlocker { //TODO rename ConfigureBlocker when this class is Xdg only - public: - RequestGeometryBlocker(XdgShellClient *client) - : m_client(client) - { - m_client->m_requestGeometryBlockCounter++; - } - ~RequestGeometryBlocker() - { - m_client->m_requestGeometryBlockCounter--; - if (m_client->m_requestGeometryBlockCounter == 0) { - m_client->requestGeometry(m_client->m_blockedRequestGeometry); - } - } - private: - XdgShellClient *m_client; - }; - friend class RequestGeometryBlocker; - int m_requestGeometryBlockCounter = 0; - QRect m_blockedRequestGeometry; - QString m_caption; - QString m_captionSuffix; - QHash m_pingSerials; - + bool m_isTransient = false; bool m_isInitialized = false; +}; + +class XdgPopupClient final : public XdgSurfaceClient +{ + Q_OBJECT + +public: + explicit XdgPopupClient(KWaylandServer::XdgPopupInterface *shellSurface); + ~XdgPopupClient() override; + + void debug(QDebug &stream) const override; + NET::WindowType windowType(bool direct = false, int supported_types = 0) const override; + bool hasPopupGrab() const override; + void popupDone() override; + bool isPopupWindow() const override; + bool isTransient() const override; + bool isResizable() const override; + bool isMovable() const override; + bool isMovableAcrossScreens() const override; + bool hasTransientPlacementHint() const override; + QRect transientPlacement(const QRect &bounds) const override; + bool isCloseable() const override; + void closeWindow() override; + void updateColorScheme() override; + bool noBorder() const override; + bool userCanSetNoBorder() const override; + void setNoBorder(bool set) override; + void updateDecoration(bool check_workspace_pos, bool force = false) override; + void showOnScreenEdge() override; + bool wantsInput() const override; + void takeFocus() override; + bool supportsWindowRules() const override; + +protected: + bool acceptsFocus() const override; + XdgSurfaceConfigure *sendRoleConfigure() const override; - friend class Workspace; +private: + void handleGrabRequested(KWaylandServer::SeatInterface *seat, quint32 serial); + void initialize(); + + KWaylandServer::XdgPopupInterface *m_shellSurface; + bool m_haveExplicitGrab = false; }; -} +} // namespace KWin -Q_DECLARE_METATYPE(KWin::XdgShellClient *) +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::XdgSurfaceConfigure::ConfigureFields) diff --git a/xdgshellclient.cpp b/xdgshellclient.cpp --- a/xdgshellclient.cpp +++ b/xdgshellclient.cpp @@ -20,913 +20,857 @@ along with this program. If not, see . *********************************************************************/ #include "xdgshellclient.h" -#include "cursor.h" -#include "decorations/decoratedclient.h" -#include "decorations/decorationbridge.h" #include "deleted.h" -#include "placement.h" #include "screenedge.h" #include "screens.h" +#include "subsurfacemonitor.h" +#include "wayland_server.h" +#include "workspace.h" + #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif -#include "virtualdesktops.h" -#include "wayland_server.h" -#include "workspace.h" #include #include #include #include -#include -#include +#include #include -#include #include #include #include -#include #include -#include +#include +#include + +using namespace KWaylandServer; + +namespace KWin +{ + +XdgSurfaceClient::XdgSurfaceClient(XdgSurfaceInterface *shellSurface) + : WaylandClient(shellSurface->surface()) + , m_shellSurface(shellSurface) + , m_configureTimer(new QTimer(this)) +{ + setupCompositing(); -#include + connect(shellSurface, &XdgSurfaceInterface::configureAcknowledged, + this, &XdgSurfaceClient::handleConfigureAcknowledged); + connect(shellSurface->surface(), &SurfaceInterface::committed, + this, &XdgSurfaceClient::handleCommit); + connect(shellSurface->surface(), &SurfaceInterface::shadowChanged, + this, &XdgSurfaceClient::updateShadow); + connect(shellSurface->surface(), &SurfaceInterface::unmapped, + this, &XdgSurfaceClient::internalUnmap); + connect(shellSurface->surface(), &SurfaceInterface::unbound, + this, &XdgSurfaceClient::destroyClient); + connect(shellSurface->surface(), &SurfaceInterface::destroyed, + this, &XdgSurfaceClient::destroyClient); -#include -#include + // The effective window geometry is determined by two things: (a) the rectangle that bounds + // the main surface and all of its sub-surfaces, (b) the client-specified window geometry, if + // any. If the client hasn't provided the window geometry, we fallback to the bounding sub- + // surface rectangle. If the client has provided the window geometry, we intersect it with + // the bounding rectangle and that will be the effective window geometry. It's worth to point + // out that geometry updates do not occur that frequently, so we don't need to recompute the + // bounding geometry every time the client commits the surface. -#include + SubSurfaceMonitor *treeMonitor = new SubSurfaceMonitor(surface(), this); -Q_DECLARE_METATYPE(NET::WindowType) + connect(treeMonitor, &SubSurfaceMonitor::subSurfaceAdded, + this, &XdgSurfaceClient::setHaveNextWindowGeometry); + connect(treeMonitor, &SubSurfaceMonitor::subSurfaceRemoved, + this, &XdgSurfaceClient::setHaveNextWindowGeometry); + connect(treeMonitor, &SubSurfaceMonitor::subSurfaceMoved, + this, &XdgSurfaceClient::setHaveNextWindowGeometry); + connect(treeMonitor, &SubSurfaceMonitor::subSurfaceResized, + this, &XdgSurfaceClient::setHaveNextWindowGeometry); + connect(shellSurface, &XdgSurfaceInterface::windowGeometryChanged, + this, &XdgSurfaceClient::setHaveNextWindowGeometry); + connect(surface(), &SurfaceInterface::sizeChanged, + this, &XdgSurfaceClient::setHaveNextWindowGeometry); -using namespace KWaylandServer; + // Configure events are not sent immediately, but rather scheduled to be sent when the event + // loop is about to be idle. By doing this, we can avoid sending configure events that do + // nothing, and implementation-wise, it's simpler. -namespace KWin + m_configureTimer->setSingleShot(true); + connect(m_configureTimer, &QTimer::timeout, this, &XdgSurfaceClient::sendConfigure); + + // Unfortunately, AbstractClient::checkWorkspacePosition() operates on the geometry restore + // so we need to initialize it with some reasonable value; otherwise bad things will happen + // when we want to decorate the client or move the client to another screen. This is a hack. + + connect(this, &XdgSurfaceClient::frameGeometryChanged, + this, &XdgSurfaceClient::updateGeometryRestoreHack); +} + +XdgSurfaceClient::~XdgSurfaceClient() { + qDeleteAll(m_configureEvents); +} -XdgShellClient::XdgShellClient(XdgShellSurfaceInterface *surface) - : AbstractClient() - , m_xdgShellToplevel(surface) - , m_xdgShellPopup(nullptr) +QRect XdgSurfaceClient::requestedFrameGeometry() const { - setSurface(surface->surface()); - init(); + return m_requestedFrameGeometry; } -XdgShellClient::XdgShellClient(XdgShellPopupInterface *surface) - : AbstractClient() - , m_xdgShellToplevel(nullptr) - , m_xdgShellPopup(surface) +QPoint XdgSurfaceClient::requestedPos() const { - setSurface(surface->surface()); - init(); + return m_requestedFrameGeometry.topLeft(); } -XdgShellClient::~XdgShellClient() = default; +QSize XdgSurfaceClient::requestedSize() const +{ + return m_requestedFrameGeometry.size(); +} -void XdgShellClient::init() +QRect XdgSurfaceClient::requestedClientGeometry() const { - m_requestGeometryBlockCounter++; + return m_requestedClientGeometry; +} - connect(this, &XdgShellClient::desktopFileNameChanged, this, &XdgShellClient::updateIcon); - createWindowId(); - setupCompositing(); - updateIcon(); +/** + * \todo When the client is not server-side decorated we probably need to resort to the + * bounding geometry, i.e. the rectangle that bounds the main surface and sub-surfaces. + */ +QRect XdgSurfaceClient::inputGeometry() const +{ + return isDecorated() ? AbstractClient::inputGeometry() : bufferGeometry(); +} - // TODO: Initialize with null rect. - m_frameGeometry = QRect(0, 0, -1, -1); - m_windowGeometry = QRect(0, 0, -1, -1); +QRect XdgSurfaceClient::bufferGeometry() const +{ + return m_bufferGeometry; +} - if (waylandServer()->inputMethodConnection() == surface()->client()) { - m_windowType = NET::OnScreenDisplay; - } +QSize XdgSurfaceClient::requestedClientSize() const +{ + return requestedClientGeometry().size(); +} - connect(surface(), &SurfaceInterface::unmapped, this, &XdgShellClient::unmap); - connect(surface(), &SurfaceInterface::unbound, this, &XdgShellClient::destroyClient); - connect(surface(), &SurfaceInterface::destroyed, this, &XdgShellClient::destroyClient); +QRect XdgSurfaceClient::clientGeometry() const +{ + return m_clientGeometry; +} - if (m_xdgShellToplevel) { - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::destroyed, this, &XdgShellClient::destroyClient); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged); +QSize XdgSurfaceClient::clientSize() const +{ + return m_clientGeometry.size(); +} - m_caption = m_xdgShellToplevel->title().simplified(); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::titleChanged, this, &XdgShellClient::handleWindowTitleChanged); - QTimer::singleShot(0, this, &XdgShellClient::updateCaption); +QMatrix4x4 XdgSurfaceClient::inputTransformation() const +{ + QMatrix4x4 transformation; + transformation.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); + return transformation; +} - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::moveRequested, this, &XdgShellClient::handleMoveRequested); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::resizeRequested, this, &XdgShellClient::handleResizeRequested); +XdgSurfaceConfigure *XdgSurfaceClient::lastAcknowledgedConfigure() const +{ + return m_lastAcknowledgedConfigure; +} - // Determine the resource name, this is inspired from ICCCM 4.1.2.5 - // the binary name of the invoked client. - QFileInfo info{m_xdgShellToplevel->client()->executablePath()}; - QByteArray resourceName; - if (info.exists()) { - resourceName = info.fileName().toUtf8(); - } - setResourceClass(resourceName, m_xdgShellToplevel->windowClass()); - setDesktopFileName(m_xdgShellToplevel->windowClass()); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowClassChanged, this, &XdgShellClient::handleWindowClassChanged); +void XdgSurfaceClient::scheduleConfigure() +{ + if (!isClosing()) { + m_configureTimer->start(); + } +} - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::minimizeRequested, this, &XdgShellClient::handleMinimizeRequested); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::maximizedChanged, this, &XdgShellClient::handleMaximizeRequested); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::fullscreenChanged, this, &XdgShellClient::handleFullScreenRequested); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowMenuRequested, this, &XdgShellClient::handleWindowMenuRequested); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::transientForChanged, this, &XdgShellClient::handleTransientForChanged); - connect(m_xdgShellToplevel, &XdgShellSurfaceInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged); +void XdgSurfaceClient::sendConfigure() +{ + XdgSurfaceConfigure *configureEvent = sendRoleConfigure(); - auto global = static_cast(m_xdgShellToplevel->global()); - connect(global, &XdgShellInterface::pingDelayed, this, &XdgShellClient::handlePingDelayed); - connect(global, &XdgShellInterface::pingTimeout, this, &XdgShellClient::handlePingTimeout); - connect(global, &XdgShellInterface::pongReceived, this, &XdgShellClient::handlePongReceived); + if (configureEvent->position != pos()) + configureEvent->presentFields |= XdgSurfaceConfigure::PositionField; + if (configureEvent->size != size()) + configureEvent->presentFields |= XdgSurfaceConfigure::SizeField; - auto configure = [this] { - if (m_closing) { - return; - } - if (m_requestGeometryBlockCounter != 0 || areGeometryUpdatesBlocked()) { - return; - } - m_xdgShellToplevel->configure(xdgSurfaceStates(), m_requestedClientSize); - }; - connect(this, &AbstractClient::activeChanged, this, configure); - connect(this, &AbstractClient::clientStartUserMovedResized, this, configure); - connect(this, &AbstractClient::clientFinishUserMovedResized, this, configure); + m_configureEvents.append(configureEvent); +} - connect(this, &XdgShellClient::frameGeometryChanged, this, &XdgShellClient::updateClientOutputs); - connect(screens(), &Screens::changed, this, &XdgShellClient::updateClientOutputs); - } else if (m_xdgShellPopup) { - connect(m_xdgShellPopup, &XdgShellPopupInterface::configureAcknowledged, this, &XdgShellClient::handleConfigureAcknowledged); - connect(m_xdgShellPopup, &XdgShellPopupInterface::grabRequested, this, &XdgShellClient::handleGrabRequested); - connect(m_xdgShellPopup, &XdgShellPopupInterface::destroyed, this, &XdgShellClient::destroyClient); - connect(m_xdgShellPopup, &XdgShellPopupInterface::windowGeometryChanged, this, &XdgShellClient::handleWindowGeometryChanged); +void XdgSurfaceClient::handleConfigureAcknowledged(quint32 serial) +{ + while (!m_configureEvents.isEmpty()) { + if (serial < m_configureEvents.first()->serial) { + break; + } + delete m_lastAcknowledgedConfigure; + m_lastAcknowledgedConfigure = m_configureEvents.takeFirst(); } +} - // set initial desktop - setDesktop(VirtualDesktopManager::self()->current()); +void XdgSurfaceClient::handleCommit() +{ + if (!surface()->buffer()) { + return; + } - // setup shadow integration - updateShadow(); - connect(surface(), &SurfaceInterface::shadowChanged, this, &Toplevel::updateShadow); + if (haveNextWindowGeometry()) { + handleNextWindowGeometry(); + resetHaveNextWindowGeometry(); + } - connect(waylandServer(), &WaylandServer::foreignTransientChanged, this, [this](KWaylandServer::SurfaceInterface *child) { - if (child == surface()) { - handleTransientForChanged(); - } - }); - handleTransientForChanged(); + handleRoleCommit(); - AbstractClient::updateColorScheme(QString()); + delete m_lastAcknowledgedConfigure; + m_lastAcknowledgedConfigure = nullptr; - connect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit); + internalMap(); + updateDepth(); } -void XdgShellClient::finishInit() +void XdgSurfaceClient::handleRoleCommit() { - disconnect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::finishInit); - - connect(surface(), &SurfaceInterface::committed, this, &XdgShellClient::handleCommitted); +} - bool needsPlacement = !isInitialPositionSet(); +void XdgSurfaceClient::handleNextWindowGeometry() +{ + const QRect boundingGeometry = surface()->boundingRect(); - if (supportsWindowRules()) { - setupWindowRules(false); + // The effective window geometry is defined as the intersection of the window geometry + // and the rectangle that bounds the main surface and all of its sub-surfaces. If the + // client hasn't specified the window geometry, we must fallback to the bounding geometry. + // Note that the xdg-shell spec is not clear about when exactly we have to clamp the + // window geometry. - const QRect originalGeometry = frameGeometry(); - const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true); - if (originalGeometry != ruledGeometry) { - setFrameGeometry(ruledGeometry); - } + m_windowGeometry = m_shellSurface->windowGeometry(); + if (m_windowGeometry.isValid()) { + m_windowGeometry &= boundingGeometry; + } else { + m_windowGeometry = boundingGeometry; + } - maximize(rules()->checkMaximize(maximizeMode(), true)); + if (m_windowGeometry.isEmpty()) { + qCWarning(KWIN_CORE) << "Committed empty window geometry, dealing with a buggy client!"; + } - setDesktop(rules()->checkDesktop(desktop(), true)); - setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); - if (rules()->checkMinimize(isMinimized(), true)) { - minimize(true); // No animation. - } - setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true)); - setSkipPager(rules()->checkSkipPager(skipPager(), true)); - setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true)); - setKeepAbove(rules()->checkKeepAbove(keepAbove(), true)); - setKeepBelow(rules()->checkKeepBelow(keepBelow(), true)); - setShortcut(rules()->checkShortcut(shortcut().toString(), true)); - updateColorScheme(); + QRect frameGeometry(pos(), clientSizeToFrameSize(m_windowGeometry.size())); - // Don't place the client if its position is set by a rule. - if (rules()->checkPosition(invalidPoint, true) != invalidPoint) { - needsPlacement = false; - } + // We're not done yet. The xdg-shell spec allows clients to attach buffers smaller than + // we asked. Normally, this is not a big deal, but when the client is being interactively + // resized, it may cause the window contents to bounce. In order to counter this, we have + // to "gravitate" the new geometry according to the current move-resize pointer mode so + // the opposite window corner stays still. - // Don't place the client if the maximize state is set by a rule. - if (requestedMaximizeMode() != MaximizeRestore) { - needsPlacement = false; - } + if (isMoveResize()) { + frameGeometry = adjustMoveResizeGeometry(frameGeometry); + } else if (lastAcknowledgedConfigure()) { + XdgSurfaceConfigure *configureEvent = lastAcknowledgedConfigure(); - discardTemporaryRules(); - RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. - updateWindowRules(Rules::All); + if (configureEvent->presentFields & XdgSurfaceConfigure::PositionField) + frameGeometry.moveTopLeft(configureEvent->position); } - if (isFullScreen()) { - needsPlacement = false; - } + updateGeometry(frameGeometry); - if (needsPlacement) { - const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); - placeIn(area); + if (isResize()) { + performMoveResize(); } +} - m_requestGeometryBlockCounter--; - if (m_requestGeometryBlockCounter == 0) { - requestGeometry(m_blockedRequestGeometry); - } +bool XdgSurfaceClient::haveNextWindowGeometry() const +{ + return m_haveNextWindowGeometry || m_lastAcknowledgedConfigure; +} - m_isInitialized = true; +void XdgSurfaceClient::setHaveNextWindowGeometry() +{ + m_haveNextWindowGeometry = true; } -void XdgShellClient::destroyClient() +void XdgSurfaceClient::resetHaveNextWindowGeometry() { - m_closing = true; -#ifdef KWIN_BUILD_TABBOX - TabBox::TabBox *tabBox = TabBox::TabBox::self(); - if (tabBox->isDisplayed() && tabBox->currentClient() == this) { - tabBox->nextPrev(true); - } -#endif - if (isMoveResize()) { - leaveMoveResize(); - } + m_haveNextWindowGeometry = false; +} - // Replace ShellClient with an instance of Deleted in the stacking order. - Deleted *deleted = Deleted::create(this); - emit windowClosed(this, deleted); +QRect XdgSurfaceClient::adjustMoveResizeGeometry(const QRect &rect) const +{ + QRect geometry = rect; - // Remove Force Temporarily rules. - RuleBook::self()->discardUsed(this, true); + switch (moveResizePointerMode()) { + case PositionTopLeft: + geometry.moveRight(moveResizeGeometry().right()); + geometry.moveBottom(moveResizeGeometry().bottom()); + break; + case PositionTop: + case PositionTopRight: + geometry.moveLeft(moveResizeGeometry().left()); + geometry.moveBottom(moveResizeGeometry().bottom()); + break; + case PositionRight: + case PositionBottomRight: + case PositionBottom: + case PositionCenter: + geometry.moveLeft(moveResizeGeometry().left()); + geometry.moveTop(moveResizeGeometry().top()); + break; + case PositionBottomLeft: + case PositionLeft: + geometry.moveRight(moveResizeGeometry().right()); + geometry.moveTop(moveResizeGeometry().top()); + break; + } - destroyWindowManagementInterface(); - destroyDecoration(); + return geometry; +} - StackingUpdatesBlocker blocker(workspace()); - if (transientFor()) { - transientFor()->removeTransient(this); - } - for (auto it = transients().constBegin(); it != transients().constEnd();) { - if ((*it)->transientFor() == this) { - removeTransient(*it); - it = transients().constBegin(); // restart, just in case something more has changed with the list +/** + * Sets the frame geometry of the XdgSurfaceClient to \a rect. + * + * Because geometry updates are asynchronous on Wayland, there are no any guarantees that + * the frame geometry will be changed immediately. We may need to send a configure event to + * the client if the current window geometry size and the requested window geometry size + * don't match. frameGeometryChanged() will be emitted when the requested frame geometry + * has been applied. + * + * Notice that the client may attach a buffer smaller than the one in the configure event. + */ +void XdgSurfaceClient::setFrameGeometry(const QRect &rect, ForceGeometry_t force) +{ + m_requestedFrameGeometry = rect; + + // XdgToplevelClient currently doesn't support shaded clients, but let's stick with + // what X11Client does. Hopefully, one day we will be able to unify setFrameGeometry() + // for all AbstractClient subclasses. It's going to be great! + + if (isShade()) { + if (m_requestedFrameGeometry.height() == borderTop() + borderBottom()) { + qCDebug(KWIN_CORE) << "Passed shaded frame geometry to setFrameGeometry()"; } else { - ++it; + m_requestedClientGeometry = frameRectToClientRect(m_requestedFrameGeometry); + m_requestedFrameGeometry.setHeight(borderTop() + borderBottom()); } + } else { + m_requestedClientGeometry = frameRectToClientRect(m_requestedFrameGeometry); } - waylandServer()->removeClient(this); + if (areGeometryUpdatesBlocked()) { + m_frameGeometry = m_requestedFrameGeometry; + if (pendingGeometryUpdate() == PendingGeometryForced) { + return; + } + if (force == ForceGeometrySet) { + setPendingGeometryUpdate(PendingGeometryForced); + } else { + setPendingGeometryUpdate(PendingGeometryNormal); + } + return; + } - deleted->unrefWindow(); + m_frameGeometry = frameGeometryBeforeUpdateBlocking(); - m_xdgShellToplevel = nullptr; - m_xdgShellPopup = nullptr; - deleteClient(this); -} + // Notice that the window geometry size of (0, 0) has special meaning to xdg shell clients. + // It basically says "pick whatever size you think is the best, dawg." -void XdgShellClient::deleteClient(XdgShellClient *c) -{ - delete c; + if (requestedClientSize() != clientSize()) { + requestGeometry(requestedFrameGeometry()); + } else { + updateGeometry(requestedFrameGeometry()); + } } -QRect XdgShellClient::inputGeometry() const +void XdgSurfaceClient::move(int x, int y, ForceGeometry_t force) { - if (isDecorated()) { - return AbstractClient::inputGeometry(); + Q_ASSERT(pendingGeometryUpdate() == PendingGeometryNone || areGeometryUpdatesBlocked()); + QPoint p(x, y); + if (!areGeometryUpdatesBlocked() && p != rules()->checkPosition(p)) { + qCDebug(KWIN_CORE) << "forced position fail:" << p << ":" << rules()->checkPosition(p); } - // TODO: What about sub-surfaces sticking outside the main surface? - return m_bufferGeometry; + m_requestedFrameGeometry.moveTopLeft(p); + m_requestedClientGeometry.moveTopLeft(framePosToClientPos(p)); + if (force == NormalGeometrySet && m_frameGeometry.topLeft() == p) { + return; + } + m_frameGeometry.moveTopLeft(m_requestedFrameGeometry.topLeft()); + if (areGeometryUpdatesBlocked()) { + if (pendingGeometryUpdate() == PendingGeometryForced) { + return; + } + if (force == ForceGeometrySet) { + setPendingGeometryUpdate(PendingGeometryForced); + } else { + setPendingGeometryUpdate(PendingGeometryNormal); + } + return; + } + m_clientGeometry.moveTopLeft(m_requestedClientGeometry.topLeft()); + m_bufferGeometry = frameRectToBufferRect(m_frameGeometry); + updateWindowRules(Rules::Position); + screens()->setCurrent(this); + workspace()->updateStackingOrder(); + emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); + addRepaintDuringGeometryUpdates(); + updateGeometryBeforeUpdateBlocking(); } -QRect XdgShellClient::bufferGeometry() const +/** + * \internal + * + * Schedules the frame geometry of the XdgSurfaceClient to be updated to \a rect. + * + * This method is usually called when the current window geometry size and the requested + * window geometry size don't match. Under hood, this method will schedule a configure + * event. When the configure event is acknowledged by the client, the frame geometry will + * be changed to \a rect. + */ +void XdgSurfaceClient::requestGeometry(const QRect &rect) { - return m_bufferGeometry; -} + m_requestedFrameGeometry = rect; + m_requestedClientGeometry = frameRectToClientRect(rect); -QStringList XdgShellClient::activities() const -{ - // TODO: implement - return QStringList(); + // Note that we don't have to send the configure event immediately! + scheduleConfigure(); } -QPoint XdgShellClient::clientContentPos() const +void XdgSurfaceClient::updateGeometry(const QRect &rect) { - return -1 * clientPos(); -} + const QRect oldFrameGeometry = m_frameGeometry; -QSize XdgShellClient::clientSize() const -{ - const QRect boundingRect = surface()->boundingRect(); - return m_windowGeometry.size().boundedTo(boundingRect.size()); + m_frameGeometry = rect; + m_bufferGeometry = frameRectToBufferRect(rect); + m_clientGeometry = frameRectToClientRect(rect); + + if (oldFrameGeometry == m_frameGeometry) + return; + + updateWindowRules(Rules::Position | Rules::Size); + updateGeometryBeforeUpdateBlocking(); + + emit frameGeometryChanged(this, oldFrameGeometry); + emit geometryShapeChanged(this, oldFrameGeometry); + + addRepaintDuringGeometryUpdates(); } -QSize XdgShellClient::minSize() const +/** + * \internal + * \todo We have to check the current frame geometry in checkWorskpacePosition(). + * + * Sets the geometry restore to the first valid frame geometry. This is a HACK! + * + * Unfortunately, AbstractClient::checkWorkspacePosition() operates on the geometry restore + * rather than the current frame geometry, so we have to ensure that it's initialized with + * some reasonable value even if the client is not maximized or quick tiled. + */ +void XdgSurfaceClient::updateGeometryRestoreHack() { - if (m_xdgShellToplevel) { - return rules()->checkMinSize(m_xdgShellToplevel->minimumSize()); + if (isUnmapped() && geometryRestore().isEmpty() && !frameGeometry().isEmpty()) { + setGeometryRestore(frameGeometry()); } - return QSize(0, 0); } -QSize XdgShellClient::maxSize() const +/** + * \todo The depth value must be set per surface basis. Drop this method when the scene is + * redesigned for sub-surfaces. + */ +void XdgSurfaceClient::updateDepth() { - if (m_xdgShellToplevel) { - return rules()->checkMaxSize(m_xdgShellToplevel->maximumSize()); + if (surface()->buffer()->hasAlphaChannel() && !isDesktop()) { + setDepth(32); + } else { + setDepth(24); } - return QSize(INT_MAX, INT_MAX); } -void XdgShellClient::debug(QDebug &stream) const +QRect XdgSurfaceClient::frameRectToBufferRect(const QRect &rect) const { - stream.nospace(); - stream << "\'XdgShellClient:" << surface() << ";WMCLASS:" << resourceClass() << ":" - << resourceName() << ";Caption:" << caption() << "\'"; + const int left = rect.left() + borderLeft() - m_windowGeometry.left(); + const int top = rect.top() + borderTop() - m_windowGeometry.top(); + return QRect(QPoint(left, top), surface()->size()); } -bool XdgShellClient::belongsToDesktop() const +/** + * Reimplemented to schedule a repaint when the main surface is damaged. + * + * \todo This is actually incorrect. We need to schedule repaints per surface basis, not + * just for the main surface. Drop this method once the scene is redesigned. + */ +void XdgSurfaceClient::addDamage(const QRegion &damage) { - const auto clients = waylandServer()->clients(); - - return std::any_of(clients.constBegin(), clients.constEnd(), - [this](const AbstractClient *client) { - if (belongsToSameApplication(client, SameApplicationChecks())) { - return client->isDesktop(); - } - return false; - } - ); + const int offsetX = m_bufferGeometry.x() - m_frameGeometry.x(); + const int offsetY = m_bufferGeometry.y() - m_frameGeometry.y(); + repaints_region += damage.translated(offsetX, offsetY); + Toplevel::addDamage(damage); } -Layer XdgShellClient::layerForDock() const +bool XdgSurfaceClient::isShown(bool shaded_is_shown) const { - if (m_plasmaShellSurface) { - switch (m_plasmaShellSurface->panelBehavior()) { - case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: - return NormalLayer; - case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: - return AboveLayer; - case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: - case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: - return DockLayer; - default: - Q_UNREACHABLE(); - break; - } - } - return AbstractClient::layerForDock(); + Q_UNUSED(shaded_is_shown) + return !isClosing() && !isHidden() && !isMinimized() && !isUnmapped(); } -QRect XdgShellClient::transparentRect() const +bool XdgSurfaceClient::isHiddenInternal() const { - // TODO: implement - return QRect(); + return isHidden() || isUnmapped(); } -NET::WindowType XdgShellClient::windowType(bool direct, int supported_types) const +void XdgSurfaceClient::hideClient(bool hide) { - // TODO: implement - Q_UNUSED(direct) - Q_UNUSED(supported_types) - return m_windowType; + if (hide) { + internalHide(); + } else { + internalShow(); + } } -double XdgShellClient::opacity() const +bool XdgSurfaceClient::isHidden() const { - return m_opacity; + return m_isHidden; } -void XdgShellClient::setOpacity(double opacity) +void XdgSurfaceClient::internalShow() { - const qreal newOpacity = qBound(0.0, opacity, 1.0); - if (newOpacity == m_opacity) { + if (!isHidden()) { return; } - const qreal oldOpacity = m_opacity; - m_opacity = newOpacity; + m_isHidden = false; addRepaintFull(); - emit opacityChanged(this, oldOpacity); + emit windowShown(this); } -void XdgShellClient::addDamage(const QRegion &damage) +void XdgSurfaceClient::internalHide() { - const int offsetX = m_bufferGeometry.x() - frameGeometry().x(); - const int offsetY = m_bufferGeometry.y() - frameGeometry().y(); - repaints_region += damage.translated(offsetX, offsetY); + if (isHidden()) { + return; + } + if (isMoveResize()) { + leaveMoveResize(); + } + m_isHidden = true; + addWorkspaceRepaint(visibleRect()); + workspace()->clientHidden(this); + emit windowHidden(this); +} - Toplevel::addDamage(damage); +/** + * \todo We just need to destroy XdgSurfaceClient when the xdg-surface is unmapped. + */ +bool XdgSurfaceClient::isUnmapped() const +{ + return m_isUnmapped; } -void XdgShellClient::markAsMapped() +/** + * \todo We just need to destroy XdgSurfaceClient when the xdg-surface is unmapped. + */ +void XdgSurfaceClient::internalMap() { - if (!m_unmapped) { + if (!isUnmapped()) { return; } - - m_unmapped = false; - if (!ready_for_painting) { - setReadyForPainting(); - } else { + m_isUnmapped = false; + if (readyForPainting()) { addRepaintFull(); emit windowShown(this); + } else { + setReadyForPainting(); } - if (shouldExposeToWindowManagement()) { - setupWindowManagementInterface(); - } - updateShowOnScreenEdge(); + emit windowMapped(); } -void XdgShellClient::updateDecoration(bool check_workspace_pos, bool force) +/** + * \todo We just need to destroy XdgSurfaceClient when the xdg-surface is unmapped. + */ +void XdgSurfaceClient::internalUnmap() { - if (!force && - ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) + if (isUnmapped()) { return; - QRect oldgeom = frameGeometry(); - QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom()); - blockGeometryUpdates(true); - if (force) - destroyDecoration(); - if (!noBorder()) { - createDecoration(oldgeom); - } else - destroyDecoration(); - if (m_serverDecoration && isDecorated()) { - m_serverDecoration->setMode(KWaylandServer::ServerSideDecorationManagerInterface::Mode::Server); } - if (m_xdgDecoration) { - auto mode = isDecorated() || m_userNoBorder ? XdgDecorationInterface::Mode::ServerSide: XdgDecorationInterface::Mode::ClientSide; - m_xdgDecoration->configure(mode); - if (m_requestGeometryBlockCounter == 0) { - m_xdgShellToplevel->configure(xdgSurfaceStates(), m_requestedClientSize); - } + if (isMoveResize()) { + leaveMoveResize(); } - updateShadow(); - if (check_workspace_pos) - checkWorkspacePosition(oldgeom, -2, oldClientGeom); - blockGeometryUpdates(false); + m_isUnmapped = true; + m_requestedClientGeometry = QRect(); + m_lastAcknowledgedConfigure = nullptr; + m_configureTimer->stop(); + qDeleteAll(m_configureEvents); + m_configureEvents.clear(); + addWorkspaceRepaint(visibleRect()); + workspace()->clientHidden(this); + emit windowHidden(this); + emit windowUnmapped(); } -void XdgShellClient::setFrameGeometry(const QRect &rect, ForceGeometry_t force) +bool XdgSurfaceClient::isClosing() const { - const QRect newGeometry = rules()->checkGeometry(rect); - - if (areGeometryUpdatesBlocked()) { - // when the GeometryUpdateBlocker exits the current geom is passed to setGeometry - // thus we need to set it here. - m_frameGeometry = newGeometry; - if (pendingGeometryUpdate() == PendingGeometryForced) { - // maximum, nothing needed - } else if (force == ForceGeometrySet) { - setPendingGeometryUpdate(PendingGeometryForced); - } else { - setPendingGeometryUpdate(PendingGeometryNormal); - } - return; - } - - if (pendingGeometryUpdate() != PendingGeometryNone) { - // reset geometry to the one before blocking, so that we can compare properly - m_frameGeometry = frameGeometryBeforeUpdateBlocking(); - } - - const QSize requestedClientSize = newGeometry.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); - - if (requestedClientSize == m_windowGeometry.size() && - (m_requestedClientSize.isEmpty() || requestedClientSize == m_requestedClientSize)) { - // size didn't change, and we don't need to explicitly request a new size - doSetGeometry(newGeometry); - updateMaximizeMode(m_requestedMaximizeMode); - } else { - // size did change, Client needs to provide a new buffer - requestGeometry(newGeometry); - } + return m_isClosing; } -QRect XdgShellClient::determineBufferGeometry() const +void XdgSurfaceClient::destroyClient() { - // Offset of the main surface relative to the frame rect. - const int offsetX = borderLeft() - m_windowGeometry.left(); - const int offsetY = borderTop() - m_windowGeometry.top(); - - QRect bufferGeometry; - bufferGeometry.setX(x() + offsetX); - bufferGeometry.setY(y() + offsetY); - bufferGeometry.setSize(surface()->size()); - - return bufferGeometry; + m_isClosing = true; + m_configureTimer->stop(); + if (isMoveResize()) { + leaveMoveResize(); + } + cleanTabBox(); + Deleted *deleted = Deleted::create(this); + emit windowClosed(this, deleted); + StackingUpdatesBlocker blocker(workspace()); + RuleBook::self()->discardUsed(this, true); + destroyWindowManagementInterface(); + destroyDecoration(); + cleanGrouping(); + waylandServer()->removeClient(this); + deleted->unrefWindow(); + delete this; } -void XdgShellClient::doSetGeometry(const QRect &rect) +void XdgSurfaceClient::cleanGrouping() { - bool frameGeometryIsChanged = false; - bool bufferGeometryIsChanged = false; - - if (m_frameGeometry != rect) { - m_frameGeometry = rect; - frameGeometryIsChanged = true; - } - - const QRect bufferGeometry = determineBufferGeometry(); - if (m_bufferGeometry != bufferGeometry) { - m_bufferGeometry = bufferGeometry; - bufferGeometryIsChanged = true; - } - - if (!frameGeometryIsChanged && !bufferGeometryIsChanged) { - return; - } - - if (m_unmapped && geometryRestore().isEmpty() && !m_frameGeometry.isEmpty()) { - // use first valid geometry as restore geometry - setGeometryRestore(m_frameGeometry); + if (transientFor()) { + transientFor()->removeTransient(this); } - - if (frameGeometryIsChanged) { - if (hasStrut()) { - workspace()->updateClientArea(); + for (auto it = transients().constBegin(); it != transients().constEnd();) { + if ((*it)->transientFor() == this) { + removeTransient(*it); + it = transients().constBegin(); // restart, just in case something more has changed with the list + } else { + ++it; } - updateWindowRules(Rules::Position | Rules::Size); - emit frameGeometryChanged(this, frameGeometryBeforeUpdateBlocking()); - } - - emit geometryShapeChanged(this, frameGeometryBeforeUpdateBlocking()); - - addRepaintDuringGeometryUpdates(); - updateGeometryBeforeUpdateBlocking(); - - if (isResize()) { - performMoveResize(); } } -void XdgShellClient::doMove(int x, int y) +void XdgSurfaceClient::cleanTabBox() { - Q_UNUSED(x) - Q_UNUSED(y) - m_bufferGeometry = determineBufferGeometry(); -} - -QByteArray XdgShellClient::windowRole() const -{ - return QByteArray(); -} - -bool XdgShellClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const -{ - if (checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) { - if (other->desktopFileName() == desktopFileName()) { - return true; - } - } - if (auto s = other->surface()) { - return s->client() == surface()->client(); +#ifdef KWIN_BUILD_TABBOX + TabBox::TabBox *tabBox = TabBox::TabBox::self(); + if (tabBox->isDisplayed() && tabBox->currentClient() == this) { + tabBox->nextPrev(true); } - return false; -} - -void XdgShellClient::blockActivityUpdates(bool b) -{ - Q_UNUSED(b) -} - -QString XdgShellClient::captionNormal() const -{ - return m_caption; +#endif } -QString XdgShellClient::captionSuffix() const +XdgToplevelClient::XdgToplevelClient(XdgToplevelInterface *shellSurface) + : XdgSurfaceClient(shellSurface->xdgSurface()) + , m_shellSurface(shellSurface) { - return m_captionSuffix; -} + setupWindowManagementIntegration(); + setupPlasmaShellIntegration(); + setDesktop(VirtualDesktopManager::self()->current()); -void XdgShellClient::updateCaption() -{ - const QString oldSuffix = m_captionSuffix; - const auto shortcut = shortcutCaptionSuffix(); - m_captionSuffix = shortcut; - if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { - int i = 2; - do { - m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); - i++; - } while (findClientWithSameCaption()); - } - if (m_captionSuffix != oldSuffix) { - emit captionChanged(); - } -} + if (waylandServer()->inputMethodConnection() == surface()->client()) + m_windowType = NET::OnScreenDisplay; -void XdgShellClient::closeWindow() + connect(shellSurface, &XdgToplevelInterface::windowTitleChanged, + this, &XdgToplevelClient::handleWindowTitleChanged); + connect(shellSurface, &XdgToplevelInterface::windowClassChanged, + this, &XdgToplevelClient::handleWindowClassChanged); + connect(shellSurface, &XdgToplevelInterface::windowMenuRequested, + this, &XdgToplevelClient::handleWindowMenuRequested); + connect(shellSurface, &XdgToplevelInterface::moveRequested, + this, &XdgToplevelClient::handleMoveRequested); + connect(shellSurface, &XdgToplevelInterface::resizeRequested, + this, &XdgToplevelClient::handleResizeRequested); + connect(shellSurface, &XdgToplevelInterface::maximizeRequested, + this, &XdgToplevelClient::handleMaximizeRequested); + connect(shellSurface, &XdgToplevelInterface::unmaximizeRequested, + this, &XdgToplevelClient::handleUnmaximizeRequested); + connect(shellSurface, &XdgToplevelInterface::fullscreenRequested, + this, &XdgToplevelClient::handleFullscreenRequested); + connect(shellSurface, &XdgToplevelInterface::unfullscreenRequested, + this, &XdgToplevelClient::handleUnfullscreenRequested); + connect(shellSurface, &XdgToplevelInterface::minimizeRequested, + this, &XdgToplevelClient::handleMinimizeRequested); + connect(shellSurface, &XdgToplevelInterface::parentXdgToplevelChanged, + this, &XdgToplevelClient::handleTransientForChanged); + connect(shellSurface, &XdgToplevelInterface::initializeRequested, + this, &XdgToplevelClient::initialize); + connect(shellSurface, &XdgToplevelInterface::destroyed, + this, &XdgToplevelClient::destroyClient); + connect(shellSurface->shell(), &XdgShellInterface::pingTimeout, + this, &XdgToplevelClient::handlePingTimeout); + connect(shellSurface->shell(), &XdgShellInterface::pingDelayed, + this, &XdgToplevelClient::handlePingDelayed); + connect(shellSurface->shell(), &XdgShellInterface::pongReceived, + this, &XdgToplevelClient::handlePongReceived); + + connect(waylandServer(), &WaylandServer::foreignTransientChanged, + this, &XdgToplevelClient::handleForeignTransientForChanged); + + connect(this, &XdgToplevelClient::clientStartUserMovedResized, + this, &XdgToplevelClient::scheduleConfigure); + connect(this, &XdgToplevelClient::clientFinishUserMovedResized, + this, &XdgToplevelClient::scheduleConfigure); +} + +XdgToplevelClient::~XdgToplevelClient() +{ +} + +void XdgToplevelClient::debug(QDebug &stream) const +{ + stream << this; +} + +NET::WindowType XdgToplevelClient::windowType(bool direct, int supported_types) const { - if (m_xdgShellToplevel && isCloseable()) { - m_xdgShellToplevel->close(); - ping(PingReason::CloseWindow); - } + Q_UNUSED(direct) + Q_UNUSED(supported_types) + return m_windowType; } -AbstractClient *XdgShellClient::findModal(bool allow_itself) +MaximizeMode XdgToplevelClient::maximizeMode() const { - Q_UNUSED(allow_itself) - return nullptr; + return m_maximizeMode; } -bool XdgShellClient::isCloseable() const +MaximizeMode XdgToplevelClient::requestedMaximizeMode() const { - if (m_windowType == NET::Desktop || m_windowType == NET::Dock) { - return false; - } - if (m_xdgShellToplevel) { - return true; - } - return false; + return m_requestedMaximizeMode; } -bool XdgShellClient::isFullScreen() const +QSize XdgToplevelClient::minSize() const { - return m_fullScreen; + return rules()->checkMinSize(m_shellSurface->minimumSize()); } -bool XdgShellClient::isMaximizable() const +QSize XdgToplevelClient::maxSize() const { - if (!isResizable()) { - return false; - } - if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || rules()->checkMaximize(MaximizeFull) != MaximizeFull) { - return false; - } - return true; + return rules()->checkMaxSize(m_shellSurface->maximumSize()); } -bool XdgShellClient::isMinimizable() const +bool XdgToplevelClient::isFullScreen() const { - if (!rules()->checkMinimize(true)) { - return false; - } - return (!m_plasmaShellSurface || m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal); + return m_isFullScreen; } -bool XdgShellClient::isMovable() const +bool XdgToplevelClient::isMovable() const { if (isFullScreen()) { return false; } - if (rules()->checkPosition(invalidPoint) != invalidPoint) { + if (isSpecialWindow() && !isSplash() && !isToolbar()) { return false; } - if (m_plasmaShellSurface) { - return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; - } - if (m_xdgShellPopup) { + if (rules()->checkPosition(invalidPoint) != invalidPoint) { return false; } return true; } -bool XdgShellClient::isMovableAcrossScreens() const +bool XdgToplevelClient::isMovableAcrossScreens() const { - if (rules()->checkPosition(invalidPoint) != invalidPoint) { + if (isSpecialWindow() && !isSplash() && !isToolbar()) { return false; } - if (m_plasmaShellSurface) { - return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; - } - if (m_xdgShellPopup) { + if (rules()->checkPosition(invalidPoint) != invalidPoint) { return false; } return true; } -bool XdgShellClient::isResizable() const +bool XdgToplevelClient::isResizable() const { if (isFullScreen()) { return false; } - if (rules()->checkSize(QSize()).isValid()) { + if (isSpecialWindow() || isSplash() || isToolbar()) { return false; } - if (m_plasmaShellSurface) { - return m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Normal; - } - if (m_xdgShellPopup) { + if (rules()->checkSize(QSize()).isValid()) { return false; } return true; } -bool XdgShellClient::isShown(bool shaded_is_shown) const +bool XdgToplevelClient::isCloseable() const { - Q_UNUSED(shaded_is_shown) - return !m_closing && !m_unmapped && !isMinimized() && !m_hidden; + return !isDesktop() && !isDock(); } -bool XdgShellClient::isHiddenInternal() const +bool XdgToplevelClient::isFullScreenable() const { - return m_unmapped || m_hidden; -} - -void XdgShellClient::hideClient(bool hide) -{ - if (m_hidden == hide) { - return; - } - m_hidden = hide; - if (hide) { - addWorkspaceRepaint(visibleRect()); - workspace()->clientHidden(this); - emit windowHidden(this); - } else { - emit windowShown(this); + if (!rules()->checkFullScreen(true)) { + return false; } + return !isSpecialWindow(); } -static bool changeMaximizeRecursion = false; -void XdgShellClient::changeMaximize(bool horizontal, bool vertical, bool adjust) +bool XdgToplevelClient::isMaximizable() const { - if (changeMaximizeRecursion) { - return; - } - if (!isResizable()) { - return; - } - - const QRect clientArea = isElectricBorderMaximizing() ? - workspace()->clientArea(MaximizeArea, Cursors::self()->mouse()->pos(), desktop()) : - workspace()->clientArea(MaximizeArea, this); - - const MaximizeMode oldMode = m_requestedMaximizeMode; - const QRect oldGeometry = frameGeometry(); - - // 'adjust == true' means to update the size only, e.g. after changing workspace size - if (!adjust) { - if (vertical) - m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); - if (horizontal) - m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); - } - - m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode); - if (!adjust && m_requestedMaximizeMode == oldMode) { - return; - } - - StackingUpdatesBlocker blocker(workspace()); - RequestGeometryBlocker geometryBlocker(this); - - // call into decoration update borders - if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { - changeMaximizeRecursion = true; - const auto c = decoration()->client().toStrongRef(); - if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { - emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); - } - if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { - emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); - } - if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { - emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull); - } - changeMaximizeRecursion = false; + return false; } - - if (options->borderlessMaximizedWindows()) { - // triggers a maximize change. - // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry - changeMaximizeRecursion = true; - setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull)); - changeMaximizeRecursion = false; + if (rules()->checkMaximize(MaximizeRestore) != MaximizeRestore || + rules()->checkMaximize(MaximizeFull) != MaximizeFull) { + return false; } + return true; +} - // Conditional quick tiling exit points - const auto oldQuickTileMode = quickTileMode(); - if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { - if (oldMode == MaximizeFull && - !clientArea.contains(geometryRestore().center())) { - // Not restoring on the same screen - // TODO: The following doesn't work for some reason - //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually - } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || - (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { - // Modifying geometry of a tiled window - updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry - } +bool XdgToplevelClient::isMinimizable() const +{ + if (isSpecialWindow() && !isTransient()) { + return false; } - - if (m_requestedMaximizeMode == MaximizeFull) { - setGeometryRestore(oldGeometry); - // TODO: Client has more checks - if (options->electricBorderMaximize()) { - updateQuickTileMode(QuickTileFlag::Maximize); - } else { - updateQuickTileMode(QuickTileFlag::None); - } - if (quickTileMode() != oldQuickTileMode) { - emit quickTileModeChanged(); - } - setFrameGeometry(workspace()->clientArea(MaximizeArea, this)); - workspace()->raiseClient(this); - } else { - if (m_requestedMaximizeMode == MaximizeRestore) { - updateQuickTileMode(QuickTileFlag::None); - } - if (quickTileMode() != oldQuickTileMode) { - emit quickTileModeChanged(); - } - - if (geometryRestore().isValid()) { - setFrameGeometry(geometryRestore()); - } else { - setFrameGeometry(workspace()->clientArea(PlacementArea, this)); - } + if (!rules()->checkMinimize(true)) { + return false; } + return true; } -MaximizeMode XdgShellClient::maximizeMode() const +bool XdgToplevelClient::isTransient() const { - return m_maximizeMode; + return m_isTransient; } -MaximizeMode XdgShellClient::requestedMaximizeMode() const +bool XdgToplevelClient::userCanSetFullScreen() const { - return m_requestedMaximizeMode; + return true; } -bool XdgShellClient::noBorder() const +bool XdgToplevelClient::userCanSetNoBorder() const { if (m_serverDecoration) { - if (m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { - return m_userNoBorder || isFullScreen(); + switch (m_serverDecoration->mode()) { + case ServerSideDecorationManagerInterface::Mode::Server: + return !isFullScreen() && !isShade(); + case ServerSideDecorationManagerInterface::Mode::Client: + case ServerSideDecorationManagerInterface::Mode::None: + return false; } } - if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { - return m_userNoBorder || isFullScreen(); - } - return true; -} - -bool XdgShellClient::isFullScreenable() const -{ - if (!rules()->checkFullScreen(true)) { - return false; + if (m_xdgDecoration) { + switch (m_xdgDecoration->preferredMode()) { + case XdgToplevelDecorationV1Interface::Mode::Server: + case XdgToplevelDecorationV1Interface::Mode::Undefined: + return !isFullScreen() && !isShade(); + case XdgToplevelDecorationV1Interface::Mode::Client: + return false; + } } - return !isSpecialWindow(); + return false; } -void XdgShellClient::setFullScreen(bool set, bool user) +bool XdgToplevelClient::noBorder() const { - set = rules()->checkFullScreen(set); - - const bool wasFullscreen = isFullScreen(); - if (wasFullscreen == set) { - return; - } - if (isSpecialWindow()) { - return; - } - if (user && !userCanSetFullScreen()) { - return; - } - - if (wasFullscreen) { - workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event - } else { - m_geomFsRestore = frameGeometry(); - } - m_fullScreen = set; - - if (set) { - workspace()->raiseClient(this); + if (m_serverDecoration) { + switch (m_serverDecoration->mode()) { + case ServerSideDecorationManagerInterface::Mode::Server: + return m_userNoBorder || isFullScreen(); + case ServerSideDecorationManagerInterface::Mode::Client: + case ServerSideDecorationManagerInterface::Mode::None: + return true; + } } - RequestGeometryBlocker requestBlocker(this); - StackingUpdatesBlocker blocker1(workspace()); - GeometryUpdatesBlocker blocker2(this); - - workspace()->updateClientLayer(this); // active fullscreens get different layer - updateDecoration(false, false); - - if (set) { - setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); - } else { - if (m_geomFsRestore.isValid()) { - int currentScreen = screen(); - setFrameGeometry(QRect(m_geomFsRestore.topLeft(), constrainFrameSize(m_geomFsRestore.size()))); - if( currentScreen != screen()) - workspace()->sendClientToScreen( this, currentScreen ); - } else { - // this can happen when the window was first shown already fullscreen, - // so let the client set the size by itself - setFrameGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0))); + if (m_xdgDecoration) { + switch (m_xdgDecoration->preferredMode()) { + case XdgToplevelDecorationV1Interface::Mode::Server: + case XdgToplevelDecorationV1Interface::Mode::Undefined: + return m_userNoBorder || isFullScreen(); + case XdgToplevelDecorationV1Interface::Mode::Client: + return true; } } - - updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); - emit fullScreenChanged(); + return true; } -void XdgShellClient::setNoBorder(bool set) +void XdgToplevelClient::setNoBorder(bool set) { if (!userCanSetNoBorder()) { return; @@ -940,252 +884,242 @@ updateWindowRules(Rules::NoBorder); } -void XdgShellClient::setOnAllActivities(bool set) -{ - Q_UNUSED(set) -} - -void XdgShellClient::takeFocus() +void XdgToplevelClient::updateDecoration(bool check_workspace_pos, bool force) { - if (rules()->checkAcceptFocus(wantsInput())) { - if (m_xdgShellToplevel) { - ping(PingReason::FocusWindow); + if (!force && ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder()))) { + return; + } + const QRect oldFrameGeometry = frameGeometry(); + const QRect oldClientGeometry = clientGeometry(); + blockGeometryUpdates(true); + if (force) { + destroyDecoration(); + } + if (!noBorder()) { + createDecoration(oldFrameGeometry); + } else { + destroyDecoration(); + } + if (m_serverDecoration && isDecorated()) { + m_serverDecoration->setMode(ServerSideDecorationManagerInterface::Mode::Server); + } + if (m_xdgDecoration) { + if (isDecorated() || m_userNoBorder) { + m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Server); + } else { + m_xdgDecoration->sendConfigure(XdgToplevelDecorationV1Interface::Mode::Client); } - setActive(true); + scheduleConfigure(); } - - if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) { - workspace()->setShowingDesktop(false); + updateShadow(); + if (check_workspace_pos) { + checkWorkspacePosition(oldFrameGeometry, -2, oldClientGeometry); } + blockGeometryUpdates(false); } -void XdgShellClient::doSetActive() +bool XdgToplevelClient::supportsWindowRules() const { - if (!isActive()) { - return; - } - StackingUpdatesBlocker blocker(workspace()); - workspace()->focusToNull(); + return !m_plasmaShellSurface; } -bool XdgShellClient::userCanSetFullScreen() const +bool XdgToplevelClient::hasStrut() const { - if (m_xdgShellToplevel) { - return true; + if (!isShown(true)) { + return false; } - return false; + if (!m_plasmaShellSurface) { + return false; + } + if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { + return false; + } + return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; } -bool XdgShellClient::userCanSetNoBorder() const +void XdgToplevelClient::showOnScreenEdge() { - if (m_serverDecoration && m_serverDecoration->mode() == ServerSideDecorationManagerInterface::Mode::Server) { - return !isFullScreen() && !isShade(); + if (!m_plasmaShellSurface || isUnmapped()) { + return; } - if (m_xdgDecoration && m_xdgDecoration->requestedMode() != XdgDecorationInterface::Mode::ClientSide) { - return !isFullScreen() && !isShade(); + hideClient(false); + workspace()->raiseClient(this); + if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { + m_plasmaShellSurface->showAutoHidingPanel(); } - return false; } -bool XdgShellClient::wantsInput() const +bool XdgToplevelClient::isInitialPositionSet() const { - return rules()->checkAcceptFocus(acceptsFocus()); + return m_plasmaShellSurface ? m_plasmaShellSurface->isPositionSet() : false; } -bool XdgShellClient::acceptsFocus() const +void XdgToplevelClient::closeWindow() { - if (waylandServer()->inputMethodConnection() == surface()->client()) { - return false; + if (isCloseable()) { + sendPing(PingReason::CloseWindow); + m_shellSurface->sendClose(); } - if (m_plasmaShellSurface) { - if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || - m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) { - return false; - } +} - if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification || - m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::CriticalNotification) { - return m_plasmaShellSurface->panelTakesFocus(); - } +XdgSurfaceConfigure *XdgToplevelClient::sendRoleConfigure() const +{ + XdgToplevelInterface::States xdgStates; + + if (isActive()) { + xdgStates |= XdgToplevelInterface::State::Activated; } - if (m_closing) { - // a closing window does not accept focus - return false; + if (isResize()) { + xdgStates |= XdgToplevelInterface::State::Resizing; } - if (m_unmapped) { - // an unmapped window does not accept focus - return false; + if (requestedMaximizeMode() & MaximizeHorizontal) { + xdgStates |= XdgToplevelInterface::State::MaximizedHorizontal; } - if (m_xdgShellToplevel) { - // TODO: proper - return true; + if (requestedMaximizeMode() & MaximizeVertical) { + xdgStates |= XdgToplevelInterface::State::MaximizedVertical; + } + if (isFullScreen()) { + xdgStates |= XdgToplevelInterface::State::FullScreen; } - return false; -} -void XdgShellClient::createWindowId() -{ - m_windowId = waylandServer()->createWindowId(surface()); + const quint32 serial = m_shellSurface->sendConfigure(requestedClientSize(), xdgStates); + + XdgToplevelConfigure *configureEvent = new XdgToplevelConfigure(); + configureEvent->position = requestedPos(); + configureEvent->size = requestedSize(); + configureEvent->states = xdgStates; + configureEvent->serial = serial; + + return configureEvent; } -pid_t XdgShellClient::pid() const +void XdgToplevelClient::handleRoleCommit() { - return surface()->client()->processId(); + auto configureEvent = static_cast(lastAcknowledgedConfigure()); + if (configureEvent) + handleStatesAcknowledged(configureEvent->states); } -bool XdgShellClient::isLockScreen() const +void XdgToplevelClient::doMinimize() { - return surface()->client() == waylandServer()->screenLockerClientConnection(); + if (isMinimized()) { + workspace()->clientHidden(this); + } else { + emit windowShown(this); + } + workspace()->updateMinimizedOfTransients(this); } -bool XdgShellClient::isInputMethod() const +void XdgToplevelClient::doResizeSync() { - return surface()->client() == waylandServer()->inputMethodConnection(); + requestGeometry(moveResizeGeometry()); } -void XdgShellClient::requestGeometry(const QRect &rect) +void XdgToplevelClient::doSetActive() { - if (m_requestGeometryBlockCounter != 0) { - m_blockedRequestGeometry = rect; + scheduleConfigure(); + if (!isActive()) { return; } - - QSize size; - if (rect.isValid()) { - size = rect.size() - QSize(borderLeft() + borderRight(), borderTop() + borderBottom()); - } else { - size = QSize(0, 0); - } - m_requestedClientSize = size; - - quint64 serialId = 0; - - if (m_xdgShellToplevel) { - serialId = m_xdgShellToplevel->configure(xdgSurfaceStates(), size); - } - if (m_xdgShellPopup) { - auto parent = transientFor(); - if (parent) { - const QPoint globalClientContentPos = parent->frameGeometry().topLeft() + parent->clientPos(); - const QPoint relativeOffset = rect.topLeft() - globalClientContentPos; - serialId = m_xdgShellPopup->configure(QRect(relativeOffset, size)); - } - } - - if (rect.isValid()) { //if there's no requested size, then there's implicity no positional information worth using - PendingConfigureRequest configureRequest; - configureRequest.serialId = serialId; - configureRequest.positionAfterResize = rect.topLeft(); - configureRequest.maximizeMode = m_requestedMaximizeMode; - m_pendingConfigureRequests.append(configureRequest); - } - - m_blockedRequestGeometry = QRect(); + StackingUpdatesBlocker blocker(workspace()); + workspace()->focusToNull(); } -void XdgShellClient::updatePendingGeometry() +void XdgToplevelClient::takeFocus() { - QPoint position = pos(); - MaximizeMode maximizeMode = m_maximizeMode; - for (auto it = m_pendingConfigureRequests.begin(); it != m_pendingConfigureRequests.end(); it++) { - if (it->serialId > m_lastAckedConfigureRequest) { - //this serial is not acked yet, therefore we know all future serials are not - break; - } - if (it->serialId == m_lastAckedConfigureRequest) { - if (position != it->positionAfterResize) { - addLayerRepaint(frameGeometry()); - } - position = it->positionAfterResize; - maximizeMode = it->maximizeMode; - - m_pendingConfigureRequests.erase(m_pendingConfigureRequests.begin(), ++it); - break; - } - //else serialId < m_lastAckedConfigureRequest and the state is now irrelevant and can be ignored - } - QRect geometry = QRect(position, adjustedSize()); - if (isMove()) { - geometry = adjustMoveGeometry(geometry); + if (wantsInput()) { + sendPing(PingReason::FocusWindow); + setActive(true); } - if (isResize()) { - geometry = adjustResizeGeometry(geometry); + if (!keepAbove() && !isOnScreenDisplay() && !belongsToDesktop()) { + workspace()->setShowingDesktop(false); } - doSetGeometry(geometry); - updateMaximizeMode(maximizeMode); } -void XdgShellClient::handleConfigureAcknowledged(quint32 serial) +bool XdgToplevelClient::wantsInput() const { - m_lastAckedConfigureRequest = serial; + return rules()->checkAcceptFocus(acceptsFocus()); } -void XdgShellClient::handleTransientForChanged() +bool XdgToplevelClient::dockWantsInput() const { - SurfaceInterface *transientSurface = nullptr; - if (m_xdgShellToplevel) { - if (auto transient = m_xdgShellToplevel->transientFor().data()) { - transientSurface = transient->surface(); + if (m_plasmaShellSurface) { + if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { + return m_plasmaShellSurface->panelTakesFocus(); } } - if (m_xdgShellPopup) { - transientSurface = m_xdgShellPopup->transientFor().data(); - } - if (!transientSurface) { - transientSurface = waylandServer()->findForeignTransientForSurface(surface()); + return false; +} + +bool XdgToplevelClient::acceptsFocus() const +{ + if (isInputMethod()) { + return false; } - AbstractClient *transientClient = waylandServer()->findClient(transientSurface); - if (transientClient != transientFor()) { - // Remove from main client. - if (transientFor()) { - transientFor()->removeTransient(this); + if (m_plasmaShellSurface) { + if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::OnScreenDisplay || + m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::ToolTip) { + return false; } - setTransientFor(transientClient); - if (transientClient) { - transientClient->addTransient(this); + if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Notification || + m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::CriticalNotification) { + return m_plasmaShellSurface->panelTakesFocus(); } } - m_transient = (transientSurface != nullptr); + return !isClosing() && !isUnmapped(); } -void XdgShellClient::handleWindowClassChanged(const QByteArray &windowClass) +Layer XdgToplevelClient::layerForDock() const { - setResourceClass(resourceName(), windowClass); - if (m_isInitialized && supportsWindowRules()) { - setupWindowRules(true); - applyWindowRules(); + if (m_plasmaShellSurface) { + switch (m_plasmaShellSurface->panelBehavior()) { + case PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover: + return NormalLayer; + case PlasmaShellSurfaceInterface::PanelBehavior::AutoHide: + return AboveLayer; + case PlasmaShellSurfaceInterface::PanelBehavior::WindowsGoBelow: + case PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible: + return DockLayer; + default: + Q_UNREACHABLE(); + break; + } } - setDesktopFileName(windowClass); + return AbstractClient::layerForDock(); } -void XdgShellClient::handleWindowGeometryChanged(const QRect &windowGeometry) +void XdgToplevelClient::handleWindowTitleChanged() { - m_windowGeometry = windowGeometry; - m_hasWindowGeometry = true; + setCaption(m_shellSurface->windowTitle()); } -void XdgShellClient::handleWindowTitleChanged(const QString &title) +void XdgToplevelClient::handleWindowClassChanged() { - const QString oldSuffix = m_captionSuffix; - m_caption = title.simplified(); - updateCaption(); - if (m_captionSuffix == oldSuffix) { - // Don't emit caption change twice it already got emitted by the changing suffix. - emit captionChanged(); + const QByteArray applicationId = m_shellSurface->windowClass().toUtf8(); + setResourceClass(resourceName(), applicationId); + if (m_isInitialized && supportsWindowRules()) { + evaluateWindowRules(); } + setDesktopFileName(applicationId); } -void XdgShellClient::handleMoveRequested(SeatInterface *seat, quint32 serial) +void XdgToplevelClient::handleWindowMenuRequested(SeatInterface *seat, const QPoint &surfacePos, + quint32 serial) +{ + Q_UNUSED(seat) + Q_UNUSED(serial) + performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); +} + +void XdgToplevelClient::handleMoveRequested(SeatInterface *seat, quint32 serial) { - // FIXME: Check the seat and serial. Q_UNUSED(seat) Q_UNUSED(serial) performMouseCommand(Options::MouseMove, Cursors::self()->mouse()->pos()); } -void XdgShellClient::handleResizeRequested(SeatInterface *seat, quint32 serial, Qt::Edges edges) +void XdgToplevelClient::handleResizeRequested(SeatInterface *seat, Qt::Edges edges, quint32 serial) { - // FIXME: Check the seat and serial. Q_UNUSED(seat) Q_UNUSED(serial) if (!isResizable() || isShade()) { @@ -1219,129 +1153,297 @@ updateCursor(); } -void XdgShellClient::handleMinimizeRequested() +void XdgToplevelClient::handleStatesAcknowledged(const XdgToplevelInterface::States &states) +{ + const XdgToplevelInterface::States delta = m_lastAcknowledgedStates ^ states; + + if (delta & XdgToplevelInterface::State::Maximized) { + MaximizeMode maximizeMode = MaximizeRestore; + if (states & XdgToplevelInterface::State::MaximizedHorizontal) { + maximizeMode = MaximizeMode(maximizeMode | MaximizeHorizontal); + } + if (states & XdgToplevelInterface::State::MaximizedVertical) { + maximizeMode = MaximizeMode(maximizeMode | MaximizeVertical); + } + updateMaximizeMode(maximizeMode); + } + if (delta & XdgToplevelInterface::State::FullScreen) { + updateFullScreenMode(states & XdgToplevelInterface::State::FullScreen); + } + + m_lastAcknowledgedStates = states; +} + +void XdgToplevelClient::handleMaximizeRequested() +{ + maximize(MaximizeFull); + scheduleConfigure(); +} + +void XdgToplevelClient::handleUnmaximizeRequested() +{ + maximize(MaximizeRestore); + scheduleConfigure(); +} + +void XdgToplevelClient::handleFullscreenRequested(OutputInterface *output) +{ + Q_UNUSED(output) + setFullScreen(/* set */ true, /* user */ false); + scheduleConfigure(); +} + +void XdgToplevelClient::handleUnfullscreenRequested() +{ + setFullScreen(/* set */ false, /* user */ false); + scheduleConfigure(); +} + +void XdgToplevelClient::handleMinimizeRequested() { performMouseCommand(Options::MouseMinimize, Cursors::self()->mouse()->pos()); } -void XdgShellClient::handleMaximizeRequested(bool maximized) -{ - // If the maximized state of the client hasn't been changed due to a window - // rule or because the requested state is the same as the current, then the - // compositor still has to send a configure event. - RequestGeometryBlocker blocker(this); +void XdgToplevelClient::handleTransientForChanged() +{ + SurfaceInterface *transientForSurface = nullptr; + if (XdgToplevelInterface *parentToplevel = m_shellSurface->parentXdgToplevel()) { + transientForSurface = parentToplevel->surface(); + } + if (!transientForSurface) { + transientForSurface = waylandServer()->findForeignTransientForSurface(surface()); + } + AbstractClient *transientForClient = waylandServer()->findClient(transientForSurface); + if (transientForClient != transientFor()) { + if (transientFor()) { + transientFor()->removeTransient(this); + } + if (transientForClient) { + transientForClient->addTransient(this); + } + setTransientFor(transientForClient); + } + m_isTransient = transientForClient; +} + +void XdgToplevelClient::handleForeignTransientForChanged(SurfaceInterface *child) +{ + if (surface() == child) { + handleTransientForChanged(); + } +} + +void XdgToplevelClient::handlePingTimeout(quint32 serial) +{ + auto pingIt = m_pings.find(serial); + if (pingIt == m_pings.end()) { + return; + } + if (pingIt.value() == PingReason::CloseWindow) { + qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); + + // For internal windows, killing the window will delete this. + QPointer guard(this); + killWindow(); + if (!guard) { + return; + } + } + m_pings.erase(pingIt); +} + +void XdgToplevelClient::handlePingDelayed(quint32 serial) +{ + auto it = m_pings.find(serial); + if (it != m_pings.end()) { + qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); + setUnresponsive(true); + } +} + +void XdgToplevelClient::handlePongReceived(quint32 serial) +{ + auto it = m_pings.find(serial); + if (it != m_pings.end()) { + setUnresponsive(false); + m_pings.erase(it); + } +} + +void XdgToplevelClient::sendPing(PingReason reason) +{ + XdgShellInterface *shell = m_shellSurface->shell(); + XdgSurfaceInterface *surface = m_shellSurface->xdgSurface(); + + const quint32 serial = shell->ping(surface); + m_pings.insert(serial, reason); +} + +void XdgToplevelClient::initialize() +{ + blockGeometryUpdates(true); + + bool needsPlacement = !isInitialPositionSet(); + + if (supportsWindowRules()) { + setupWindowRules(false); + + const QRect originalGeometry = frameGeometry(); + const QRect ruledGeometry = rules()->checkGeometry(originalGeometry, true); + if (originalGeometry != ruledGeometry) { + setFrameGeometry(ruledGeometry); + } + maximize(rules()->checkMaximize(maximizeMode(), true)); + setDesktop(rules()->checkDesktop(desktop(), true)); + setDesktopFileName(rules()->checkDesktopFile(desktopFileName(), true).toUtf8()); + if (rules()->checkMinimize(isMinimized(), true)) { + minimize(true); // No animation. + } + setSkipTaskbar(rules()->checkSkipTaskbar(skipTaskbar(), true)); + setSkipPager(rules()->checkSkipPager(skipPager(), true)); + setSkipSwitcher(rules()->checkSkipSwitcher(skipSwitcher(), true)); + setKeepAbove(rules()->checkKeepAbove(keepAbove(), true)); + setKeepBelow(rules()->checkKeepBelow(keepBelow(), true)); + setShortcut(rules()->checkShortcut(shortcut().toString(), true)); + + // Don't place the client if its position is set by a rule. + if (rules()->checkPosition(invalidPoint, true) != invalidPoint) { + needsPlacement = false; + } + + // Don't place the client if the maximize state is set by a rule. + if (requestedMaximizeMode() != MaximizeRestore) { + needsPlacement = false; + } + + discardTemporaryRules(); + RuleBook::self()->discardUsed(this, false); // Remove Apply Now rules. + updateWindowRules(Rules::All); + } + if (isFullScreen()) { + needsPlacement = false; + } + if (needsPlacement) { + const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); + placeIn(area); + } + + blockGeometryUpdates(false); + scheduleConfigure(); + updateColorScheme(); - maximize(maximized ? MaximizeFull : MaximizeRestore); + m_isInitialized = true; } -void XdgShellClient::handleFullScreenRequested(bool fullScreen, OutputInterface *output) +void XdgToplevelClient::updateMaximizeMode(MaximizeMode maximizeMode) { - // FIXME: Consider output as well. - Q_UNUSED(output); - setFullScreen(fullScreen, false); + if (m_maximizeMode == maximizeMode) { + return; + } + m_maximizeMode = maximizeMode; + updateWindowRules(Rules::MaximizeVert | Rules::MaximizeHoriz); + emit clientMaximizedStateChanged(this, maximizeMode); + emit clientMaximizedStateChanged(this, maximizeMode & MaximizeHorizontal, maximizeMode & MaximizeVertical); } -void XdgShellClient::handleWindowMenuRequested(SeatInterface *seat, quint32 serial, const QPoint &surfacePos) +void XdgToplevelClient::updateFullScreenMode(bool set) { - // FIXME: Check the seat and serial. - Q_UNUSED(seat) - Q_UNUSED(serial) - performMouseCommand(Options::MouseOperationsMenu, pos() + surfacePos); + if (m_isFullScreen == set) { + return; + } + m_isFullScreen = set; + updateWindowRules(Rules::Fullscreen); + emit fullScreenChanged(); } -void XdgShellClient::handleGrabRequested(SeatInterface *seat, quint32 serial) +void XdgToplevelClient::updateColorScheme() { - // FIXME: Check the seat and serial as well whether the parent had focus. - Q_UNUSED(seat) - Q_UNUSED(serial) - m_hasPopupGrab = true; + if (m_paletteInterface) { + AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); + } else { + AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); + } } -void XdgShellClient::handlePingDelayed(quint32 serial) +void XdgToplevelClient::installAppMenu(AppMenuInterface *appMenu) { - auto it = m_pingSerials.find(serial); - if (it != m_pingSerials.end()) { - qCDebug(KWIN_CORE) << "First ping timeout:" << caption(); - setUnresponsive(true); - } + m_appMenuInterface = appMenu; + + auto updateMenu = [this](const AppMenuInterface::InterfaceAddress &address) { + updateApplicationMenuServiceName(address.serviceName); + updateApplicationMenuObjectPath(address.objectPath); + }; + connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, updateMenu); + updateMenu(appMenu->address()); } -void XdgShellClient::handlePingTimeout(quint32 serial) +void XdgToplevelClient::installServerDecoration(ServerSideDecorationInterface *decoration) { - auto it = m_pingSerials.find(serial); - if (it != m_pingSerials.end()) { - if (it.value() == PingReason::CloseWindow) { - qCDebug(KWIN_CORE) << "Final ping timeout on a close attempt, asking to kill:" << caption(); + m_serverDecoration = decoration; - //for internal windows, killing the window will delete this - QPointer guard(this); - killWindow(); - if (!guard) { - return; + connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, [this] { + if (!isClosing() && !isUnmapped()) { + updateDecoration(/* check_workspace_pos */ true); + } + }); + connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, + [this] (ServerSideDecorationManagerInterface::Mode mode) { + const bool changed = mode != m_serverDecoration->mode(); + if (changed && !isUnmapped()) { + updateDecoration(/* check_workspace_pos */ false); } } - m_pingSerials.erase(it); - } -} - -void XdgShellClient::handlePongReceived(quint32 serial) -{ - auto it = m_pingSerials.find(serial); - if (it != m_pingSerials.end()) { - setUnresponsive(false); - m_pingSerials.erase(it); + ); + if (!isUnmapped()) { + updateDecoration(/* check_workspace_pos */ true); } } -void XdgShellClient::handleCommitted() +void XdgToplevelClient::installXdgDecoration(XdgToplevelDecorationV1Interface *decoration) { - if (!surface()->buffer()) { - return; - } + m_xdgDecoration = decoration; - if (!m_hasWindowGeometry) { - m_windowGeometry = surface()->boundingRect(); - } - - updatePendingGeometry(); - - setDepth((surface()->buffer()->hasAlphaChannel() && !isDesktop()) ? 32 : 24); - markAsMapped(); + connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::destroyed, this, [this] { + if (!isClosing()) { + updateDecoration(/* check_workspace_pos */ true); + } + }); + connect(m_xdgDecoration, &XdgToplevelDecorationV1Interface::preferredModeChanged, this, [this] { + // force is true as we must send a new configure response. + updateDecoration(/* check_workspace_pos */ false, /* force */ true); + }); } -void XdgShellClient::resizeWithChecks(const QSize &size, ForceGeometry_t force) +void XdgToplevelClient::installPalette(ServerSideDecorationPaletteInterface *palette) { - // don't allow growing larger than workarea - const QRect area = workspace()->clientArea(WorkArea, this); - setFrameGeometry(QRect{pos(), size.boundedTo(area.size())}, force); -} + m_paletteInterface = palette; -void XdgShellClient::unmap() -{ - m_unmapped = true; - if (isMoveResize()) { - leaveMoveResize(); - } - m_requestedClientSize = QSize(0, 0); - destroyWindowManagementInterface(); - if (Workspace::self()) { - addWorkspaceRepaint(visibleRect()); - workspace()->clientHidden(this); - } - emit windowHidden(this); + auto updatePalette = [this](const QString &palette) { + AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); + }; + connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { + updatePalette(palette); + }); + connect(m_paletteInterface, &QObject::destroyed, this, [=]() { + updatePalette(QString()); + }); + updatePalette(palette->palette()); } -void XdgShellClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *surface) +/** + * \todo This whole plasma shell surface thing doesn't seem right. It turns xdg-toplevel into + * something completely different! Perhaps plasmashell surfaces need to be implemented via a + * proprietary protocol that doesn't piggyback on existing shell surface protocols. It'll lead + * to cleaner code and will be technically correct, but I'm not sure whether this is do-able. + */ +void XdgToplevelClient::installPlasmaShellSurface(PlasmaShellSurfaceInterface *shellSurface) { - m_plasmaShellSurface = surface; - auto updatePosition = [this, surface] { - // That's a mis-use of doSetGeometry method. One should instead use move method. - QRect rect = QRect(surface->position(), size()); - doSetGeometry(rect); - }; - auto updateRole = [this, surface] { + m_plasmaShellSurface = shellSurface; + + auto updatePosition = [this, shellSurface] { move(shellSurface->position()); }; + auto updateRole = [this, shellSurface] { NET::WindowType type = NET::Unknown; - switch (surface->role()) { + switch (shellSurface->role()) { case PlasmaShellSurfaceInterface::Role::Desktop: type = NET::Desktop; break; @@ -1365,70 +1467,74 @@ type = NET::Normal; break; } - if (type != m_windowType) { - m_windowType = type; - if (m_windowType == NET::Desktop || type == NET::Dock || type == NET::OnScreenDisplay || type == NET::Notification || type == NET::Tooltip || type == NET::CriticalNotification) { - setOnAllDesktops(true); - } - workspace()->updateClientArea(); + if (m_windowType == type) { + return; + } + m_windowType = type; + switch (m_windowType) { + case NET::Desktop: + case NET::Dock: + case NET::OnScreenDisplay: + case NET::Notification: + case NET::CriticalNotification: + case NET::Tooltip: + setOnAllDesktops(true); + break; + default: + break; } + workspace()->updateClientArea(); }; - connect(surface, &PlasmaShellSurfaceInterface::panelTakesFocusChanged , this, [this, surface]() { - if (surface->panelTakesFocus()) { - workspace()->activateClient(this); - } + connect(shellSurface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); + connect(shellSurface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); + connect(shellSurface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, [this] { + updateShowOnScreenEdge(); + workspace()->updateClientArea(); }); - connect(surface, &PlasmaShellSurfaceInterface::positionChanged, this, updatePosition); - connect(surface, &PlasmaShellSurfaceInterface::roleChanged, this, updateRole); - connect(surface, &PlasmaShellSurfaceInterface::panelBehaviorChanged, this, - [this] { - updateShowOnScreenEdge(); - workspace()->updateClientArea(); - } - ); - connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, - [this] { - hideClient(true); - m_plasmaShellSurface->hideAutoHidingPanel(); - updateShowOnScreenEdge(); - } - ); - connect(surface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, - [this] { - hideClient(false); - ScreenEdges::self()->reserve(this, ElectricNone); - m_plasmaShellSurface->showAutoHidingPanel(); - } - ); - if (surface->isPositionSet()) + connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideHideRequested, this, [this] { + hideClient(true); + m_plasmaShellSurface->hideAutoHidingPanel(); + updateShowOnScreenEdge(); + }); + connect(shellSurface, &PlasmaShellSurfaceInterface::panelAutoHideShowRequested, this, [this] { + hideClient(false); + ScreenEdges::self()->reserve(this, ElectricNone); + m_plasmaShellSurface->showAutoHidingPanel(); + }); + if (shellSurface->isPositionSet()) updatePosition(); updateRole(); updateShowOnScreenEdge(); - connect(this, &XdgShellClient::frameGeometryChanged, this, &XdgShellClient::updateShowOnScreenEdge); + connect(this, &XdgToplevelClient::frameGeometryChanged, + this, &XdgToplevelClient::updateShowOnScreenEdge); + connect(this, &XdgToplevelClient::windowShown, + this, &XdgToplevelClient::updateShowOnScreenEdge); - setSkipTaskbar(surface->skipTaskbar()); - connect(surface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { + setSkipTaskbar(shellSurface->skipTaskbar()); + connect(shellSurface, &PlasmaShellSurfaceInterface::skipTaskbarChanged, this, [this] { setSkipTaskbar(m_plasmaShellSurface->skipTaskbar()); }); - setSkipSwitcher(surface->skipSwitcher()); - connect(surface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { + setSkipSwitcher(shellSurface->skipSwitcher()); + connect(shellSurface, &PlasmaShellSurfaceInterface::skipSwitcherChanged, this, [this] { setSkipSwitcher(m_plasmaShellSurface->skipSwitcher()); }); } -void XdgShellClient::updateShowOnScreenEdge() +void XdgToplevelClient::updateShowOnScreenEdge() { if (!ScreenEdges::self()) { return; } - if (m_unmapped || !m_plasmaShellSurface || m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { + if (isUnmapped() || !m_plasmaShellSurface || + m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { ScreenEdges::self()->reserve(this, ElectricNone); return; } - if ((m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && m_hidden) || - m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { - // screen edge API requires an edge, thus we need to figure out which edge the window borders + const PlasmaShellSurfaceInterface::PanelBehavior panelBehavior = m_plasmaShellSurface->panelBehavior(); + if ((panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide && isHidden()) || + panelBehavior == PlasmaShellSurfaceInterface::PanelBehavior::WindowsCanCover) { + // Screen edge API requires an edge, thus we need to figure out which edge the window borders. const QRect clientGeometry = frameGeometry(); Qt::Edges edges; for (int i = 0; i < screens()->count(); i++) { @@ -1446,23 +1552,24 @@ edges |= Qt::BottomEdge; } } - // a panel might border multiple screen edges. E.g. a horizontal panel at the bottom will - // also border the left and right edge - // let's remove such cases - if (edges.testFlag(Qt::LeftEdge) && edges.testFlag(Qt::RightEdge)) { + + // A panel might border multiple screen edges. E.g. a horizontal panel at the bottom will + // also border the left and right edge. Let's remove such cases. + if (edges & Qt::LeftEdge && edges & Qt::RightEdge) { edges = edges & (~(Qt::LeftEdge | Qt::RightEdge)); } - if (edges.testFlag(Qt::TopEdge) && edges.testFlag(Qt::BottomEdge)) { + if (edges & Qt::TopEdge && edges & Qt::BottomEdge) { edges = edges & (~(Qt::TopEdge | Qt::BottomEdge)); } - // it's still possible that a panel borders two edges, e.g. bottom and left - // in that case the one which is sharing more with the edge wins - auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horiz, Qt::Edge vert) { - if (edges.testFlag(horiz) && edges.testFlag(vert)) { + + // It's still possible that a panel borders two edges, e.g. bottom and left + // in that case the one which is sharing more with the edge wins. + auto check = [clientGeometry](Qt::Edges edges, Qt::Edge horizontal, Qt::Edge vertical) { + if (edges & horizontal && edges & vertical) { if (clientGeometry.width() >= clientGeometry.height()) { - return edges & ~horiz; + return edges & ~horizontal; } else { - return edges & ~vert; + return edges & ~vertical; } } return edges; @@ -1473,136 +1580,338 @@ edges = check(edges, Qt::RightEdge, Qt::BottomEdge); ElectricBorder border = ElectricNone; - if (edges.testFlag(Qt::LeftEdge)) { + if (edges & Qt::LeftEdge) { border = ElectricLeft; } - if (edges.testFlag(Qt::RightEdge)) { + if (edges & Qt::RightEdge) { border = ElectricRight; } - if (edges.testFlag(Qt::TopEdge)) { + if (edges & Qt::TopEdge) { border = ElectricTop; } - if (edges.testFlag(Qt::BottomEdge)) { + if (edges & Qt::BottomEdge) { border = ElectricBottom; } ScreenEdges::self()->reserve(this, border); } else { ScreenEdges::self()->reserve(this, ElectricNone); } } -bool XdgShellClient::isInitialPositionSet() const +void XdgToplevelClient::setupWindowManagementIntegration() { - if (m_plasmaShellSurface) { - return m_plasmaShellSurface->isPositionSet(); + if (isLockScreen()) { + return; } - return false; + connect(this, &XdgToplevelClient::windowMapped, + this, &XdgToplevelClient::setupWindowManagementInterface); + connect(this, &XdgToplevelClient::windowUnmapped, + this, &XdgToplevelClient::destroyWindowManagementInterface); } -void XdgShellClient::installAppMenu(AppMenuInterface *menu) +void XdgToplevelClient::setupPlasmaShellIntegration() { - m_appMenuInterface = menu; + connect(this, &XdgToplevelClient::windowMapped, + this, &XdgToplevelClient::updateShowOnScreenEdge); +} - auto updateMenu = [this](AppMenuInterface::InterfaceAddress address) { - updateApplicationMenuServiceName(address.serviceName); - updateApplicationMenuObjectPath(address.objectPath); - }; - connect(m_appMenuInterface, &AppMenuInterface::addressChanged, this, [=](AppMenuInterface::InterfaceAddress address) { - updateMenu(address); - }); - updateMenu(menu->address()); +void XdgToplevelClient::setFullScreen(bool set, bool user) +{ + set = rules()->checkFullScreen(set); + + const bool wasFullscreen = isFullScreen(); + if (wasFullscreen == set) { + return; + } + if (isSpecialWindow()) { + return; + } + if (user && !userCanSetFullScreen()) { + return; + } + + if (wasFullscreen) { + workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event + } else { + m_fullScreenGeometryRestore = frameGeometry(); + } + m_isFullScreen = set; + + if (set) { + workspace()->raiseClient(this); + } + StackingUpdatesBlocker blocker1(workspace()); + GeometryUpdatesBlocker blocker2(this); + + workspace()->updateClientLayer(this); // active fullscreens get different layer + updateDecoration(false, false); + + if (set) { + setFrameGeometry(workspace()->clientArea(FullScreenArea, this)); + } else { + if (m_fullScreenGeometryRestore.isValid()) { + int currentScreen = screen(); + setFrameGeometry(QRect(m_fullScreenGeometryRestore.topLeft(), + constrainFrameSize(m_fullScreenGeometryRestore.size()))); + if( currentScreen != screen()) + workspace()->sendClientToScreen( this, currentScreen ); + } else { + // this can happen when the window was first shown already fullscreen, + // so let the client set the size by itself + setFrameGeometry(QRect(workspace()->clientArea(PlacementArea, this).topLeft(), QSize(0, 0))); + } + } + + scheduleConfigure(); + + updateWindowRules(Rules::Fullscreen|Rules::Position|Rules::Size); + emit fullScreenChanged(); } -void XdgShellClient::installPalette(ServerSideDecorationPaletteInterface *palette) +/** + * \todo Move to AbstractClient. + */ +static bool changeMaximizeRecursion = false; +void XdgToplevelClient::changeMaximize(bool horizontal, bool vertical, bool adjust) { - m_paletteInterface = palette; + if (changeMaximizeRecursion) { + return; + } - auto updatePalette = [this](const QString &palette) { - AbstractClient::updateColorScheme(rules()->checkDecoColor(palette)); - }; - connect(m_paletteInterface, &ServerSideDecorationPaletteInterface::paletteChanged, this, [=](const QString &palette) { - updatePalette(palette); - }); - connect(m_paletteInterface, &QObject::destroyed, this, [=]() { - updatePalette(QString()); - }); - updatePalette(palette->palette()); + if (!isResizable()) { + return; + } + + const QRect clientArea = isElectricBorderMaximizing() ? + workspace()->clientArea(MaximizeArea, Cursors::self()->mouse()->pos(), desktop()) : + workspace()->clientArea(MaximizeArea, this); + + const MaximizeMode oldMode = m_requestedMaximizeMode; + const QRect oldGeometry = frameGeometry(); + + // 'adjust == true' means to update the size only, e.g. after changing workspace size + if (!adjust) { + if (vertical) + m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeVertical); + if (horizontal) + m_requestedMaximizeMode = MaximizeMode(m_requestedMaximizeMode ^ MaximizeHorizontal); + } + + m_requestedMaximizeMode = rules()->checkMaximize(m_requestedMaximizeMode); + if (!adjust && m_requestedMaximizeMode == oldMode) { + return; + } + + StackingUpdatesBlocker blocker(workspace()); + + // call into decoration update borders + if (isDecorated() && decoration()->client() && !(options->borderlessMaximizedWindows() && m_requestedMaximizeMode == KWin::MaximizeFull)) { + changeMaximizeRecursion = true; + const auto c = decoration()->client().toStrongRef(); + if ((m_requestedMaximizeMode & MaximizeVertical) != (oldMode & MaximizeVertical)) { + emit c->maximizedVerticallyChanged(m_requestedMaximizeMode & MaximizeVertical); + } + if ((m_requestedMaximizeMode & MaximizeHorizontal) != (oldMode & MaximizeHorizontal)) { + emit c->maximizedHorizontallyChanged(m_requestedMaximizeMode & MaximizeHorizontal); + } + if ((m_requestedMaximizeMode == MaximizeFull) != (oldMode == MaximizeFull)) { + emit c->maximizedChanged(m_requestedMaximizeMode & MaximizeFull); + } + changeMaximizeRecursion = false; + } + + if (options->borderlessMaximizedWindows()) { + // triggers a maximize change. + // The next setNoBorder interation will exit since there's no change but the first recursion pullutes the restore geometry + changeMaximizeRecursion = true; + setNoBorder(rules()->checkNoBorder(m_requestedMaximizeMode == MaximizeFull)); + changeMaximizeRecursion = false; + } + + // Conditional quick tiling exit points + const auto oldQuickTileMode = quickTileMode(); + if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { + if (oldMode == MaximizeFull && + !clientArea.contains(geometryRestore().center())) { + // Not restoring on the same screen + // TODO: The following doesn't work for some reason + //quick_tile_mode = QuickTileNone; // And exit quick tile mode manually + } else if ((oldMode == MaximizeVertical && m_requestedMaximizeMode == MaximizeRestore) || + (oldMode == MaximizeFull && m_requestedMaximizeMode == MaximizeHorizontal)) { + // Modifying geometry of a tiled window + updateQuickTileMode(QuickTileFlag::None); // Exit quick tile mode without restoring geometry + } + } + + if (m_requestedMaximizeMode == MaximizeFull) { + setGeometryRestore(oldGeometry); + // TODO: Client has more checks + if (options->electricBorderMaximize()) { + updateQuickTileMode(QuickTileFlag::Maximize); + } else { + updateQuickTileMode(QuickTileFlag::None); + } + if (quickTileMode() != oldQuickTileMode) { + emit quickTileModeChanged(); + } + setFrameGeometry(workspace()->clientArea(MaximizeArea, this)); + workspace()->raiseClient(this); + } else { + if (m_requestedMaximizeMode == MaximizeRestore) { + updateQuickTileMode(QuickTileFlag::None); + } + if (quickTileMode() != oldQuickTileMode) { + emit quickTileModeChanged(); + } + + if (geometryRestore().isValid()) { + setFrameGeometry(geometryRestore()); + } else { + setFrameGeometry(workspace()->clientArea(PlacementArea, this)); + } + } + + scheduleConfigure(); +} + +XdgPopupClient::XdgPopupClient(XdgPopupInterface *shellSurface) + : XdgSurfaceClient(shellSurface->xdgSurface()) + , m_shellSurface(shellSurface) +{ + setDesktop(VirtualDesktopManager::self()->current()); + + connect(shellSurface, &XdgPopupInterface::grabRequested, + this, &XdgPopupClient::handleGrabRequested); + connect(shellSurface, &XdgPopupInterface::initializeRequested, + this, &XdgPopupClient::initialize); + connect(shellSurface, &XdgPopupInterface::destroyed, + this, &XdgPopupClient::destroyClient); + + // The xdg-shell spec states that the parent xdg-surface may be null if it is specified + // via "some other protocol," but we don't support any such protocol yet. Notice that the + // xdg-foreign protocol is only for toplevel surfaces. + + XdgSurfaceInterface *parentShellSurface = shellSurface->parentXdgSurface(); + AbstractClient *parentClient = waylandServer()->findClient(parentShellSurface->surface()); + parentClient->addTransient(this); + setTransientFor(parentClient); +} + +XdgPopupClient::~XdgPopupClient() +{ } -void XdgShellClient::updateColorScheme() +void XdgPopupClient::debug(QDebug &stream) const { - if (m_paletteInterface) { - AbstractClient::updateColorScheme(rules()->checkDecoColor(m_paletteInterface->palette())); - } else { - AbstractClient::updateColorScheme(rules()->checkDecoColor(QString())); - } + stream << this; } -void XdgShellClient::updateMaximizeMode(MaximizeMode maximizeMode) +NET::WindowType XdgPopupClient::windowType(bool direct, int supported_types) const { - if (maximizeMode == m_maximizeMode) { - return; - } + Q_UNUSED(direct) + Q_UNUSED(supported_types) + return NET::Normal; +} - m_maximizeMode = maximizeMode; - updateWindowRules(Rules::MaximizeHoriz | Rules::MaximizeVert | Rules::Position | Rules::Size); +bool XdgPopupClient::hasPopupGrab() const +{ + return m_haveExplicitGrab; +} - emit clientMaximizedStateChanged(this, m_maximizeMode); - emit clientMaximizedStateChanged(this, m_maximizeMode & MaximizeHorizontal, m_maximizeMode & MaximizeVertical); +void XdgPopupClient::popupDone() +{ + m_shellSurface->sendPopupDone(); } -bool XdgShellClient::hasStrut() const +bool XdgPopupClient::isPopupWindow() const { - if (!isShown(true)) { - return false; - } - if (!m_plasmaShellSurface) { - return false; - } - if (m_plasmaShellSurface->role() != PlasmaShellSurfaceInterface::Role::Panel) { - return false; - } - return m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AlwaysVisible; + return true; } -quint32 XdgShellClient::windowId() const +bool XdgPopupClient::isTransient() const { - return m_windowId; + return true; } -void XdgShellClient::updateIcon() +bool XdgPopupClient::isResizable() const { - const QString waylandIconName = QStringLiteral("wayland"); - const QString dfIconName = iconFromDesktopFile(); - const QString iconName = dfIconName.isEmpty() ? waylandIconName : dfIconName; - if (iconName == icon().name()) { - return; - } - setIcon(QIcon::fromTheme(iconName)); + return false; +} + +bool XdgPopupClient::isMovable() const +{ + return false; } -bool XdgShellClient::isTransient() const +bool XdgPopupClient::isMovableAcrossScreens() const { - return m_transient; + return false; } -bool XdgShellClient::hasTransientPlacementHint() const +bool XdgPopupClient::hasTransientPlacementHint() const { - return isTransient() && transientFor() && m_xdgShellPopup; + return true; } -QRect XdgShellClient::transientPlacement(const QRect &bounds) const +static QPoint popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, + const Qt::Edges gravity, const QSize popupSize) { - Q_ASSERT(m_xdgShellPopup); + QPoint anchorPoint; + switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { + case Qt::LeftEdge: + anchorPoint.setX(anchorRect.x()); + break; + case Qt::RightEdge: + anchorPoint.setX(anchorRect.x() + anchorRect.width()); + break; + default: + anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); + } + switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { + case Qt::TopEdge: + anchorPoint.setY(anchorRect.y()); + break; + case Qt::BottomEdge: + anchorPoint.setY(anchorRect.y() + anchorRect.height()); + break; + default: + anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); + } + + // calculate where the top left point of the popup will end up with the applied gravity + // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge + // will next to the anchor point + QPoint popupPosAdjust; + switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { + case Qt::LeftEdge: + popupPosAdjust.setX(-popupSize.width()); + break; + case Qt::RightEdge: + popupPosAdjust.setX(0); + break; + default: + popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); + } + switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { + case Qt::TopEdge: + popupPosAdjust.setY(-popupSize.height()); + break; + case Qt::BottomEdge: + popupPosAdjust.setY(0); + break; + default: + popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); + } + + return anchorPoint + popupPosAdjust; +} - QRect anchorRect; - Qt::Edges anchorEdge; - Qt::Edges gravity; - QPoint offset; - PositionerConstraints constraintAdjustments; - QSize size = frameGeometry().size(); +QRect XdgPopupClient::transientPlacement(const QRect &bounds) const +{ + const XdgPositioner positioner = m_shellSurface->positioner(); + const QSize desiredSize = isUnmapped() ? positioner.size() : size(); - const QPoint parentClientPos = transientFor()->pos() + transientFor()->clientPos(); + const QPoint parentPosition = transientFor()->framePosToClientPos(transientFor()->pos()); // returns if a target is within the supplied bounds, optional edges argument states which side to check auto inBounds = [bounds](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool { @@ -1622,51 +1931,42 @@ return true; }; - anchorRect = m_xdgShellPopup->anchorRect(); - anchorEdge = m_xdgShellPopup->anchorEdge(); - gravity = m_xdgShellPopup->gravity(); - offset = m_xdgShellPopup->anchorOffset(); - constraintAdjustments = m_xdgShellPopup->constraintAdjustments(); - if (!size.isValid()) { - size = m_xdgShellPopup->initialSize(); - } - - QRect popupRect(popupOffset(anchorRect, anchorEdge, gravity, size) + offset + parentClientPos, size); + QRect popupRect(popupOffset(positioner.anchorRect(), positioner.anchorEdges(), positioner.gravityEdges(), desiredSize) + positioner.offset() + parentPosition, desiredSize); //if that fits, we don't need to do anything if (inBounds(popupRect)) { return popupRect; } //otherwise apply constraint adjustment per axis in order XDG Shell Popup states - if (constraintAdjustments & PositionerConstraint::FlipX) { + if (positioner.flipConstraintAdjustments() & Qt::Horizontal) { if (!inBounds(popupRect, Qt::LeftEdge | Qt::RightEdge)) { //flip both edges (if either bit is set, XOR both) - auto flippedAnchorEdge = anchorEdge; + auto flippedAnchorEdge = positioner.anchorEdges(); if (flippedAnchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { flippedAnchorEdge ^= (Qt::LeftEdge | Qt::RightEdge); } - auto flippedGravity = gravity; + auto flippedGravity = positioner.gravityEdges(); if (flippedGravity & (Qt::LeftEdge | Qt::RightEdge)) { flippedGravity ^= (Qt::LeftEdge | Qt::RightEdge); } - auto flippedPopupRect = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); + auto flippedPopupRect = QRect(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) { popupRect.moveLeft(flippedPopupRect.left()); } } } - if (constraintAdjustments & PositionerConstraint::SlideX) { + if (positioner.slideConstraintAdjustments() & Qt::Horizontal) { if (!inBounds(popupRect, Qt::LeftEdge)) { popupRect.moveLeft(bounds.left()); } if (!inBounds(popupRect, Qt::RightEdge)) { popupRect.moveRight(bounds.right()); } } - if (constraintAdjustments & PositionerConstraint::ResizeX) { + if (positioner.resizeConstraintAdjustments() & Qt::Horizontal) { QRect unconstrainedRect = popupRect; if (!inBounds(unconstrainedRect, Qt::LeftEdge)) { @@ -1681,34 +1981,34 @@ } } - if (constraintAdjustments & PositionerConstraint::FlipY) { + if (positioner.flipConstraintAdjustments() & Qt::Vertical) { if (!inBounds(popupRect, Qt::TopEdge | Qt::BottomEdge)) { //flip both edges (if either bit is set, XOR both) - auto flippedAnchorEdge = anchorEdge; + auto flippedAnchorEdge = positioner.anchorEdges(); if (flippedAnchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { flippedAnchorEdge ^= (Qt::TopEdge | Qt::BottomEdge); } - auto flippedGravity = gravity; + auto flippedGravity = positioner.gravityEdges(); if (flippedGravity & (Qt::TopEdge | Qt::BottomEdge)) { flippedGravity ^= (Qt::TopEdge | Qt::BottomEdge); } - auto flippedPopupRect = QRect(popupOffset(anchorRect, flippedAnchorEdge, flippedGravity, size) + offset + parentClientPos, size); + auto flippedPopupRect = QRect(popupOffset(positioner.anchorRect(), flippedAnchorEdge, flippedGravity, desiredSize) + positioner.offset() + parentPosition, desiredSize); //if it still doesn't fit we should continue with the unflipped version if (inBounds(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) { popupRect.moveTop(flippedPopupRect.top()); } } } - if (constraintAdjustments & PositionerConstraint::SlideY) { + if (positioner.slideConstraintAdjustments() & Qt::Vertical) { if (!inBounds(popupRect, Qt::TopEdge)) { popupRect.moveTop(bounds.top()); } if (!inBounds(popupRect, Qt::BottomEdge)) { popupRect.moveBottom(bounds.bottom()); } } - if (constraintAdjustments & PositionerConstraint::ResizeY) { + if (positioner.resizeConstraintAdjustments() & Qt::Vertical) { QRect unconstrainedRect = popupRect; if (!inBounds(unconstrainedRect, Qt::TopEdge)) { @@ -1726,298 +2026,92 @@ return popupRect; } -QPoint XdgShellClient::popupOffset(const QRect &anchorRect, const Qt::Edges anchorEdge, const Qt::Edges gravity, const QSize popupSize) const -{ - QPoint anchorPoint; - switch (anchorEdge & (Qt::LeftEdge | Qt::RightEdge)) { - case Qt::LeftEdge: - anchorPoint.setX(anchorRect.x()); - break; - case Qt::RightEdge: - anchorPoint.setX(anchorRect.x() + anchorRect.width()); - break; - default: - anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0)); - } - switch (anchorEdge & (Qt::TopEdge | Qt::BottomEdge)) { - case Qt::TopEdge: - anchorPoint.setY(anchorRect.y()); - break; - case Qt::BottomEdge: - anchorPoint.setY(anchorRect.y() + anchorRect.height()); - break; - default: - anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0)); - } - - // calculate where the top left point of the popup will end up with the applied gravity - // gravity indicates direction. i.e if gravitating towards the top the popup's bottom edge - // will next to the anchor point - QPoint popupPosAdjust; - switch (gravity & (Qt::LeftEdge | Qt::RightEdge)) { - case Qt::LeftEdge: - popupPosAdjust.setX(-popupSize.width()); - break; - case Qt::RightEdge: - popupPosAdjust.setX(0); - break; - default: - popupPosAdjust.setX(qRound(-popupSize.width() / 2.0)); - } - switch (gravity & (Qt::TopEdge | Qt::BottomEdge)) { - case Qt::TopEdge: - popupPosAdjust.setY(-popupSize.height()); - break; - case Qt::BottomEdge: - popupPosAdjust.setY(0); - break; - default: - popupPosAdjust.setY(qRound(-popupSize.height() / 2.0)); - } - - return anchorPoint + popupPosAdjust; -} - -void XdgShellClient::doResizeSync() -{ - requestGeometry(moveResizeGeometry()); -} - -QMatrix4x4 XdgShellClient::inputTransformation() const +bool XdgPopupClient::isCloseable() const { - QMatrix4x4 matrix; - matrix.translate(-m_bufferGeometry.x(), -m_bufferGeometry.y()); - return matrix; + return false; } -void XdgShellClient::installServerSideDecoration(KWaylandServer::ServerSideDecorationInterface *deco) +void XdgPopupClient::closeWindow() { - if (m_serverDecoration == deco) { - return; - } - m_serverDecoration = deco; - connect(m_serverDecoration, &ServerSideDecorationInterface::destroyed, this, - [this] { - m_serverDecoration = nullptr; - if (m_closing || !Workspace::self()) { - return; - } - if (!m_unmapped) { - // maybe delay to next event cycle in case the XdgShellClient is getting destroyed, too - updateDecoration(true); - } - } - ); - if (!m_unmapped) { - updateDecoration(true); - } - connect(m_serverDecoration, &ServerSideDecorationInterface::modeRequested, this, - [this] (ServerSideDecorationManagerInterface::Mode mode) { - const bool changed = mode != m_serverDecoration->mode(); - if (changed && !m_unmapped) { - updateDecoration(false); - } - } - ); } -void XdgShellClient::installXdgDecoration(XdgDecorationInterface *deco) +void XdgPopupClient::updateColorScheme() { - Q_ASSERT(m_xdgShellToplevel); - - 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); - }); + AbstractClient::updateColorScheme(QString()); } -bool XdgShellClient::shouldExposeToWindowManagement() +bool XdgPopupClient::noBorder() const { - if (isLockScreen()) { - return false; - } - if (m_xdgShellPopup) { - return false; - } return true; } -KWaylandServer::XdgShellSurfaceInterface::States XdgShellClient::xdgSurfaceStates() const +bool XdgPopupClient::userCanSetNoBorder() const { - XdgShellSurfaceInterface::States states; - if (isActive()) { - states |= XdgShellSurfaceInterface::State::Activated; - } - if (isFullScreen()) { - states |= XdgShellSurfaceInterface::State::Fullscreen; - } - if (m_requestedMaximizeMode == MaximizeMode::MaximizeFull) { - states |= XdgShellSurfaceInterface::State::Maximized; - } - if (isResize()) { - states |= XdgShellSurfaceInterface::State::Resizing; - } - return states; + return false; } -void XdgShellClient::doMinimize() +void XdgPopupClient::setNoBorder(bool set) { - if (isMinimized()) { - workspace()->clientHidden(this); - } else { - emit windowShown(this); - } - workspace()->updateMinimizedOfTransients(this); + Q_UNUSED(set) } -void XdgShellClient::showOnScreenEdge() +void XdgPopupClient::updateDecoration(bool check_workspace_pos, bool force) { - if (!m_plasmaShellSurface || m_unmapped) { - return; - } - hideClient(false); - workspace()->raiseClient(this); - if (m_plasmaShellSurface->panelBehavior() == PlasmaShellSurfaceInterface::PanelBehavior::AutoHide) { - m_plasmaShellSurface->showAutoHidingPanel(); - } + Q_UNUSED(check_workspace_pos) + Q_UNUSED(force) } -bool XdgShellClient::dockWantsInput() const +void XdgPopupClient::showOnScreenEdge() { - if (m_plasmaShellSurface) { - if (m_plasmaShellSurface->role() == PlasmaShellSurfaceInterface::Role::Panel) { - return m_plasmaShellSurface->panelTakesFocus(); - } - } - return false; } -void XdgShellClient::killWindow() +bool XdgPopupClient::supportsWindowRules() const { - if (!surface()) { - return; - } - auto c = surface()->client(); - if (c->processId() == getpid() || c->processId() == 0) { - c->destroy(); - return; - } - ::kill(c->processId(), SIGTERM); - // give it time to terminate and only if terminate fails, try destroy Wayland connection - QTimer::singleShot(5000, c, &ClientConnection::destroy); + return false; } -bool XdgShellClient::isLocalhost() const +bool XdgPopupClient::wantsInput() const { - return true; + return false; } -bool XdgShellClient::hasPopupGrab() const +void XdgPopupClient::takeFocus() { - return m_hasPopupGrab; } -void XdgShellClient::popupDone() +bool XdgPopupClient::acceptsFocus() const { - if (m_xdgShellPopup) { - m_xdgShellPopup->popupDone(); - } + return false; } -void XdgShellClient::updateClientOutputs() +XdgSurfaceConfigure *XdgPopupClient::sendRoleConfigure() const { - QVector clientOutputs; - const auto outputs = waylandServer()->display()->outputs(); - for (OutputInterface *output : outputs) { - const QRect outputGeometry(output->globalPosition(), output->pixelSize() / output->scale()); - if (frameGeometry().intersects(outputGeometry)) { - clientOutputs << output; - } - } - surface()->setOutputs(clientOutputs); -} + const QPoint parentPosition = transientFor()->framePosToClientPos(transientFor()->pos()); + const QPoint popupPosition = requestedPos() - parentPosition; -bool XdgShellClient::isPopupWindow() const -{ - if (Toplevel::isPopupWindow()) { - return true; - } - if (m_xdgShellPopup != nullptr) { - return true; - } - return false; -} + const quint32 serial = m_shellSurface->sendConfigure(QRect(popupPosition, requestedClientSize())); -bool XdgShellClient::supportsWindowRules() const -{ - if (m_plasmaShellSurface) { - return false; - } - return m_xdgShellToplevel; -} + XdgSurfaceConfigure *configureEvent = new XdgSurfaceConfigure(); + configureEvent->position = requestedPos(); + configureEvent->size = requestedSize(); + configureEvent->serial = serial; -QRect XdgShellClient::adjustMoveGeometry(const QRect &rect) const -{ - QRect geometry = rect; - geometry.moveTopLeft(moveResizeGeometry().topLeft()); - return geometry; + return configureEvent; } -QRect XdgShellClient::adjustResizeGeometry(const QRect &rect) const +void XdgPopupClient::handleGrabRequested(SeatInterface *seat, quint32 serial) { - QRect geometry = rect; - - // We need to adjust frame geometry because configure events carry the maximum window geometry - // size. A client that has aspect ratio can attach a buffer with smaller size than the one in - // a configure event. - switch (moveResizePointerMode()) { - case PositionTopLeft: - geometry.moveRight(moveResizeGeometry().right()); - geometry.moveBottom(moveResizeGeometry().bottom()); - break; - case PositionTop: - case PositionTopRight: - geometry.moveLeft(moveResizeGeometry().left()); - geometry.moveBottom(moveResizeGeometry().bottom()); - break; - case PositionRight: - case PositionBottomRight: - case PositionBottom: - geometry.moveLeft(moveResizeGeometry().left()); - geometry.moveTop(moveResizeGeometry().top()); - break; - case PositionBottomLeft: - case PositionLeft: - geometry.moveRight(moveResizeGeometry().right()); - geometry.moveTop(moveResizeGeometry().top()); - break; - case PositionCenter: - Q_UNREACHABLE(); - } - - return geometry; + Q_UNUSED(seat) + Q_UNUSED(serial) + m_haveExplicitGrab = true; } -void XdgShellClient::ping(PingReason reason) +void XdgPopupClient::initialize() { - Q_ASSERT(m_xdgShellToplevel); + const QRect area = workspace()->clientArea(PlacementArea, Screens::self()->current(), desktop()); + placeIn(area); - XdgShellInterface *shell = static_cast(m_xdgShellToplevel->global()); - const quint32 serial = shell->ping(m_xdgShellToplevel); - m_pingSerials.insert(serial, reason); + scheduleConfigure(); } -} +} // namespace KWin