From 7e8facc3fde853a93683554e5fc9bfd4985e4760 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Mon, 29 Oct 2018 22:29:15 +0000 Subject: [PATCH] [wayland] Use the new plasma virtual desktop protocol Summary: implement virtual desktop support for Wayland. use the new virtual desktop protocol from D12820 The VirtualDesktopManager class needed some big change in order to accomodate it, which is where most changes are. Other than that, it's mostly connections to wire up VirtualDesktopsManager and VirtualDesktopsManagement(the wayland protocol impl) Depends on D12820 Other notable detail, is the client visibility updated to reflect the presence of the client in the plasmavirtualdesktop. (and the unSetDesktop concept) Test Plan: used a bit a plasma session together with D12820, D13748 and D13746 Reviewers: #plasma, #kwin, graesslin, davidedmundson Reviewed By: #plasma, #kwin, davidedmundson Subscribers: hein, zzag, davidedmundson, kwin Tags: #kwin Maniphest Tasks: T4457 Differential Revision: https://phabricator.kde.org/D13887 --- CMakeLists.txt | 3 + abstract_client.cpp | 134 +++++++- abstract_client.h | 19 +- autotests/CMakeLists.txt | 2 + .../integration/virtual_desktop_test.cpp | 146 +++++++++ autotests/test_virtual_desktops.cpp | 7 +- autotests/test_window_paint_data.cpp | 2 + dbusinterface.cpp | 177 +++++++++++ dbusinterface.h | 73 +++++ deleted.cpp | 6 + deleted.h | 2 + effects.cpp | 3 +- effects/desktopgrid/desktopgrid.cpp | 50 +-- effects/desktopgrid/desktopgrid.h | 3 +- libkwineffects/kwineffects.cpp | 19 +- libkwineffects/kwineffects.h | 22 +- org.kde.KWin.VirtualDesktopManager.xml | 50 +++ toplevel.h | 14 +- unmanaged.cpp | 5 + unmanaged.h | 1 + useractions.cpp | 120 ++++++- useractions.h | 15 + virtualdesktops.cpp | 296 ++++++++++++++++-- virtualdesktops.h | 105 +++++-- virtualdesktopsdbustypes.cpp | 70 +++++ virtualdesktopsdbustypes.h | 47 +++ wayland_server.cpp | 9 + wayland_server.h | 5 + workspace.cpp | 38 ++- workspace.h | 1 - 30 files changed, 1326 insertions(+), 118 deletions(-) create mode 100644 org.kde.KWin.VirtualDesktopManager.xml create mode 100644 virtualdesktopsdbustypes.cpp create mode 100644 virtualdesktopsdbustypes.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ec80340c08..074d0263e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,6 +381,7 @@ add_subdirectory(helpers) set(kwin_KDEINIT_SRCS workspace.cpp dbusinterface.cpp + virtualdesktopsdbustypes.cpp abstract_client.cpp client.cpp client_machine.cpp @@ -533,6 +534,7 @@ qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.Compositing.xml dbusinterfa qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.ColorCorrect.xml colorcorrection/colorcorrectdbusinterface.h KWin::ColorCorrect::ColorCorrectDBusInterface ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl ) qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.kwin.OrientationSensor.xml orientation_sensor.h KWin::OrientationSensor) +qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface ) qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface) @@ -671,6 +673,7 @@ install( org.kde.kwin.Compositing.xml org.kde.kwin.ColorCorrect.xml org.kde.kwin.Effects.xml + org.kde.KWin.VirtualDesktopManager.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} ) diff --git a/abstract_client.cpp b/abstract_client.cpp index 9f93b1ee1e..3e4c504712 100644 --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -489,16 +489,44 @@ void AbstractClient::setDesktop(int desktop) if (desktop != NET::OnAllDesktops) // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); - if (m_desktop == desktop) - return; - int was_desk = m_desktop; - const bool wasOnCurrentDesktop = isOnCurrentDesktop(); - m_desktop = desktop; + VirtualDesktop *virtualDesktop = desktop == NET::OnAllDesktops ? nullptr : VirtualDesktopManager::self()->desktopForX11Id(desktop); + + // Don't do anything if we're already there, if the desktop is already in desktops or if the desktop is NET::OnAllDesktops and m_desktops is already empty. + if (m_desktops.contains(virtualDesktop) || + (desktop == NET::OnAllDesktops && m_desktops.isEmpty())) { + return; + } + + int was_desk = AbstractClient::desktop(); + const bool wasOnCurrentDesktop = isOnCurrentDesktop() && was_desk >= 0; + + //on x11 we can have only one desktop at a time + if (kwinApp()->operationMode() == Application::OperationModeX11) { + m_desktops.clear(); + } + if (desktop == NET::OnAllDesktops) { + m_desktops.clear(); + } else { + //if would become on all desktops, clear the list, as empty == on all desktops + if (m_desktops.count() > 1 && static_cast(m_desktops.count()) == VirtualDesktopManager::self()->count() - 1) { + m_desktops.clear(); + } else { + m_desktops << virtualDesktop; + } + } + if (windowManagementInterface()) { + if (m_desktops.isEmpty()) { + windowManagementInterface()->setOnAllDesktops(true); + } else { + windowManagementInterface()->addPlasmaVirtualDesktop(virtualDesktop->id()); + } + } if (info) { info->setDesktop(desktop); } + if ((was_desk == NET::OnAllDesktops) != (desktop == NET::OnAllDesktops)) { // onAllDesktops changed workspace()->updateOnAllDesktopsOfTransients(this); @@ -526,6 +554,7 @@ void AbstractClient::setDesktop(int desktop) emit desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) emit desktopPresenceChanged(this, was_desk); + emit x11DesktopIdsChanged(); } void AbstractClient::doSetDesktop(int desktop, int was_desk) @@ -534,6 +563,34 @@ void AbstractClient::doSetDesktop(int desktop, int was_desk) Q_UNUSED(was_desk) } +void AbstractClient::unSetDesktop(int desktop) +{ + // Case in which we are on all desktops and gets asked to unset + if (desktop == NET::OnAllDesktops) { + if (m_desktops.isEmpty()) { + setOnAllDesktops(false); + } + + return; + } + + // Out of range + if (desktop < 1 || desktop > VirtualDesktopManager::self()->count()) { + return; + } + + VirtualDesktop *virtualDesktop = VirtualDesktopManager::self()->desktopForX11Id(desktop); + + m_desktops.removeAll(virtualDesktop); + + if (!windowManagementInterface()) { + return; + } + + windowManagementInterface()->removePlasmaVirtualDesktop(virtualDesktop->id()); + emit x11DesktopIdsChanged(); +} + void AbstractClient::setOnAllDesktops(bool b) { if ((b && isOnAllDesktops()) || @@ -545,6 +602,20 @@ void AbstractClient::setOnAllDesktops(bool b) setDesktop(VirtualDesktopManager::self()->current()); } +QList AbstractClient::x11DesktopIds() const +{ + const auto desks = desktops(); + QList x11Ids; + x11Ids.reserve(desks.count()); + std::transform(desks.constBegin(), desks.constEnd(), + std::back_inserter(x11Ids), + [] (const VirtualDesktop *vd) { + return vd->x11DesktopNumber(); + } + ); + return x11Ids; +} + bool AbstractClient::isShadeable() const { return false; @@ -808,16 +879,7 @@ void AbstractClient::setupWindowManagementInterface() } ); connect(this, &AbstractClient::captionChanged, w, [w, this] { w->setTitle(caption()); }); - connect(this, &AbstractClient::desktopChanged, w, - [w, this] { - if (isOnAllDesktops()) { - w->setOnAllDesktops(true); - return; - } - w->setVirtualDesktop(desktop() - 1); - w->setOnAllDesktops(false); - } - ); + connect(this, &AbstractClient::activeChanged, w, [w, this] { w->setActive(isActive()); }); connect(this, &AbstractClient::fullScreenChanged, w, [w, this] { w->setFullscreen(isFullScreen()); }); connect(this, &AbstractClient::keepAboveChanged, w, &PlasmaWindowInterface::setKeepAbove); @@ -912,6 +974,48 @@ void AbstractClient::setupWindowManagementInterface() setShade(set); } ); + + for (const auto vd : m_desktops) { + w->addPlasmaVirtualDesktop(vd->id()); + } + + //this is only for the legacy + connect(this, &AbstractClient::desktopChanged, w, + [w, this] { + if (isOnAllDesktops()) { + w->setOnAllDesktops(true); + return; + } + w->setVirtualDesktop(desktop() - 1); + w->setOnAllDesktops(false); + } + ); + + //Plasma Virtual desktop management + //show/hide when the window enters/exits from desktop + connect(w, &PlasmaWindowInterface::enterPlasmaVirtualDesktopRequested, this, + [this] (const QString &desktopId) { + VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8()); + if (vd) { + workspace()->sendClientToDesktop(this, vd->x11DesktopNumber(), false); + } + } + ); + connect(w, &PlasmaWindowInterface::enterNewPlasmaVirtualDesktopRequested, this, + [this] () { + VirtualDesktopManager::self()->setCount(VirtualDesktopManager::self()->count() + 1); + workspace()->sendClientToDesktop(this, VirtualDesktopManager::self()->count(), false); + } + ); + connect(w, &PlasmaWindowInterface::leavePlasmaVirtualDesktopRequested, this, + [this] (const QString &desktopId) { + VirtualDesktop *vd = VirtualDesktopManager::self()->desktopForId(desktopId.toUtf8()); + if (vd) { + unSetDesktop(vd->x11DesktopNumber()); + } + } + ); + m_windowManagementInterface = w; } diff --git a/abstract_client.h b/abstract_client.h index 3100add698..f8e00ed044 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -84,12 +84,17 @@ class KWIN_EXPORT AbstractClient : public Toplevel Q_PROPERTY(bool active READ isActive NOTIFY activeChanged) /** * The desktop this Client is on. If the Client is on all desktops the property has value -1. + * This is a legacy property, use x11DesktopIds instead **/ Q_PROPERTY(int desktop READ desktop WRITE setDesktop NOTIFY desktopChanged) /** * Whether the Client is on all desktops. That is desktop is -1. **/ Q_PROPERTY(bool onAllDesktops READ isOnAllDesktops WRITE setOnAllDesktops NOTIFY desktopChanged) + /** + * The x11 ids for all desktops this client is in. On X11 this list will always have a length of 1 + **/ + Q_PROPERTY(QList x11DesktopIds READ x11DesktopIds NOTIFY x11DesktopIdsChanged) /** * Indicates that the window should not be included on a taskbar. **/ @@ -423,9 +428,18 @@ public: virtual bool performMouseCommand(Options::MouseCommand, const QPoint &globalPos); void setOnAllDesktops(bool set); void setDesktop(int); + Q_INVOKABLE virtual void unSetDesktop(int desktop); int desktop() const override { - return m_desktop; + return m_desktops.isEmpty() ? (int)NET::OnAllDesktops : m_desktops.last()->x11DesktopNumber(); } + virtual QList desktops() const { + return m_desktops; + } + void removeDesktop(VirtualDesktop *desktop) { + m_desktops.removeAll(desktop); + } + QList x11DesktopIds() const; + void setMinimized(bool set); /** * Minimizes this client plus its transients @@ -757,6 +771,7 @@ Q_SIGNALS: void demandsAttentionChanged(); void desktopPresenceChanged(KWin::AbstractClient*, int); // to be forwarded by Workspace void desktopChanged(); + void x11DesktopIdsChanged(); void shadeChanged(); void minimizedChanged(); void clientMinimized(KWin::AbstractClient* client, bool animate); @@ -1107,7 +1122,7 @@ private: bool m_demandsAttention = false; bool m_minimized = false; QTimer *m_autoRaiseTimer = nullptr; - int m_desktop = 0; // 0 means not on any desktop yet + QList m_desktops; QString m_colorScheme; std::shared_ptr m_palette; diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 35b420233e..fff43e9151 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -43,6 +43,7 @@ target_link_libraries( testVirtualDesktops KF5::GlobalAccel KF5::ConfigCore KF5::WindowSystem + KF5::WaylandServer ) add_test(NAME kwin-testVirtualDesktops COMMAND testVirtualDesktops) ecm_mark_as_test(testVirtualDesktops) @@ -336,6 +337,7 @@ target_link_libraries(testScreenEdges KF5::GlobalAccel KF5::Notifications KF5::WindowSystem + KF5::WaylandServer XCB::XCB XCB::RANDR XCB::XFIXES diff --git a/autotests/integration/virtual_desktop_test.cpp b/autotests/integration/virtual_desktop_test.cpp index 71a3ec0e7a..6339b3c72d 100644 --- a/autotests/integration/virtual_desktop_test.cpp +++ b/autotests/integration/virtual_desktop_test.cpp @@ -43,6 +43,10 @@ private Q_SLOTS: void testNetCurrentDesktop(); void testLastDesktopRemoved_data(); void testLastDesktopRemoved(); + void testWindowOnMultipleDesktops_data(); + void testWindowOnMultipleDesktops(); + void testRemoveDesktopWithWindow_data(); + void testRemoveDesktopWithWindow(); }; void VirtualDesktopTest::initTestCase() @@ -157,12 +161,154 @@ void VirtualDesktopTest::testLastDesktopRemoved() QSignalSpy desktopPresenceChangedSpy(client, &ShellClient::desktopPresenceChanged); QVERIFY(desktopPresenceChangedSpy.isValid()); + QCOMPARE(client->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), client->desktops().first()); + // and remove last desktop VirtualDesktopManager::self()->setCount(1); QCOMPARE(VirtualDesktopManager::self()->count(), 1u); // now the client should be moved as well QTRY_COMPARE(desktopPresenceChangedSpy.count(), 1); QCOMPARE(client->desktop(), 1); + + QCOMPARE(client->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), client->desktops().first()); +} + +void VirtualDesktopTest::testWindowOnMultipleDesktops_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; +} + +void VirtualDesktopTest::testWindowOnMultipleDesktops() +{ + // first create two new desktops + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + VirtualDesktopManager::self()->setCount(3); + QCOMPARE(VirtualDesktopManager::self()->count(), 3u); + + // switch to last desktop + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().last()); + QCOMPARE(VirtualDesktopManager::self()->current(), 3u); + + // now create a window on this desktop + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + + QVERIFY(client); + QCOMPARE(client->desktop(), 3u); + QSignalSpy desktopPresenceChangedSpy(client, &ShellClient::desktopPresenceChanged); + QVERIFY(desktopPresenceChangedSpy.isValid()); + + QCOMPARE(client->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), client->desktops().first()); + + //Set the window on desktop 2 as well + client->setDesktop(2u); + QCOMPARE(client->desktops().count(), 2u); + QCOMPARE(VirtualDesktopManager::self()->desktops()[2], client->desktops()[0]); + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], client->desktops()[1]); + QVERIFY(client->isOnDesktop(2)); + QVERIFY(client->isOnDesktop(3)); + + //leave desktop 3 + client->unSetDesktop(3); + QCOMPARE(client->desktops().count(), 1u); + //leave desktop 2 + client->unSetDesktop(2); + QCOMPARE(client->desktops().count(), 0u); + //we should be on all desktops now + QVERIFY(client->isOnAllDesktops()); + //put on desktop 1 + client->setDesktop(1); + QVERIFY(client->isOnDesktop(1)); + QVERIFY(!client->isOnDesktop(2)); + QVERIFY(!client->isOnDesktop(3)); + QCOMPARE(client->desktops().count(), 1u); + //put on desktop 2 + client->setDesktop(2); + QVERIFY(client->isOnDesktop(1)); + QVERIFY(client->isOnDesktop(2)); + QVERIFY(!client->isOnDesktop(3)); + QCOMPARE(client->desktops().count(), 2u); + //put on desktop 3 + client->setDesktop(3); + QVERIFY(client->isOnDesktop(1)); + QVERIFY(client->isOnDesktop(2)); + QVERIFY(client->isOnDesktop(3)); + QVERIFY(client->isOnAllDesktops()); + //when it gets on all desktops, it loses all desktops() + QCOMPARE(client->desktops().count(), 0u); +} + +void VirtualDesktopTest::testRemoveDesktopWithWindow_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; +} + +void VirtualDesktopTest::testRemoveDesktopWithWindow() +{ + // first create two new desktops + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + VirtualDesktopManager::self()->setCount(3); + QCOMPARE(VirtualDesktopManager::self()->count(), 3u); + + // switch to last desktop + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().last()); + QCOMPARE(VirtualDesktopManager::self()->current(), 3u); + + // now create a window on this desktop + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + auto client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + + QVERIFY(client); + QCOMPARE(client->desktop(), 3u); + QSignalSpy desktopPresenceChangedSpy(client, &ShellClient::desktopPresenceChanged); + QVERIFY(desktopPresenceChangedSpy.isValid()); + + QCOMPARE(client->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), client->desktops().first()); + + //Set the window on desktop 2 as well + client->setDesktop(2u); + QCOMPARE(client->desktops().count(), 2u); + QCOMPARE(VirtualDesktopManager::self()->desktops()[2], client->desktops()[0]); + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], client->desktops()[1]); + QVERIFY(client->isOnDesktop(2)); + QVERIFY(client->isOnDesktop(3)); + + //remove desktop 3 + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(client->desktops().count(), 1u); + //window is only on desktop 2 + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], client->desktops()[0]); + + //Again 3 desktops + VirtualDesktopManager::self()->setCount(3); + //move window to be only on desktop 3 + client->setDesktop(3); + client->unSetDesktop(2); + QCOMPARE(client->desktops().count(), 1u); + //window is only on desktop 3 + QCOMPARE(VirtualDesktopManager::self()->desktops()[2], client->desktops()[0]); + + //remove desktop 3 + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(client->desktops().count(), 1u); + //window is only on desktop 2 + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], client->desktops()[0]); } WAYLANDTEST_MAIN(VirtualDesktopTest) diff --git a/autotests/test_virtual_desktops.cpp b/autotests/test_virtual_desktops.cpp index feb6b85eca..52370adefe 100644 --- a/autotests/test_virtual_desktops.cpp +++ b/autotests/test_virtual_desktops.cpp @@ -132,8 +132,10 @@ void TestVirtualDesktops::count() vds->setCount(s_countInitValue); QSignalSpy spy(vds, SIGNAL(countChanged(uint,uint))); - QSignalSpy desktopsRemoved(vds, SIGNAL(desktopsRemoved(uint))); + QSignalSpy desktopsRemoved(vds, SIGNAL(desktopRemoved(KWin::VirtualDesktop *))); + auto vdToRemove = vds->desktops().last(); + QFETCH(uint, request); QFETCH(uint, result); QFETCH(bool, signal); @@ -153,8 +155,7 @@ void TestVirtualDesktops::count() if (!desktopsRemoved.isEmpty()) { QList arguments = desktopsRemoved.takeFirst(); QCOMPARE(arguments.count(), 1); - QCOMPARE(arguments.at(0).type(), QVariant::UInt); - QCOMPARE(arguments.at(0).toUInt(), s_countInitValue); + QCOMPARE(arguments.at(0).value(), vdToRemove); } } diff --git a/autotests/test_window_paint_data.cpp b/autotests/test_window_paint_data.cpp index 8b8c9f793b..5ae193480d 100644 --- a/autotests/test_window_paint_data.cpp +++ b/autotests/test_window_paint_data.cpp @@ -19,6 +19,7 @@ along with this program. If not, see . *********************************************************************/ #include +#include "../virtualdesktops.h" #include #include @@ -72,6 +73,7 @@ public: virtual void setData(int role, const QVariant &data); virtual void referencePreviousWindowPixmap() {} virtual void unreferencePreviousWindowPixmap() {} + QList desktops() const { return {};} }; MockEffectWindow::MockEffectWindow(QObject *parent) diff --git a/dbusinterface.cpp b/dbusinterface.cpp index 9bfd9950a8..1a4da99a63 100644 --- a/dbusinterface.cpp +++ b/dbusinterface.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . // own #include "dbusinterface.h" #include "compositingadaptor.h" +#include "virtualdesktopmanageradaptor.h" // kwin #include "abstract_client.h" @@ -315,4 +316,180 @@ QStringList CompositorDBusInterface::supportedOpenGLPlatformInterfaces() const return interfaces; } + + + +VirtualDesktopManagerDBusInterface::VirtualDesktopManagerDBusInterface(VirtualDesktopManager *parent) + : QObject(parent) + , m_manager(parent) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + new VirtualDesktopManagerAdaptor(this); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/VirtualDesktopManager"), + QStringLiteral("org.kde.KWin.VirtualDesktopManager"), + this + ); + + connect(m_manager, &VirtualDesktopManager::currentChanged, this, + [this](uint previousDesktop, uint newDesktop) { + Q_UNUSED(previousDesktop); + Q_UNUSED(newDesktop); + emit currentChanged(m_manager->currentDesktop()->id()); + } + ); + + connect(m_manager, &VirtualDesktopManager::countChanged, this, + [this](uint previousCount, uint newCount) { + Q_UNUSED(previousCount); + emit countChanged(newCount); + emit desktopsChanged(desktops()); + } + ); + + connect(m_manager, &VirtualDesktopManager::navigationWrappingAroundChanged, this, + [this]() { + emit navigationWrappingAroundChanged(isNavigationWrappingAround()); + } + ); + + connect(m_manager, &VirtualDesktopManager::rowsChanged, this, &VirtualDesktopManagerDBusInterface::rowsChanged); + + for (auto *vd : m_manager->desktops()) { + connect(vd, &VirtualDesktop::x11DesktopNumberChanged, this, + [this, vd]() { + DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; + emit desktopDataChanged(vd->id(), data); + emit desktopsChanged(desktops()); + } + ); + connect(vd, &VirtualDesktop::nameChanged, this, + [this, vd]() { + DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; + emit desktopDataChanged(vd->id(), data); + emit desktopsChanged(desktops()); + } + ); + } + connect(m_manager, &VirtualDesktopManager::desktopCreated, this, + [this](VirtualDesktop *vd) { + connect(vd, &VirtualDesktop::x11DesktopNumberChanged, this, + [this, vd]() { + DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; + emit desktopDataChanged(vd->id(), data); + emit desktopsChanged(desktops()); + } + ); + connect(vd, &VirtualDesktop::nameChanged, this, + [this, vd]() { + DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; + emit desktopDataChanged(vd->id(), data); + emit desktopsChanged(desktops()); + } + ); + DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; + emit desktopCreated(vd->id(), data); + emit desktopsChanged(desktops()); + } + ); + connect(m_manager, &VirtualDesktopManager::desktopRemoved, this, + [this](VirtualDesktop *vd) { + emit desktopRemoved(vd->id()); + emit desktopsChanged(desktops()); + } + ); +} + +uint VirtualDesktopManagerDBusInterface::count() const +{ + return m_manager->count(); +} + +void VirtualDesktopManagerDBusInterface::setRows(uint rows) +{ + if (static_cast(m_manager->grid().height()) == rows) { + return; + } + + m_manager->setRows(rows); + m_manager->save(); +} + +uint VirtualDesktopManagerDBusInterface::rows() const +{ + return m_manager->rows(); +} + +void VirtualDesktopManagerDBusInterface::setCurrent(const QString &id) +{ + if (m_manager->currentDesktop()->id() == id) { + return; + } + + auto *vd = m_manager->desktopForId(id.toUtf8()); + if (vd) { + m_manager->setCurrent(vd); + } +} + +QString VirtualDesktopManagerDBusInterface::current() const +{ + return m_manager->currentDesktop()->id(); +} + +void VirtualDesktopManagerDBusInterface::setNavigationWrappingAround(bool wraps) +{ + if (m_manager->isNavigationWrappingAround() == wraps) { + return; + } + + m_manager->setNavigationWrappingAround(wraps); +} + +bool VirtualDesktopManagerDBusInterface::isNavigationWrappingAround() const +{ + return m_manager->isNavigationWrappingAround(); +} + +DBusDesktopDataVector VirtualDesktopManagerDBusInterface::desktops() const +{ + const auto desks = m_manager->desktops(); + DBusDesktopDataVector desktopVect; + desktopVect.reserve(m_manager->count()); + + std::transform(desks.constBegin(), desks.constEnd(), + std::back_inserter(desktopVect), + [] (const VirtualDesktop *vd) { + return DBusDesktopDataStruct{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; + } + ); + + return desktopVect; +} + +void VirtualDesktopManagerDBusInterface::createDesktop(uint position, const QString &name) +{ + m_manager->createVirtualDesktop(position + 1, name); +} + +void VirtualDesktopManagerDBusInterface::setDesktopName(const QString &id, const QString &name) +{ + VirtualDesktop *vd = m_manager->desktopForId(id.toUtf8()); + if (!vd) { + return; + } + if (vd->name() == name) { + return; + } + + vd->setName(name); + m_manager->save(); +} + +void VirtualDesktopManagerDBusInterface::removeDesktop(const QString &id) +{ + m_manager->removeVirtualDesktop(id.toUtf8()); +} + } // namespace diff --git a/dbusinterface.h b/dbusinterface.h index dd23d7c3cf..e07019ba32 100644 --- a/dbusinterface.h +++ b/dbusinterface.h @@ -24,10 +24,13 @@ along with this program. If not, see . #include #include +#include "virtualdesktopsdbustypes.h" + namespace KWin { class Compositor; +class VirtualDesktopManager; /** * @brief This class is a wrapper for the org.kde.KWin D-Bus interface. @@ -169,6 +172,76 @@ private: Compositor *m_compositor; }; +//TODO: disable all of this in case of kiosk? + +class VirtualDesktopManagerDBusInterface : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.VirtualDesktopManager") + + /** + * The number of virtual desktops currently available. + * The ids of the virtual desktops are in the range [1, VirtualDesktopManager::maximum()]. + **/ + Q_PROPERTY(uint count READ count NOTIFY countChanged) + /** + * The number of rows the virtual desktops will be laid out in + **/ + Q_PROPERTY(uint rows READ rows WRITE setRows NOTIFY rowsChanged) + /** + * The id of the virtual desktop which is currently in use. + **/ + Q_PROPERTY(QString current READ current WRITE setCurrent NOTIFY currentChanged) + /** + * Whether navigation in the desktop layout wraps around at the borders. + **/ + Q_PROPERTY(bool navigationWrappingAround READ isNavigationWrappingAround WRITE setNavigationWrappingAround NOTIFY navigationWrappingAroundChanged) + + /** + * list of key/value pairs which every one of them is representing a desktop + */ + Q_PROPERTY(KWin::DBusDesktopDataVector desktops READ desktops NOTIFY desktopsChanged); + +public: + VirtualDesktopManagerDBusInterface(VirtualDesktopManager *parent); + ~VirtualDesktopManagerDBusInterface() = default; + + uint count() const; + + void setRows(uint rows); + uint rows() const; + + void setCurrent(const QString &id); + QString current() const; + + void setNavigationWrappingAround(bool wraps); + bool isNavigationWrappingAround() const; + + KWin::DBusDesktopDataVector desktops() const; + +Q_SIGNALS: + void countChanged(uint count); + void rowsChanged(uint rows); + void currentChanged(const QString &id); + void navigationWrappingAroundChanged(bool wraps); + void desktopsChanged(KWin::DBusDesktopDataVector); + void desktopDataChanged(const QString &id, KWin::DBusDesktopDataStruct); + void desktopCreated(const QString &id, KWin::DBusDesktopDataStruct); + void desktopRemoved(const QString &id); + +public Q_SLOTS: + /** + * Create a desktop with a new name at a given position + * note: the position starts from 1 + */ + void createDesktop(uint position, const QString &name); + void setDesktopName(const QString &id, const QString &name); + void removeDesktop(const QString &id); + +private: + VirtualDesktopManager *m_manager; +}; + } // namespace #endif // KWIN_DBUS_INTERFACE_H diff --git a/deleted.cpp b/deleted.cpp index 6541376a28..fe2e4956e3 100644 --- a/deleted.cpp +++ b/deleted.cpp @@ -94,6 +94,7 @@ void Deleted::copyToDeleted(Toplevel* c) assert(dynamic_cast< Deleted* >(c) == NULL); Toplevel::copyToDeleted(c); desk = c->desktop(); + m_desktops = c->desktops(); activityList = c->activities(); contentsRect = QRect(c->clientPos(), c->clientSize()); m_contentPos = c->clientContentPos(); @@ -177,6 +178,11 @@ QStringList Deleted::activities() const return activityList; } +QList Deleted::desktops() const +{ + return m_desktops; +} + QPoint Deleted::clientPos() const { return contentsRect.topLeft(); diff --git a/deleted.h b/deleted.h index ae8daa40a7..778b716660 100644 --- a/deleted.h +++ b/deleted.h @@ -52,6 +52,7 @@ public: void discard(); virtual int desktop() const; virtual QStringList activities() const; + virtual QList desktops() const; virtual QPoint clientPos() const; virtual QSize clientSize() const; QPoint clientContentPos() const override { @@ -195,6 +196,7 @@ private: QPoint m_contentPos; QRect transparent_rect; xcb_window_t m_frame; + QList m_desktops; bool no_border; QRect decoration_left; diff --git a/effects.cpp b/effects.cpp index 5b573be3fb..5e219941b4 100644 --- a/effects.cpp +++ b/effects.cpp @@ -911,8 +911,9 @@ void EffectsHandlerImpl::moveWindow(EffectWindow* w, const QPoint& pos, bool sna void EffectsHandlerImpl::windowToDesktop(EffectWindow* w, int desktop) { AbstractClient* cl = dynamic_cast< AbstractClient* >(static_cast(w)->window()); - if (cl && !cl->isDesktop() && !cl->isDock()) + if (cl && !cl->isDesktop() && !cl->isDock()) { Workspace::self()->sendClientToDesktop(cl, desktop, true); + } } void EffectsHandlerImpl::windowToScreen(EffectWindow* w, int screen) diff --git a/effects/desktopgrid/desktopgrid.cpp b/effects/desktopgrid/desktopgrid.cpp index 7cb5612108..7b2c9dd1ed 100644 --- a/effects/desktopgrid/desktopgrid.cpp +++ b/effects/desktopgrid/desktopgrid.cpp @@ -42,6 +42,8 @@ along with this program. If not, see . #include #include +#include + namespace KWin { @@ -52,6 +54,7 @@ DesktopGridEffect::DesktopGridEffect() , timeline() , keyboardGrab(false) , wasWindowMove(false) + , wasWindowCopy(false) , wasDesktopMove(false) , isValidMove(false) , windowMove(NULL) @@ -297,7 +300,9 @@ void DesktopGridEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data void DesktopGridEffect::paintWindow(EffectWindow* w, int mask, QRegion region, WindowPaintData& data) { if (timeline.currentValue() != 0 || (isUsingPresentWindows() && isMotionManagerMovingWindows())) { - if (isUsingPresentWindows() && w == windowMove && wasWindowMove) { + if (isUsingPresentWindows() && w == windowMove && wasWindowMove && + ((!wasWindowCopy && sourceDesktop == paintingDesktop) || + (sourceDesktop != highlightedDesktop && highlightedDesktop == paintingDesktop))) { return; // will be painted on top of all other windows } foreach (DesktopButtonsView *view, m_desktopButtonsViews) { @@ -487,7 +492,6 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) if (!wasWindowMove) { // Activate on move if (isUsingPresentWindows()) { foreach (const int i, desktopList(windowMove)) { - const int sourceDesktop = windowMove->isOnAllDesktops() ? d : windowMove->desktop(); WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()]; if ((i + 1) == sourceDesktop) { const QRectF transformedGeo = manager.transformedGeometry(windowMove); @@ -513,9 +517,18 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) effects->moveWindow(windowMove, unscalePos(me->pos(), NULL) + windowMoveDiff, true, 1.0 / scale[screen]); } if (wasWindowMove) { - effects->defineCursor(Qt::ClosedHandCursor); + if (!effects->waylandDisplay() || (me->modifiers() & Qt::ControlModifier)) { + wasWindowCopy = true; + effects->defineCursor(Qt::DragCopyCursor); + } else { + wasWindowCopy = false; + effects->defineCursor(Qt::ClosedHandCursor); + } if (d != highlightedDesktop) { effects->windowToDesktop(windowMove, d); // Not true all desktop move + if (highlightedDesktop != sourceDesktop || !wasWindowCopy) { + effects->removeWindowFromDesktop(windowMove, highlightedDesktop); + } const int screen = effects->screenNumber(me->pos()); if (screen != windowMove->screen()) effects->windowToScreen(windowMove, screen); @@ -549,6 +562,8 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) continue; foreach (EffectWindow *w, stack[i]) { effects->windowToDesktop(w, desks[i+1]); + effects->removeWindowFromDesktop(w, desks[i]); + if (isUsingPresentWindows()) { m_managers[(desks[i]-1)*(effects->numScreens()) + w->screen()].unmanage(w); m_managers[(desks[i+1]-1)*(effects->numScreens()) + w->screen()].manage(w); @@ -572,12 +587,15 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) if (me->buttons() == Qt::LeftButton) { isValidMove = true; dragStartPos = me->pos(); - bool isDesktop = (me->modifiers() & Qt::ControlModifier); + sourceDesktop = posToDesktop(me->pos()); + bool isDesktop = (me->modifiers() & Qt::ShiftModifier); EffectWindow* w = isDesktop ? NULL : windowAt(me->pos()); if (w != NULL) isDesktop = w->isDesktop(); if (isDesktop) m_originalMovingDesktop = posToDesktop(me->pos()); + else + m_originalMovingDesktop = 0; if (w != NULL && !w->isDesktop() && (w->isMovable() || w->isMovableAcrossScreens() || isUsingPresentWindows())) { // Prepare it for moving windowMoveDiff = w->pos() - unscalePos(me->pos(), NULL); @@ -590,12 +608,10 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) w = nullptr; } if (w != NULL) { - int desktop = 0; + const int desktop = posToDesktop(me->pos()); if (w->isOnAllDesktops()) { - desktop = posToDesktop(me->pos()); effects->windowToDesktop(w, desktop); } else { - desktop = w->desktop(); effects->windowToDesktop(w, NET::OnAllDesktops); } const bool isOnAllDesktops = w->isOnAllDesktops(); @@ -630,7 +646,7 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) } if (windowMove) { if (wasWindowMove && isUsingPresentWindows()) { - const int targetDesktop = windowMove->isOnAllDesktops() ? posToDesktop(cursorPos()) : windowMove->desktop(); + const int targetDesktop = posToDesktop(cursorPos()); foreach (const int i, desktopList(windowMove)) { WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()]; manager.manage(windowMove); @@ -648,6 +664,7 @@ void DesktopGridEffect::windowInputMouseEvent(QEvent* e) windowMove = NULL; } wasWindowMove = false; + wasWindowCopy = false; wasDesktopMove = false; } } @@ -1360,7 +1377,7 @@ void DesktopGridEffect::desktopsRemoved(int old) // and repaint effects->addRepaintFull(); } - +//TODO: kill this function? or at least keep a consistent numeration with desktops starting from 1 QVector DesktopGridEffect::desktopList(const EffectWindow *w) const { if (w->isOnAllDesktops()) { @@ -1373,16 +1390,13 @@ QVector DesktopGridEffect::desktopList(const EffectWindow *w) const return allDesktops; } - if (w->desktop() > effects->numberOfDesktops() || w->desktop() < 1) { // sic! desktops are [1,n] - static QVector emptyVector; - emptyVector.resize(0); - return emptyVector; + QVector desks; + desks.resize(w->desktops().count()); + int i = 0; + for (const int desk : w->desktops()) { + desks[i++] = desk-1; } - - static QVector singleDesktop; - singleDesktop.resize(1); - singleDesktop[0] = w->desktop() - 1; - return singleDesktop; + return desks; } bool DesktopGridEffect::isActive() const diff --git a/effects/desktopgrid/desktopgrid.h b/effects/desktopgrid/desktopgrid.h index 3c95eee0c2..62c4d7166b 100644 --- a/effects/desktopgrid/desktopgrid.h +++ b/effects/desktopgrid/desktopgrid.h @@ -151,9 +151,10 @@ private: QTimeLine timeline; int paintingDesktop; int highlightedDesktop; + int sourceDesktop; int m_originalMovingDesktop; bool keyboardGrab; - bool wasWindowMove, wasDesktopMove, isValidMove; + bool wasWindowMove, wasWindowCopy, wasDesktopMove, isValidMove; EffectWindow* windowMove; QPoint windowMoveDiff; QPoint dragStartPos; diff --git a/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp index 197136b8e4..92c7498da4 100644 --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -762,6 +762,13 @@ bool EffectsHandler::isOpenGLCompositing() const return compositing_type & OpenGLCompositing; } +void EffectsHandler::removeWindowFromDesktop(KWin::EffectWindow* w, int desktop) +{ + if (w->parent() && !w->isDesktop() && !w->isDock()) { + QMetaObject::invokeMethod(w->parent(), "unSetDesktop", Q_ARG(int, desktop)); + } +} + EffectsHandler* effects = nullptr; @@ -826,7 +833,9 @@ WINDOW_HELPER(int, screen, "screen") WINDOW_HELPER(QRect, geometry, "geometry") WINDOW_HELPER(QRect, expandedGeometry, "visibleRect") WINDOW_HELPER(QRect, rect, "rect") +#ifndef KWIN_NO_DEPRECATED WINDOW_HELPER(int, desktop, "desktop") +#endif WINDOW_HELPER(bool, isDesktop, "desktopWindow") WINDOW_HELPER(bool, isDock, "dock") WINDOW_HELPER(bool, isToolbar, "toolbar") @@ -849,6 +858,11 @@ WINDOW_HELPER(QStringList, activities, "activities") WINDOW_HELPER(bool, skipsCloseAnimation, "skipsCloseAnimation") WINDOW_HELPER(KWayland::Server::SurfaceInterface *, surface, "surface") +QList EffectWindow::desktops() const +{ + return parent()->property("x11DesktopIds").value >(); +} + QString EffectWindow::windowClass() const { return parent()->property("resourceName").toString() + QLatin1Char(' ') + parent()->property("resourceClass").toString(); @@ -974,12 +988,13 @@ bool EffectWindow::isOnCurrentDesktop() const bool EffectWindow::isOnDesktop(int d) const { - return desktop() == d || isOnAllDesktops(); + const QList ds = desktops(); + return ds.isEmpty() || ds.contains(d); } bool EffectWindow::isOnAllDesktops() const { - return desktop() == NET::OnAllDesktops; + return desktops().isEmpty(); } bool EffectWindow::hasDecoration() const diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index c3ce5cde0a..d28f70ca47 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -944,6 +944,10 @@ public: virtual KWin::EffectWindow* activeWindow() const = 0 ; Q_SCRIPTABLE virtual void moveWindow(KWin::EffectWindow* w, const QPoint& pos, bool snap = false, double snapAdjust = 1.0) = 0; Q_SCRIPTABLE virtual void windowToDesktop(KWin::EffectWindow* w, int desktop) = 0; + /** + * Removes a window from a desktop on wayland, no-op on X11 + */ + Q_SCRIPTABLE void removeWindowFromDesktop(KWin::EffectWindow* w, int desktop); Q_SCRIPTABLE virtual void windowToScreen(KWin::EffectWindow* w, int screen) = 0; virtual void setShowingDesktop(bool showing) = 0; @@ -2071,7 +2075,23 @@ public: Q_SCRIPTABLE bool isOnDesktop(int d) const; bool isOnCurrentDesktop() const; bool isOnAllDesktops() const; - int desktop() const; // prefer isOnXXX() + /** + * The desktop this window is in. This mkaes sense only on X11 + * where desktops are mutually exclusive, on Wayland it's the last + * desktop the window has been added to. + * use desktops() instead. + * @see desktops() + * @deprecated + */ +#ifndef KWIN_NO_DEPRECATED + int KWIN_DEPRECATED desktop() const; // prefer isOnXXX() +#endif + /** + * All the desktops by number that the window is in. On X11 this list will always have + * a length of 1, on Wayland can be any subset. + * If the list is empty it means the window is on all desktops + */ + QList desktops() const; int x() const; int y() const; diff --git a/org.kde.KWin.VirtualDesktopManager.xml b/org.kde.KWin.VirtualDesktopManager.xml new file mode 100644 index 0000000000..3283764f0f --- /dev/null +++ b/org.kde.KWin.VirtualDesktopManager.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toplevel.h b/toplevel.h index be8542d3d2..1588f03f8c 100644 --- a/toplevel.h +++ b/toplevel.h @@ -290,6 +290,7 @@ public: * isOnDesktop() instead. */ virtual int desktop() const = 0; + virtual QList desktops() const = 0; virtual QStringList activities() const = 0; bool isOnDesktop(int d) const; bool isOnActivity(const QString &activity) const; @@ -790,7 +791,12 @@ const EffectWindowImpl* Toplevel::effectWindow() const inline bool Toplevel::isOnAllDesktops() const { - return desktop() == NET::OnAllDesktops; + return kwinApp()->operationMode() == Application::OperationModeWaylandOnly || + kwinApp()->operationMode() == Application::OperationModeXwayland + //Wayland + ? desktops().isEmpty() + //X11 + : desktop() == NET::OnAllDesktops; } inline bool Toplevel::isOnAllActivities() const @@ -800,7 +806,11 @@ inline bool Toplevel::isOnAllActivities() const inline bool Toplevel::isOnDesktop(int d) const { - return desktop() == d || /*desk == 0 ||*/ isOnAllDesktops(); + return (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || + kwinApp()->operationMode() == Application::OperationModeXwayland + ? desktops().contains(VirtualDesktopManager::self()->desktopForX11Id(d)) + : desktop() == d + ) || isOnAllDesktops(); } inline bool Toplevel::isOnActivity(const QString &activity) const diff --git a/unmanaged.cpp b/unmanaged.cpp index c10a674a8b..90cc1fb24c 100644 --- a/unmanaged.cpp +++ b/unmanaged.cpp @@ -123,6 +123,11 @@ QStringList Unmanaged::activities() const return QStringList(); } +QList Unmanaged::desktops() const +{ + return QList(); +} + QPoint Unmanaged::clientPos() const { return QPoint(0, 0); // unmanaged windows don't have decorations diff --git a/unmanaged.h b/unmanaged.h index b7dc1ec62d..a24679dec0 100644 --- a/unmanaged.h +++ b/unmanaged.h @@ -39,6 +39,7 @@ public: static void deleteUnmanaged(Unmanaged* c); virtual int desktop() const; virtual QStringList activities() const; + virtual QList desktops() const override; virtual QPoint clientPos() const; virtual QSize clientSize() const; virtual QRect transparentRect() const; diff --git a/useractions.cpp b/useractions.cpp index 1d10f7a228..82b51edff9 100644 --- a/useractions.cpp +++ b/useractions.cpp @@ -402,6 +402,7 @@ void UserActionsMenu::discard() delete m_menu; m_menu = NULL; m_desktopMenu = NULL; + m_multipleDesktopsMenu = nullptr; m_screenMenu = NULL; m_activityMenu = NULL; m_switchToTabMenu = NULL; @@ -417,6 +418,8 @@ void UserActionsMenu::menuAboutToShow() if (VirtualDesktopManager::self()->count() == 1) { delete m_desktopMenu; m_desktopMenu = 0; + delete m_multipleDesktopsMenu; + m_multipleDesktopsMenu = nullptr; } else { initDesktopPopup(); } @@ -604,17 +607,34 @@ void UserActionsMenu::initTabbingPopups() void UserActionsMenu::initDesktopPopup() { - if (m_desktopMenu) - return; + if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || + kwinApp()->operationMode() == Application::OperationModeXwayland) { + if (m_multipleDesktopsMenu) { + return; + } - m_desktopMenu = new QMenu(m_menu); - connect(m_desktopMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToDesktop); - connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow); + m_multipleDesktopsMenu = new QMenu(m_menu); + connect(m_multipleDesktopsMenu, &QMenu::triggered, this, &UserActionsMenu::slotToggleOnVirtualDesktop); + connect(m_multipleDesktopsMenu, &QMenu::aboutToShow, this, &UserActionsMenu::multipleDesktopsPopupAboutToShow); - QAction *action = m_desktopMenu->menuAction(); - // set it as the first item - m_menu->insertAction(m_minimizeOperation, action); - action->setText(i18n("Move To &Desktop")); + QAction *action = m_multipleDesktopsMenu->menuAction(); + // set it as the first item + m_menu->insertAction(m_minimizeOperation, action); + action->setText(i18n("&Desktops")); + + } else { + if (m_desktopMenu) + return; + + m_desktopMenu = new QMenu(m_menu); + connect(m_desktopMenu, &QMenu::triggered, this, &UserActionsMenu::slotSendToDesktop); + connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow); + + QAction *action = m_desktopMenu->menuAction(); + // set it as the first item + m_menu->insertAction(m_minimizeOperation, action); + action->setText(i18n("Move To &Desktop")); + } } void UserActionsMenu::initScreenPopup() @@ -667,6 +687,7 @@ void UserActionsMenu::desktopPopupAboutToShow() m_desktopMenu->addSeparator(); const uint BASE = 10; + for (uint i = 1; i <= vds->count(); ++i) { QString basic_name(QStringLiteral("%1 %2")); if (i < BASE) { @@ -690,6 +711,58 @@ void UserActionsMenu::desktopPopupAboutToShow() action->setEnabled(false); } +void UserActionsMenu::multipleDesktopsPopupAboutToShow() +{ + if (!m_multipleDesktopsMenu) + return; + const VirtualDesktopManager *vds = VirtualDesktopManager::self(); + + m_multipleDesktopsMenu->clear(); + m_multipleDesktopsMenu->setPalette(m_client.data()->palette()); + QAction *action = m_multipleDesktopsMenu->addAction(i18n("&All Desktops")); + action->setData(0); + action->setCheckable(true); + QActionGroup *allDesktopsGroup = new QActionGroup(m_multipleDesktopsMenu); + allDesktopsGroup->addAction(action); + + if (!m_client.isNull() && m_client.data()->isOnAllDesktops()) { + action->setChecked(true); + } + m_multipleDesktopsMenu->addSeparator(); + + + const uint BASE = 10; + + for (uint i = 1; i <= vds->count(); ++i) { + QString basic_name(QStringLiteral("%1 %2")); + if (i < BASE) { + basic_name.prepend(QLatin1Char('&')); + } + QWidgetAction *action = new QWidgetAction(m_multipleDesktopsMenu); + QCheckBox *box = new QCheckBox(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&"))), m_multipleDesktopsMenu); + action->setDefaultWidget(box); + + box->setBackgroundRole(m_multipleDesktopsMenu->backgroundRole()); + box->setForegroundRole(m_multipleDesktopsMenu->foregroundRole()); + box->setPalette(m_multipleDesktopsMenu->palette()); + connect(box, &QCheckBox::clicked, action, &QAction::triggered); + m_multipleDesktopsMenu->addAction(action); + action->setData(i); + + if (!m_client.isNull() && + !m_client.data()->isOnAllDesktops() && m_client.data()->isOnDesktop(i)) { + box->setChecked(true); + } + } + + m_multipleDesktopsMenu->addSeparator(); + action = m_multipleDesktopsMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop")); + action->setData(vds->count() + 1); + + if (vds->count() >= vds->maximum()) + action->setEnabled(false); +} + void UserActionsMenu::screenPopupAboutToShow() { if (!m_screenMenu) { @@ -816,6 +889,35 @@ void UserActionsMenu::slotSendToDesktop(QAction *action) ws->sendClientToDesktop(m_client.data(), desk, false); } +void UserActionsMenu::slotToggleOnVirtualDesktop(QAction *action) +{ + bool ok = false; + uint desk = action->data().toUInt(&ok); + if (!ok) { + return; + } + if (m_client.isNull()) { + return; + } + + Workspace *ws = Workspace::self(); + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + if (desk == 0) { + // the 'on_all_desktops' menu entry + m_client.data()->setOnAllDesktops(!m_client.data()->isOnAllDesktops()); + return; + } else if (desk > vds->count()) { + vds->setCount(desk); + } + + VirtualDesktop *virtualDesktop = VirtualDesktopManager::self()->desktopForX11Id(desk); + if (m_client.data()->desktops().contains(virtualDesktop)) { + m_client.data()->unSetDesktop(desk); + } else { + ws->sendClientToDesktop(m_client.data(), desk, false); + } +} + void UserActionsMenu::slotSendToScreen(QAction *action) { const int screen = action->data().toInt(); diff --git a/useractions.h b/useractions.h index 005b2ccfa3..e032fbf265 100644 --- a/useractions.h +++ b/useractions.h @@ -144,6 +144,11 @@ private Q_SLOTS: * the Client. **/ void desktopPopupAboutToShow(); + /** + * Adjusts the multipleDesktopsMenu popup to the current values and the location of + * the Client, Wayland only. + **/ + void multipleDesktopsPopupAboutToShow(); /** * Adjusts the screen popup to the current values and the location of * the Client. @@ -160,6 +165,12 @@ private Q_SLOTS: * @param action Invoked Action containing the Desktop as data element **/ void slotSendToDesktop(QAction *action); + /** + * Toggle whether the Client is on a desktop (Wayland only) + * + * @param action Invoked Action containing the Desktop as data element + **/ + void slotToggleOnVirtualDesktop(QAction *action); /** * Sends the Client to screen \a screen * @@ -217,6 +228,10 @@ private: * The move to desktop sub menu. **/ QMenu* m_desktopMenu; + /** + * The move to desktop sub menu, with the Wayland protocol. + **/ + QMenu* m_multipleDesktopsMenu; /** * The move to screen sub menu. **/ diff --git a/virtualdesktops.cpp b/virtualdesktops.cpp index 6b52d5ec47..3332b2871b 100644 --- a/virtualdesktops.cpp +++ b/virtualdesktops.cpp @@ -25,11 +25,14 @@ along with this program. If not, see . #include #include #include + +#include // Qt #include +#include #include - +#include namespace KWin { extern int screen_number; @@ -44,6 +47,81 @@ VirtualDesktop::~VirtualDesktop() emit aboutToBeDestroyed(); } +void VirtualDesktopManager::setVirtualDesktopManagement(KWayland::Server::PlasmaVirtualDesktopManagementInterface *management) +{ + using namespace KWayland::Server; + Q_ASSERT(!m_virtualDesktopManagement); + m_virtualDesktopManagement = management; + + connect(this, &VirtualDesktopManager::desktopCreated, this, + [this](VirtualDesktop *desktop) { + using namespace KWayland::Server; + PlasmaVirtualDesktopInterface *pvd = m_virtualDesktopManagement->createDesktop(desktop->id(), desktop->x11DesktopNumber() - 1); + pvd->setName(desktop->name()); + pvd->sendDone(); + connect(desktop, &VirtualDesktop::nameChanged, this, + [this, desktop, pvd]() { + pvd->setName(desktop->name()); + } + ); + } + ); + + //handle removed: from VirtualDesktopManager to the wayland interface + connect(this, &VirtualDesktopManager::desktopRemoved, this, + [this](VirtualDesktop *desktop) { + m_virtualDesktopManagement->removeDesktop(desktop->id()); + } + ); + + //create a new desktop when the client asks to + connect (m_virtualDesktopManagement, &PlasmaVirtualDesktopManagementInterface::desktopCreateRequested, this, + [this](const QString &name, quint32 position) { + VirtualDesktop *vd = createVirtualDesktop(position); + if (vd) { + vd->setName(name); + } + } + ); + + //remove when the client asks to + connect (m_virtualDesktopManagement, &PlasmaVirtualDesktopManagementInterface::desktopRemoveRequested, this, + [this](const QString &id) { + //here there can be some nice kauthorized check? + //remove only from VirtualDesktopManager, the other connections will remove it from m_virtualDesktopManagement as well + removeVirtualDesktop(id.toUtf8()); + } + ); + + for (quint32 i = 1; i <= count(); ++i) { + VirtualDesktop *internalDesktop = desktopForX11Id(i); + PlasmaVirtualDesktopInterface *desktop = m_virtualDesktopManagement->createDesktop(internalDesktop->id()); + + desktop->setName(desktop->name()); + desktop->sendDone(); + + connect(desktop, &PlasmaVirtualDesktopInterface::activateRequested, this, + [this, desktop] () { + setCurrent(desktopForId(desktop->id().toUtf8())); + } + ); + } + //Now we are sure all ids are there + save(); + + connect(this, &VirtualDesktopManager::currentChanged, this, + [this]() { + for (auto *deskInt : m_virtualDesktopManagement->desktops()) { + if (deskInt->id() == currentDesktop()->id()) { + deskInt->setActive(true); + } else { + deskInt->setActive(false); + } + } + } + ); +} + void VirtualDesktop::setId(const QByteArray &id) { Q_ASSERT(m_id.isEmpty()); @@ -52,8 +130,16 @@ void VirtualDesktop::setId(const QByteArray &id) void VirtualDesktop::setX11DesktopNumber(uint number) { - Q_ASSERT(m_x11DesktopNumber == 0); + //x11DesktopNumber can be changed now + if (static_cast(m_x11DesktopNumber) == number) { + return; + } + m_x11DesktopNumber = number; + + if (m_x11DesktopNumber != 0) { + emit x11DesktopNumberChanged(); + } } void VirtualDesktop::setName(const QString &name) @@ -68,7 +154,7 @@ void VirtualDesktop::setName(const QString &name) VirtualDesktopGrid::VirtualDesktopGrid() : m_size(1, 2) // Default to tow rows , m_grid(QVector>{QVector{}, QVector{}}) -{ +{ } VirtualDesktopGrid::~VirtualDesktopGrid() = default; @@ -325,6 +411,99 @@ VirtualDesktop *VirtualDesktopManager::desktopForX11Id(uint id) const return m_desktops.at(id - 1); } +VirtualDesktop *VirtualDesktopManager::desktopForId(const QByteArray &id) const +{ + auto desk = std::find_if( + m_desktops.constBegin(), + m_desktops.constEnd(), + [id] (const VirtualDesktop *desk ) { + return desk->id() == id; + } + ); + + if (desk != m_desktops.constEnd()) { + return *desk; + } + + return nullptr; +} + +VirtualDesktop *VirtualDesktopManager::createVirtualDesktop(uint number, const QString &name) +{ + //too many, can't insert new ones + if ((uint)m_desktops.count() == VirtualDesktopManager::maximum()) { + return nullptr; + } + + const uint actualNumber = qBound(0, number, VirtualDesktopManager::maximum()); + auto *vd = new VirtualDesktop(this); + vd->setX11DesktopNumber(actualNumber); + //TODO: depend on Qt 5.11, use toString(QUuid::WithoutBraces) + vd->setId(QUuid::createUuid().toString().toUtf8()); + vd->setName(name); + if (m_rootInfo) { + connect(vd, &VirtualDesktop::nameChanged, this, + [this, vd]() { + if (m_rootInfo) { + m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); + } + } + ); + m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); + } + + //update the id of displaced desktops + for (uint i = actualNumber; i < (uint)m_desktops.count(); ++i) { + m_desktops[i]->setX11DesktopNumber(i + 1); + if (m_rootInfo) { + m_rootInfo->setDesktopName(i + 1, m_desktops[i]->name().toUtf8().data()); + } + } + + m_desktops.insert(actualNumber - 1, vd); + save(); + + updateRootInfo(); + emit desktopCreated(vd); + emit countChanged(m_desktops.count()-1, m_desktops.count()); + return vd; +} + +void VirtualDesktopManager::removeVirtualDesktop(const QByteArray &id) +{ + //don't end up without any desktop + if (m_desktops.count() == 1) { + return; + } + auto desktop = desktopForId(id); + if (!desktop) { + return; + } + + const uint oldCurrent = m_current->x11DesktopNumber(); + const uint i = desktop->x11DesktopNumber() - 1; + m_desktops.remove(i); + + for (uint j = i; j < (uint)m_desktops.count(); ++j) { + m_desktops[j]->setX11DesktopNumber(j + 1); + if (m_rootInfo) { + m_rootInfo->setDesktopName(j + 1, m_desktops[j]->name().toUtf8().data()); + } + } + + const uint newCurrent = qMin(oldCurrent, (uint)m_desktops.count()); + m_current = m_desktops.at(newCurrent - 1); + if (oldCurrent != newCurrent) { + emit currentChanged(oldCurrent, newCurrent); + } + + updateRootInfo(); + emit desktopRemoved(desktop); + emit countChanged(m_desktops.count()+1, m_desktops.count()); + + desktop->deleteLater(); +} + uint VirtualDesktopManager::current() const { return m_current ? m_current->x11DesktopNumber() : 0; @@ -364,33 +543,79 @@ void VirtualDesktopManager::setCount(uint count) // nothing to change return; } + QList newDesktops; const uint oldCount = m_desktops.count(); - const uint oldCurrent = current(); - while (uint(m_desktops.count()) > count) { - delete m_desktops.takeLast(); - } - while (uint(m_desktops.count()) < count) { - auto vd = new VirtualDesktop(this); - vd->setX11DesktopNumber(m_desktops.count() + 1); - m_desktops << vd; - } - if (oldCount > count) { - handleDesktopsRemoved(oldCount, oldCurrent); + //this explicit check makes it more readable + if ((uint)m_desktops.count() > count) { + const auto desktopsToRemove = m_desktops.mid(count); + m_desktops.resize(count); + if (m_current) { + uint oldCurrent = current(); + uint newCurrent = qMin(oldCurrent, count); + m_current = m_desktops.at(newCurrent - 1); + if (oldCurrent != newCurrent) { + emit currentChanged(oldCurrent, newCurrent); + } + } + for (auto desktop : desktopsToRemove) { + emit desktopRemoved(desktop); + desktop->deleteLater(); + } + } else { + while (uint(m_desktops.count()) < count) { + auto vd = new VirtualDesktop(this); + vd->setX11DesktopNumber(m_desktops.count() + 1); + if (!m_isLoading) { + vd->setId(QUuid::createUuid().toString().toUtf8()); + } + m_desktops << vd; + newDesktops << vd; + if (m_rootInfo) { + connect(vd, &VirtualDesktop::nameChanged, this, + [this, vd]() { + if (m_rootInfo) { + m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); + } + } + ); + m_rootInfo->setDesktopName(vd->x11DesktopNumber(), vd->name().toUtf8().data()); + } + } } updateRootInfo(); save(); + for (auto vd : newDesktops) { + emit desktopCreated(vd); + } emit countChanged(oldCount, m_desktops.count()); } -void VirtualDesktopManager::handleDesktopsRemoved(uint previousCount, uint previousCurrent) + +uint VirtualDesktopManager::rows() const { - if (!m_current) { - m_current = m_desktops.last(); - emit currentChanged(previousCurrent, m_current->x11DesktopNumber()); + return grid().height(); +} + +void VirtualDesktopManager::setRows(uint rows) +{ + if (static_cast(grid().height()) == rows || rows == 0 || rows > count()) { + return; } - emit desktopsRemoved(previousCount); + + int columns = count() / rows; + if (count() % rows > 0) { + columns++; + } + if (m_rootInfo) { + m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); + m_rootInfo->activate(); + } + + updateLayout(); + + //rowsChanged will be emitted by setNETDesktopLayout called by updateLayout } void VirtualDesktopManager::updateRootInfo() @@ -437,6 +662,8 @@ void VirtualDesktopManager::load() if (!m_config) { return; } + //FIXME: how to avoid this? + m_isLoading = true; QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); @@ -446,14 +673,31 @@ void VirtualDesktopManager::load() KConfigGroup group(m_config, groupname); const int n = group.readEntry("Number", 1); setCount(n); - if (m_rootInfo) { - for (int i = 1; i <= n; i++) { - QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i)); + + for (int i = 1; i <= n; i++) { + QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i)); + if (m_rootInfo) { m_rootInfo->setDesktopName(i, s.toUtf8().data()); - // TODO: update desktop focus chain, why? -// m_desktopFocusChain.value()[i-1] = i; + } + m_desktops[i-1]->setName(s.toUtf8().data()); + + QString sId = group.readEntry(QStringLiteral("Id_%1").arg(i), QString()); + + //load gets called 2 times, see workspace.cpp line 416 and BUG 385260 + if (m_desktops[i-1]->id().isEmpty()) { + if (sId.isEmpty()) { + sId = QUuid::createUuid().toString(); + } + m_desktops[i-1]->setId(sId.toUtf8().data()); + } else { + Q_ASSERT(sId.isEmpty() || m_desktops[i-1]->id() == sId.toUtf8().data()); } + // TODO: update desktop focus chain, why? +// m_desktopFocusChain.value()[i-1] = i; + } + + if (m_rootInfo) { int rows = group.readEntry("Rows", 2); rows = qBound(1, rows, n); // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused @@ -464,7 +708,9 @@ void VirtualDesktopManager::load() m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); m_rootInfo->activate(); } + s_loadingDesktopSettings = false; + m_isLoading = false; } void VirtualDesktopManager::save() @@ -502,6 +748,7 @@ void VirtualDesktopManager::save() group.deleteEntry(QStringLiteral("Name_%1").arg(i)); } } + group.writeEntry(QStringLiteral("Id_%1").arg(i), m_desktops[i-1]->id()); } // Save to disk @@ -536,6 +783,7 @@ void VirtualDesktopManager::setNETDesktopLayout(Qt::Orientation orientation, uin m_grid.update(QSize(width, height), orientation, m_desktops); // TODO: why is there no call to m_rootInfo->setDesktopLayout? emit layoutChanged(width, height); + emit rowsChanged(height); } void VirtualDesktopManager::initShortcuts() diff --git a/virtualdesktops.h b/virtualdesktops.h index 420bbc965a..0b233ac132 100644 --- a/virtualdesktops.h +++ b/virtualdesktops.h @@ -27,6 +27,7 @@ along with this program. If not, see . #include #include #include + // KDE includes #include #include @@ -35,13 +36,21 @@ class KLocalizedString; class NETRootInfo; class QAction; +namespace KWayland +{ +namespace Server +{ +class PlasmaVirtualDesktopManagementInterface; +} +} + namespace KWin { class KWIN_EXPORT VirtualDesktop : public QObject { Q_OBJECT Q_PROPERTY(QByteArray id READ id CONSTANT) - Q_PROPERTY(uint x11DesktopNumber READ x11DesktopNumber CONSTANT) + Q_PROPERTY(uint x11DesktopNumber READ x11DesktopNumber NOTIFY x11DesktopNumberChanged) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) public: explicit VirtualDesktop(QObject *parent = nullptr); @@ -64,6 +73,7 @@ public: Q_SIGNALS: void nameChanged(); + void x11DesktopNumberChanged(); /** * Emitted just before the desktop gets destroyed. **/ @@ -148,9 +158,13 @@ class KWIN_EXPORT VirtualDesktopManager : public QObject public: virtual ~VirtualDesktopManager(); /** - * @internal + * @internal, for X11 case **/ void setRootInfo(NETRootInfo *info); + /** + * @internal, for Wayland case + **/ + void setVirtualDesktopManagement(KWayland::Server::PlasmaVirtualDesktopManagementInterface *management); /** * @internal **/ @@ -161,6 +175,12 @@ public: * @see countChanged */ uint count() const; + /** + * @returns the number of rows the layout has. + * @see setRows + * @see rowsChanged + */ + uint rows() const; /** * @returns The ID of the current desktop. * @see setCurrent @@ -263,6 +283,30 @@ public: **/ VirtualDesktop *desktopForX11Id(uint id) const; + /** + * @returns The VirtualDesktop for the internal desktop string @p id, if no such VirtualDesktop @c null is returned + **/ + VirtualDesktop *desktopForId(const QByteArray &id) const; + + /** + * Create a new virtual desktop at the requested position. + * The difference with setCount is that setCount always adds new desktops at the end of the chain. The Id is automatically generated. + * @param x11DesktopNumber number for the desktop. The desktop created will have an + * x11DesktopNumber guaranteed to be between 1 and numberOfDesktops(). + * Existing desktops will eventually have their x11DesktopNumber increased. + * @param name The name for the new desktop, if empty the default name will be used. + * @returns the new VirtualDesktop, nullptr if we reached the maximum number of desktops + */ + VirtualDesktop *createVirtualDesktop(uint x11DesktopNumber, const QString &name = QString()); + + /** + * Remove the virtual desktop identified by id, if it exists + * difference with setCount is that is possible to remove an arbitrary desktop, + * not only the last one. + * @param id the string id of the desktop to remove + */ + void removeVirtualDesktop(const QByteArray &id); + /** * Updates the net root info for new number of desktops **/ @@ -283,13 +327,14 @@ public Q_SLOTS: * @link countChanged signal. * * In case the @link current desktop is on a desktop higher than the new count, the current desktop - * is changed to be the new desktop with highest id. In that situation the signal @link desktopsRemoved + * is changed to be the new desktop with highest id. In that situation the signal @link desktopRemoved * is emitted. * @param count The new number of desktops to use * @see count * @see maximum * @see countChanged - * @see desktopsRemoved + * @see desktopCreated + * @see desktopRemoved */ void setCount(uint count); /** @@ -308,6 +353,10 @@ public Q_SLOTS: * @see moveTo **/ bool setCurrent(VirtualDesktop *current); + /** + * Updates the layout to a new number of rows. The number of columns will be calculated accordingly + */ + void setRows(uint rows); /** * Called from within setCount() to ensure the desktop layout is still valid. */ @@ -334,18 +383,27 @@ Q_SIGNALS: * @param newCount The new current number of desktops **/ void countChanged(uint previousCount, uint newCount); + /** - * Signal emitted whenever the number of virtual desktops changes in a way - * that existing desktops are removed. - * - * The signal is emitted after the @c count property has been updated but prior - * to the @link countChanged signal being emitted. - * @param previousCount The number of desktops prior to the change. - * @see countChanged - * @see setCount - * @see count - **/ - void desktopsRemoved(uint previousCount); + * Signal when the number of rows in the layout changes + * @param new number of rows + */ + void rowsChanged(uint rows); + + /** + * A new desktop has been created + * @param desktop the new just crated desktop + */ + void desktopCreated(KWin::VirtualDesktop *desktop); + + /** + * A desktop has been removed and is about to be deleted + * @param desktop the desktop that has been removed. + * It's guaranteed to stil la valid pointer when the signal arrives, + * but it's about to be deleted. + */ + void desktopRemoved(KWin::VirtualDesktop *desktop); + /** * Signal emitted whenever the current desktop changes. * @param previousDesktop The virtual desktop changed from @@ -396,21 +454,6 @@ private Q_SLOTS: void slotDown(); private: - /** - * This method is called when the number of desktops is updated in a way that desktops - * are removed. At the time when this method is invoked the count property is already - * updated but the corresponding signal has not been emitted yet. - * - * Ensures that in case the current desktop is on one of the removed - * desktops the last desktop after the change becomes the new desktop. - * Emits the signal @link desktopsRemoved. - * - * @param previousCount The number of desktops prior to the change. - * @param previousCurrent The number of the previously current desktop. - * @see setCount - * @see desktopsRemoved - **/ - void handleDesktopsRemoved(uint previousCount, uint previousCurrent); /** * Generate a desktop layout from EWMH _NET_DESKTOP_LAYOUT property parameters. */ @@ -450,7 +493,9 @@ private: VirtualDesktopGrid m_grid; // TODO: QPointer NETRootInfo *m_rootInfo; + KWayland::Server::PlasmaVirtualDesktopManagementInterface *m_virtualDesktopManagement = nullptr; KSharedConfig::Ptr m_config; + bool m_isLoading = false; KWIN_SINGLETON_VARIABLE(VirtualDesktopManager, s_manager) }; diff --git a/virtualdesktopsdbustypes.cpp b/virtualdesktopsdbustypes.cpp new file mode 100644 index 0000000000..aa9af454a2 --- /dev/null +++ b/virtualdesktopsdbustypes.cpp @@ -0,0 +1,70 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2018 Marco Martin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +// own +#include "virtualdesktopsdbustypes.h" + + +// Marshall the DBusDesktopDataStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KWin::DBusDesktopDataStruct &desk) +{ + argument.beginStructure(); + argument << desk.position; + argument << desk.id; + argument << desk.name; + argument.endStructure(); + return argument; +} +// Retrieve +const QDBusArgument &operator>>(const QDBusArgument &argument, KWin::DBusDesktopDataStruct &desk) +{ + argument.beginStructure(); + argument >> desk.position; + argument >> desk.id; + argument >> desk.name; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator<<(QDBusArgument &argument, const KWin::DBusDesktopDataVector &deskVector) +{ + argument.beginArray(qMetaTypeId()); + for (int i = 0; i < deskVector.size(); ++i) { + argument << deskVector[i]; + } + argument.endArray(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, KWin::DBusDesktopDataVector &deskVector) +{ + argument.beginArray(); + deskVector.clear(); + + while (!argument.atEnd()) { + KWin::DBusDesktopDataStruct element; + argument >> element; + deskVector.append(element); + } + + argument.endArray(); + + return argument; +} diff --git a/virtualdesktopsdbustypes.h b/virtualdesktopsdbustypes.h new file mode 100644 index 0000000000..071ad0d940 --- /dev/null +++ b/virtualdesktopsdbustypes.h @@ -0,0 +1,47 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2018 Marco Martin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#ifndef KWIN_VIRTUALDESKTOPS_DBUS_TYPES_H +#define KWIN_VIRTUALDESKTOPS_DBUS_TYPES_H + +#include + +namespace KWin +{ + +struct DBusDesktopDataStruct { + uint position; + QString id; + QString name; +}; +typedef QVector DBusDesktopDataVector; +} + +const QDBusArgument &operator<<(QDBusArgument &argument, const KWin::DBusDesktopDataStruct &desk); +const QDBusArgument &operator>>(const QDBusArgument &argument, KWin::DBusDesktopDataStruct &desk); + +Q_DECLARE_METATYPE(KWin::DBusDesktopDataStruct) + +const QDBusArgument &operator<<(QDBusArgument &argument, const KWin::DBusDesktopDataVector &deskVector); +const QDBusArgument &operator>>(const QDBusArgument &argument, KWin::DBusDesktopDataVector &deskVector); + +Q_DECLARE_METATYPE(KWin::DBusDesktopDataVector) + +#endif // KWIN_VIRTUALDESKTOPS_DBUS_TYPES_H diff --git a/wayland_server.cpp b/wayland_server.cpp index ad4367d050..7ddb93927c 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -43,6 +43,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -333,6 +334,12 @@ bool WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) workspace()->setShowingDesktop(set); } ); + + + m_virtualDesktopManagement = m_display->createPlasmaVirtualDesktopManagement(m_display); + m_virtualDesktopManagement->create(); + m_windowManagement->setPlasmaVirtualDesktopManagementInterface(m_virtualDesktopManagement); + auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); @@ -390,6 +397,8 @@ void WaylandServer::shellClientShown(Toplevel *t) void WaylandServer::initWorkspace() { + VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement); + if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { diff --git a/wayland_server.h b/wayland_server.h index 6751cdd4b7..60dbcc0ade 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -54,6 +54,7 @@ class SurfaceInterface; class OutputInterface; class PlasmaShellInterface; class PlasmaShellSurfaceInterface; +class PlasmaVirtualDesktopManagementInterface; class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; @@ -99,6 +100,9 @@ public: KWayland::Server::ShellInterface *shell() { return m_shell; } + KWayland::Server::PlasmaVirtualDesktopManagementInterface *virtualDesktopManagement() { + return m_virtualDesktopManagement; + } KWayland::Server::PlasmaWindowManagementInterface *windowManagement() { return m_windowManagement; } @@ -227,6 +231,7 @@ private: KWayland::Server::XdgShellInterface *m_xdgShell = nullptr; KWayland::Server::PlasmaShellInterface *m_plasmaShell = nullptr; KWayland::Server::PlasmaWindowManagementInterface *m_windowManagement = nullptr; + KWayland::Server::PlasmaVirtualDesktopManagementInterface *m_virtualDesktopManagement = nullptr; KWayland::Server::QtSurfaceExtensionInterface *m_qtExtendedSurface = nullptr; KWayland::Server::ServerSideDecorationManagerInterface *m_decorationManager = nullptr; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; diff --git a/workspace.cpp b/workspace.cpp index 7c2daeb4d6..9c778bb373 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -165,6 +165,8 @@ Workspace::Workspace(const QString &sessionKey) // and prior to TabBox, due to TabBox connecting to signals // actual initialization happens in init() VirtualDesktopManager::create(this); + //dbus interface + new VirtualDesktopManagerDBusInterface(VirtualDesktopManager::self()); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup @@ -219,7 +221,30 @@ void Workspace::init() // create VirtualDesktopManager and perform dependency injection VirtualDesktopManager *vds = VirtualDesktopManager::self(); - connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); + connect(vds, &VirtualDesktopManager::desktopRemoved, this, + [this](KWin::VirtualDesktop *desktop) { + //Wayland + if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly || + kwinApp()->operationMode() == Application::OperationModeXwayland) { + for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { + const bool needsMove = (*it)->desktops().count() == 1; + (*it)->removeDesktop(desktop); + if (needsMove) { + const VirtualDesktop *otherDesktop = VirtualDesktopManager::self()->desktops().first(); + sendClientToDesktop(*it, qMin(desktop->x11DesktopNumber(), VirtualDesktopManager::self()->count()), true); + } + } + //X11 + } else { + for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { + if (!(*it)->isOnAllDesktops() && ((*it)->desktop() > static_cast(VirtualDesktopManager::self()->count()))) { + sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); + } + } + } + } + ); + connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); vds->setNavigationWrappingAround(options->isRollOverDesktops()); @@ -232,6 +257,9 @@ void Workspace::init() // positioning object needs to be created before the virtual desktops are loaded. vds->load(); vds->updateLayout(); + //makes sure any autogenerated id is saved, necessary as in case of xwayland, load will be called 2 times + // load is needed to be called again when starting xwayalnd to sync to RootInfo, see BUG 385260 + vds->save(); if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop)) VirtualDesktopManager::self()->setCurrent(1); @@ -1074,14 +1102,6 @@ void Workspace::updateCurrentActivity(const QString &new_activity) #endif } -void Workspace::moveClientsFromRemovedDesktops() -{ - for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) { - if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast(VirtualDesktopManager::self()->count())) - sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); - } -} - void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) { Q_UNUSED(previousCount) diff --git a/workspace.h b/workspace.h index d24bc9bf01..7d4c45df5b 100644 --- a/workspace.h +++ b/workspace.h @@ -457,7 +457,6 @@ private Q_SLOTS: void slotReloadConfig(); void updateCurrentActivity(const QString &new_activity); // virtual desktop handling - void moveClientsFromRemovedDesktops(); void slotDesktopCountChanged(uint previousCount, uint newCount); void slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop);