352 lines
14 KiB
Diff
352 lines
14 KiB
Diff
From 6c605b8bb359baa90f951233b7e12fa4a2c934fe Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Dav=C3=AD=C3=B0=20Steinn=20Geirsson?= <david@dsg.is>
|
|
Date: Tue, 17 Feb 2026 23:49:05 +0000
|
|
Subject: [PATCH 2/5] libtaskmanager: add VmSilo roles for VM-sandboxed window
|
|
metadata
|
|
|
|
Expose VmSiloColor and VmSiloName model roles in libtaskmanager by
|
|
consuming the is-dsg-vmsilo-v1 Wayland protocol. This allows the task
|
|
manager applet to render colored borders and VM labels for windows
|
|
running inside VM silos.
|
|
|
|
- Add VmSiloColor (QColor) and VmSiloName (QString) to AdditionalRoles
|
|
- Copy is-dsg-vmsilo-v1.xml protocol and add client code generation
|
|
- Implement VmsiloManager as a QWaylandClientExtension that maintains
|
|
a UUID-to-{name,color} map from protocol events
|
|
- Wire VmsiloManager into WaylandTasksModel::data() for the new roles
|
|
- Add group aggregation in TaskGroupingProxyModel (unanimous-or-empty)
|
|
|
|
Cherry-picked from 631b2c947a (vmsilo-6.5.5), adapted for 6.6.0:
|
|
- Rebase onto new IsExcludedFromCapture role added in 6.6
|
|
- Drop rulesConfig/configWatcher removed upstream in 6.6
|
|
|
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
---
|
|
libtaskmanager/CMakeLists.txt | 1 +
|
|
libtaskmanager/abstracttasksmodel.cpp | 5 ++
|
|
libtaskmanager/abstracttasksmodel.h | 2 +
|
|
libtaskmanager/protocols/is-dsg-vmsilo-v1.xml | 63 +++++++++++++
|
|
libtaskmanager/taskgroupingproxymodel.cpp | 25 ++++++
|
|
libtaskmanager/waylandtasksmodel.cpp | 90 +++++++++++++++++++
|
|
shell/org.kde.plasmashell.desktop.cmake | 2 +-
|
|
7 files changed, 187 insertions(+), 1 deletion(-)
|
|
create mode 100644 libtaskmanager/protocols/is-dsg-vmsilo-v1.xml
|
|
|
|
diff --git a/libtaskmanager/CMakeLists.txt b/libtaskmanager/CMakeLists.txt
|
|
index b50d24fc61..ae99065686 100644
|
|
--- a/libtaskmanager/CMakeLists.txt
|
|
+++ b/libtaskmanager/CMakeLists.txt
|
|
@@ -51,6 +51,7 @@ qt6_generate_wayland_protocol_client_sources(taskmanager
|
|
FILES ${PLASMA_WAYLAND_PROTOCOLS_DIR}/org-kde-plasma-virtual-desktop.xml
|
|
FILES ${PLASMA_WAYLAND_PROTOCOLS_DIR}/plasma-window-management.xml
|
|
FILES ${PLASMA_WAYLAND_PROTOCOLS_DIR}/zkde-screencast-unstable-v1.xml
|
|
+ FILES ${CMAKE_CURRENT_SOURCE_DIR}/protocols/is-dsg-vmsilo-v1.xml
|
|
)
|
|
|
|
generate_export_header(taskmanager)
|
|
diff --git a/libtaskmanager/abstracttasksmodel.cpp b/libtaskmanager/abstracttasksmodel.cpp
|
|
index 4ada403145..8f24743057 100644
|
|
--- a/libtaskmanager/abstracttasksmodel.cpp
|
|
+++ b/libtaskmanager/abstracttasksmodel.cpp
|
|
@@ -6,6 +6,7 @@
|
|
|
|
#include "abstracttasksmodel.h"
|
|
|
|
+#include <QColor>
|
|
#include <QMetaEnum>
|
|
|
|
namespace TaskManager
|
|
@@ -103,6 +104,10 @@ QVariant AbstractTasksModel::data(const QModelIndex &, int role) const
|
|
case HasNoBorder:
|
|
case IsExcludedFromCapture:
|
|
return false;
|
|
+ case VmSiloColor:
|
|
+ return QColor();
|
|
+ case VmSiloName:
|
|
+ return QString();
|
|
default:
|
|
return {};
|
|
}
|
|
diff --git a/libtaskmanager/abstracttasksmodel.h b/libtaskmanager/abstracttasksmodel.h
|
|
index 9bc5a72af5..1ced297e8e 100644
|
|
--- a/libtaskmanager/abstracttasksmodel.h
|
|
+++ b/libtaskmanager/abstracttasksmodel.h
|
|
@@ -96,6 +96,8 @@ public:
|
|
HasNoBorder, /**< Whether the task's window has the no border state set. @since 6.4 */
|
|
CanSetNoBorder, /**< Whether the task can set no border state. @since 6.4 */
|
|
IsExcludedFromCapture, /**< Whether the task's window has been hidden from screencasts. @since 6.6 */
|
|
+ VmSiloColor, /**< QColor: ARGB border color for VM-sandboxed windows. Invalid if not a vmsilo window. @since 6.6 */
|
|
+ VmSiloName, /**< QString: Human-readable VM name for VM-sandboxed windows. Empty if not a vmsilo window. @since 6.6 */
|
|
};
|
|
Q_ENUM(AdditionalRoles)
|
|
|
|
diff --git a/libtaskmanager/protocols/is-dsg-vmsilo-v1.xml b/libtaskmanager/protocols/is-dsg-vmsilo-v1.xml
|
|
new file mode 100644
|
|
index 0000000000..6876661753
|
|
--- /dev/null
|
|
+++ b/libtaskmanager/protocols/is-dsg-vmsilo-v1.xml
|
|
@@ -0,0 +1,63 @@
|
|
+<?xml version="1.0" encoding="UTF-8"?>
|
|
+<protocol name="is_dsg_vmsilo_v1">
|
|
+ <copyright>
|
|
+ SPDX-FileCopyrightText: 2026, Davíð Steinn Geirsson
|
|
+
|
|
+ SPDX-License-Identifier: MIT
|
|
+ </copyright>
|
|
+
|
|
+ <interface name="is_dsg_vmsilo_manager_v1" version="1">
|
|
+ <description summary="broadcast vmsilo window information">
|
|
+ The is_dsg_vmsilo_manager_v1 interface allows clients to receive
|
|
+ information about windows that belong to vmsilo security contexts.
|
|
+
|
|
+ The compositor sends window events for each existing vmsilo window
|
|
+ when a client binds this interface, followed by a done event. After
|
|
+ the initial batch, the compositor continues to send window and
|
|
+ window_removed events as vmsilo windows are created and destroyed.
|
|
+
|
|
+ Clients can match the uuid values with PlasmaWindowManagement UUIDs
|
|
+ to associate vmsilo metadata (such as VM name and color) with
|
|
+ specific windows.
|
|
+ </description>
|
|
+
|
|
+ <request name="destroy" type="destructor">
|
|
+ <description summary="destroy the manager">
|
|
+ Destroy this is_dsg_vmsilo_manager_v1 object. The compositor will
|
|
+ no longer send events to this client.
|
|
+ </description>
|
|
+ </request>
|
|
+
|
|
+ <event name="window">
|
|
+ <description summary="a vmsilo window exists">
|
|
+ Sent for each existing vmsilo window when the client first binds
|
|
+ the interface, and again whenever a new vmsilo window is created.
|
|
+
|
|
+ The uuid identifies the window and corresponds to the UUID exposed
|
|
+ by the PlasmaWindowManagement protocol. The vm_name is the
|
|
+ human-readable name of the virtual machine. The color is an ARGB
|
|
+ value representing the VM's assigned color.
|
|
+ </description>
|
|
+ <arg name="uuid" type="string" summary="window UUID matching PlasmaWindowManagement"/>
|
|
+ <arg name="vm_name" type="string" summary="human-readable VM name"/>
|
|
+ <arg name="color" type="uint" summary="ARGB color value for the VM"/>
|
|
+ </event>
|
|
+
|
|
+ <event name="window_removed">
|
|
+ <description summary="a vmsilo window was destroyed">
|
|
+ Sent when a previously advertised vmsilo window is destroyed.
|
|
+ The uuid identifies which window was removed.
|
|
+ </description>
|
|
+ <arg name="uuid" type="string" summary="UUID of the removed window"/>
|
|
+ </event>
|
|
+
|
|
+ <event name="done">
|
|
+ <description summary="end of initial window list">
|
|
+ Sent after the initial batch of window events when a client first
|
|
+ binds the interface. This signals that all existing vmsilo windows
|
|
+ have been reported. Subsequent window and window_removed events
|
|
+ are sent individually as changes occur.
|
|
+ </description>
|
|
+ </event>
|
|
+ </interface>
|
|
+</protocol>
|
|
diff --git a/libtaskmanager/taskgroupingproxymodel.cpp b/libtaskmanager/taskgroupingproxymodel.cpp
|
|
index e4957055ef..ae3e3d0ce9 100644
|
|
--- a/libtaskmanager/taskgroupingproxymodel.cpp
|
|
+++ b/libtaskmanager/taskgroupingproxymodel.cpp
|
|
@@ -8,6 +8,7 @@
|
|
#include "abstracttasksmodel.h"
|
|
#include "tasktools.h"
|
|
|
|
+#include <QColor>
|
|
#include <QDateTime>
|
|
#include <QSet>
|
|
#include <algorithm>
|
|
@@ -705,6 +706,30 @@ QVariant TaskGroupingProxyModel::data(const QModelIndex &proxyIndex, int role) c
|
|
return d->all(proxyIndex, AbstractTasksModel::HasNoBorder);
|
|
} else if (role == AbstractTasksModel::IsExcludedFromCapture) {
|
|
return d->all(proxyIndex, AbstractTasksModel::IsExcludedFromCapture);
|
|
+ } else if (role == AbstractTasksModel::VmSiloColor) {
|
|
+ // Return the color only if all children share the same color
|
|
+ const QColor first = index(0, 0, proxyIndex).data(AbstractTasksModel::VmSiloColor).value<QColor>();
|
|
+ if (!first.isValid()) {
|
|
+ return QColor();
|
|
+ }
|
|
+ for (int i = 1; i < rowCount(proxyIndex); ++i) {
|
|
+ if (index(i, 0, proxyIndex).data(AbstractTasksModel::VmSiloColor).value<QColor>() != first) {
|
|
+ return QColor();
|
|
+ }
|
|
+ }
|
|
+ return first;
|
|
+ } else if (role == AbstractTasksModel::VmSiloName) {
|
|
+ // Return the name only if all children share the same name
|
|
+ const QString first = index(0, 0, proxyIndex).data(AbstractTasksModel::VmSiloName).toString();
|
|
+ if (first.isEmpty()) {
|
|
+ return QString();
|
|
+ }
|
|
+ for (int i = 1; i < rowCount(proxyIndex); ++i) {
|
|
+ if (index(i, 0, proxyIndex).data(AbstractTasksModel::VmSiloName).toString() != first) {
|
|
+ return QString();
|
|
+ }
|
|
+ }
|
|
+ return first;
|
|
} else if (role == AbstractTasksModel::IsVirtualDesktopsChangeable) {
|
|
return d->all(proxyIndex, AbstractTasksModel::IsVirtualDesktopsChangeable);
|
|
} else if (role == AbstractTasksModel::VirtualDesktops) {
|
|
diff --git a/libtaskmanager/waylandtasksmodel.cpp b/libtaskmanager/waylandtasksmodel.cpp
|
|
index 995178e6b1..292c753ca4 100644
|
|
--- a/libtaskmanager/waylandtasksmodel.cpp
|
|
+++ b/libtaskmanager/waylandtasksmodel.cpp
|
|
@@ -14,7 +14,9 @@
|
|
#include <KWindowSystem>
|
|
|
|
#include <qwayland-plasma-window-management.h>
|
|
+#include <qwayland-is-dsg-vmsilo-v1.h>
|
|
|
|
+#include <QColor>
|
|
#include <QDateTime>
|
|
#include <QFuture>
|
|
#include <QFutureWatcher>
|
|
@@ -437,6 +439,49 @@ void PlasmaWindowManagement::org_kde_plasma_window_management_stacking_order_cha
|
|
m_pendingStackingOrder.reset(new PlasmaStackingOrder(this, org_kde_plasma_window_management_get_stacking_order(object())));
|
|
}
|
|
|
|
+class VmsiloManager : public QWaylandClientExtensionTemplate<VmsiloManager>, public QtWayland::is_dsg_vmsilo_manager_v1
|
|
+{
|
|
+ Q_OBJECT
|
|
+public:
|
|
+ static constexpr int s_version = 1;
|
|
+ VmsiloManager()
|
|
+ : QWaylandClientExtensionTemplate(s_version)
|
|
+ {
|
|
+ initialize();
|
|
+ }
|
|
+ ~VmsiloManager()
|
|
+ {
|
|
+ if (isActive()) {
|
|
+ destroy();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ struct Info {
|
|
+ QString vmName;
|
|
+ QColor color;
|
|
+ };
|
|
+ QHash<QString, Info> windows; // keyed by PlasmaWindowManagement UUID
|
|
+
|
|
+protected:
|
|
+ void is_dsg_vmsilo_manager_v1_window(const QString &uuid, const QString &vm_name, uint32_t color) override
|
|
+ {
|
|
+ windows[uuid] = {vm_name, QColor::fromRgba(color)};
|
|
+ Q_EMIT windowAdded(uuid);
|
|
+ }
|
|
+ void is_dsg_vmsilo_manager_v1_window_removed(const QString &uuid) override
|
|
+ {
|
|
+ windows.remove(uuid);
|
|
+ Q_EMIT windowRemoved(uuid);
|
|
+ }
|
|
+ void is_dsg_vmsilo_manager_v1_done() override
|
|
+ {
|
|
+ }
|
|
+
|
|
+Q_SIGNALS:
|
|
+ void windowAdded(const QString &uuid);
|
|
+ void windowRemoved(const QString &uuid);
|
|
+};
|
|
+
|
|
class Q_DECL_HIDDEN WaylandTasksModel::Private
|
|
{
|
|
public:
|
|
@@ -450,6 +495,7 @@ public:
|
|
// key=leader, values=transient children
|
|
QMultiHash<PlasmaWindow *, PlasmaWindow *> transientsDemandingAttention;
|
|
std::unique_ptr<PlasmaWindowManagement> windowManagement;
|
|
+ std::unique_ptr<VmsiloManager> vmsiloManager;
|
|
VirtualDesktopInfo *virtualDesktopInfo = nullptr;
|
|
static QUuid uuid;
|
|
QList<QString> stackingOrder;
|
|
@@ -468,6 +514,7 @@ public:
|
|
|
|
void dataChanged(PlasmaWindow *window, int role);
|
|
void dataChanged(PlasmaWindow *window, const QList<int> &roles);
|
|
+ void vmsiloDataChanged(const QString &uuid);
|
|
|
|
private:
|
|
WaylandTasksModel *const q;
|
|
@@ -513,6 +560,22 @@ void WaylandTasksModel::Private::initWayland()
|
|
this->dataChanged(window.get(), StackingOrder);
|
|
}
|
|
});
|
|
+
|
|
+ vmsiloManager = std::make_unique<VmsiloManager>();
|
|
+
|
|
+ QObject::connect(vmsiloManager.get(), &VmsiloManager::activeChanged, q, [this] {
|
|
+ if (!vmsiloManager->isActive()) {
|
|
+ vmsiloManager->windows.clear();
|
|
+ }
|
|
+ });
|
|
+
|
|
+ QObject::connect(vmsiloManager.get(), &VmsiloManager::windowAdded, q, [this](const QString &uuid) {
|
|
+ vmsiloDataChanged(uuid);
|
|
+ });
|
|
+
|
|
+ QObject::connect(vmsiloManager.get(), &VmsiloManager::windowRemoved, q, [this](const QString &uuid) {
|
|
+ vmsiloDataChanged(uuid);
|
|
+ });
|
|
}
|
|
|
|
auto WaylandTasksModel::Private::findWindow(PlasmaWindow *window) const
|
|
@@ -842,6 +905,17 @@ void WaylandTasksModel::Private::dataChanged(PlasmaWindow *window, const QList<i
|
|
Q_EMIT q->dataChanged(idx, idx, roles);
|
|
}
|
|
|
|
+void WaylandTasksModel::Private::vmsiloDataChanged(const QString &uuid)
|
|
+{
|
|
+ for (size_t i = 0; i < windows.size(); ++i) {
|
|
+ if (windows[i]->uuid == uuid) {
|
|
+ QModelIndex idx = q->index(i);
|
|
+ Q_EMIT q->dataChanged(idx, idx, QList<int>{AbstractTasksModel::VmSiloColor, AbstractTasksModel::VmSiloName});
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
WaylandTasksModel::WaylandTasksModel(QObject *parent)
|
|
: AbstractWindowTasksModel(parent)
|
|
, d(new Private(this))
|
|
@@ -957,6 +1031,22 @@ QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const
|
|
return window->applicationMenuService;
|
|
} else if (role == CanLaunchNewInstance) {
|
|
return canLauchNewInstance(d->appData(window));
|
|
+ } else if (role == VmSiloColor) {
|
|
+ if (d->vmsiloManager) {
|
|
+ auto it = d->vmsiloManager->windows.constFind(window->uuid);
|
|
+ if (it != d->vmsiloManager->windows.constEnd()) {
|
|
+ return it->color;
|
|
+ }
|
|
+ }
|
|
+ return QColor();
|
|
+ } else if (role == VmSiloName) {
|
|
+ if (d->vmsiloManager) {
|
|
+ auto it = d->vmsiloManager->windows.constFind(window->uuid);
|
|
+ if (it != d->vmsiloManager->windows.constEnd()) {
|
|
+ return it->vmName;
|
|
+ }
|
|
+ }
|
|
+ return QString();
|
|
}
|
|
|
|
return AbstractTasksModel::data(index, role);
|
|
diff --git a/shell/org.kde.plasmashell.desktop.cmake b/shell/org.kde.plasmashell.desktop.cmake
|
|
index 3fee14565f..f0b51077e0 100644
|
|
--- a/shell/org.kde.plasmashell.desktop.cmake
|
|
+++ b/shell/org.kde.plasmashell.desktop.cmake
|
|
@@ -72,5 +72,5 @@ Icon=plasmashell
|
|
NoDisplay=true
|
|
X-systemd-skip=true
|
|
|
|
-X-KDE-Wayland-Interfaces=org_kde_plasma_window_management,org_kde_kwin_keystate,zkde_screencast_unstable_v1,org_kde_plasma_activation_feedback,kde_lockscreen_overlay_v1
|
|
+X-KDE-Wayland-Interfaces=org_kde_plasma_window_management,org_kde_kwin_keystate,zkde_screencast_unstable_v1,org_kde_plasma_activation_feedback,kde_lockscreen_overlay_v1,is_dsg_vmsilo_manager_v1
|
|
X-KDE-DBUS-Restricted-Interfaces=org.kde.KWin.ScreenShot2
|
|
--
|
|
2.53.0
|
|
|