vmsilo/patches/plasma-workspace-0002-libtaskmanager-add-VmSilo-roles-for-VM-sandboxed-win.patch

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