diff --git a/libtaskmanager/CMakeLists.txt b/libtaskmanager/CMakeLists.txt --- a/libtaskmanager/CMakeLists.txt +++ b/libtaskmanager/CMakeLists.txt @@ -46,6 +46,7 @@ Qt5::DBus KF5::Activities KF5::ConfigCore + KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::ProcessCore diff --git a/libtaskmanager/abstracttasksmodel.h b/libtaskmanager/abstracttasksmodel.h --- a/libtaskmanager/abstracttasksmodel.h +++ b/libtaskmanager/abstracttasksmodel.h @@ -52,7 +52,7 @@ GenericName, /**< Generic application name. */ LauncherUrl, /**< URL that can be used to launch this application (.desktop or executable). */ LauncherUrlWithoutIcon, /**< Special path to get a launcher URL while skipping fallback icon encoding. Used as speed optimization. */ - LegacyWinIdList, /**< X11 window ids. Stopgap until we have something better. */ + WinIdList, /**< NOTE: On Wayland, these ids are only useful within the same process. On X11, they are global window ids. */ MimeType, /**< MIME type for this task (window, window group), needed for DND. */ MimeData, /**< Data for MimeType. */ IsWindow, /**< This is a window task. */ @@ -76,8 +76,8 @@ IsFullScreen, /**< Task (i.e. window) is fullscreen. */ IsShadeable, /**< requestToggleShade (see below) available. */ IsShaded, /**< Task (i.e. window) is shaded. */ - IsVirtualDesktopChangeable, /**< requestVirtualDesktop (see below) available. */ - VirtualDesktop, /**< Virtual desktop for the task (i.e. window). */ + IsVirtualDesktopsChangeable, /**< requestVirtualDesktop (see below) available. */ + VirtualDesktops, /**< Virtual desktops for the task (i.e. window). */ IsOnAllVirtualDesktops, /**< Task is on all virtual desktops. */ Geometry, /**< The task's geometry (i.e. the window's). */ ScreenGeometry, /**< Screen geometry for the task (i.e. the window's screen). */ @@ -239,18 +239,32 @@ void requestToggleShaded(const QModelIndex &index) override; /** - * Request moving the task at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktops, + * leaving any other desktops. * - * This is meant for tasks that have an associated window, and may be - * a no-op when there is no window. + * On Wayland, virtual desktop ids are QStrings. On X11, they are uint >0. * - * This base implementation does nothing. + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. * - * @param index An index in this tasks model. - * @param desktop A virtual desktop number. + * On X11, a window can only be on one or all virtual desktops. Therefore, only the + * first list entry is actually used. + * + * On X11, the id 0 has a special meaning: The window is entered on all virtual + * desktops in the session. + * + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids. + **/ + void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. + * + * @param index An index in this window tasks model. **/ - void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; + void requestNewVirtualDesktop(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified activities. diff --git a/libtaskmanager/abstracttasksmodel.cpp b/libtaskmanager/abstracttasksmodel.cpp --- a/libtaskmanager/abstracttasksmodel.cpp +++ b/libtaskmanager/abstracttasksmodel.cpp @@ -113,10 +113,15 @@ Q_UNUSED(index) } -void AbstractTasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) +void AbstractTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) +{ + Q_UNUSED(index) + Q_UNUSED(desktops) +} + +void AbstractTasksModel::requestNewVirtualDesktop(const QModelIndex &index) { Q_UNUSED(index) - Q_UNUSED(desktop) } void AbstractTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities) diff --git a/libtaskmanager/abstracttasksmodeliface.h b/libtaskmanager/abstracttasksmodeliface.h --- a/libtaskmanager/abstracttasksmodeliface.h +++ b/libtaskmanager/abstracttasksmodeliface.h @@ -157,16 +157,32 @@ virtual void requestToggleShaded(const QModelIndex &index) = 0; /** - * Request moving the task at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktops, + * leaving any other desktops. * - * This is meant for tasks that have an associated window, and may be - * a no-op when there is no window. + * On Wayland, virtual desktop ids are QStrings. On X11, they are uint >0. * - * @param index An index in this tasks model. - * @param desktop A virtual desktop number. + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. + * + * On X11, a window can only be on one or all virtual desktops. Therefore, only the + * first list entry is actually used. + * + * On X11, the id 0 has a special meaning: The window is entered on all virtual + * desktops in the session. + * + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids. + **/ + virtual void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) = 0; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. + * + * @param index An index in this window tasks model. **/ - virtual void requestVirtualDesktop(const QModelIndex &index, qint32 desktop = -1) = 0; + virtual void requestNewVirtualDesktop(const QModelIndex &index) = 0; /** * Request moving the task at the given index to the specified virtual diff --git a/libtaskmanager/abstracttasksproxymodeliface.h b/libtaskmanager/abstracttasksproxymodeliface.h --- a/libtaskmanager/abstracttasksproxymodeliface.h +++ b/libtaskmanager/abstracttasksproxymodeliface.h @@ -158,16 +158,32 @@ void requestToggleShaded(const QModelIndex &index) override; /** - * Request moving the task at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktops, + * leaving any other desktops. * - * This is meant for tasks that have an associated window, and may be - * a no-op when there is no window. + * On Wayland, virtual desktop ids are QStrings. On X11, they are uint >0. * - * @param index An index in this tasks model. - * @param desktop A virtual desktop number. + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. + * + * On X11, a window can only be on one or all virtual desktops. Therefore, only the + * first list entry is actually used. + * + * On X11, the id 0 has a special meaning: The window is entered on all virtual + * desktops in the session. + * + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids. + **/ + void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. + * + * @param index An index in this window tasks model. **/ - void requestVirtualDesktop(const QModelIndex &index, qint32 desktop = -1) override; + void requestNewVirtualDesktop(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified activities. diff --git a/libtaskmanager/abstracttasksproxymodeliface.cpp b/libtaskmanager/abstracttasksproxymodeliface.cpp --- a/libtaskmanager/abstracttasksproxymodeliface.cpp +++ b/libtaskmanager/abstracttasksproxymodeliface.cpp @@ -193,7 +193,7 @@ } } -void AbstractTasksProxyModelIface::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) +void AbstractTasksProxyModelIface::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) { if (!index.isValid()) { return; @@ -203,7 +203,21 @@ const AbstractTasksModelIface *m = dynamic_cast(sourceIndex.model()); if (m) { - const_cast(m)->requestVirtualDesktop(sourceIndex, desktop); + const_cast(m)->requestVirtualDesktops(sourceIndex, desktops); + } +} + +void AbstractTasksProxyModelIface::requestNewVirtualDesktop(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + + const QModelIndex &sourceIndex = mapIfaceToSource(index); + const AbstractTasksModelIface *m = dynamic_cast(sourceIndex.model()); + + if (m) { + const_cast(m)->requestNewVirtualDesktop(sourceIndex); } } @@ -221,7 +235,6 @@ } } - void AbstractTasksProxyModelIface::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate) { if (!index.isValid()) { diff --git a/libtaskmanager/launchertasksmodel.cpp b/libtaskmanager/launchertasksmodel.cpp --- a/libtaskmanager/launchertasksmodel.cpp +++ b/libtaskmanager/launchertasksmodel.cpp @@ -351,6 +351,8 @@ return url; } else if (role == IsLauncher) { return true; + } else if (role == IsVirtualDesktopsChangeable) { + return false; } else if (role == IsOnAllVirtualDesktops) { return true; } else if (role == Activities) { diff --git a/libtaskmanager/startuptasksmodel.cpp b/libtaskmanager/startuptasksmodel.cpp --- a/libtaskmanager/startuptasksmodel.cpp +++ b/libtaskmanager/startuptasksmodel.cpp @@ -265,8 +265,10 @@ return d->launcherUrls.value(id); } else if (role == IsStartup) { return true; - } else if (role == VirtualDesktop) { - return data.desktop(); + } else if (role == IsVirtualDesktopsChangeable) { + return false; + } else if (role == VirtualDesktops) { + return QVariantList() << QVariant(data.desktop()); } else if (role == IsOnAllVirtualDesktops) { return (data.desktop() == 0); } diff --git a/libtaskmanager/taskfilterproxymodel.h b/libtaskmanager/taskfilterproxymodel.h --- a/libtaskmanager/taskfilterproxymodel.h +++ b/libtaskmanager/taskfilterproxymodel.h @@ -45,7 +45,7 @@ { Q_OBJECT - Q_PROPERTY(int virtualDesktop READ virtualDesktop WRITE setVirtualDesktop NOTIFY virtualDesktopChanged) + Q_PROPERTY(QVariant virtualDesktop READ virtualDesktop WRITE setVirtualDesktop NOTIFY virtualDesktopChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged) Q_PROPERTY(QString activity READ activity WRITE setActivity NOTIFY activityChanged) @@ -65,26 +65,25 @@ void setSourceModel(QAbstractItemModel *sourceModel) override; /** - * The number of the virtual desktop used in filtering by virtual - * desktop. Usually set to the number of the current virtual desktop. - * Defaults to @c 0 (virtual desktop numbers start at 1). + * The id of the virtual desktop used in filtering by virtual + * desktop. Usually set to the id of the current virtual desktop. + * Defaults to empty. * * @see setVirtualDesktop * @returns the number of the virtual desktop used in filtering. **/ - uint virtualDesktop() const; + QVariant virtualDesktop() const; /** - * Set the number of the virtual desktop to use in filtering by virtual + * Set the id of the virtual desktop to use in filtering by virtual * desktop. * - * If set to 0 (virtual desktop numbers start at 1), filtering by virtual - * desktop is disabled. + * If set to an empty id, filtering by virtual desktop is disabled. * * @see virtualDesktop - * @param virtualDesktop A virtual desktop number. + * @param desktop A virtual desktop id (QString on Wayland; uint >0 on X11). **/ - void setVirtualDesktop(uint virtualDesktop); + void setVirtualDesktop(const QVariant &desktop = QVariant()); /** * The geometry of the screen used in filtering by screen. Defaults diff --git a/libtaskmanager/taskfilterproxymodel.cpp b/libtaskmanager/taskfilterproxymodel.cpp --- a/libtaskmanager/taskfilterproxymodel.cpp +++ b/libtaskmanager/taskfilterproxymodel.cpp @@ -33,7 +33,7 @@ AbstractTasksModelIface *sourceTasksModel = nullptr; - uint virtualDesktop = 0; + QVariant virtualDesktop; QRect screenGeometry; QString activity; @@ -68,15 +68,15 @@ QSortFilterProxyModel::setSourceModel(sourceModel); } -uint TaskFilterProxyModel::virtualDesktop() const +QVariant TaskFilterProxyModel::virtualDesktop() const { return d->virtualDesktop; } -void TaskFilterProxyModel::setVirtualDesktop(uint virtualDesktop) +void TaskFilterProxyModel::setVirtualDesktop(const QVariant &desktop) { - if (d->virtualDesktop != virtualDesktop) { - d->virtualDesktop = virtualDesktop; + if (d->virtualDesktop != desktop) { + d->virtualDesktop = desktop; if (d->filterByVirtualDesktop) { invalidateFilter(); @@ -258,18 +258,13 @@ } // Filter by virtual desktop. - if (d->filterByVirtualDesktop && d->virtualDesktop != 0) { + if (d->filterByVirtualDesktop && !d->virtualDesktop.isNull()) { if (!sourceIdx.data(AbstractTasksModel::IsOnAllVirtualDesktops).toBool() && (!d->demandingAttentionSkipsFilters || !sourceIdx.data(AbstractTasksModel::IsDemandingAttention).toBool())) { - const QVariant &virtualDesktop = sourceIdx.data(AbstractTasksModel::VirtualDesktop); + const QVariantList &virtualDesktops = sourceIdx.data(AbstractTasksModel::VirtualDesktops).toList(); - if (!virtualDesktop.isNull()) { - bool ok = false; - const uint i = virtualDesktop.toUInt(&ok); - - if (ok && i != d->virtualDesktop) { - return false; - } + if (!virtualDesktops.isEmpty() && !virtualDesktops.contains(d->virtualDesktop)) { + return false; } } } diff --git a/libtaskmanager/taskgroupingproxymodel.h b/libtaskmanager/taskgroupingproxymodel.h --- a/libtaskmanager/taskgroupingproxymodel.h +++ b/libtaskmanager/taskgroupingproxymodel.h @@ -314,16 +314,32 @@ void requestToggleShaded(const QModelIndex &index) override; /** - * Request moving the task at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktops, + * leaving any other desktops. * - * This is meant for tasks that have an associated window, and may be - * a no-op when there is no window. + * On Wayland, virtual desktop ids are QStrings. On X11, they are uint >0. * - * @param index An index in this tasks model. - * @param desktop A virtual desktop number. + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. + * + * On X11, a window can only be on one or all virtual desktops. Therefore, only the + * first list entry is actually used. + * + * On X11, the id 0 has a special meaning: The window is entered on all virtual + * desktops in the session. + * + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids. + **/ + void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. + * + * @param index An index in this window tasks model. **/ - void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; + void requestNewVirtualDesktop(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified activities. diff --git a/libtaskmanager/taskgroupingproxymodel.cpp b/libtaskmanager/taskgroupingproxymodel.cpp --- a/libtaskmanager/taskgroupingproxymodel.cpp +++ b/libtaskmanager/taskgroupingproxymodel.cpp @@ -671,16 +671,15 @@ } return appName; - } else if (role == AbstractTasksModel::LegacyWinIdList) { + } else if (role == AbstractTasksModel::WinIdList) { QVariantList winIds; for (int i = 0; i < rowCount(proxyIndex); ++i) { - winIds.append(proxyIndex.child(i, 0).data(AbstractTasksModel::LegacyWinIdList).toList()); + winIds.append(proxyIndex.child(i, 0).data(AbstractTasksModel::WinIdList).toList()); } return winIds; } else if (role == AbstractTasksModel::MimeType) { - // FIXME: Legacy X11 stuff, but it's what we have for now. return QStringLiteral("windowsystem/multiple-winids"); } else if (role == AbstractTasksModel::MimeData) { // FIXME TODO: Implement. @@ -719,22 +718,17 @@ return d->all(proxyIndex, AbstractTasksModel::IsShadeable); } else if (role == AbstractTasksModel::IsShaded) { return d->all(proxyIndex, AbstractTasksModel::IsShaded); - } else if (role == AbstractTasksModel::IsVirtualDesktopChangeable) { - return d->all(proxyIndex, AbstractTasksModel::IsVirtualDesktopChangeable); - } else if (role == AbstractTasksModel::VirtualDesktop) { - // Returns the lowest virtual desktop id among all children of the - // group. - int virtualDesktop = INT_MAX; + } else if (role == AbstractTasksModel::IsVirtualDesktopsChangeable) { + return d->all(proxyIndex, AbstractTasksModel::IsVirtualDesktopsChangeable); + } else if (role == AbstractTasksModel::VirtualDesktops) { + QStringList desktops; for (int i = 0; i < rowCount(proxyIndex); ++i) { - const int childVirtualDesktop = proxyIndex.child(i, 0).data(AbstractTasksModel::VirtualDesktop).toInt(); - - if (childVirtualDesktop < virtualDesktop) { - virtualDesktop = childVirtualDesktop; - } + desktops.append(proxyIndex.child(i, 0).data(AbstractTasksModel::VirtualDesktops).toStringList()); } - return virtualDesktop; + desktops.removeDuplicates(); + return desktops; } else if (role == AbstractTasksModel::ScreenGeometry) { // TODO: Nothing needs this for now and it would add complexity to // make it a list; skip it until needed. Once it is, do it similarly @@ -1109,24 +1103,53 @@ } } -void TaskGroupingProxyModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) +void TaskGroupingProxyModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) { if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) { return; } if (index.parent().isValid() || !d->isGroup(index.row())) { - d->abstractTasksSourceModel->requestVirtualDesktop(mapToSource(index), desktop); + d->abstractTasksSourceModel->requestVirtualDesktops(mapToSource(index), desktops); } else { - const int row = index.row(); + QVector groupChildren; - for (int i = (rowCount(index) - 1); i >= 1; --i) { - const QModelIndex &sourceChild = mapToSource(index.child(i, 0)); - d->abstractTasksSourceModel->requestVirtualDesktop(sourceChild, desktop); + const int childCount = rowCount(index); + + groupChildren.reserve(childCount); + + for (int i = (childCount - 1); i >= 0; --i) { + groupChildren.append(mapToSource(index.child(i, 0))); + } + + for (const QModelIndex &idx : groupChildren) { + d->abstractTasksSourceModel->requestVirtualDesktops(idx, desktops); + } + } +} + +void TaskGroupingProxyModel::requestNewVirtualDesktop(const QModelIndex &index) +{ + if (!d->abstractTasksSourceModel || !index.isValid() || index.model() != this) { + return; + } + + if (index.parent().isValid() || !d->isGroup(index.row())) { + d->abstractTasksSourceModel->requestNewVirtualDesktop(mapToSource(index)); + } else { + QVector groupChildren; + + const int childCount = rowCount(index); + + groupChildren.reserve(childCount); + + for (int i = (childCount - 1); i >= 0; --i) { + groupChildren.append(mapToSource(index.child(i, 0))); } - d->abstractTasksSourceModel->requestVirtualDesktop(mapToSource(TaskGroupingProxyModel::index(row, 0)), - desktop); + for (const QModelIndex &idx : groupChildren) { + d->abstractTasksSourceModel->requestNewVirtualDesktop(idx); + } } } @@ -1139,15 +1162,19 @@ if (index.parent().isValid() || !d->isGroup(index.row())) { d->abstractTasksSourceModel->requestActivities(mapToSource(index), activities); } else { - const int row = index.row(); + QVector groupChildren; - for (int i = (rowCount(index) - 1); i >= 1; --i) { - const QModelIndex &sourceChild = mapToSource(index.child(i, 0)); - d->abstractTasksSourceModel->requestActivities(sourceChild, activities); + const int childCount = rowCount(index); + + groupChildren.reserve(childCount); + + for (int i = (childCount - 1); i >= 0; --i) { + groupChildren.append(mapToSource(index.child(i, 0))); } - d->abstractTasksSourceModel->requestActivities(mapToSource(TaskGroupingProxyModel::index(row, 0)), - activities); + for (const QModelIndex &idx : groupChildren) { + d->abstractTasksSourceModel->requestActivities(idx, activities); + } } } diff --git a/libtaskmanager/tasksmodel.h b/libtaskmanager/tasksmodel.h --- a/libtaskmanager/tasksmodel.h +++ b/libtaskmanager/tasksmodel.h @@ -66,7 +66,7 @@ Q_PROPERTY(bool anyTaskDemandsAttention READ anyTaskDemandsAttention NOTIFY anyTaskDemandsAttentionChanged) - Q_PROPERTY(int virtualDesktop READ virtualDesktop WRITE setVirtualDesktop NOTIFY virtualDesktopChanged) + Q_PROPERTY(QVariant virtualDesktop READ virtualDesktop WRITE setVirtualDesktop NOTIFY virtualDesktopChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged) Q_PROPERTY(QString activity READ activity WRITE setActivity NOTIFY activityChanged) @@ -150,25 +150,25 @@ bool anyTaskDemandsAttention() const; /** - * The number of the virtual desktop used in filtering by virtual - * desktop. Usually set to the number of the current virtual desktop. - * Defaults to @c -1. + * The id of the virtual desktop used in filtering by virtual + * desktop. Usually set to the id of the current virtual desktop. + * Defaults to empty. * * @see setVirtualDesktop * @returns the number of the virtual desktop used in filtering. **/ - int virtualDesktop() const; + QVariant virtualDesktop() const; /** - * Set the number of the virtual desktop to use in filtering by virtual + * Set the id of the virtual desktop to use in filtering by virtual * desktop. * - * If set to @c -1, filtering by virtual desktop is disabled. + * If set to an empty id, filtering by virtual desktop is disabled. * * @see virtualDesktop - * @param virtualDesktop A virtual desktop number. + * @param desktop A virtual desktop id (QString on Wayland; uint >0 on X11). **/ - void setVirtualDesktop(int virtualDesktop); + void setVirtualDesktop(const QVariant &desktop = QVariant()); /** * The geometry of the screen used in filtering by screen. Defaults @@ -712,16 +712,31 @@ Q_INVOKABLE void requestToggleShaded(const QModelIndex &index) override; /** - * Request moving the task at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktops. * - * This is meant for tasks that have an associated window, and may be - * a no-op when there is no window. + * On Wayland, virtual desktop ids are QStrings. On X11, they are uint >0. * - * @param index An index in this tasks model. - * @param desktop A virtual desktop number. + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. + * + * On X11, a window can only be on one or all virtual desktops. Therefore, only the + * first list entry is actually used. + * + * On X11, the id 0 has a special meaning: The window is entered on all virtual + * desktops in the session. + * + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids. + **/ + Q_INVOKABLE void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. + * + * @param index An index in this window tasks model. **/ - Q_INVOKABLE void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; + Q_INVOKABLE void requestNewVirtualDesktop(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified activities. diff --git a/libtaskmanager/tasksmodel.cpp b/libtaskmanager/tasksmodel.cpp --- a/libtaskmanager/tasksmodel.cpp +++ b/libtaskmanager/tasksmodel.cpp @@ -25,6 +25,7 @@ #include "taskfilterproxymodel.h" #include "taskgroupingproxymodel.h" #include "tasktools.h" +#include "virtualdesktopinfo.h" #include "launchertasksmodel.h" #include "startuptasksmodel.h" @@ -61,9 +62,6 @@ bool anyTaskDemandsAttention = false; int launcherCount = 0; - int virtualDesktop = -1; - int screen = -1; - QString activity; SortMode sortMode = SortAlpha; bool separateLaunchers = true; @@ -75,6 +73,8 @@ QVector sortRowInsertQueue; bool sortRowInsertQueueStale = false; QHash activityTaskCounts; + static VirtualDesktopInfo *virtualDesktopInfo; + static int virtualDesktopInfoUsers; static ActivityInfo* activityInfo; static int activityInfoUsers; @@ -122,6 +122,8 @@ int TasksModel::Private::instanceCount = 0; WindowTasksModel* TasksModel::Private::windowTasksModel = nullptr; StartupTasksModel* TasksModel::Private::startupTasksModel = nullptr; +VirtualDesktopInfo* TasksModel::Private::virtualDesktopInfo = nullptr; +int TasksModel::Private::virtualDesktopInfoUsers = 0; ActivityInfo* TasksModel::Private::activityInfo = nullptr; int TasksModel::Private::activityInfoUsers = 0; @@ -144,6 +146,8 @@ windowTasksModel = nullptr; delete startupTasksModel; startupTasksModel = nullptr; + delete virtualDesktopInfo; + virtualDesktopInfo = nullptr; delete activityInfo; activityInfo = nullptr; } @@ -877,21 +881,50 @@ // Sort other cases by sort mode. switch (sortMode) { case SortVirtualDesktop: { - const QVariant &leftDesktopVariant = left.data(AbstractTasksModel::VirtualDesktop); - bool leftOk = false; - const int leftDesktop = leftDesktopVariant.toInt(&leftOk); + const bool leftAll = left.data(AbstractTasksModel::IsOnAllVirtualDesktops).toBool(); + const bool rightAll = right.data(AbstractTasksModel::IsOnAllVirtualDesktops).toBool(); - const QVariant &rightDesktopVariant = right.data(AbstractTasksModel::VirtualDesktop); - bool rightOk = false; - const int rightDesktop = rightDesktopVariant.toInt(&rightOk); + if (leftAll && !rightAll) { + return true; + } else if (rightAll && !leftAll) { + return true; + } + + const QVariantList &leftDesktops = left.data(AbstractTasksModel::VirtualDesktops).toList(); + QVariant leftDesktop; + int leftDesktopPos = virtualDesktopInfo->numberOfDesktops(); + + for (const QVariant &desktop : leftDesktops) { + const int desktopPos = virtualDesktopInfo->position(desktop); + + if (desktopPos < leftDesktopPos) { + leftDesktop = desktop; + leftDesktopPos = desktopPos; + } + } - if (leftOk && rightOk && (leftDesktop != rightDesktop)) { - return (leftDesktop < rightDesktop); - } else if (leftOk && !rightOk) { + const QVariantList &rightDesktops = right.data(AbstractTasksModel::VirtualDesktops).toList(); + QVariant rightDesktop; + int rightDesktopPos = virtualDesktopInfo->numberOfDesktops(); + + for (const QVariant &desktop : rightDesktops) { + const int desktopPos = virtualDesktopInfo->position(desktop); + + if (desktopPos < rightDesktopPos) { + rightDesktop = desktop; + rightDesktopPos = desktopPos; + } + } + + if (!leftDesktop.isNull() && !rightDesktop.isNull() && (leftDesktop != rightDesktop)) { + return (virtualDesktopInfo->position(leftDesktop) < virtualDesktopInfo->position(rightDesktop)); + } else if (!leftDesktop.isNull() && rightDesktop.isNull()) { return false; - } else if (!leftOk && rightOk) { + } else if (leftDesktop.isNull() && !rightDesktop.isNull()) { return true; } + + return false; } case SortActivity: { // updateActivityTaskCounts() counts the number of window tasks on each @@ -934,6 +967,8 @@ } } // Fall through to source order if sorting is disabled or manual, or alphabetical by app name otherwise. + // This marker comment makes gcc/clang happy: + // fall through default: { if (sortMode == SortDisabled) { return (left.row() < right.row()); @@ -1039,11 +1074,11 @@ return false; } - } else if (rowCount(proxyIndex) && role == AbstractTasksModel::LegacyWinIdList) { + } else if (rowCount(proxyIndex) && role == AbstractTasksModel::WinIdList) { QVariantList winIds; for (int i = 0; i < rowCount(proxyIndex); ++i) { - winIds.append(proxyIndex.child(i, 0).data(AbstractTasksModel::LegacyWinIdList).toList()); + winIds.append(proxyIndex.child(i, 0).data(AbstractTasksModel::WinIdList).toList()); } return winIds; @@ -1082,14 +1117,14 @@ return d->anyTaskDemandsAttention; } -int TasksModel::virtualDesktop() const +QVariant TasksModel::virtualDesktop() const { return d->filterProxyModel->virtualDesktop(); } -void TasksModel::setVirtualDesktop(int virtualDesktop) +void TasksModel::setVirtualDesktop(const QVariant &desktop) { - d->filterProxyModel->setVirtualDesktop(virtualDesktop); + d->filterProxyModel->setVirtualDesktop(desktop); } QRect TasksModel::screenGeometry() const @@ -1166,6 +1201,25 @@ d->sortedPreFilterRows.clear(); } + if (mode == SortVirtualDesktop) { + if (!d->virtualDesktopInfo) { + d->virtualDesktopInfo = new VirtualDesktopInfo(); + } + + ++d->virtualDesktopInfoUsers; + + setSortRole(AbstractTasksModel::VirtualDesktops); + } else if (d->sortMode == SortVirtualDesktop) { + --d->virtualDesktopInfoUsers; + + if (!d->virtualDesktopInfoUsers) { + delete d->virtualDesktopInfo; + d->virtualDesktopInfo = nullptr; + } + + setSortRole(Qt::DisplayRole); + } + if (mode == SortActivity) { if (!d->activityInfo) { d->activityInfo = new ActivityInfo(); @@ -1505,10 +1559,17 @@ } } -void TasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) +void TasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) +{ + if (index.isValid() && index.model() == this) { + d->abstractTasksSourceModel->requestVirtualDesktops(mapToSource(index), desktops); + } +} + +void TasksModel::requestNewVirtualDesktop(const QModelIndex &index) { if (index.isValid() && index.model() == this) { - d->abstractTasksSourceModel->requestVirtualDesktop(mapToSource(index), desktop); + d->abstractTasksSourceModel->requestNewVirtualDesktop(mapToSource(index)); } } diff --git a/libtaskmanager/virtualdesktopinfo.h b/libtaskmanager/virtualdesktopinfo.h --- a/libtaskmanager/virtualdesktopinfo.h +++ b/libtaskmanager/virtualdesktopinfo.h @@ -29,26 +29,22 @@ { /** - * @short Provides basic virtual desktop information. + * @short Provides basic virtual desktop information. The underlying windowing + * system is abstracted away. * * This class provides basic information about the virtual desktops present * in the session as a set of notifiable properties. * - * @NOTE: This is a placeholder, to be moved into KWindowSystem (which it - * wraps) or the Task Manager applet backend (which used to fill this role - * in the past). - * - * @see KWindowSystem - * * @author Eike Hein **/ class TASKMANAGER_EXPORT VirtualDesktopInfo : public QObject { Q_OBJECT - Q_PROPERTY(int currentDesktop READ currentDesktop NOTIFY currentDesktopChanged) + Q_PROPERTY(QVariant currentDesktop READ currentDesktop NOTIFY currentDesktopChanged) Q_PROPERTY(int numberOfDesktops READ numberOfDesktops NOTIFY numberOfDesktopsChanged) + Q_PROPERTY(QVariantList desktopIds READ desktopIds NOTIFY desktopIdsChanged) Q_PROPERTY(QStringList desktopNames READ desktopNames NOTIFY desktopNamesChanged) Q_PROPERTY(int desktopLayoutRows READ desktopLayoutRows NOTIFY desktopLayoutRowsChanged) @@ -59,9 +55,10 @@ /** * The currently active virtual desktop. * - * @returns the number of the currently active virtual desktop. + * @returns the id of the currently active virtual desktop. QString on + * Wayland; uint >0 on X11. **/ - int currentDesktop() const; + QVariant currentDesktop() const; /** * The number of virtual desktops present in the session. @@ -71,27 +68,80 @@ int numberOfDesktops() const; /** - * The names of all virtual desktops present in the session. Note that - * virtual desktops are indexed starting at 1, so the name for virtual - * desktop 1 is at index 0 in this list. + * The ids of all virtual desktops present in the session. + * + * On Wayland, the ids are QString. On X11, they are uint >0. + * + * @returns a the list of ids of the virtual desktops present in the + * session. + **/ + QVariantList desktopIds() const; + + /** + * The names of all virtual desktops present in the session. + * + * Note that on X11, virtual desktops are indexed starting at 1, so + * the name for virtual desktop 1 is at index 0 in this list. * - * @returns a the list of names for the virtual desktops present in the + * @returns the list of names of the virtual desktops present in the * session. **/ QStringList desktopNames() const; + /** + * Returns the position of the passed-in virtual desktop. + * @param desktop A virtual desktop id (QString on Wayland; uint >0 on X11). + * @returns the position of the virtual desktop, or -1 if the desktop + * id is not valid. + **/ + quint32 position(const QVariant &desktop) const; + /** * The number of rows in the virtual desktop layout. * * @returns the number of rows in the virtual desktop layout. **/ int desktopLayoutRows() const; + /** + * Request activating the passed-in virtual desktop. + * + * @param desktop A virtual desktop id (QString on Wayland; uint >0 on X11). + **/ + void requestActivate(const QVariant &desktop); + + /** + * Request adding a new virtual desktop at the specified position. + * + * On X11, the position parameter is ignored and the new desktop is always + * created at the end of the list. + * + * @param position The position of the requested new virtual desktop (ignored on X11). + **/ + void requestCreateDesktop(quint32 position); + + /** + * Request removing the virtual desktop at the specified position. + * + * On X11, the position parameter is ignored and the last desktop in the list + * is always the one removed. + * + * @param position The position of the virtual desktop to remove (ignored on X11). + **/ + void requestRemoveDesktop(quint32 position); + Q_SIGNALS: void currentDesktopChanged() const; void numberOfDesktopsChanged() const; + void desktopIdsChanged() const; void desktopNamesChanged() const; void desktopLayoutRowsChanged() const; + +private: + class Private; + class XWindowPrivate; + class WaylandPrivate; + static Private *d; }; } diff --git a/libtaskmanager/virtualdesktopinfo.cpp b/libtaskmanager/virtualdesktopinfo.cpp --- a/libtaskmanager/virtualdesktopinfo.cpp +++ b/libtaskmanager/virtualdesktopinfo.cpp @@ -20,6 +20,10 @@ #include "virtualdesktopinfo.h" +#include +#include +#include +#include #include #include @@ -33,42 +37,105 @@ namespace TaskManager { +class Q_DECL_HIDDEN VirtualDesktopInfo::Private : public QObject +{ + Q_OBJECT -VirtualDesktopInfo::VirtualDesktopInfo(QObject *parent) : QObject(parent) +public: + Private(VirtualDesktopInfo *q); + virtual ~Private() {} + + uint refCount = 1; + + virtual void init() = 0; + virtual QVariant currentDesktop() const = 0; + virtual int numberOfDesktops() const = 0; + virtual QVariantList desktopIds() const = 0; + virtual QStringList desktopNames() const = 0; + virtual quint32 position(const QVariant &desktop) const = 0; + virtual int desktopLayoutRows() const = 0; + virtual void requestActivate(const QVariant &desktop) = 0; + virtual void requestCreateDesktop(quint32 position) = 0; + virtual void requestRemoveDesktop(quint32 position) = 0; + +Q_SIGNALS: + void currentDesktopChanged() const; + void numberOfDesktopsChanged() const; + void desktopIdsChanged() const; + void desktopNamesChanged() const; + void desktopLayoutRowsChanged() const; + +protected: + VirtualDesktopInfo *q; +}; + +VirtualDesktopInfo::Private::Private(VirtualDesktopInfo *q) + : q(q) +{ +} + +#if HAVE_X11 +class Q_DECL_HIDDEN VirtualDesktopInfo::XWindowPrivate : public VirtualDesktopInfo::Private +{ +public: + XWindowPrivate(VirtualDesktopInfo *q); + + void init() override; + QVariant currentDesktop() const override; + int numberOfDesktops() const override; + QVariantList desktopIds() const override; + QStringList desktopNames() const override; + quint32 position(const QVariant &desktop) const override; + int desktopLayoutRows() const override; + void requestActivate(const QVariant &desktop) override; + void requestCreateDesktop(quint32 position) override; + void requestRemoveDesktop(quint32 position) override; +}; + +VirtualDesktopInfo::XWindowPrivate::XWindowPrivate(VirtualDesktopInfo *q) + : VirtualDesktopInfo::Private(q) +{ + init(); +} + +void VirtualDesktopInfo::XWindowPrivate::init() { connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, - this, &VirtualDesktopInfo::currentDesktopChanged); + this, &VirtualDesktopInfo::XWindowPrivate::currentDesktopChanged); connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, - this, &VirtualDesktopInfo::numberOfDesktopsChanged); + this, &VirtualDesktopInfo::XWindowPrivate::numberOfDesktopsChanged); connect(KWindowSystem::self(), &KWindowSystem::desktopNamesChanged, - this, &VirtualDesktopInfo::desktopNamesChanged); + this, &VirtualDesktopInfo::XWindowPrivate::desktopNamesChanged); -#if HAVE_X11 - if (KWindowSystem::isPlatformX11()) { - QDBusConnection dbus = QDBusConnection::sessionBus(); - dbus.connect(QString(), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"), - this, SIGNAL(desktopLayoutRowsChanged())); - } -#endif + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.connect(QString(), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"), + this, SIGNAL(desktopLayoutRowsChanged())); } -VirtualDesktopInfo::~VirtualDesktopInfo() +QVariant VirtualDesktopInfo::XWindowPrivate::currentDesktop() const { + return KWindowSystem::currentDesktop(); } -int VirtualDesktopInfo::currentDesktop() const +int VirtualDesktopInfo::XWindowPrivate::numberOfDesktops() const { - return KWindowSystem::currentDesktop(); + return KWindowSystem::numberOfDesktops(); } -int VirtualDesktopInfo::numberOfDesktops() const +QVariantList VirtualDesktopInfo::XWindowPrivate::desktopIds() const { - return KWindowSystem::numberOfDesktops(); + QVariantList ids; + + for (int i = 1; i <= KWindowSystem::numberOfDesktops(); ++i) { + ids << i; + } + + return ids; } -QStringList VirtualDesktopInfo::desktopNames() const +QStringList VirtualDesktopInfo::XWindowPrivate::desktopNames() const { QStringList names; @@ -80,16 +147,330 @@ return names; } -int VirtualDesktopInfo::desktopLayoutRows() const +quint32 VirtualDesktopInfo::XWindowPrivate::position(const QVariant &desktop) const { -#if HAVE_X11 - if (KWindowSystem::isPlatformX11()) { - const NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout); - return info.desktopLayoutColumnsRows().height(); + bool ok = false; + + const quint32 desktopNumber = desktop.toUInt(&ok); + + if (!ok) { + return -1; + } + + return desktopNumber; +} + +int VirtualDesktopInfo::XWindowPrivate::desktopLayoutRows() const +{ + const NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout); + return info.desktopLayoutColumnsRows().height(); +} + +void VirtualDesktopInfo::XWindowPrivate::requestActivate(const QVariant &desktop) +{ + bool ok = false; + const int desktopNumber = desktop.toInt(&ok); + + // Virtual desktop numbers start at 1. + if (ok && desktopNumber > 0 && desktopNumber <= KWindowSystem::numberOfDesktops()) { + KWindowSystem::setCurrentDesktop(desktopNumber); } +} + +void VirtualDesktopInfo::XWindowPrivate::requestCreateDesktop(quint32 position) +{ + Q_UNUSED(position) + + NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); + info.setNumberOfDesktops(info.numberOfDesktops() + 1); +} + +void VirtualDesktopInfo::XWindowPrivate::requestRemoveDesktop(quint32 position) +{ + Q_UNUSED(position) + + NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); + + if (info.numberOfDesktops() > 1) { + info.setNumberOfDesktops(info.numberOfDesktops() - 1); + } +} #endif +class Q_DECL_HIDDEN VirtualDesktopInfo::WaylandPrivate : public VirtualDesktopInfo::Private +{ +public: + WaylandPrivate(VirtualDesktopInfo *q); + + QVariant currentVirtualDesktop; + QStringList virtualDesktops; + KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement = nullptr; + + void init() override; + void addDesktop(const QString &id, quint32 position); + QVariant currentDesktop() const override; + int numberOfDesktops() const override; + QVariantList desktopIds() const override; + QStringList desktopNames() const override; + quint32 position(const QVariant &desktop) const override; + int desktopLayoutRows() const override; + void requestActivate(const QVariant &desktop) override; + void requestCreateDesktop(quint32 position) override; + void requestRemoveDesktop(quint32 position) override; +}; + +VirtualDesktopInfo::WaylandPrivate::WaylandPrivate(VirtualDesktopInfo *q) + : VirtualDesktopInfo::Private(q) +{ + init(); +} + +void VirtualDesktopInfo::WaylandPrivate::init() +{ + if (!KWindowSystem::isPlatformWayland()) { + return; + } + + KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q); + + if (!connection) { + return; + } + + KWayland::Client::Registry *registry = new KWayland::Client::Registry(q); + registry->create(connection); + + QObject::connect(registry, &KWayland::Client::Registry::plasmaVirtualDesktopManagementAnnounced, + [this, registry] (quint32 name, quint32 version) { + virtualDesktopManagement = registry->createPlasmaVirtualDesktopManagement(name, version, q); + + const QList &desktops = virtualDesktopManagement->desktops(); + + /* FIXME: PlasmaVirtualDesktopManagement doesn't have this ...? + QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::interfaceAboutToBeReleased, q, + [this] { + virtualDesktops.clear(); + emit numberOfDesktopsChanged(); + emit currentDesktopChanged(); + emit desktopIdsChanged(); + emit desktopNamesChanged(); + emit desktopLayoutRowsChanged(); + } + ); + */ + + QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, q, + [this](const QString &id, quint32 position) { + addDesktop(id, position); + } + ); + + QObject::connect(virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, q, + [this](const QString &id) { + virtualDesktops.removeOne(id); + + emit numberOfDesktopsChanged(); + emit desktopIdsChanged(); + emit desktopNamesChanged(); + + if (currentVirtualDesktop == id) { + currentVirtualDesktop.clear(); + emit currentDesktopChanged(); + } + } + ); + } + ); + + registry->setup(); +} + +void VirtualDesktopInfo::WaylandPrivate::addDesktop(const QString &id, quint32 position) +{ + if (virtualDesktops.indexOf(id) != -1) { + return; + } + + virtualDesktops.insert(position, id); + + emit numberOfDesktopsChanged(); + emit desktopIdsChanged(); + emit desktopNamesChanged(); + + const KWayland::Client::PlasmaVirtualDesktop *desktop = virtualDesktopManagement->getVirtualDesktop(id); + + QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, q, + [desktop, this]() { + currentVirtualDesktop = desktop->id(); + emit currentDesktopChanged(); + } + ); + + if (desktop->isActive()) { + currentVirtualDesktop = id; + emit currentDesktopChanged(); + } +} + +QVariant VirtualDesktopInfo::WaylandPrivate::currentDesktop() const +{ + return currentVirtualDesktop; +} + +int VirtualDesktopInfo::WaylandPrivate::numberOfDesktops() const +{ + return virtualDesktops.count(); +} + +quint32 VirtualDesktopInfo::WaylandPrivate::position(const QVariant &desktop) const +{ + return virtualDesktops.indexOf(desktop.toString()); +} + +QVariantList VirtualDesktopInfo::WaylandPrivate::desktopIds() const +{ + QVariantList ids; + + foreach (const QString &id, virtualDesktops) { + ids << id; + } + + return ids; +} + +QStringList VirtualDesktopInfo::WaylandPrivate::desktopNames() const +{ + QStringList names; + + foreach(const QString &id, virtualDesktops) { + const KWayland::Client::PlasmaVirtualDesktop *desktop = virtualDesktopManagement->getVirtualDesktop(id); + + if (desktop) { + names << desktop->name(); + } + } + + return names; +} + +int VirtualDesktopInfo::WaylandPrivate::desktopLayoutRows() const +{ + // TODO FIXME: We don't have virtual desktop layout information in the Wayland + // protocol yet. return 0; } +void VirtualDesktopInfo::WaylandPrivate::requestActivate(const QVariant &desktop) +{ + KWayland::Client::PlasmaVirtualDesktop *desktopObj = virtualDesktopManagement->getVirtualDesktop(desktop.toString()); + + if (desktopObj) { + desktopObj->requestActivate(); + } +} + +void VirtualDesktopInfo::WaylandPrivate::requestCreateDesktop(quint32 position) +{ + virtualDesktopManagement->requestCreateVirtualDesktop(i18n("New Desktop"), position); +} + +void VirtualDesktopInfo::WaylandPrivate::requestRemoveDesktop(quint32 position) +{ + if (virtualDesktops.count() == 1) { + return; + } + + if (position > ((quint32)virtualDesktops.count() - 1)) { + return; + } + + virtualDesktopManagement->requestRemoveVirtualDesktop(virtualDesktops.at(position)); } + +VirtualDesktopInfo::Private* VirtualDesktopInfo::d = nullptr; + +VirtualDesktopInfo::VirtualDesktopInfo(QObject *parent) : QObject(parent) +{ + if (!d) { + #if HAVE_X11 + if (KWindowSystem::isPlatformX11()) { + d = new VirtualDesktopInfo::XWindowPrivate(this); + } else + #endif + { + d = new VirtualDesktopInfo::WaylandPrivate(this); + } + } else { + ++d->refCount; + } + + connect(d, &VirtualDesktopInfo::Private::currentDesktopChanged, + this, &VirtualDesktopInfo::currentDesktopChanged); + connect(d, &VirtualDesktopInfo::Private::numberOfDesktopsChanged, + this, &VirtualDesktopInfo::numberOfDesktopsChanged); + connect(d, &VirtualDesktopInfo::Private::desktopIdsChanged, + this, &VirtualDesktopInfo::desktopIdsChanged); + connect(d, &VirtualDesktopInfo::Private::desktopNamesChanged, + this, &VirtualDesktopInfo::desktopNamesChanged); + connect(d, &VirtualDesktopInfo::Private::desktopLayoutRowsChanged, + this, &VirtualDesktopInfo::desktopLayoutRowsChanged); +} + +VirtualDesktopInfo::~VirtualDesktopInfo() +{ + --d->refCount; + + if (!d->refCount) { + delete d; + d = nullptr; + } +} + +QVariant VirtualDesktopInfo::currentDesktop() const +{ + return d->currentDesktop(); +} + +int VirtualDesktopInfo::numberOfDesktops() const +{ + return d->numberOfDesktops(); +} + +QVariantList VirtualDesktopInfo::desktopIds() const +{ + return d->desktopIds(); +} + +QStringList VirtualDesktopInfo::desktopNames() const +{ + return d->desktopNames(); +} + +quint32 VirtualDesktopInfo::position(const QVariant &desktop) const +{ + return d->position(desktop); +} + +int VirtualDesktopInfo::desktopLayoutRows() const +{ + return d->desktopLayoutRows(); +} + +void VirtualDesktopInfo::requestActivate(const QVariant &desktop) +{ + d->requestActivate(desktop); +} + +void VirtualDesktopInfo::requestCreateDesktop(quint32 position) +{ + return d->requestCreateDesktop(position); +} + +void VirtualDesktopInfo::requestRemoveDesktop(quint32 position) +{ + return d->requestRemoveDesktop(position); +} + +} + +#include "virtualdesktopinfo.moc" diff --git a/libtaskmanager/waylandtasksmodel.h b/libtaskmanager/waylandtasksmodel.h --- a/libtaskmanager/waylandtasksmodel.h +++ b/libtaskmanager/waylandtasksmodel.h @@ -44,7 +44,8 @@ * server the host process is connected to. * * FIXME: Filtering by window type still needed. - * FIXME: Support for taskmanagerrulesrc (maybe) still needed. + * + * @see WindowTasksModel * * @author Eike Hein */ @@ -162,15 +163,26 @@ void requestToggleShaded(const QModelIndex &index) override; /** - * Request moving the window at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktops, + * leaving any other desktops. * - * FIXME: X Windows version has extra virtual desktop logic. + * Virtual desktop ids are QStrings. + * + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. + * + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids. + **/ + void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. * * @param index An index in this window tasks model. - * @param desktop A virtual desktop number. **/ - void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; + void requestNewVirtualDesktop(const QModelIndex &index) override; /** * Request moving the window at the given index to the specified activities @@ -204,6 +216,22 @@ void requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate = nullptr) override; + /** + * Tries to extract a process-internal Wayland window id from supplied mime data. + * + * @param mimeData Some mime data. + * @param @ok Set to true or false on success or failure. + */ + static quint32 winIdFromMimeData(const QMimeData *mimeData, bool *ok = nullptr); + + /** + * Tries to extract process-internal Wayland window ids from supplied mime data. + * + * @param mimeData Some mime data. + * @param @ok Set to true or false on success or failure. + */ + static QList winIdsFromMimeData(const QMimeData *mimeData, bool *ok = nullptr); + private: class Private; QScopedPointer d; diff --git a/libtaskmanager/waylandtasksmodel.cpp b/libtaskmanager/waylandtasksmodel.cpp --- a/libtaskmanager/waylandtasksmodel.cpp +++ b/libtaskmanager/waylandtasksmodel.cpp @@ -20,6 +20,7 @@ #include "waylandtasksmodel.h" #include "tasktools.h" +#include "virtualdesktopinfo.h" #include #include @@ -33,10 +34,12 @@ #include #include +#include #include #include #include #include +#include #include namespace TaskManager @@ -51,6 +54,8 @@ KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr; KSharedConfig::Ptr rulesConfig; KDirWatch *configWatcher = nullptr; + VirtualDesktopInfo *virtualDesktopInfo = nullptr; + static QUuid uuid; void init(); void initWayland(); @@ -60,13 +65,18 @@ QIcon icon(KWayland::Client::PlasmaWindow *window); + static QString mimeType(); + static QString groupMimeType(); + void dataChanged(KWayland::Client::PlasmaWindow *window, int role); void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector &roles); private: WaylandTasksModel *q; }; +QUuid WaylandTasksModel::Private::uuid = QUuid::createUuid(); + WaylandTasksModel::Private::Private(WaylandTasksModel *q) : q(q) { @@ -106,6 +116,8 @@ QObject::connect(configWatcher, &KDirWatch::created, rulesConfigChange); QObject::connect(configWatcher, &KDirWatch::deleted, rulesConfigChange); + virtualDesktopInfo = new VirtualDesktopInfo(q); + initWayland(); } @@ -260,16 +272,33 @@ [window, this] { this->dataChanged(window, IsShadeable); } ); - QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChangeableChanged, q, - [window, this] { this->dataChanged(window, IsVirtualDesktopChangeable); } - ); +// FIXME +// QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChangeableChanged, q, +// // TODO: This is marked deprecated in KWayland, but (IMHO) shouldn't be. +// [window, this] { this->dataChanged(window, IsVirtualDesktopsChangeable); } +// ); + + QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopEntered, q, + [window, this] { + this->dataChanged(window, VirtualDesktops); - QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChanged, q, - [window, this] { this->dataChanged(window, VirtualDesktop); } + // If the count has changed from 0, the window may no longer be on all virtual + // desktops. + if (window->plasmaVirtualDesktops().count() > 0) { + this->dataChanged(window, VirtualDesktops); + } + } ); - QObject::connect(window, &KWayland::Client::PlasmaWindow::onAllDesktopsChanged, q, - [window, this] { this->dataChanged(window, IsOnAllVirtualDesktops); } + QObject::connect(window, &KWayland::Client::PlasmaWindow::plasmaVirtualDesktopLeft, q, + [window, this] { + this->dataChanged(window, VirtualDesktops); + + // If the count has changed to 0, the window is now on all virtual desktops. + if (window->plasmaVirtualDesktops().count() == 0) { + this->dataChanged(window, VirtualDesktops); + } + } ); QObject::connect(window, &KWayland::Client::PlasmaWindow::geometryChanged, q, @@ -314,6 +343,20 @@ return window->icon(); } +QString WaylandTasksModel::Private::mimeType() +{ + // Use a unique format id to make this intentionally useless for + // cross-process DND. + return QString("windowsystem/winid+" + uuid.toString().toAscii()); +} + +QString WaylandTasksModel::Private::groupMimeType() +{ + // Use a unique format id to make this intentionally useless for + // cross-process DND. + return QString("windowsystem/multiple-winids+" + uuid.toString().toAscii()); +} + void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role) { QModelIndex idx = q->index(windows.indexOf(window)); @@ -361,6 +404,12 @@ return d->appData(window).genericName; } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { return d->appData(window).url; + } else if (role == WinIdList) { + return QVariantList() << window->internalId(); + } else if (role == MimeType) { + return d->mimeType(); + } else if (role == MimeData) { + return QByteArray::number(window->internalId()); } else if (role == IsWindow) { return true; } else if (role == IsActive) { @@ -391,12 +440,13 @@ return window->isShadeable(); } else if (role == IsShaded) { return window->isShaded(); - } else if (role == IsVirtualDesktopChangeable) { - return window->isVirtualDesktopChangeable(); - } else if (role == VirtualDesktop) { - return window->virtualDesktop(); + } else if (role == IsVirtualDesktopsChangeable) { + // FIXME Currently not implemented in KWayland. + return true; + } else if (role == VirtualDesktops) { + return window->plasmaVirtualDesktops(); } else if (role == IsOnAllVirtualDesktops) { - return window->isOnAllDesktops(); + return window->plasmaVirtualDesktops().isEmpty(); } else if (role == Geometry) { return window->geometry(); } else if (role == ScreenGeometry) { @@ -468,46 +518,70 @@ void WaylandTasksModel::requestMove(const QModelIndex &index) { - // FIXME Move-to-desktop logic from XWindows version. (See also others.) - if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } - d->windows.at(index.row())->requestMove(); + KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); + + const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString(); + + if (!currentDesktop.isEmpty()) { + window->requestEnterVirtualDesktop(currentDesktop); + } + + window->requestMove(); } void WaylandTasksModel::requestResize(const QModelIndex &index) { - // FIXME Move-to-desktop logic from XWindows version. (See also others.) - if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } - d->windows.at(index.row())->requestResize(); + KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); + + const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString(); + + if (!currentDesktop.isEmpty()) { + window->requestEnterVirtualDesktop(currentDesktop); + } + + window->requestResize(); } void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index) { - // FIXME Move-to-desktop logic from XWindows version. (See also others.) - if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } - d->windows.at(index.row())->requestToggleMinimized(); + KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); + + const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString(); + + if (!currentDesktop.isEmpty()) { + window->requestEnterVirtualDesktop(currentDesktop); + } + + window->requestToggleMinimized(); } void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index) { - // FIXME Move-to-desktop logic from XWindows version. (See also others.) - if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } - d->windows.at(index.row())->requestToggleMaximized(); + KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); + + const QString ¤tDesktop = d->virtualDesktopInfo->currentDesktop().toString(); + + if (!currentDesktop.isEmpty()) { + window->requestEnterVirtualDesktop(currentDesktop); + } + + window->requestToggleMaximized(); } void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index) @@ -544,16 +618,53 @@ d->windows.at(index.row())->requestToggleShaded(); } -void WaylandTasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) +void WaylandTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) { - // FIXME Lacks add-new-desktop code from XWindows version. - // FIXME Does this do the set-on-all-desktops stuff from the XWindows version? + // FIXME TODO: Lacks the "if we've requested the current desktop, force-activate + // the window" logic from X11 version. This behavior should be in KWin rather than + // libtm however. if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } - d->windows.at(index.row())->requestVirtualDesktop(desktop); + KWayland::Client::PlasmaWindow *window = d->windows.at(index.row()); + + if (desktops.isEmpty()) { + foreach (const QVariant &desktop, window->plasmaVirtualDesktops()) { + window->requestLeaveVirtualDesktop(desktop.toString()); + } + } else { + const QStringList &now = window->plasmaVirtualDesktops(); + QStringList next; + + foreach (const QVariant &desktop, desktops) { + const QString &desktopId = desktop.toString(); + + if (!desktopId.isEmpty()) { + next << desktopId; + + if (!now.contains(desktopId)) { + window->requestEnterVirtualDesktop(desktopId); + } + } + } + + foreach (const QString &desktop, now) { + if (!next.contains(desktop)) { + window->requestLeaveVirtualDesktop(desktop); + } + } + } +} + +void WaylandTasksModel::requestNewVirtualDesktop(const QModelIndex &index) +{ + if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { + return; + } + + d->windows.at(index.row())->requestEnterNewVirtualDesktop(); } void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities) @@ -605,4 +716,53 @@ window->setMinimizedGeometry(surface, rect); } +quint32 WaylandTasksModel::winIdFromMimeData(const QMimeData *mimeData, bool *ok) +{ + Q_ASSERT(mimeData); + + if (ok) { + *ok = false; + } + + if (!mimeData->hasFormat(Private::mimeType())) { + return 0; + } + + quint32 id = mimeData->data(Private::mimeType()).toUInt(ok); + + return id; +} + +QList WaylandTasksModel::winIdsFromMimeData(const QMimeData *mimeData, bool *ok) +{ + Q_ASSERT(mimeData); + QList ids; + + if (ok) { + *ok = false; + } + + if (!mimeData->hasFormat(Private::groupMimeType())) { + // Try to extract single window id. + bool singularOk; + WId id = winIdFromMimeData(mimeData, &singularOk); + + if (ok) { + *ok = singularOk; + } + + if (singularOk) { + ids << id; + } + + return ids; + } + + // FIXME: Extracting multiple winids is still unimplemented; + // TaskGroupingProxy::data(..., ::MimeData) can't produce + // a payload with them anyways. + + return ids; +} + } diff --git a/libtaskmanager/windowtasksmodel.h b/libtaskmanager/windowtasksmodel.h --- a/libtaskmanager/windowtasksmodel.h +++ b/libtaskmanager/windowtasksmodel.h @@ -37,6 +37,9 @@ * windowing server the host process is connected to. The underlying * windowing system is abstracted away. * + * @see WaylandTasksModel + * @see XWindowTasksModel + * * @author Eike Hein **/ diff --git a/libtaskmanager/windowtasksmodel.cpp b/libtaskmanager/windowtasksmodel.cpp --- a/libtaskmanager/windowtasksmodel.cpp +++ b/libtaskmanager/windowtasksmodel.cpp @@ -60,7 +60,6 @@ { --instanceCount; - if (!instanceCount) { delete sourceTasksModel; sourceTasksModel = nullptr; diff --git a/libtaskmanager/xwindowtasksmodel.h b/libtaskmanager/xwindowtasksmodel.h --- a/libtaskmanager/xwindowtasksmodel.h +++ b/libtaskmanager/xwindowtasksmodel.h @@ -34,7 +34,7 @@ { /** - * @short A tasks model for X Windows windows. + * @short A tasks model for X Window System windows. * * This model presents tasks sourced from window data on the X Windows * server the host process is connected to. @@ -44,6 +44,8 @@ * transients for an otherwise-included window) are omitted from the * model. * + * @see WindowTasksModel + * * @author Eike Hein */ @@ -174,22 +176,29 @@ void requestToggleShaded(const QModelIndex &index) override; /** - * Request moving the window at the given index to the specified virtual - * desktop. + * Request entering the window at the given index on the specified virtual desktop. + * For compatibility across windowing systems the library supports, the desktops + * parameter is a list; however, on X11 a window can only be on one or all virtual + * desktops. Therefore, only the first list entry is actually used. * - * If the specified virtual desktop is 0, IsOnAllVirtualDesktops is - * toggled instead. + * An empty list has a special meaning: The window is entered on all virtual desktops + * in the session. * - * If the specified desktop number exceeds the number of virtual - * desktops in the session, a new desktop is created before moving - * the window. + * The id 0 has a special meaning: The window is entered on all virtual desktops in + * the session. * - * FIXME: Desktop logic should maybe move into proxy. + * @param index An index in this window tasks model. + * @param desktops A list of virtual desktop ids (uint). + **/ + void requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) override; + + /** + * Request entering the window at the given index on a new virtual desktop, + * which is created in response to this request. * * @param index An index in this window tasks model. - * @param desktop A virtual desktop number. **/ - void requestVirtualDesktop(const QModelIndex &index, qint32 desktop) override; + void requestNewVirtualDesktop(const QModelIndex &index) override; /** * Request moving the task at the given index to the specified activities. diff --git a/libtaskmanager/xwindowtasksmodel.cpp b/libtaskmanager/xwindowtasksmodel.cpp --- a/libtaskmanager/xwindowtasksmodel.cpp +++ b/libtaskmanager/xwindowtasksmodel.cpp @@ -365,12 +365,12 @@ if (properties2 & NET::WM2AllowedActions) { wipeInfoCache = true; changedRoles << IsClosable << IsMovable << IsResizable << IsMaximizable << IsMinimizable; - changedRoles << IsFullScreenable << IsShadeable << IsVirtualDesktopChangeable; + changedRoles << IsFullScreenable << IsShadeable << IsVirtualDesktopsChangeable; } if (properties & NET::WMDesktop) { wipeInfoCache = true; - changedRoles << VirtualDesktop << IsOnAllVirtualDesktops; + changedRoles << VirtualDesktops << IsOnAllVirtualDesktops; } if (properties & NET::WMGeometry) { @@ -591,7 +591,7 @@ return d->launcherUrl(window); } else if (role == LauncherUrlWithoutIcon) { return d->launcherUrl(window, false /* encodeFallbackIcon */); - } else if (role == LegacyWinIdList) { + } else if (role == WinIdList) { return QVariantList() << window; } else if (role == MimeType) { return d->mimeType(); @@ -628,10 +628,10 @@ return d->windowInfo(window)->actionSupported(NET::ActionShade); } else if (role == IsShaded) { return d->windowInfo(window)->hasState(NET::Shaded); - } else if (role == IsVirtualDesktopChangeable) { + } else if (role == IsVirtualDesktopsChangeable) { return d->windowInfo(window)->actionSupported(NET::ActionChangeDesktop); - } else if (role == VirtualDesktop) { - return d->windowInfo(window)->desktop(); + } else if (role == VirtualDesktops) { + return QVariantList() << d->windowInfo(window)->desktop(); } else if (role == IsOnAllVirtualDesktops) { return d->windowInfo(window)->onAllDesktops(); } else if (role == Geometry) { @@ -910,12 +910,28 @@ } } -void XWindowTasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop) +void XWindowTasksModel::requestVirtualDesktops(const QModelIndex &index, const QVariantList &desktops) { if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { return; } + int desktop = 0; + + if (!desktops.isEmpty()) { + bool ok = false; + + desktop = desktops.first().toUInt(&ok); + + if (!ok) { + return; + } + } + + if (desktop > KWindowSystem::numberOfDesktops()) { + return; + } + const WId window = d->windows.at(index.row()); const KWindowInfo *info = d->windowInfo(window); @@ -928,17 +944,6 @@ } return; - // FIXME Move add-new-desktop logic up into proxy. - } else if (desktop > KWindowSystem::numberOfDesktops()) { - desktop = KWindowSystem::numberOfDesktops() + 1; - - // FIXME Arbitrary limit of 20 copied from old code. - if (desktop > 20) { - return; - } - - NETRootInfo ri(QX11Info::connection(), NET::NumberOfDesktops); - ri.setNumberOfDesktops(desktop); } KWindowSystem::setOnDesktop(window, desktop); @@ -948,6 +953,26 @@ } } +void XWindowTasksModel::requestNewVirtualDesktop(const QModelIndex &index) +{ + if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) { + return; + } + + const WId window = d->windows.at(index.row()); + const int desktop = KWindowSystem::numberOfDesktops() + 1; + + // FIXME Arbitrary limit of 20 copied from old code. + if (desktop > 20) { + return; + } + + NETRootInfo ri(QX11Info::connection(), NET::NumberOfDesktops); + ri.setNumberOfDesktops(desktop); + + KWindowSystem::setOnDesktop(window, desktop); +} + void XWindowTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities) { if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {