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 @@ -531,6 +532,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) @@ -669,6 +671,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. **/ @@ -414,9 +419,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 @@ -736,6 +750,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); @@ -1085,7 +1100,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 @@ -484,16 +484,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); @@ -521,14 +549,29 @@ 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) +{ + 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()) || @@ -540,6 +583,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; @@ -802,16 +859,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); @@ -906,6 +954,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_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 { diff --git a/deleted.cpp b/deleted.cpp --- a/deleted.cpp +++ b/deleted.cpp @@ -141,6 +141,11 @@ return activityList; } +QList Deleted::desktops() const +{ + return {}; +} + QPoint Deleted::clientPos() const { return contentsRect.topLeft(); diff --git a/effects.cpp b/effects.cpp --- a/effects.cpp +++ b/effects.cpp @@ -873,8 +873,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/kcmkwin/kwindesktop/CMakeLists.txt b/kcmkwin/kwindesktop/CMakeLists.txt --- a/kcmkwin/kwindesktop/CMakeLists.txt +++ b/kcmkwin/kwindesktop/CMakeLists.txt @@ -1,32 +1,27 @@ +include(ECMQMLModules) +ecm_find_qmlmodule(org.kde.plasma.core 2.0) -########### next target ############### -# KI18N Translation Domain for this library -add_definitions(-DTRANSLATION_DOMAIN=\"kcm_kwindesktop\") +# KI18N Translation Domain for this library. +add_definitions(-DTRANSLATION_DOMAIN=\"kcm_kwin_virtualdesktops\") -include_directories(${KWIN_SOURCE_DIR}/effects) +########### next target ############### -set(kcm_kwindesktop_PART_SRCS main.cpp desktopnameswidget.cpp) -ki18n_wrap_ui(kcm_kwindesktop_PART_SRCS main.ui) -qt5_add_dbus_interface( kcm_kwindesktop_PART_SRCS - ${KWIN_SOURCE_DIR}/org.kde.kwin.Effects.xml kwin_effects_interface) +set(kcm_kwin_virtualdesktops_PART_SRCS virtualdesktops.cpp desktopsmodel.cpp ../../virtualdesktopsdbustypes.cpp) -add_library(kcm_kwindesktop MODULE ${kcm_kwindesktop_PART_SRCS}) +add_library(kcm_kwin_virtualdesktops MODULE ${kcm_kwin_virtualdesktops_PART_SRCS}) -target_link_libraries(kcm_kwindesktop - Qt5::X11Extras - KF5::KCMUtils - KF5::Completion - KF5::GlobalAccel +target_link_libraries(kcm_kwin_virtualdesktops + Qt5::DBus KF5::I18n - KF5::WindowSystem - KF5::XmlGui - ${X11_LIBRARIES} - kwin4_effect_builtins + KF5::KCMUtils + KF5::QuickAddons ) -install(TARGETS kcm_kwindesktop DESTINATION ${PLUGIN_INSTALL_DIR} ) - +kcoreaddons_desktop_to_json(kcm_kwin_virtualdesktops "kcm_kwin_virtualdesktops.desktop") ########### install files ############### -install( FILES desktop.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +install(TARGETS kcm_kwin_virtualdesktops DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms) +install(FILES kcm_kwin_virtualdesktops.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) +kpackage_install_package(package kcm_kwin_virtualdesktops kcms) diff --git a/kcmkwin/kwindesktop/Messages.sh b/kcmkwin/kwindesktop/Messages.sh --- a/kcmkwin/kwindesktop/Messages.sh +++ b/kcmkwin/kwindesktop/Messages.sh @@ -1,4 +1,2 @@ #! /usr/bin/env bash -$EXTRACTRC `find . -name \*.ui` >> rc.cpp || exit 11 -$XGETTEXT *.cpp -o $podir/kcm_kwindesktop.pot -rm -f rc.cpp +$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_kwin_virtualdesktops.pot diff --git a/kcmkwin/kwindesktop/desktopnameswidget.h b/kcmkwin/kwindesktop/desktopnameswidget.h deleted file mode 100644 --- a/kcmkwin/kwindesktop/desktopnameswidget.h +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************** -KWin - the KDE window manager -This file is part of the KDE project. - -Copyright (C) 2009 Martin Gräßlin - -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 DESKTOPNAMESWIDGET_H -#define DESKTOPNAMESWIDGET_H - -#include -#include - -class KLineEdit; -class QLabel; -class QGridLayout; - -namespace KWin -{ -class KWinDesktopConfig; - -class DesktopNamesWidget : public QWidget -{ - Q_OBJECT -public: - explicit DesktopNamesWidget(QWidget *parent); - ~DesktopNamesWidget(); - QString name(int desktop); - void setName(int desktop, QString desktopName); - void setDefaultName(int desktop); - void setMaxDesktops(int maxDesktops); - void setDesktopConfig(KWinDesktopConfig *desktopConfig); - -Q_SIGNALS: - void changed(); - -public Q_SLOTS: - void numberChanged(int number); - -private: - QList< QLabel* > m_nameLabels; - QList< KLineEdit* > m_nameInputs; - QGridLayout* m_namesLayout; - int m_maxDesktops; - KWinDesktopConfig *m_desktopConfig; -}; - -} // namespace - -#endif // DESKTOPNAMESWIDGET_H diff --git a/kcmkwin/kwindesktop/desktopnameswidget.cpp b/kcmkwin/kwindesktop/desktopnameswidget.cpp deleted file mode 100644 --- a/kcmkwin/kwindesktop/desktopnameswidget.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************** -KWin - the KDE window manager -This file is part of the KDE project. - -Copyright (C) 2009 Martin Gräßlin - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*********************************************************************/ - -#include "desktopnameswidget.h" -#include "main.h" - -#include -#include - -#include -#include - -namespace KWin -{ - -DesktopNamesWidget::DesktopNamesWidget(QWidget *parent) - : QWidget(parent) - , m_maxDesktops(0) - , m_desktopConfig(0) -{ - m_namesLayout = new QGridLayout; - m_namesLayout->setMargin(0); - - setLayout(m_namesLayout); -} - -DesktopNamesWidget::~DesktopNamesWidget() -{ -} - -void DesktopNamesWidget::numberChanged(int number) -{ - if ((number < 1) || (number > m_maxDesktops)) - return; - if (m_nameInputs.size() != number) { - if (number < m_nameInputs.size()) { - // remove widgets - while (number != m_nameInputs.size()) { - KLineEdit* edit = m_nameInputs.last(); - m_nameInputs.removeLast(); - delete edit; - QLabel* label = m_nameLabels.last(); - m_nameLabels.removeLast(); - delete label; - } - } else { - // add widgets - while (number != m_nameInputs.size()) { - int desktop = m_nameInputs.size(); - QLabel* label = new QLabel(i18n("Desktop %1:", desktop + 1), this); - KLineEdit* edit = new KLineEdit(this); - label->setWhatsThis(i18n("Here you can enter the name for desktop %1", desktop + 1)); - edit->setWhatsThis(i18n("Here you can enter the name for desktop %1", desktop + 1)); - - m_namesLayout->addWidget(label, desktop % 10, 0 + 2 *(desktop >= 10), 1, 1); - m_namesLayout->addWidget(edit, desktop % 10, 1 + 2 *(desktop >= 10), 1, 1); - m_nameInputs << edit; - m_nameLabels << label; - - setDefaultName(desktop + 1); - if (desktop > 1) { - setTabOrder(m_nameInputs[desktop - 1], m_nameInputs[desktop]); - } - connect(edit, SIGNAL(textChanged(QString)), SIGNAL(changed())); - } - } - } -} - -QString DesktopNamesWidget::name(int desktop) -{ - if ((desktop < 1) || (desktop > m_maxDesktops) || (desktop > m_nameInputs.size())) - return QString(); - return m_nameInputs[ desktop -1 ]->text(); -} - - -void DesktopNamesWidget::setName(int desktop, QString desktopName) -{ - if ((desktop < 1) || (desktop > m_maxDesktops) || (desktop > m_nameInputs.size())) - return; - m_nameInputs[ desktop-1 ]->setText(desktopName); -} - -void DesktopNamesWidget::setDefaultName(int desktop) -{ - if ((desktop < 1) || (desktop > m_maxDesktops)) - return; - QString name = m_desktopConfig->cachedDesktopName(desktop); - if (name.isEmpty()) - name = i18n("Desktop %1", desktop); - m_nameInputs[ desktop -1 ]->setText(name); -} - - -void DesktopNamesWidget::setMaxDesktops(int maxDesktops) -{ - m_maxDesktops = maxDesktops; -} - -void DesktopNamesWidget::setDesktopConfig(KWinDesktopConfig* desktopConfig) -{ - m_desktopConfig = desktopConfig; -} - -} // namespace - diff --git a/kcmkwin/kwindesktop/desktopsmodel.h b/kcmkwin/kwindesktop/desktopsmodel.h new file mode 100644 --- /dev/null +++ b/kcmkwin/kwindesktop/desktopsmodel.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 Eike Hein + * 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 DESKTOPSMODEL_H +#define DESKTOPSMODEL_H + +#include + +#include "../virtualdesktopsdbustypes.h" + +class QDBusArgument; +class QDBusMessage; + + +namespace KWin +{ + +class DesktopsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) + Q_PROPERTY(QString error READ error NOTIFY errorChanged) + Q_PROPERTY(bool userModified READ userModified NOTIFY userModifiedChanged) + Q_PROPERTY(bool serverModified READ serverModified NOTIFY serverModifiedChanged) + Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) + + public: + enum AdditionalRoles { + Id = Qt::UserRole + 1, + DesktopRow + }; + Q_ENUM(AdditionalRoles) + + explicit DesktopsModel(QObject *parent = nullptr); + ~DesktopsModel() override; + + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = {}) const override; + + bool ready() const; + QString error() const; + + bool userModified() const; + bool serverModified() const; + + int rows() const; + void setRows(int rows); + + Q_INVOKABLE void createDesktop(const QString &name); + Q_INVOKABLE void removeDesktop(const QString &id); + Q_INVOKABLE void setDesktopName(const QString &id, const QString &name); + + Q_INVOKABLE void syncWithServer(); + + Q_SIGNALS: + void needsSaving(bool needsSaving) const; + void readyChanged() const; + void errorChanged() const; + void userModifiedChanged() const; + void serverModifiedChanged() const; + void rowsChanged() const; + + protected Q_SLOTS: + void initialize(const QDBusMessage &msg); + void desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data); + void desktopRemoved(const QString &id); + void desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data); + void desktopRowsChanged(uint rows); + void checkModifiedState(bool server = false); + void handleCallError(); + + private: + QString m_error; + bool m_userModified; + bool m_serverModified; + QStringList m_serverSideDesktops; + QHash m_serverSideNames; + int m_serverSideRows; + QStringList m_desktops; + QHash m_names; + int m_rows; + bool m_synchronizing; +}; + +} + +#endif diff --git a/kcmkwin/kwindesktop/desktopsmodel.cpp b/kcmkwin/kwindesktop/desktopsmodel.cpp new file mode 100644 --- /dev/null +++ b/kcmkwin/kwindesktop/desktopsmodel.cpp @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2018 Eike Hein + * 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 . + */ + +#include "desktopsmodel.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace KWin +{ + +static const QString s_serviceName(QStringLiteral("org.kde.KWin")); +static const QString s_virtualDesktopsInterface(QStringLiteral("org.kde.KWin.VirtualDesktopManager")); +static const QString s_virtDesktopsPath(QStringLiteral("/VirtualDesktopManager")); +static const QString s_fdoPropertiesInterface(QStringLiteral("org.freedesktop.DBus.Properties")); + +DesktopsModel::DesktopsModel(QObject *parent) + : QAbstractListModel(parent) + , m_userModified(false) + , m_serverModified(false) + , m_serverSideRows(-1) + , m_synchronizing(false) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + auto initializeCall = QDBusMessage::createMethodCall( + s_serviceName, + s_virtDesktopsPath, + s_fdoPropertiesInterface, + QStringLiteral("GetAll")); + + initializeCall.setArguments({s_virtualDesktopsInterface}); + + QDBusConnection::sessionBus().callWithCallback( + initializeCall, + this, + SLOT(initialize(QDBusMessage)), + SLOT(handleCallError())); +} + +DesktopsModel::~DesktopsModel() +{ +} + +QHash DesktopsModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + + QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); + + for (int i = 0; i < e.keyCount(); ++i) { + roles.insert(e.value(i), e.key(i)); + } + + return roles; +} + +QVariant DesktopsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() > (m_desktops.count() - 1)) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return m_names.value(m_desktops.at(index.row())); + } else if (role == Id) { + return m_desktops.at(index.row()); + } else if (role == DesktopRow) { + const int rows = std::max(m_rows, 1); + const int perRow = std::ceil((qreal)m_desktops.count() / (qreal)rows); + + return (index.row() / perRow) + 1; + + } + + return QVariant(); +} + +int DesktopsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_desktops.count(); +} + +bool DesktopsModel::ready() const +{ + return !m_serverSideDesktops.isEmpty(); +} + +QString DesktopsModel::error() const +{ + return m_error; +} + +bool DesktopsModel::userModified() const +{ + return m_userModified; +} + +bool DesktopsModel::serverModified() const +{ + return m_serverModified; +} + +int DesktopsModel::rows() const +{ + return m_rows; +} + +void DesktopsModel::setRows(int rows) +{ + if (!ready()) { + return; + } + + if (m_rows != rows) { + m_rows = rows; + + emit rowsChanged(); + emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector{DesktopRow}); + + checkModifiedState(); + } +} + +void DesktopsModel::createDesktop(const QString &name) +{ + if (!ready()) { + return; + } + + beginInsertRows(QModelIndex(), m_serverSideDesktops.count(), m_serverSideDesktops.count()); + + const QString &dummyId = QUuid::createUuid().toString(QUuid::WithoutBraces); + + m_desktops.append(dummyId); + m_names[dummyId] = name; + + endInsertRows(); + + checkModifiedState(); +} + +void DesktopsModel::removeDesktop(const QString &id) +{ + if (!ready() || !m_desktops.contains(id)) { + return; + } + + const int desktopIndex = m_desktops.indexOf(id); + + beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); + + m_desktops.removeAt(desktopIndex); + m_names.remove(id); + + endRemoveRows(); + + checkModifiedState(); +} + +void DesktopsModel::setDesktopName(const QString &id, const QString &name) +{ + if (!ready() || !m_desktops.contains(id)) { + return; + } + + const int desktopIndex = m_desktops.indexOf(id); + + m_desktops[desktopIndex] = id; + m_names[id] = name; + + const QModelIndex &idx = index(desktopIndex, 0); + + dataChanged(idx, idx, QVector{Qt::DisplayRole}); + + checkModifiedState(); +} + +void DesktopsModel::syncWithServer() +{ + m_synchronizing = true; + + auto callFinished = [this](QDBusPendingCallWatcher *call) { + QDBusPendingReply reply = *call; + + if (reply.isError()) { + handleCallError(); + } + + call->deleteLater(); + }; + + if (m_desktops.count() > m_serverSideDesktops.count()) { + auto call = QDBusMessage::createMethodCall( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("createDesktop")); + + const int newIndex = m_serverSideDesktops.count(); + + call.setArguments({(uint)newIndex, m_names.value(m_desktops.at(newIndex))}); + + QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); + + const auto *watcher = new QDBusPendingCallWatcher(pending, this); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); + + return; // The change-handling slot will call syncWithServer() again, + // until everything is in sync. + } + + if (m_desktops.count() < m_serverSideDesktops.count()) { + auto call = QDBusMessage::createMethodCall( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("removeDesktop")); + + call.setArguments({m_desktops.last()}); + + QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); + + const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); + + return; // The change-handling slot will call syncWithServer() again, + // until everything is in sync. + } + + // Sync names. + if (m_names != m_serverSideNames) { + QHashIterator i(m_names); + + while (i.hasNext()) { + i.next(); + + if (i.value() != m_serverSideNames.value(i.key())) { + auto call = QDBusMessage::createMethodCall( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("setDesktopName")); + + call.setArguments({i.key(), i.value()}); + + QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); + + const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); + + break; + } + } + + return; // The change-handling slot will call syncWithServer() again, + // until everything is in sync.. + } + + // Sync rows. + if (m_rows != m_serverSideRows) { + auto call = QDBusMessage::createMethodCall( + s_serviceName, + s_virtDesktopsPath, + s_fdoPropertiesInterface, + QStringLiteral("Set")); + + call.setArguments({s_virtualDesktopsInterface, + QStringLiteral("rows"), QVariant::fromValue(QDBusVariant(QVariant((uint)m_rows)))}); + + QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); + + const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); + } +} + +void DesktopsModel::initialize(const QDBusMessage &msg) +{ + beginResetModel(); + + const QVariantMap &data = qdbus_cast(msg.arguments().at(0).value()); + + const KWin::DBusDesktopDataVector &desktops = qdbus_cast( + data.value(QStringLiteral("desktops")).value() + ); + + m_serverSideRows = data.value(QStringLiteral("rows")).toUInt(); + m_rows = m_serverSideRows; + + for (const KWin::DBusDesktopDataStruct &d : desktops) { + m_serverSideDesktops.append(d.id); + m_desktops = m_serverSideDesktops; + m_serverSideNames[d.id] = d.name; + m_names = m_serverSideNames; + } + + endResetModel(); + + emit readyChanged(); + + auto handleConnectionError = [this]() { + m_error = i18n("There was an error connecting to the compositor."); + emit errorChanged(); + }; + + bool connected = QDBusConnection::sessionBus().connect( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("desktopCreated"), + this, + SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct))); + + if (!connected) { + handleConnectionError(); + + return; + } + + connected = QDBusConnection::sessionBus().connect( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("desktopRemoved"), + this, + SLOT(desktopRemoved(QString))); + + if (!connected) { + handleConnectionError(); + + return; + } + + connected = QDBusConnection::sessionBus().connect( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("desktopDataChanged"), + this, + SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct))); + + if (!connected) { + handleConnectionError(); + + return; + } + + connected = QDBusConnection::sessionBus().connect( + s_serviceName, + s_virtDesktopsPath, + s_virtualDesktopsInterface, + QStringLiteral("rowsChanged"), + this, + SLOT(desktopRowsChanged(uint))); + + if (!connected) { + handleConnectionError(); + + return; + } +} + +void DesktopsModel::desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data) +{ + m_serverSideDesktops.insert(data.position, id); + m_serverSideNames[data.id] = data.name; + + // If the user didn't make any changes, we can just stay in sync. + if (!m_userModified) { + beginInsertRows(QModelIndex(), data.position, data.position); + + m_desktops = m_serverSideDesktops; + m_names = m_serverSideNames; + + endInsertRows(); + } else { + // Swap dummyId inserted in `createDesktop()` for real id, + // so we can determine when we are in sync. + const QString &dummyId = m_desktops.at(data.position); + m_names.remove(dummyId); + m_desktops.insert(data.position, id); + m_names[data.id] = data.name; + + checkModifiedState(/* server */ true); + } +} + +void DesktopsModel::desktopRemoved(const QString &id) +{ + const int desktopIndex = m_serverSideDesktops.indexOf(id); + + m_serverSideDesktops.removeAt(desktopIndex); + m_serverSideNames.remove(id); + + // If the user didn't make any changes, we can just stay in sync. + if (!m_userModified) { + beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); + + m_desktops = m_serverSideDesktops; + m_names = m_serverSideNames; + + endRemoveRows(); + } else { + checkModifiedState(/* server */ true); + } +} + +void DesktopsModel::desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data) +{ + const int desktopIndex = m_serverSideDesktops.indexOf(id); + + m_serverSideDesktops[desktopIndex] = id; + m_serverSideNames[id] = data.name; + + // If the user didn't make any changes, we can just stay in sync. + if (!m_userModified) { + m_desktops = m_serverSideDesktops; + m_names = m_serverSideNames; + + const QModelIndex &idx = index(desktopIndex, 0); + + dataChanged(idx, idx, QVector{Qt::DisplayRole}); + } else { + checkModifiedState(/* server */ true); + } +} + +void DesktopsModel::desktopRowsChanged(uint rows) +{ + m_serverSideRows = rows; + + // If the user didn't make any changes, we can just stay in sync. + if (!m_userModified) { + m_rows = m_serverSideRows; + + emit rowsChanged(); + emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector{DesktopRow}); + } else { + checkModifiedState(/* server */ true); + } +} + +void DesktopsModel::checkModifiedState(bool server) +{ + if (m_desktops == m_serverSideDesktops + && m_names == m_serverSideNames + && m_rows == m_serverSideRows) { + m_userModified = false; + emit userModifiedChanged(); + + m_serverModified = false; + emit serverModifiedChanged(); + + emit needsSaving(false); + + m_synchronizing = false; + } else { + if (m_synchronizing) { + m_serverModified = false; + emit serverModifiedChanged(); + + syncWithServer(); + } else if (server) { + m_serverModified = true; + emit serverModifiedChanged(); + } else { + m_userModified = true; + emit userModifiedChanged(); + + emit needsSaving(true); + } + } +} + +void DesktopsModel::handleCallError() +{ + if (m_synchronizing) { + m_synchronizing = false; + + m_serverModified = false; + emit serverModifiedChanged(); + + m_error = i18n("There was an error saving the settings to the compositor."); + emit errorChanged(); + } else { + m_error = i18n("There was an error requesting information from the compositor."); + emit errorChanged(); + } +} + +} diff --git a/kcmkwin/kwindesktop/desktop.desktop b/kcmkwin/kwindesktop/kcm_kwin_virtualdesktops.desktop rename from kcmkwin/kwindesktop/desktop.desktop rename to kcmkwin/kwindesktop/kcm_kwin_virtualdesktops.desktop --- a/kcmkwin/kwindesktop/desktop.desktop +++ b/kcmkwin/kwindesktop/kcm_kwin_virtualdesktops.desktop @@ -1,11 +1,11 @@ [Desktop Entry] +Exec=kcmshell5 kcm_kwin_virtualdesktops +Icon=preferences-desktop Type=Service X-KDE-ServiceTypes=KCModule -X-DocPath=kcontrol/desktop/index.html -Icon=preferences-desktop -Exec=kcmshell5 desktop +X-DocPath=kcontrol/kwin_virtualdesktops/index.html -X-KDE-Library=kcm_kwindesktop +X-KDE-Library=kcm_kwin_virtualdesktops X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=desktopbehavior @@ -143,7 +143,7 @@ X-KDE-Keywords[nl]=bureaublad,bureaubladen,aantal,virtueel bureaublad,meervoudige bureaubladen,pager,pager-widget,pager-applet,pagerinstellingen X-KDE-Keywords[nn]=skrivebord,mengd,tal,virtuelt skrivebord,fleire skrivebord,vekslar,vekslarelement,vekslarelement,vekslerinnstillinger,vekslaroppsett X-KDE-Keywords[pa]=ਡੈਸਕਟਾਪ,ਗਿਣਤੀ,ਨੰਬਰ,ਅੰਕ,ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ,ਕਈ ਡੈਸਕਟਾਪ,ਪੇਜ਼ਰ,ਪੇਜ਼ਰ ਵਿਜੈਟ,ਪੇਜ਼ਰ ਐਪਲਿਟ,ਪੇਜ਼ਰ ਸੈਟਿੰਗਾਂ -X-KDE-Keywords[pl]=pulpit,pulpity,liczba,pulpity wirtualne,wiele pulpitów +X-KDE-Keywords[pl]=pulpit,pulpity,liczba,pulpity wirtualne,wiele pulpitów X-KDE-Keywords[pt]=ecrã,ecrãs,número,ecrã virtual,múltiplos ecrãs,paginador,elemento paginador,'applet' do paginador,configuração do paginador X-KDE-Keywords[pt_BR]=área de trabalho,áreas de trabalho,desktop,desktops,número,área de trabalho virtual,múltiplas áreas de trabalho,paginador,elemento paginador,miniaplicativo do paginador,configurações do paginador X-KDE-Keywords[ru]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,рабочий стол,рабочие столы,число,виртуальный рабочий стол,несколько рабочих столов,переключатель,переключение,виджет переключения,аплет переключения,параметры переключения,настройки переключения @@ -159,3 +159,6 @@ X-KDE-Keywords[x-test]=xxdesktopxx,xxdesktopsxx,xxnumberxx,xxvirtual desktopxx,xxmultiple desktopsxx,xxpagerxx,xxpager widgetxx,xxpager appletxx,xxpager settingsxx X-KDE-Keywords[zh_CN]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,桌面,虚拟桌面,多桌面,分页,分页器,分页器组件,分页器设置 X-KDE-Keywords[zh_TW]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings + + +Categories=Qt;KDE;X-KDE-settings-translations; diff --git a/kcmkwin/kwindesktop/main.h b/kcmkwin/kwindesktop/main.h deleted file mode 100644 --- a/kcmkwin/kwindesktop/main.h +++ /dev/null @@ -1,92 +0,0 @@ -/******************************************************************** -KWin - the KDE window manager -This file is part of the KDE project. - -Copyright (C) 2009 Martin Gräßlin - -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 __MAIN_H__ -#define __MAIN_H__ - -#include -#include - -#include "ui_main.h" - -class KActionCollection; -class KConfigGroup; -class KShortcutsEditor; - -namespace KWin -{ -// if you change this, update also the number of keyboard shortcuts in kwin/kwinbindings.cpp -static const int maxDesktops = 20; -static const int defaultDesktops = 4; - -class KWinDesktopConfigForm : public QWidget, public Ui::KWinDesktopConfigForm -{ - Q_OBJECT - -public: - explicit KWinDesktopConfigForm(QWidget* parent); -}; - -class KWinDesktopConfig : public KCModule -{ - Q_OBJECT - -public: - explicit KWinDesktopConfig(QWidget* parent, const QVariantList& args); - ~KWinDesktopConfig(); - QString cachedDesktopName(int desktop); - - // undo all changes - void undo(); - -public Q_SLOTS: - virtual void save(); - virtual void load(); - virtual void defaults(); - - -private Q_SLOTS: - void slotChangeShortcuts(int number); - void slotShowAllShortcuts(); - void slotEffectSelectionChanged(int index); - void slotAboutEffectClicked(); - void slotConfigureEffectClicked(); - -private: - void init(); - void addAction(const QString &name, const QString &label); - bool effectEnabled(const QString& effect, const KConfigGroup& cfg) const; - QString extrapolatedShortcut(int desktop) const; - -private: - KWinDesktopConfigForm* m_ui; - KSharedConfigPtr m_config; - // cache for desktop names given by NETRootInfo - // needed as the widget only stores the names for actual number of desktops - QStringList m_desktopNames; - // Collection for switching desktops like ctrl+f1 - KActionCollection* m_actionCollection; - // Collection for next, previous, up, down desktop - KActionCollection* m_switchDesktopCollection; - KShortcutsEditor* m_editor; -}; - -} // namespace - -#endif diff --git a/kcmkwin/kwindesktop/main.cpp b/kcmkwin/kwindesktop/main.cpp deleted file mode 100644 --- a/kcmkwin/kwindesktop/main.cpp +++ /dev/null @@ -1,662 +0,0 @@ -/******************************************************************** -KWin - the KDE window manager -This file is part of the KDE project. - -Copyright (C) 2009 Martin Gräßlin - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*********************************************************************/ - -#include "main.h" -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -K_PLUGIN_FACTORY(KWinDesktopConfigFactory, registerPlugin();) - -namespace KWin -{ - -KWinDesktopConfigForm::KWinDesktopConfigForm(QWidget* parent) - : QWidget(parent) -{ - setupUi(this); -} - -KWinDesktopConfig::KWinDesktopConfig(QWidget* parent, const QVariantList& args) - : KCModule(KAboutData::pluginData(QStringLiteral("kcm_kwindesktop")), parent, args) - , m_config(KSharedConfig::openConfig("kwinrc")) - , m_actionCollection(nullptr) - , m_switchDesktopCollection(nullptr) -{ - init(); -} - -void KWinDesktopConfig::init() -{ - m_ui = new KWinDesktopConfigForm(this); - // TODO: there has to be a way to add the shortcuts editor to the ui file - m_editor = new KShortcutsEditor(m_ui, KShortcutsEditor::GlobalAction); - m_ui->editorFrame->setLayout(new QVBoxLayout()); - m_ui->editorFrame->layout()->setMargin(0); - m_ui->editorFrame->layout()->addWidget(m_editor); - - m_ui->desktopNames->setDesktopConfig(this); - m_ui->desktopNames->setMaxDesktops(maxDesktops); - m_ui->desktopNames->numberChanged(defaultDesktops); - - QVBoxLayout* layout = new QVBoxLayout(this); - layout->addWidget(m_ui); - - setQuickHelp(i18n("

Multiple Desktops

In this module, you can configure how many virtual desktops you want and how these should be labeled.")); - - // Shortcut config. The shortcut belongs to the component "kwin"! - m_actionCollection = new KActionCollection(this, QStringLiteral("kwin")); - m_actionCollection->setConfigGroup("Desktop Switching"); - m_actionCollection->setConfigGlobal(true); - - m_switchDesktopCollection = new KActionCollection(this, QStringLiteral("kwin")); - m_switchDesktopCollection->setConfigGroup("Desktop Switching"); - m_switchDesktopCollection->setConfigGlobal(true); - - // actions for switch desktop collection - other action is filled dynamically - addAction("Switch to Next Desktop", i18n("Switch to Next Desktop")); - addAction("Switch to Previous Desktop", i18n("Switch to Previous Desktop")); - addAction("Switch One Desktop to the Right", i18n("Switch One Desktop to the Right")); - addAction("Switch One Desktop to the Left", i18n("Switch One Desktop to the Left")); - addAction("Switch One Desktop Up", i18n("Switch One Desktop Up")); - addAction("Switch One Desktop Down", i18n("Switch One Desktop Down")); - addAction("Walk Through Desktops", i18n("Walk Through Desktops")); - addAction("Walk Through Desktops (Reverse)", i18n("Walk Through Desktops (Reverse)")); - addAction("Walk Through Desktop List", i18n("Walk Through Desktop List")); - addAction("Walk Through Desktop List (Reverse)", i18n("Walk Through Desktop List (Reverse)")); - - m_editor->addCollection(m_switchDesktopCollection, i18n("Desktop Switching")); - - // get number of desktops - int n = 1; - if (QX11Info::isPlatformX11()) { - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames); - n = info.numberOfDesktops(); - } - - auto addSwitchTo = [this](int i, const QKeySequence &sequence) { - QAction* a = m_actionCollection->addAction(QString("Switch to Desktop %1").arg(i)); - a->setProperty("isConfigurationAction", true); - a->setText(i18n("Switch to Desktop %1", i)); - KGlobalAccel::setGlobalShortcut(a, sequence); - }; - if (n >= 2) { - addSwitchTo(1, Qt::CTRL + Qt::Key_F1); - addSwitchTo(2, Qt::CTRL + Qt::Key_F2); - } - if (n >= 3) { - addSwitchTo(3, Qt::CTRL + Qt::Key_F3); - } - if (n >= 4) { - addSwitchTo(4, Qt::CTRL + Qt::Key_F4); - } - for (int i = 5; i <= n; ++i) { - addSwitchTo(i, QKeySequence()); - } - - // This should be after the "Switch to Desktop %1" loop. It HAS to be - // there after numberSpinBox is connected to slotChangeShortcuts. We would - // overwrite the users settings if not, - m_ui->numberSpinBox->setValue(n); - - m_editor->addCollection(m_actionCollection, i18n("Desktop Switching")); - - // search the effect names - // TODO: way to recognize if a effect is not found - KServiceTypeTrader* trader = KServiceTypeTrader::self(); - QString fadedesktop; - KService::List services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_fadedesktop'"); - if (!services.isEmpty()) - fadedesktop = services.first()->name(); - - m_ui->effectComboBox->addItem(i18n("No Animation")); - m_ui->effectComboBox->addItem(BuiltInEffects::effectData(BuiltInEffect::Slide).displayName); - m_ui->effectComboBox->addItem(BuiltInEffects::effectData(BuiltInEffect::CubeSlide).displayName); - m_ui->effectComboBox->addItem(fadedesktop); - - // effect config and info button - m_ui->effectInfoButton->setIcon(QIcon::fromTheme("dialog-information")); - m_ui->effectConfigButton->setIcon(QIcon::fromTheme("configure")); - - connect(m_ui->rowsSpinBox, SIGNAL(valueChanged(int)), SLOT(changed())); - connect(m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(changed())); - connect(m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(slotChangeShortcuts(int))); - connect(m_ui->desktopNames, SIGNAL(changed()), SLOT(changed())); - connect(m_ui->popupInfoCheckBox, SIGNAL(toggled(bool)), SLOT(changed())); - connect(m_ui->popupHideSpinBox, SIGNAL(valueChanged(int)), SLOT(changed())); - connect(m_ui->desktopLayoutIndicatorCheckBox, SIGNAL(stateChanged(int)), SLOT(changed())); - connect(m_ui->wrapAroundBox, SIGNAL(stateChanged(int)), SLOT(changed())); - connect(m_editor, SIGNAL(keyChange()), SLOT(changed())); - connect(m_ui->allShortcutsCheckBox, SIGNAL(stateChanged(int)), SLOT(slotShowAllShortcuts())); - connect(m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changed())); - connect(m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotEffectSelectionChanged(int))); - connect(m_ui->effectInfoButton, SIGNAL(clicked()), SLOT(slotAboutEffectClicked())); - connect(m_ui->effectConfigButton, SIGNAL(clicked()), SLOT(slotConfigureEffectClicked())); - - // Begin check for immutable - taken from old desktops kcm - int kwin_screen_number = QX11Info::appScreen(); - - m_config = KSharedConfig::openConfig("kwinrc"); - - QByteArray groupname; - if (kwin_screen_number == 0) - groupname = "Desktops"; - else - groupname = "Desktops-screen-" + QByteArray::number(kwin_screen_number); - - if (m_config->isGroupImmutable(groupname)) { - m_ui->nameGroup->setEnabled(false); - //number of desktops widgets - m_ui->numberLabel->setEnabled(false); - m_ui->numberSpinBox->setEnabled(false); - m_ui->rowsSpinBox->setEnabled(false); - } else { - KConfigGroup cfgGroup(m_config.data(), groupname.constData()); - if (cfgGroup.isEntryImmutable("Number")) { - //number of desktops widgets - m_ui->numberLabel->setEnabled(false); - m_ui->numberSpinBox->setEnabled(false); - m_ui->rowsSpinBox->setEnabled(false); - } - } - // End check for immutable -} - -KWinDesktopConfig::~KWinDesktopConfig() -{ - undo(); -} - -void KWinDesktopConfig::addAction(const QString &name, const QString &label) -{ - QAction* a = m_switchDesktopCollection->addAction(name); - a->setProperty("isConfigurationAction", true); - a->setText(label); - KGlobalAccel::setGlobalShortcut(a, QKeySequence()); -} - -void KWinDesktopConfig::defaults() -{ - // TODO: plasma stuff - m_ui->numberSpinBox->setValue(defaultDesktops); - m_ui->desktopNames->numberChanged(defaultDesktops); - for (int i = 1; i <= maxDesktops; i++) { - m_desktopNames[i-1] = i18n("Desktop %1", i); - if (i <= defaultDesktops) - m_ui->desktopNames->setDefaultName(i); - } - - // popup info - m_ui->popupInfoCheckBox->setChecked(false); - m_ui->popupHideSpinBox->setValue(1000); - m_ui->desktopLayoutIndicatorCheckBox->setChecked(true); - - m_ui->effectComboBox->setCurrentIndex(1); - - m_ui->wrapAroundBox->setChecked(true); - - m_ui->rowsSpinBox->setValue(2); - - m_editor->allDefault(); - - emit changed(true); -} - - -void KWinDesktopConfig::load() -{ - // This method is called on reset(). So undo all changes. - undo(); - - if (QX11Info::isPlatformX11()) { - // get number of desktops - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout); - - for (int i = 1; i <= maxDesktops; i++) { - QString name = QString::fromUtf8(info.desktopName(i)); - m_desktopNames << name; - m_ui->desktopNames->setName(i, name); - } - m_ui->rowsSpinBox->setValue(info.desktopLayoutColumnsRows().height()); - } else { - // TODO: proper implementation - m_ui->rowsSpinBox->setValue(1); - } - - // Popup info - KConfigGroup effectconfig(m_config, "Plugins"); - KConfigGroup popupInfo(m_config, "Script-desktopchangeosd"); - m_ui->popupInfoCheckBox->setChecked(effectconfig.readEntry("desktopchangeosdEnabled", false)); - m_ui->popupHideSpinBox->setValue(popupInfo.readEntry("PopupHideDelay", 1000)); - m_ui->desktopLayoutIndicatorCheckBox->setChecked(!popupInfo.readEntry("TextOnly", false)); - - // Wrap Around on screen edge - KConfigGroup windowConfig(m_config, "Windows"); - m_ui->wrapAroundBox->setChecked(windowConfig.readEntry("RollOverDesktops", true)); - - // Effect for desktop switching - // Set current option to "none" if no plugin is activated. - m_ui->effectComboBox->setCurrentIndex(0); - auto enableBuiltInEffect = [&effectconfig,this](BuiltInEffect effect, int index) { - const QString key = BuiltInEffects::nameForEffect(effect) + QStringLiteral("Enabled"); - if (effectconfig.readEntry(key, BuiltInEffects::enabledByDefault(effect))) { - m_ui->effectComboBox->setCurrentIndex(index); - } - }; - enableBuiltInEffect(BuiltInEffect::Slide, 1); - enableBuiltInEffect(BuiltInEffect::CubeSlide, 2); - if (effectEnabled("fadedesktop", effectconfig)) - m_ui->effectComboBox->setCurrentIndex(3); - slotEffectSelectionChanged(m_ui->effectComboBox->currentIndex()); - // TODO: plasma stuff - - emit changed(false); -} - -void KWinDesktopConfig::save() -{ - // TODO: plasma stuff - - const int numberDesktops = m_ui->numberSpinBox->value(); - int rows = m_ui->rowsSpinBox->value(); - rows = qBound(1, rows, numberDesktops); - // avoid weird cases like having 3 rows for 4 desktops, where the last row is unused - int columns = numberDesktops / rows; - if (numberDesktops % rows > 0) { - columns++; - } - - if (QX11Info::isPlatformX11()) { - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout); - // set desktop names - for (int i = 1; i <= maxDesktops; i++) { - QString desktopName = m_desktopNames[ i -1 ]; - if (i <= m_ui->numberSpinBox->value()) - desktopName = m_ui->desktopNames->name(i); - info.setDesktopName(i, desktopName.toUtf8()); - info.activate(); - } - // set number of desktops - info.setNumberOfDesktops(numberDesktops); - info.activate(); - info.setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); - info.activate(); - - XSync(QX11Info::display(), false); - } - - // save the desktops - QString groupname; - const int screenNumber = QX11Info::appScreen(); - if (screenNumber == 0) - groupname = "Desktops"; - else - groupname.sprintf("Desktops-screen-%d", screenNumber); - KConfigGroup group(m_config, groupname); - group.writeEntry("Rows", rows); - - // Popup info - KConfigGroup effectconfig(m_config, "Plugins"); - KConfigGroup popupInfo(m_config, "Script-desktopchangeosd"); - effectconfig.writeEntry("desktopchangeosdEnabled", m_ui->popupInfoCheckBox->isChecked()); - popupInfo.writeEntry("PopupHideDelay", m_ui->popupHideSpinBox->value()); - popupInfo.writeEntry("TextOnly", !m_ui->desktopLayoutIndicatorCheckBox->isChecked()); - - // Wrap Around on screen edge - KConfigGroup windowConfig(m_config, "Windows"); - windowConfig.writeEntry("RollOverDesktops", m_ui->wrapAroundBox->isChecked()); - - // Effect desktop switching - int desktopSwitcher = m_ui->effectComboBox->currentIndex(); - bool slideEnabled = false; - bool cubeSlideEnabled = false; - bool fadeEnabled = false; - switch(desktopSwitcher) { - case 1: - // slide - slideEnabled = true; - break; - case 2: - // cube - cubeSlideEnabled = true; - break; - case 3: - // fadedesktop - fadeEnabled = true; - break; - } - - effectconfig.writeEntry("slideEnabled", slideEnabled); - effectconfig.writeEntry("cubeslideEnabled", cubeSlideEnabled); - effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", fadeEnabled); - - m_editor->save(); - - m_config->sync(); - // Send signal to all kwin instances - QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); - QDBusConnection::sessionBus().send(message); - // and reconfigure the effects - OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), - QStringLiteral("/Effects"), - QDBusConnection::sessionBus()); - if (slideEnabled) { - interface.loadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::Slide)); - } else { - interface.unloadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::Slide)); - } - if (cubeSlideEnabled) { - interface.loadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::CubeSlide)); - } else { - interface.unloadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::CubeSlide)); - } - if (fadeEnabled) { - interface.loadEffect(QStringLiteral("kwin4_effect_fadedesktop")); - } else { - interface.unloadEffect(QStringLiteral("kwin4_effect_fadedesktop")); - } - - emit changed(false); -} - - -void KWinDesktopConfig::undo() -{ - // The global shortcuts editor makes changes active immediately. In case - // of undo we have to undo them manually - m_editor->undoChanges(); -} - -QString KWinDesktopConfig::cachedDesktopName(int desktop) -{ - if (desktop > m_desktopNames.size()) - return QString(); - return m_desktopNames[ desktop -1 ]; -} - -QString KWinDesktopConfig::extrapolatedShortcut(int desktop) const -{ - - if (!desktop || desktop > m_actionCollection->count()) - return QString(); - if (desktop == 1) - return QString("Ctrl+F1"); - - QAction *beforeAction = m_actionCollection->actions().at(qMin(9, desktop - 2)); - auto shortcuts = KGlobalAccel::self()->shortcut(beforeAction); - if (shortcuts.isEmpty()) { - shortcuts = KGlobalAccel::self()->defaultShortcut(beforeAction); - } - QString before; - if (!shortcuts.isEmpty()) { - before = shortcuts.first().toString(QKeySequence::PortableText); - } - - QString seq; - if (before.contains(QRegExp("F[0-9]{1,2}"))) { - if (desktop < 13) // 10? - seq = QString("F%1").arg(desktop); - else if (!before.contains("Shift")) - seq = "Shift+" + QString("F%1").arg(desktop - 10); - } else if (before.contains(QRegExp("[0-9]"))) { - if (desktop == 10) - seq = '0'; - else if (desktop > 10) { - if (!before.contains("Shift")) - seq = "Shift+" + QString::number(desktop == 20 ? 0 : (desktop - 10)); - } else - seq = QString::number(desktop); - } - - if (!seq.isEmpty()) { - if (before.contains("Ctrl")) - seq.prepend("Ctrl+"); - if (before.contains("Alt")) - seq.prepend("Alt+"); - if (before.contains("Shift")) - seq.prepend("Shift+"); - if (before.contains("Meta")) - seq.prepend("Meta+"); - } - return seq; -} - -void KWinDesktopConfig::slotChangeShortcuts(int number) -{ - if ((number < 1) || (number > maxDesktops)) - return; - - if (m_ui->allShortcutsCheckBox->isChecked()) - number = maxDesktops; - - while (number != m_actionCollection->count()) { - if (number < m_actionCollection->count()) { - // Remove the action from the action collection. The action itself - // will still exist because that's the way kwin currently works. - // No need to remove/forget it. See kwinbindings. - QAction *a = m_actionCollection->takeAction(m_actionCollection->actions().last()); - // Remove any associated global shortcut. Set it to "" - KGlobalAccel::self()->setShortcut(a, QList(), KGlobalAccel::NoAutoloading); - m_ui->messageLabel->hide(); - delete a; - } else { - // add desktop - int desktop = m_actionCollection->count() + 1; - QAction* action = m_actionCollection->addAction(QString("Switch to Desktop %1").arg(desktop)); - action->setProperty("isConfigurationAction", true); - action->setText(i18n("Switch to Desktop %1", desktop)); - KGlobalAccel::self()->setShortcut(action, QList()); - QString shortcutString = extrapolatedShortcut(desktop); - if (shortcutString.isEmpty()) { - m_ui->messageLabel->setText(i18n("No suitable Shortcut for Desktop %1 found", desktop)); - m_ui->messageLabel->show(); - } else { - QKeySequence shortcut(shortcutString); - if (!shortcut.isEmpty() && KGlobalAccel::self()->isGlobalShortcutAvailable(shortcut)) { - KGlobalAccel::self()->setShortcut(action, QList() << shortcut, KGlobalAccel::NoAutoloading); - m_ui->messageLabel->setText(i18n("Assigned global Shortcut \"%1\" to Desktop %2", shortcutString, desktop)); - m_ui->messageLabel->show(); - } else { - m_ui->messageLabel->setText(i18n("Shortcut conflict: Could not set Shortcut %1 for Desktop %2", shortcutString, desktop)); - m_ui->messageLabel->show(); - } - } - } - } - m_editor->clearCollections(); - m_editor->addCollection(m_switchDesktopCollection, i18n("Desktop Switching")); - m_editor->addCollection(m_actionCollection, i18n("Desktop Switching")); -} - -void KWinDesktopConfig::slotShowAllShortcuts() -{ - slotChangeShortcuts(m_ui->numberSpinBox->value()); -} - -void KWinDesktopConfig::slotEffectSelectionChanged(int index) -{ - bool enabled = false; - if (index != 0) - enabled = true; - m_ui->effectInfoButton->setEnabled(enabled); - - switch (index) { - case 1: // Slide - case 2: // Cube Slide - enabled = true; - break; - default: - enabled = false; - break; - } - m_ui->effectConfigButton->setEnabled(enabled); -} - - -bool KWinDesktopConfig::effectEnabled(const QString& effect, const KConfigGroup& cfg) const -{ - KService::List services = KServiceTypeTrader::self()->query( - "KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\''); - if (services.isEmpty()) - return false; - QVariant v = services.first()->property("X-KDE-PluginInfo-EnabledByDefault"); - return cfg.readEntry("kwin4_effect_" + effect + "Enabled", v.toBool()); -} - -void KWinDesktopConfig::slotAboutEffectClicked() -{ - QString effect; - bool fromKService = false; - BuiltInEffect builtIn = BuiltInEffect::Invalid; - switch(m_ui->effectComboBox->currentIndex()) { - case 1: - builtIn = BuiltInEffect::Slide; - break; - case 2: - builtIn = BuiltInEffect::CubeSlide; - break; - case 3: - effect = "fadedesktop"; - fromKService = true; - break; - default: - return; - } - auto showDialog = [this](const KAboutData &aboutData) { - QPointer aboutPlugin = new KAboutApplicationDialog(aboutData, this); - aboutPlugin->exec(); - delete aboutPlugin; - }; - if (fromKService) { - KServiceTypeTrader* trader = KServiceTypeTrader::self(); - KService::List services; - services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\''); - if (services.isEmpty()) - return; - KPluginInfo pluginInfo(services.first()); - - const QString name = pluginInfo.name(); - const QString comment = pluginInfo.comment(); - const QString author = pluginInfo.author(); - const QString email = pluginInfo.email(); - const QString website = pluginInfo.website(); - const QString version = pluginInfo.version(); - const QString license = pluginInfo.license(); - const QString icon = pluginInfo.icon(); - - KAboutData aboutData(name, name, version, comment, KAboutLicense::byKeyword(license).key(), QString(), QString(), website.toLatin1()); - aboutData.setProgramLogo(icon); - const QStringList authors = author.split(','); - const QStringList emails = email.split(','); - int i = 0; - if (authors.count() == emails.count()) { - foreach (const QString & author, authors) { - if (!author.isEmpty()) { - aboutData.addAuthor(i18n(author.toUtf8()), QString(), emails[i]); - } - i++; - } - } - showDialog(aboutData); - } else { - const BuiltInEffects::EffectData &data = BuiltInEffects::effectData(builtIn); - KAboutData aboutData(data.name, - data.displayName, - QStringLiteral(KWIN_VERSION_STRING), - data.comment, - KAboutLicense::GPL_V2); - aboutData.setProgramLogo(QIcon::fromTheme(QStringLiteral("preferences-system-windows"))); - aboutData.addAuthor(i18n("KWin development team")); - showDialog(aboutData); - } -} - -void KWinDesktopConfig::slotConfigureEffectClicked() -{ - QString effect; - switch(m_ui->effectComboBox->currentIndex()) { - case 1: - effect = BuiltInEffects::nameForEffect(BuiltInEffect::Slide); - break; - case 2: - effect = BuiltInEffects::nameForEffect(BuiltInEffect::CubeSlide); - break; - default: - return; - } - - QPointer configDialog = new QDialog(this); - KCModule *kcm = KPluginTrader::createInstanceFromQuery(QStringLiteral("kwin/effects/configs/"), QString(), - QStringLiteral("'%1' in [X-KDE-ParentComponents]").arg(effect), - configDialog); - if (!kcm) { - delete configDialog; - return; - } - configDialog->setWindowTitle(m_ui->effectComboBox->currentText()); - configDialog->setLayout(new QVBoxLayout); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults, configDialog); - connect(buttons, SIGNAL(accepted()), configDialog, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), configDialog, SLOT(reject())); - connect(buttons->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked(bool)), kcm, SLOT(defaults())); - - QWidget *showWidget = new QWidget(configDialog); - QVBoxLayout *layout = new QVBoxLayout; - showWidget->setLayout(layout); - layout->addWidget(kcm); - configDialog->layout()->addWidget(showWidget); - configDialog->layout()->addWidget(buttons); - - if (configDialog->exec() == QDialog::Accepted) { - kcm->save(); - } else { - kcm->load(); - } - delete configDialog; -} - -} // namespace - -#include "main.moc" diff --git a/kcmkwin/kwindesktop/main.ui b/kcmkwin/kwindesktop/main.ui deleted file mode 100644 --- a/kcmkwin/kwindesktop/main.ui +++ /dev/null @@ -1,326 +0,0 @@ - - - KWinDesktopConfigForm - - - - 0 - 0 - 572 - 310 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - Desktops - - - - - - Layout - - - - QFormLayout::AllNonFixedFieldsGrow - - - 0 - - - - - Here you can set how many virtual desktops you want on your KDE desktop. - - - &Number of desktops: - - - numberSpinBox - - - - - - - Here you can set how many virtual desktops you want on your KDE desktop. - - - 1 - - - 20 - - - 4 - - - - - - - true - - - N&umber of rows: - - - rowsSpinBox - - - - - - - true - - - 1 - - - 20 - - - - - - - - - - Desktop Names - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - Switching - - - - - - Enable this option if you want keyboard or active desktop border navigation beyond the edge of a desktop to take you to the opposite edge of the new desktop. - - - Desktop navigation wraps around - - - - - - - Desktop Effect Animation - - - - QFormLayout::ExpandingFieldsGrow - - - 0 - - - - - Animation: - - - effectComboBox - - - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - Desktop Switch On-Screen Display - - - true - - - false - - - - QFormLayout::AllNonFixedFieldsGrow - - - 0 - - - - - Duration: - - - popupHideSpinBox - - - - - - - msec - - - 5000 - - - 50 - - - - - - - Enabling this option will show a small preview of the desktop layout indicating the selected desktop. - - - Show desktop layout indicators - - - - - - - - - - Shortcuts - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - - - - Show shortcuts for all possible desktops - - - - - - - - - - - - - - - KWin::DesktopNamesWidget - QWidget -
desktopnameswidget.h
- 1 - - numberChanged(int) - -
-
- - - - numberSpinBox - valueChanged(int) - desktopNames - numberChanged(int) - - - 327 - 144 - - - 326 - 209 - - - - -
diff --git a/kcmkwin/kwindesktop/package/contents/ui/main.qml b/kcmkwin/kwindesktop/package/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/kcmkwin/kwindesktop/package/contents/ui/main.qml @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2018 Eike Hein + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 as QtControls +import org.kde.kirigami 2.5 as Kirigami +import org.kde.plasma.core 2.1 as PlasmaCore +import org.kde.kcm 1.2 + +ScrollViewKCM { + id: root + + ConfigModule.quickHelp: i18n("Virtual Desktops") + + Connections { + target: kcm.desktopsModel + + onReadyChanged: { + rowsSpinBox.value = kcm.desktopsModel.rows; + } + + onRowsChanged: { + rowsSpinBox.value = kcm.desktopsModel.rows; + } + } + + Component { + id: desktopsListItemComponent + + Kirigami.SwipeListItem { + id: listItem + + contentItem: RowLayout { + QtControls.TextField { + id: nameField + + background: null + leftPadding: Kirigami.Units.largeSpacing + topPadding: 0 + bottomPadding: 0 + + Layout.fillWidth: true + + Layout.alignment: Qt.AlignVCenter + + text: model.display + + readOnly: true + + onAccepted: { + readOnly = true; + Qt.callLater(kcm.desktopsModel.setDesktopName, model.Id, text); + } + } + } + + actions: [ + Kirigami.Action { + enabled: !model.IsMissing + iconName: "edit-rename" + tooltip: i18nc("@info:tooltip", "Rename") + onTriggered: { + nameField.readOnly = false; + nameField.selectAll(); + nameField.forceActiveFocus(); + } + }, + Kirigami.Action { + enabled: !model.IsMissing + iconName: "list-remove" + tooltip: i18nc("@info:tooltip", "Remove") + onTriggered: kcm.desktopsModel.removeDesktop(model.Id) + }] + } + } + + header: ColumnLayout { + id: messagesLayout + + spacing: Kirigami.Units.largeSpacing + + Kirigami.InlineMessage { + Layout.fillWidth: true + + type: Kirigami.MessageType.Error + + text: kcm.desktopsModel.error + + visible: kcm.desktopsModel.error != "" + } + + Kirigami.InlineMessage { + Layout.fillWidth: true + + type: Kirigami.MessageType.Information + + text: i18n("Virtual desktops have been changed outside this settings application. Saving now will overwrite the changes.") + + visible: kcm.desktopsModel.serverModified + } + + QtControls.Label { + Layout.fillWidth: true + + text: i18n("Manage virtual desktops.") + } + } + + view: ListView { + id: desktopsList + + model: kcm.desktopsModel.ready ? kcm.desktopsModel : null + + section.property: "DesktopRow" + section.delegate: Kirigami.AbstractListItem { + width: desktopsList.width + + backgroundColor: Kirigami.Theme.backgroundColor + + hoverEnabled: false + supportsMouseEvents: false + + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.Window + + QtControls.Label { + text: i18n("Row %1", section) + } + } + + delegate: Kirigami.DelegateRecycler { + width: desktopsList.width + + sourceComponent: desktopsListItemComponent + } + } + + footer: RowLayout { + QtControls.Label { + text: i18n("Rows:") + } + + QtControls.SpinBox { + id: rowsSpinBox + + from: 1 + + onValueModified: kcm.desktopsModel.rows = value + } + + Item { // Spacer + Layout.fillWidth: true + } + + QtControls.Button { + Layout.alignment: Qt.AlignRight + + text: i18nc("@action:button", "Add") + icon.name: "list-add" + + onClicked: kcm.desktopsModel.createDesktop(i18n("New Desktop")) + } + } +} + diff --git a/kcmkwin/kwindesktop/package/metadata.desktop b/kcmkwin/kwindesktop/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/kcmkwin/kwindesktop/package/metadata.desktop @@ -0,0 +1,116 @@ +[Desktop Entry] +Name=Virtual Desktops +Name[ar]=أسطح المكتب الافتراضية +Name[bg]=Виртуални работни плотове +Name[bs]=Virtuelne površi +Name[ca]=Escriptoris virtuals +Name[ca@valencia]=Escriptoris virtuals +Name[cs]=Virtuální plochy +Name[da]=Virtuelle skriveborde +Name[de]=Virtuelle Arbeitsflächen +Name[el]=Εικονικές επιφάνειες εργασίες +Name[en_GB]=Virtual Desktops +Name[es]=Escritorios virtuales +Name[et]=Virtuaalsed töölauad +Name[eu]=Alegiazko mahaigaina +Name[fi]=Virtuaalityöpöydät +Name[fr]=Bureaux virtuels +Name[ga]=Deasca Fíorúla +Name[gl]=Escritorios virtuais +Name[gu]=વર્ચ્યુઅલ ડેસ્કટોપો +Name[he]=שולחנות עבודה וירטואליים +Name[hi]=आभासी डेस्कटॉप +Name[hr]=Virtualne radne površine +Name[hu]=Virtuális asztalok +Name[ia]=Scriptorios virtual +Name[id]=Desktop Virtual +Name[is]=Sýndarskjáborð +Name[it]=Desktop virtuali +Name[ja]=仮想デスクトップ +Name[kk]=Виртуалды Үстелдер +Name[km]=ផ្ទៃតុ​និម្មិត +Name[kn]=ವಾಸ್ತವಪ್ರಾಯ ಗಣಕತೆರೆಗಳು +Name[ko]=가상 데스크톱 +Name[lt]=Virtualūs darbalaukiai +Name[lv]=Virtuālās darbvirsmas +Name[mr]=आभासी डेस्कटॉप +Name[nb]=Virtuelle skrivebord +Name[nds]=Mehr Schriefdischen +Name[nl]=Virtuele bureaubladen +Name[nn]=Virtuelle skrivebord +Name[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ +Name[pl]=Pulpity wirtualne +Name[pt]=Ecrãs Virtuais +Name[pt_BR]=Áreas de trabalho virtuais +Name[ro]=Birouri virtuale +Name[ru]=Рабочие столы +Name[si]=අත්ථ්‍ය වැඩතල +Name[sk]=Virtuálne pracovné plochy +Name[sl]=Navidezna namizja +Name[sr]=Виртуелне површи +Name[sr@ijekavian]=Виртуелне површи +Name[sr@ijekavianlatin]=Virtuelne površi +Name[sr@latin]=Virtuelne površi +Name[sv]=Virtuella skrivbord +Name[tg]=Мизҳои кории виртуалӣ +Name[th]=พื้นที่ทำงานเสมือน +Name[tr]=Sanal Masaüstleri +Name[ug]=مەۋھۇم ئۈستەلئۈستى +Name[uk]=Віртуальні стільниці +Name[wa]=Forveyous scribannes +Name[x-test]=xxVirtual Desktopsxx +Name[zh_CN]=虚拟桌面 +Name[zh_TW]=虛擬桌面 + +Comment=Navigation, Number and Layout of Virtual Desktops +Comment[bs]=Navigacija, broj i izgled virtualnih desktopa +Comment[ca]=Navegació, nombre i disposició dels escriptoris virtuals +Comment[ca@valencia]=Navegació, nombre i disposició dels escriptoris virtuals +Comment[cs]=Navigace, počet a rozvržení virtuálních ploch +Comment[da]=Navigation, antal og layout af virtuelle skriveborde +Comment[de]=Navigation, Anzahl und Layout virtueller Arbeitsflächen +Comment[el]=Περιήγηση, αριθμός και διάταξη εικονικών επιφανειών εργασίας +Comment[en_GB]=Navigation, Number and Layout of Virtual Desktops +Comment[es]=Navegación, número y disposición de los escritorios virtuales +Comment[et]=Virtuaalsete töölaudade vahel liikumine, nende arv ja paigutus +Comment[eu]=Nabigazioa, alegiazko mahaigainen kopurua eta antolamendua +Comment[fi]=Virtuaalityöpöytien vaihtaminen, määrä ja asettelu +Comment[fr]=Navigation, nombre et disposition des bureaux virtuels +Comment[gl]=Navegación, cantidade e disposición dos escritorios virtuais +Comment[he]=ניווט, פריסה ומספר שולחנות עבודה וירטואלים +Comment[hu]=Navigáció, a virtuális asztalok száma és elrendezése +Comment[id]=Navigasi, Jumlah dan Tata Letak Desktop Virtual +Comment[it]=Navigazione, numero e disposizione dei desktop virtuali +Comment[ko]=가상 데스크톱 탐색, 개수, 레이아웃 +Comment[lt]=Naršymas, Skaičius ir išdėstymas virtualių darbalaukių +Comment[nb]=Navigering, antall og utlegg av virtuelle skrivebord +Comment[nds]=Tall, Anornen un dat Anstüern vun de virtuellen Schriefdischen fastleggen +Comment[nl]=Navigatie door, aantal en indeling van virtuele bureaubladen +Comment[nn]=Navigering, nummer og vising av virtuelle skrivebord +Comment[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪਾਂ ਲਈ ਨੇਵੀਗੇਸ਼ਨ, ਗਿਣਤੀ ਅਤੇ ਢਾਂਚਾ +Comment[pl]=Poruszanie się, liczba i układ wirtualnych pulpitów +Comment[pt]=Navegação, Número e Disposição dos Ecrãs Virtuais +Comment[pt_BR]=Navegação, quantidade e layout das áreas de trabalho virtuais +Comment[ru]=Число, расположение и способ переключения рабочих столов +Comment[sk]=Navigácia, počet a rozloženie virtuálnych plôch +Comment[sl]=Krmarjenje med, število in razporeditev navideznih namizij +Comment[sr]=Кретање, број и распоред виртуелних површи +Comment[sr@ijekavian]=Кретање, број и распоред виртуелних површи +Comment[sr@ijekavianlatin]=Kretanje, broj i raspored virtuelnih površi +Comment[sr@latin]=Kretanje, broj i raspored virtuelnih površi +Comment[sv]=Navigering, antal och layout av virtuella skrivbord +Comment[tr]=Gezinti, Sanal Masaüstlerinin Sayısı ve Yerleşimi +Comment[uk]=Навігація, кількість та компонування віртуальних стільниць +Comment[vi]=Số lượng, bố trí và điều hướng của màn hình ảo +Comment[x-test]=xxNavigation, Number and Layout of Virtual Desktopsxx +Comment[zh_CN]=虚拟桌面的切换,数量和布局 +Comment[zh_TW]=虛擬桌面的導覽、數字與佈局 + +Icon=preferences-desktop +Type=Service +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=kcm_kwin_virtualdesktops +X-KDE-ServiceTypes=Plasma/Generic +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml diff --git a/kcmkwin/kwindesktop/virtualdesktops.h b/kcmkwin/kwindesktop/virtualdesktops.h new file mode 100644 --- /dev/null +++ b/kcmkwin/kwindesktop/virtualdesktops.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 Eike Hein + * + * 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 VIRTUALDESKTOPS_H +#define VIRTUALDESKTOPS_H + +#include + +namespace KWin +{ + +class DesktopsModel; + +class VirtualDesktops : public KQuickAddons::ConfigModule +{ + Q_OBJECT + + Q_PROPERTY(QAbstractItemModel* desktopsModel READ desktopsModel CONSTANT) + + public: + explicit VirtualDesktops(QObject *parent = nullptr, const QVariantList &list = QVariantList()); + ~VirtualDesktops() override; + + QAbstractItemModel *desktopsModel() const; + + public Q_SLOTS: + void load() override; + void save() override; + void defaults() override; + + private: + DesktopsModel *m_desktopsModel; +}; + +} + +#endif diff --git a/kcmkwin/kwindesktop/virtualdesktops.cpp b/kcmkwin/kwindesktop/virtualdesktops.cpp new file mode 100644 --- /dev/null +++ b/kcmkwin/kwindesktop/virtualdesktops.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 Eike Hein + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "virtualdesktops.h" +#include "desktopsmodel.h" + +#include + +#include +#include +#include + +K_PLUGIN_FACTORY_WITH_JSON(VirtualDesktopsFactory, "kcm_kwin_virtualdesktops.json", registerPlugin();) + +namespace KWin +{ + +VirtualDesktops::VirtualDesktops(QObject *parent, const QVariantList &args) + : KQuickAddons::ConfigModule(parent, args) + , m_desktopsModel(new KWin::DesktopsModel(this)) +{ + KAboutData *about = new KAboutData(QStringLiteral("kcm_kwin_virtualdesktops"), + i18n("Configure Virtual Desktops"), + QStringLiteral("2.0"), QString(), KAboutLicense::GPL); + setAboutData(about); + + setButtons(Apply | Default); + + QObject::connect(m_desktopsModel, &KWin::DesktopsModel::needsSaving, + this, &KQuickAddons::ConfigModule::setNeedsSave); +} + +VirtualDesktops::~VirtualDesktops() +{ +} + +QAbstractItemModel *VirtualDesktops::desktopsModel() const +{ + return m_desktopsModel; +} + +void VirtualDesktops::load() +{ + // This KCM doesn't have any configuration to load from. Initial state is retrieved + // from KWin via DBus in `DesktopsModel`. +} + +void VirtualDesktops::save() +{ + m_desktopsModel->syncWithServer(); +} + +void VirtualDesktops::defaults() +{ + m_desktopsModel->setRows(1); + m_desktopsModel->syncWithServer(); +} + +} + +#include "virtualdesktops.moc" diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -934,6 +934,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; @@ -1990,7 +1994,23 @@ 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 @@ -760,6 +760,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; @@ -794,7 +801,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") @@ -818,6 +827,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(); @@ -943,12 +957,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 @@ -285,6 +285,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; @@ -776,7 +777,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 @@ -786,7 +792,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 **/ @@ -308,6 +352,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 +382,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 +453,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 +492,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,77 @@ // 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); + 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 +660,8 @@ if (!m_config) { return; } + //FIXME: how to avoid this? + m_isLoading = true; QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); @@ -446,14 +671,30 @@ 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()); + + s = group.readEntry(QStringLiteral("Id_%1").arg(i), QString()); + if (s.isEmpty()) { + s = QUuid::createUuid().toString(); + } + //load gets called 2 times, see workspace.cpp line 416 and BUG 385260 + if (m_desktops[i-1]->id().isEmpty()) { + m_desktops[i-1]->setId(s.toUtf8().data()); + } else { + Q_ASSERT(m_desktops[i-1]->id() == s.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 +705,9 @@ m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); m_rootInfo->activate(); } + s_loadingDesktopSettings = false; + m_isLoading = false; } void VirtualDesktopManager::save() @@ -502,6 +745,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 +780,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; } @@ -226,6 +230,7 @@ KWayland::Server::XdgShellInterface *m_xdgShell6 = 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 @@ -328,6 +329,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(); @@ -385,6 +392,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)