From b925010cb3c65aa992fd02270a48ec7f2ba47d3f Mon Sep 17 00:00:00 2001 From: jbwfu <75001777+jbwfu@users.noreply.github.com> Date: Thu, 18 Jun 2026 12:57:07 +0800 Subject: [PATCH] fix(clipboard): paste selected history entry (#2660) --- .../Modals/Clipboard/ClipboardEditor.qml | 6 ++++ .../Clipboard/ClipboardHistoryContent.qml | 36 +++++++++++++++++-- .../Clipboard/ClipboardHistoryModal.qml | 23 ++++++++++-- .../Clipboard/ClipboardHistoryPopout.qml | 12 +++++-- quickshell/Services/ClipboardService.qml | 3 ++ quickshell/Services/PopoutService.qml | 2 +- 6 files changed, 74 insertions(+), 8 deletions(-) diff --git a/quickshell/Modals/Clipboard/ClipboardEditor.qml b/quickshell/Modals/Clipboard/ClipboardEditor.qml index 37207131..10f87e57 100644 --- a/quickshell/Modals/Clipboard/ClipboardEditor.qml +++ b/quickshell/Modals/Clipboard/ClipboardEditor.qml @@ -15,6 +15,12 @@ Item { property var entry: null property string editorText: "" + function releaseTextInputFocus() { + if (editField) { + editField.focus = false; + } + } + function decodeEntryData(data) { if (!data) { return ""; diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml b/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml index 8e3a6853..61495db0 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryContent.qml @@ -61,16 +61,46 @@ FocusScope { } } + function releaseTextInputFocus() { + // Drop text-input focus before hiding the Wayland surface. + if (searchField) { + searchField.setFocus(false); + } + if (editorView) { + editorView.releaseTextInputFocus(); + } + root.forceActiveFocus(); + } + + function requestClose(instant) { + releaseTextInputFocus(); + if (instant) { + root.instantCloseRequested(); + } else { + root.closeRequested(); + } + } + function hide() { - closeRequested(); + requestClose(false); } function pasteSelected() { - ClipboardService.pasteSelected(() => root.instantCloseRequested()); + const entry = selectedEntry(); + if (!entry) + return; + ClipboardService.pasteEntry(entry, () => root.requestClose(true)); } function copyEntry(entry) { - ClipboardService.copyEntry(entry, () => root.closeRequested()); + ClipboardService.copyEntry(entry, () => root.requestClose(false)); + } + + function selectedEntry() { + const entries = activeTab === "saved" ? pinnedEntries : unpinnedEntries; + if (!entries || entries.length === 0 || selectedIndex < 0 || selectedIndex >= entries.length) + return null; + return entries[selectedIndex]; } function deleteEntry(entry) { diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml index 53068495..4e0e3d33 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml @@ -45,8 +45,22 @@ DankModal { }); } + function releaseTextInputFocus() { + contentLoader.item?.releaseTextInputFocus(); + } + function hide() { - close(); + releaseTextInputFocus(); + Qt.callLater(function () { + clipboardHistoryModal.close(); + }); + } + + function instantHide() { + releaseTextInputFocus(); + Qt.callLater(function () { + clipboardHistoryModal.instantClose(); + }); } onDialogClosed: { @@ -68,6 +82,11 @@ DankModal { enableShadow: true closeOnEscapeKey: (contentLoader.item?.mode ?? "history") !== "editor" onBackgroundClicked: hide() + onShouldBeVisibleChanged: { + if (!shouldBeVisible) { + releaseTextInputFocus(); + } + } Ref { service: ClipboardService @@ -112,7 +131,7 @@ DankModal { ClipboardHistoryContent { clearConfirmDialog: clearConfirmDialog onCloseRequested: clipboardHistoryModal.hide() - onInstantCloseRequested: clipboardHistoryModal.instantClose() + onInstantCloseRequested: clipboardHistoryModal.instantHide() } } } diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryPopout.qml b/quickshell/Modals/Clipboard/ClipboardHistoryPopout.qml index ba12d8b0..027fd862 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryPopout.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryPopout.qml @@ -37,8 +37,15 @@ DankPopout { }); } + function releaseTextInputFocus() { + contentLoader.item?.releaseTextInputFocus(); + } + function hide() { - close(); + releaseTextInputFocus(); + Qt.callLater(function () { + root.close(); + }); } function clearAll() { @@ -57,6 +64,7 @@ DankPopout { onShouldBeVisibleChanged: { if (!shouldBeVisible) { + releaseTextInputFocus(); return; } if (clipboardAvailable) { @@ -134,7 +142,7 @@ DankPopout { clearConfirmDialog: clearConfirmDialog onCloseRequested: root.hide() - onInstantCloseRequested: root.close() + onInstantCloseRequested: root.hide() Component.onCompleted: { activeTab = root.activeTab; diff --git a/quickshell/Services/ClipboardService.qml b/quickshell/Services/ClipboardService.qml index fbd7a3f6..28f90d22 100644 --- a/quickshell/Services/ClipboardService.qml +++ b/quickshell/Services/ClipboardService.qml @@ -39,6 +39,9 @@ Singleton { Process { id: wtypeProcess + // TODO: This is only a paste shortcut fallback. It assumes the target + // application accepts Ctrl+V, which is false for many terminals. + // Replace with a more reliable target-aware paste strategy. command: ["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"] running: false } diff --git a/quickshell/Services/PopoutService.qml b/quickshell/Services/PopoutService.qml index cf1c146e..b6c1cb16 100644 --- a/quickshell/Services/PopoutService.qml +++ b/quickshell/Services/PopoutService.qml @@ -480,7 +480,7 @@ Singleton { } function closeClipboardHistory() { - clipboardHistoryModal?.close(); + clipboardHistoryModal?.hide(); } function unloadClipboardHistoryPopout() {