147 lines
6.7 KiB
Diff
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
|
|
|