vmsilo/patches/kwin-0005-vmsilo-fix-clipboard-lockup-by-eagerly-snapshotting-.patch

147 lines
6.7 KiB
Diff

From 7be54472f9e05eee0f79234998264b9f38b535a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dav=C3=AD=C3=B0=20Steinn=20Geirsson?= <david@dsg.is>
Date: Fri, 27 Mar 2026 13:08:28 +0000
Subject: [PATCH 5/6] vmsilo: fix clipboard lockup by eagerly snapshotting
sources on context switch
When restoring a client-owned data source to the seat, data control
clients (plasmashell/klipper) immediately try to read it. If the source's
backing client is in a deprioritized VM, the pipe read times out and
freezes the desktop.
Eagerly snapshot clipboard data into memory-backed VmsiloBufferSource
instances when saving context, and only restore those to the seat.
If the snapshot completes while we're already in the target context,
push it to the seat so the clipboard becomes available.
BUG: DataControlOffer timeout reading from pipe on context switch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
src/vmsilo_clipboard_manager.cpp | 86 ++++++++++++++++++++++++++++++++
src/vmsilo_clipboard_manager.h | 1 +
2 files changed, 87 insertions(+)
diff --git a/src/vmsilo_clipboard_manager.cpp b/src/vmsilo_clipboard_manager.cpp
index 7765f2cbd3..4c7560ca78 100644
--- a/src/vmsilo_clipboard_manager.cpp
+++ b/src/vmsilo_clipboard_manager.cpp
@@ -400,6 +400,13 @@ void VmsiloClipboardManager::saveToContext(const QString &context)
.primarySelection = primarySelection,
};
}
+
+ // Eagerly snapshot client-owned sources into memory-backed buffers.
+ // restoreFromContext() only restores VmsiloBufferSource instances to
+ // prevent data control clients (klipper) from blocking on unresponsive
+ // source clients (e.g. deprioritized VM applications).
+ snapshotSourceAsync(context, false, selection);
+ snapshotSourceAsync(context, true, primarySelection);
}
void VmsiloClipboardManager::restoreFromContext(const QString &context)
@@ -422,10 +429,89 @@ void VmsiloClipboardManager::restoreFromContext(const QString &context)
}
}
+ // Only restore memory-backed (VmsiloBufferSource) sources to the seat.
+ // Client-owned sources may be unresponsive (e.g. VM deprioritized after
+ // context switch), causing data control clients like klipper to timeout
+ // and freeze the desktop when they try to read the offered selection.
+ // The eager snapshot started in saveToContext() will replace the raw
+ // source once complete; if we're still in this context at that point,
+ // snapshotSourceAsync() will update the seat selection.
+ if (selection && !qobject_cast<VmsiloBufferSource *>(selection)) {
+ qCDebug(KWIN_CORE) << "Vmsilo: deferring restore of client-owned selection for context"
+ << (context.isEmpty() ? QStringLiteral("host") : context)
+ << "until snapshot completes";
+ selection = nullptr;
+ }
+ if (primarySelection && !qobject_cast<VmsiloBufferSource *>(primarySelection)) {
+ qCDebug(KWIN_CORE) << "Vmsilo: deferring restore of client-owned primary selection for context"
+ << (context.isEmpty() ? QStringLiteral("host") : context)
+ << "until snapshot completes";
+ primarySelection = nullptr;
+ }
+
seat->setSelection(selection, serial);
seat->setPrimarySelection(primarySelection, serial);
}
+void VmsiloClipboardManager::snapshotSourceAsync(const QString &context, bool isPrimary, AbstractDataSource *source)
+{
+ if (!source || qobject_cast<VmsiloBufferSource *>(source)) {
+ return; // Already memory-backed or null — nothing to snapshot.
+ }
+
+ auto *op = new VmsiloAsyncCopyOperation(source, this);
+ connect(op, &VmsiloAsyncCopyOperation::finished, this,
+ [this, context, isPrimary, sourceWeak = QPointer<AbstractDataSource>(source)](const QHash<QString, QByteArray> &data) {
+ auto *op = qobject_cast<VmsiloAsyncCopyOperation *>(sender());
+ if (op) {
+ op->deleteLater();
+ }
+
+ if (data.isEmpty()) {
+ qCDebug(KWIN_CORE) << "Vmsilo: snapshot produced no data for"
+ << (context.isEmpty() ? QStringLiteral("host") : context)
+ << (isPrimary ? "primary" : "selection");
+ return;
+ }
+
+ // Find the buffer slot for this context + selection type.
+ QPointer<AbstractDataSource> *target = nullptr;
+ if (context.isEmpty()) {
+ target = isPrimary ? &m_hostPrimarySelection : &m_hostSelection;
+ } else {
+ auto it = m_contextBuffers.find(context);
+ if (it != m_contextBuffers.end()) {
+ target = isPrimary ? &it->primarySelection : &it->selection;
+ }
+ }
+
+ // Only replace if the stored source hasn't changed since we started.
+ if (!target || *target != sourceWeak) {
+ return;
+ }
+
+ auto *snapshot = new VmsiloBufferSource(data, this);
+ *target = snapshot;
+
+ qCDebug(KWIN_CORE) << "Vmsilo: snapshot ready for"
+ << (context.isEmpty() ? QStringLiteral("host") : context)
+ << (isPrimary ? "primary" : "selection");
+
+ // If we're currently in this context and the seat has no selection
+ // (because restoreFromContext skipped the raw source), push the
+ // snapshot to the seat now so the clipboard becomes available.
+ if (context == m_activeContext) {
+ auto *seat = waylandServer()->seat();
+ auto *display = waylandServer()->display();
+ if (!isPrimary && !seat->selection()) {
+ seat->setSelection(snapshot, display->nextSerial());
+ } else if (isPrimary && !seat->primarySelection()) {
+ seat->setPrimarySelection(snapshot, display->nextSerial());
+ }
+ }
+ });
+}
+
void VmsiloClipboardManager::copyToGlobal()
{
auto *seat = waylandServer()->seat();
diff --git a/src/vmsilo_clipboard_manager.h b/src/vmsilo_clipboard_manager.h
index 34cfa95c07..489215c7a7 100644
--- a/src/vmsilo_clipboard_manager.h
+++ b/src/vmsilo_clipboard_manager.h
@@ -116,6 +116,7 @@ private:
void saveToContext(const QString &context);
void restoreFromContext(const QString &context);
+ void snapshotSourceAsync(const QString &context, bool isPrimary, AbstractDataSource *source);
void cleanupStaleContexts();
void scheduleCleanup();
--
2.53.0