diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,6 +381,7 @@ 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.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 @@ 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.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -84,12 +84,17 @@ 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 @@ 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 @@ 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 @@ 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/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -489,16 +489,44 @@ if (desktop != NET::OnAllDesktops) // Do range check desktop = qMax(1, qMin(numberOfDesktops, desktop)); desktop = qMin(numberOfDesktops, rules()->checkDesktop(desktop)); - if (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 = m_desktop; - const bool wasOnCurrentDesktop = isOnCurrentDesktop(); - m_desktop = desktop; + 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,14 +554,43 @@ emit desktopChanged(); if (wasOnCurrentDesktop != isOnCurrentDesktop()) emit desktopPresenceChanged(this, was_desk); + emit x11DesktopIdsChanged(); } void AbstractClient::doSetDesktop(int desktop, int was_desk) { Q_UNUSED(desktop) 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 @@ 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 @@ } ); 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 @@ 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/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -43,6 +43,7 @@ KF5::GlobalAccel KF5::ConfigCore KF5::WindowSystem + KF5::WaylandServer ) add_test(NAME kwin-testVirtualDesktops COMMAND testVirtualDesktops) ecm_mark_as_test(testVirtualDesktops) @@ -336,6 +337,7 @@ 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 --- a/autotests/integration/virtual_desktop_test.cpp +++ b/autotests/integration/virtual_desktop_test.cpp @@ -43,6 +43,10 @@ void testNetCurrentDesktop(); void testLastDesktopRemoved_data(); void testLastDesktopRemoved(); + void testWindowOnMultipleDesktops_data(); + void testWindowOnMultipleDesktops(); + void testRemoveDesktopWithWindow_data(); + void testRemoveDesktopWithWindow(); }; void VirtualDesktopTest::initTestCase() @@ -157,12 +161,154 @@ 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 --- a/autotests/test_virtual_desktops.cpp +++ b/autotests/test_virtual_desktops.cpp @@ -132,8 +132,10 @@ 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 @@ 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 --- a/autotests/test_window_paint_data.cpp +++ b/autotests/test_window_paint_data.cpp @@ -19,6 +19,7 @@ *********************************************************************/ #include +#include "../virtualdesktops.h" #include #include @@ -72,6 +73,7 @@ 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.h b/dbusinterface.h --- a/dbusinterface.h +++ b/dbusinterface.h @@ -24,10 +24,13 @@ #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 @@ 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/dbusinterface.cpp b/dbusinterface.cpp --- a/dbusinterface.cpp +++ b/dbusinterface.cpp @@ -21,6 +21,7 @@ // own #include "dbusinterface.h" #include "compositingadaptor.h" +#include "virtualdesktopmanageradaptor.h" // kwin #include "abstract_client.h" @@ -315,4 +316,180 @@ 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/deleted.h b/deleted.h --- a/deleted.h +++ b/deleted.h @@ -52,6 +52,7 @@ 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 @@ QPoint m_contentPos; QRect transparent_rect; xcb_window_t m_frame; + QList m_desktops; bool no_border; QRect decoration_left; diff --git a/deleted.cpp b/deleted.cpp --- a/deleted.cpp +++ b/deleted.cpp @@ -94,6 +94,7 @@ 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 @@ return activityList; } +QList Deleted::desktops() const +{ + return m_desktops; +} + QPoint Deleted::clientPos() const { return contentsRect.topLeft(); diff --git a/effects.cpp b/effects.cpp --- a/effects.cpp +++ b/effects.cpp @@ -911,8 +911,9 @@ 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.h b/effects/desktopgrid/desktopgrid.h --- a/effects/desktopgrid/desktopgrid.h +++ b/effects/desktopgrid/desktopgrid.h @@ -151,9 +151,10 @@ 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/effects/desktopgrid/desktopgrid.cpp b/effects/desktopgrid/desktopgrid.cpp --- a/effects/desktopgrid/desktopgrid.cpp +++ b/effects/desktopgrid/desktopgrid.cpp @@ -42,6 +42,8 @@ #include #include +#include + namespace KWin { @@ -52,6 +54,7 @@ , timeline() , keyboardGrab(false) , wasWindowMove(false) + , wasWindowCopy(false) , wasDesktopMove(false) , isValidMove(false) , windowMove(NULL) @@ -297,7 +300,9 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ windowMove = NULL; } wasWindowMove = false; + wasWindowCopy = false; wasDesktopMove = false; } } @@ -1360,7 +1377,7 @@ // 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 @@ 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/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -944,6 +944,10 @@ 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 @@ 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/libkwineffects/kwineffects.cpp b/libkwineffects/kwineffects.cpp --- a/libkwineffects/kwineffects.cpp +++ b/libkwineffects/kwineffects.cpp @@ -762,6 +762,13 @@ 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(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(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::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/org.kde.KWin.VirtualDesktopManager.xml b/org.kde.KWin.VirtualDesktopManager.xml new file mode 100644 --- /dev/null +++ b/org.kde.KWin.VirtualDesktopManager.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toplevel.h b/toplevel.h --- a/toplevel.h +++ b/toplevel.h @@ -290,6 +290,7 @@ * 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 @@ 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::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.h b/unmanaged.h --- a/unmanaged.h +++ b/unmanaged.h @@ -39,6 +39,7 @@ 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/unmanaged.cpp b/unmanaged.cpp --- a/unmanaged.cpp +++ b/unmanaged.cpp @@ -123,6 +123,11 @@ 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/useractions.h b/useractions.h --- a/useractions.h +++ b/useractions.h @@ -144,6 +144,11 @@ * 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 @@ * @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 @@ * 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/useractions.cpp b/useractions.cpp --- a/useractions.cpp +++ b/useractions.cpp @@ -402,6 +402,7 @@ 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 @@ 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::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 @@ 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 @@ 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 @@ 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/virtualdesktops.h b/virtualdesktops.h --- a/virtualdesktops.h +++ b/virtualdesktops.h @@ -27,21 +27,30 @@ #include #include #include + // KDE includes #include #include 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 @@ Q_SIGNALS: void nameChanged(); + void x11DesktopNumberChanged(); /** * Emitted just before the desktop gets destroyed. **/ @@ -148,9 +158,13 @@ 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 @@ * @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 @@ **/ 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 @@ * @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 @@ * @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 @@ * @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 @@ 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 @@ 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/virtualdesktops.cpp b/virtualdesktops.cpp --- a/virtualdesktops.cpp +++ b/virtualdesktops.cpp @@ -25,11 +25,14 @@ #include #include #include + +#include // Qt #include +#include #include - +#include namespace KWin { extern int screen_number; @@ -44,16 +47,99 @@ 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()); m_id = 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 @@ VirtualDesktopGrid::VirtualDesktopGrid() : m_size(1, 2) // Default to tow rows , m_grid(QVector>{QVector{}, QVector{}}) -{ +{ } VirtualDesktopGrid::~VirtualDesktopGrid() = default; @@ -325,6 +411,99 @@ 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 @@ // 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; + } + + int columns = count() / rows; + if (count() % rows > 0) { + columns++; } - emit desktopsRemoved(previousCount); + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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/virtualdesktopsdbustypes.h b/virtualdesktopsdbustypes.h new file mode 100644 --- /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/virtualdesktopsdbustypes.cpp b/virtualdesktopsdbustypes.cpp new file mode 100644 --- /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/wayland_server.h b/wayland_server.h --- a/wayland_server.h +++ b/wayland_server.h @@ -54,6 +54,7 @@ class OutputInterface; class PlasmaShellInterface; class PlasmaShellSurfaceInterface; +class PlasmaVirtualDesktopManagementInterface; class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; @@ -99,6 +100,9 @@ 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 @@ 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/wayland_server.cpp b/wayland_server.cpp --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -333,6 +334,12 @@ 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::initWorkspace() { + VirtualDesktopManager::self()->setVirtualDesktopManagement(m_virtualDesktopManagement); + if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { diff --git a/workspace.h b/workspace.h --- a/workspace.h +++ b/workspace.h @@ -457,7 +457,6 @@ 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); diff --git a/workspace.cpp b/workspace.cpp --- a/workspace.cpp +++ b/workspace.cpp @@ -165,6 +165,8 @@ // 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 @@ // 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 @@ // 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 @@ #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)