diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -84,13 +84,18 @@ 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. **/ Q_PROPERTY(bool skipTaskbar READ skipTaskbar WRITE setSkipTaskbar NOTIFY skipTaskbarChanged) @@ -414,9 +419,15 @@ 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; + } + QList x11DesktopIds() const; + void setMinimized(bool set); /** * Minimizes this client plus its transients @@ -736,6 +747,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 +1097,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,43 @@ 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); + + if (m_desktops.contains(virtualDesktop)) { return; + } - int was_desk = m_desktop; + int was_desk = AbstractClient::desktop(); const bool wasOnCurrentDesktop = isOnCurrentDesktop(); - m_desktop = desktop; + + //can't check windowManagementInterface yet as it gets created only on first show + //on x11 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 (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 +548,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 +582,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 +858,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 +953,55 @@ 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()); + } + } + ); + + //set initial visibility + if (m_desktops.isEmpty() || m_desktops.contains(VirtualDesktopManager::self()->currentDesktop())) { + emit windowShown(this); + } else { + workspace()->clientHidden(this); + } + m_windowManagementInterface = w; } 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/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::ShiftModifier)) { + wasWindowCopy = false; + effects->defineCursor(Qt::ClosedHandCursor); + } else { + wasWindowCopy = true; + effects->defineCursor(Qt::DragCopyCursor); + } 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(); + sourceDesktop = posToDesktop(me->pos()); bool isDesktop = (me->modifiers() & Qt::ControlModifier); EffectWindow* w = isDesktop ? NULL : windowAt(me->pos()); if (w != NULL) isDesktop = w->isDesktop(); if (isDesktop) m_originalMovingDesktop = posToDesktop(me->pos()); + else + m_originalMovingDesktop = 0; if (w != NULL && !w->isDesktop() && (w->isMovable() || w->isMovableAcrossScreens() || isUsingPresentWindows())) { // Prepare it for moving windowMoveDiff = w->pos() - unscalePos(me->pos(), NULL); @@ -590,12 +608,10 @@ w = nullptr; } if (w != NULL) { - int desktop = 0; + const int desktop = posToDesktop(me->pos()); if (w->isOnAllDesktops()) { - desktop = posToDesktop(me->pos()); effects->windowToDesktop(w, desktop); } else { - desktop = w->desktop(); effects->windowToDesktop(w, NET::OnAllDesktops); } const bool isOnAllDesktops = w->isOnAllDesktops(); @@ -630,7 +646,7 @@ } if (windowMove) { if (wasWindowMove && isUsingPresentWindows()) { - const int targetDesktop = windowMove->isOnAllDesktops() ? posToDesktop(cursorPos()) : windowMove->desktop(); + const int targetDesktop = posToDesktop(cursorPos()); foreach (const int i, desktopList(windowMove)) { WindowMotionManager& manager = m_managers[(i)*(effects->numScreens()) + windowMove->screen()]; manager.manage(windowMove); @@ -648,6 +664,7 @@ windowMove = NULL; } wasWindowMove = false; + wasWindowCopy = false; wasDesktopMove = false; } } @@ -1360,7 +1377,7 @@ // and repaint effects->addRepaintFull(); } - +//TODO: kill this function? or at least keep a consistent numeration with desktops starting from 1 QVector DesktopGridEffect::desktopList(const EffectWindow *w) const { if (w->isOnAllDesktops()) { @@ -1373,16 +1390,13 @@ return allDesktops; } - if (w->desktop() > effects->numberOfDesktops() || w->desktop() < 1) { // sic! desktops are [1,n] - static QVector emptyVector; - emptyVector.resize(0); - return emptyVector; + QVector desks; + desks.resize(w->desktops().count()); + int i = 0; + for (const int desk : w->desktops()) { + desks[i++] = desk-1; } - - static QVector singleDesktop; - singleDesktop.resize(1); - singleDesktop[0] = w->desktop() - 1; - return singleDesktop; + return desks; } bool DesktopGridEffect::isActive() const diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -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/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 @@ -145,6 +145,11 @@ **/ 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. **/ @@ -161,6 +166,12 @@ **/ 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 * * @param action Invoked Action containing the Screen as data element @@ -218,6 +229,10 @@ **/ QMenu* m_desktopMenu; /** + * The move to desktop sub menu, with the Wayland protocol. + **/ + QMenu* m_multipleDesktopsMenu; + /** * The move to screen sub menu. **/ QMenu* m_screenMenu; 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 @@ -264,6 +264,29 @@ 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(). + * @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 **/ void updateRootInfo(); @@ -334,18 +357,21 @@ * @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); + * 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 @@ -397,21 +423,6 @@ 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. */ void setNETDesktopLayout(Qt::Orientation orientation, uint width, uint height, int startingCorner); @@ -451,6 +462,7 @@ // TODO: QPointer NETRootInfo *m_rootInfo; 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 @@ -27,9 +27,10 @@ #include // Qt #include +#include #include - +#include namespace KWin { extern int screen_number; @@ -68,7 +69,7 @@ VirtualDesktopGrid::VirtualDesktopGrid() : m_size(1, 2) // Default to tow rows , m_grid(QVector>{QVector{}, QVector{}}) -{ +{ } VirtualDesktopGrid::~VirtualDesktopGrid() = default; @@ -325,6 +326,96 @@ 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(); + + 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); + } + + emit desktopRemoved(desktop); + + desktop->deleteLater(); +} + uint VirtualDesktopManager::current() const { return m_current ? m_current->x11DesktopNumber() : 0; @@ -364,33 +455,51 @@ // 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-1); + m_desktops.resize(count); + int oldCurrent = current(); + for (auto desktop : desktopsToRemove) { + emit desktopRemoved(desktop); + desktop->deleteLater(); + } + int newCurrent = qMin(oldCurrent, m_desktops.count()); + m_current = m_desktops.at(newCurrent - 1); + if (oldCurrent != newCurrent) { + emit currentChanged(oldCurrent, newCurrent); + } + } 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(); - emit countChanged(oldCount, m_desktops.count()); -} - -void VirtualDesktopManager::handleDesktopsRemoved(uint previousCount, uint previousCurrent) -{ - if (!m_current) { - m_current = m_desktops.last(); - emit currentChanged(previousCurrent, m_current->x11DesktopNumber()); + for (auto vd : newDesktops) { + emit desktopCreated(vd); } - emit desktopsRemoved(previousCount); + emit countChanged(oldCount, m_desktops.count()); } void VirtualDesktopManager::updateRootInfo() @@ -437,6 +546,8 @@ if (!m_config) { return; } + //FIXME: how to avoid this? + m_isLoading = true; QString groupname; if (screen_number == 0) { groupname = QStringLiteral("Desktops"); @@ -446,14 +557,31 @@ KConfigGroup group(m_config, groupname); const int n = group.readEntry("Number", 1); setCount(n); - if (m_rootInfo) { - for (int i = 1; i <= n; i++) { - QString s = group.readEntry(QStringLiteral("Name_%1").arg(i), i18n("Desktop %1", i)); + //Use kactivitymanagerdrc directly? + + 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 +592,9 @@ m_rootInfo->setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft); m_rootInfo->activate(); } + s_loadingDesktopSettings = false; + m_isLoading = false; } void VirtualDesktopManager::save() @@ -502,6 +632,8 @@ group.deleteEntry(QStringLiteral("Name_%1").arg(i)); } } + + group.writeEntry(QStringLiteral("Id_%1").arg(i), m_desktops[i-1]->id()); } // Save to disk 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,75 @@ void WaylandServer::initWorkspace() { + //TODO: RFC: those connections are better here or in VirtualDesktopManager itself? + //handle created: from VirtualDesktopManager to the wayland interface + connect(VirtualDesktopManager::self(), &VirtualDesktopManager::desktopCreated, this, + [this](VirtualDesktop *desktop) { + 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(VirtualDesktopManager::self(), &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 = VirtualDesktopManager::self()->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 + VirtualDesktopManager::self()->removeVirtualDesktop(id.toUtf8()); + } + ); + + for (quint32 i = 1; i <= VirtualDesktopManager::self()->count(); ++i) { + VirtualDesktop *internalDesktop = VirtualDesktopManager::self()->desktopForX11Id(i); + PlasmaVirtualDesktopInterface *desktop = m_virtualDesktopManagement->createDesktop(internalDesktop->id()); + + desktop->setName(desktop->name()); + desktop->sendDone(); + + connect(desktop, &PlasmaVirtualDesktopInterface::activateRequested, this, + [this, desktop] () { + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktopForId(desktop->id().toUtf8())); + } + ); + } + //Now we are sure all ids are there + VirtualDesktopManager::self()->save(); + + connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, + [this]() { + for (auto *deskInt : m_virtualDesktopManagement->desktops()) { + if (deskInt->id() == VirtualDesktopManager::self()->currentDesktop()->id()) { + deskInt->setActive(true); + } else { + deskInt->setActive(false); + } + } + } + ); + 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 @@ -219,7 +219,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) { + if (!(*it)->isOnAllDesktops() && (*it)->desktops().count() == 1) { + const VirtualDesktop *otherDesktop = (*it)->desktops().first(); + if (desktop == otherDesktop) { + 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 +255,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 +1100,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)