From 045ac59a44e527c27355f08a85bd1a73b0de8197 Mon Sep 17 00:00:00 2001 From: purian23 Date: Mon, 25 May 2026 23:25:57 -0400 Subject: [PATCH] feat(Clipboard): Clipboard Editor PR Revived (#2492) * feat(clipboard): Add editing capability to clipboard entries * Add split save menu for clipboard editor * Add clipboard editor shortcuts and hints * Show full clipboard text in editor * feat(Clipboard): Revive ClipboardEditor PR - Original PR #1916 by @nabaco * fix(clipboard): restore Save button targets in editor --------- Co-authored-by: Nachum Barcohen <38861757+nabaco@users.noreply.github.com> --- .../Modals/Clipboard/ClipboardContent.qml | 2 + .../Modals/Clipboard/ClipboardEditor.qml | 519 ++++++++++++++++++ .../Modals/Clipboard/ClipboardEntry.qml | 22 +- .../Clipboard/ClipboardHistoryModal.qml | 135 ++++- .../Clipboard/ClipboardKeyboardController.qml | 21 + .../Clipboard/ClipboardKeyboardHints.qml | 11 +- quickshell/Services/ClipboardService.qml | 11 + 7 files changed, 714 insertions(+), 7 deletions(-) create mode 100644 quickshell/Modals/Clipboard/ClipboardEditor.qml diff --git a/quickshell/Modals/Clipboard/ClipboardContent.qml b/quickshell/Modals/Clipboard/ClipboardContent.qml index e00bc22e..a4599b10 100644 --- a/quickshell/Modals/Clipboard/ClipboardContent.qml +++ b/quickshell/Modals/Clipboard/ClipboardContent.qml @@ -145,6 +145,7 @@ Item { onDeleteRequested: clipboardContent.modal.deleteEntry(modelData) onPinRequested: clipboardContent.modal.pinEntry(modelData) onUnpinRequested: clipboardContent.modal.unpinEntry(modelData) + onEditRequested: clipboardContent.modal.editEntry(modelData) } } @@ -204,6 +205,7 @@ Item { onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData) onPinRequested: clipboardContent.modal.pinEntry(modelData) onUnpinRequested: clipboardContent.modal.unpinEntry(modelData) + onEditRequested: clipboardContent.modal.editEntry(modelData) } } diff --git a/quickshell/Modals/Clipboard/ClipboardEditor.qml b/quickshell/Modals/Clipboard/ClipboardEditor.qml new file mode 100644 index 00000000..552343e5 --- /dev/null +++ b/quickshell/Modals/Clipboard/ClipboardEditor.qml @@ -0,0 +1,519 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + required property var modal + property var keyController: null + + property var entry: null + property string editorText: "" + + function decodeEntryData(data) { + if (!data) { + return ""; + } + if (typeof data !== "string") { + return String(data); + } + + const sanitized = data.replace(/\s+/g, ""); + if (sanitized.length === 0) { + return ""; + } + + try { + const chars = new Array(sanitized.length); + for (let i = 0; i < sanitized.length; i++) { + chars[i] = sanitized.charAt(i); + } + + let buffer = null; + if (typeof Qt !== "undefined" && typeof Qt.atob === "function") { + buffer = Qt.atob(chars); + } else if (typeof atob === "function") { + const binary = atob(sanitized); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + buffer = bytes.buffer; + } + if (!buffer || buffer.byteLength === 0) { + return data; + } + + const bytes = new Uint8Array(buffer); + let binary = ""; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + + try { + return decodeURIComponent(escape(binary)); + } catch (e) { + return binary; + } + } catch (e) { + return data; + } + } + + function setEntry(newEntry) { + entry = newEntry; + editorText = newEntry?.text ?? newEntry?.preview ?? ""; + if (editField) { + editField.text = editorText; + } + Qt.callLater(function () { + if (editField) { + editField.forceActiveFocus(); + } + }); + + if (!newEntry || newEntry.isImage) { + return; + } + + const requestedId = newEntry.id; + DMSService.sendRequest("clipboard.getEntry", { + "id": requestedId + }, function (response) { + if (response.error) { + return; + } + if (!root.entry || root.entry.id !== requestedId) { + return; + } + const result = response.result; + let fullText = ""; + if (result?.data) { + fullText = root.decodeEntryData(result.data); + } else { + fullText = result?.preview ?? ""; + } + + if (!fullText || fullText.length === 0) { + return; + } + root.editorText = fullText; + if (editField) { + editField.text = fullText; + } + }); + } + + function saveEntry(action) { + const saveAction = action ?? "history"; + DMSService.sendRequest("clipboard.copy", { + "text": root.editorText + }, function (response) { + if (response.error) { + ToastService.showError(I18n.tr("Failed to update clipboard")); + return; + } + if (saveAction === "history") { + modal.mode = "history"; + Qt.callLater(function () { + ClipboardService.reset(); + ClipboardService.refresh(); + if (keyController) { + keyController.reset(); + } + }); + return; + } + if (saveAction === "close") { + modal.hide(); + return; + } + if (saveAction === "paste") { + ClipboardService.pasteClipboard(modal.hide); + } + }); + } + + function positionSaveMenu() { + saveMenu.width = Math.max(saveMenuColumn.implicitWidth + saveMenu.padding * 2, saveButton.width); + const pos = saveButton.mapToItem(Overlay.overlay, 0, 0); + const popupW = saveMenu.width; + const popupH = saveMenu.height; + const overlayW = Overlay.overlay.width; + const overlayH = Overlay.overlay.height; + + let x = pos.x + (saveButton.width - popupW) / 2; + let y = pos.y + saveButton.height + 4; + if (y + popupH > overlayH) { + y = pos.y - popupH - 4; + } + + x = Math.max(8, Math.min(x, overlayW - popupW - 8)); + y = Math.max(8, y); + + saveMenu.x = x; + saveMenu.y = y; + } + + function toggleSaveMenu() { + if (saveMenu.visible) { + saveMenu.close(); + return; + } + saveMenu.open(); + positionSaveMenu(); + Qt.callLater(positionSaveMenu); + } + + Shortcut { + sequences: ["Escape"] + enabled: modal.mode === "editor" + onActivated: modal.mode = "history" + } + + Column { + anchors.fill: parent + anchors.margins: Theme.spacingM + spacing: Theme.spacingM + + Item { + id: editorHeader + width: parent.width + height: ClipboardConstants.headerHeight + + DankActionButton { + iconName: "arrow_back" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + onClicked: modal.mode = "history" + } + + StyledText { + text: I18n.tr("Edit Clipboard") + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium + anchors.centerIn: parent + } + + DankActionButton { + iconName: "close" + iconSize: Theme.iconSize - 4 + iconColor: Theme.surfaceText + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + onClicked: modal.mode = "history" + } + } + + StyledRect { + id: editFieldContainer + width: parent.width + height: Math.max(Theme.fontSizeMedium * 8, parent.height - editorHeader.height - editorActions.height - Theme.spacingM * 2) + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.color: editField.activeFocus ? Theme.primary : Theme.outlineMedium + border.width: editField.activeFocus ? 2 : 1 + clip: true + + DankIcon { + id: editIcon + name: "edit" + size: Theme.iconSize + color: editField.activeFocus ? Theme.primary : Theme.surfaceVariantText + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM + anchors.top: parent.top + anchors.topMargin: Theme.spacingM + } + + DankFlickable { + id: editScroll + anchors.left: editIcon.right + anchors.leftMargin: Theme.spacingS + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.rightMargin: Theme.spacingM + anchors.topMargin: Theme.spacingS + anchors.bottomMargin: Theme.spacingS + clip: true + contentWidth: width + contentHeight: editField.height + + TextEdit { + id: editField + width: editScroll.width + height: Math.max(editScroll.height, contentHeight) + text: root.editorText + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + wrapMode: TextEdit.Wrap + selectByMouse: true + onTextChanged: root.editorText = text + Keys.onPressed: function (event) { + const hasCtrl = (event.modifiers & Qt.ControlModifier) !== 0; + const hasShift = (event.modifiers & Qt.ShiftModifier) !== 0; + + if (hasCtrl && event.key === Qt.Key_S) { + root.saveEntry(hasShift ? "close" : "history"); + event.accepted = true; + return; + } + if (hasCtrl && hasShift && event.key === Qt.Key_V) { + root.saveEntry("paste"); + event.accepted = true; + return; + } + } + } + } + + StyledText { + text: I18n.tr("Edit clipboard text") + font.pixelSize: Theme.fontSizeMedium + color: Theme.outlineButton + anchors.left: editScroll.left + anchors.right: editScroll.right + anchors.top: editScroll.top + anchors.bottom: editScroll.bottom + visible: editField.text.length === 0 && !editField.activeFocus + wrapMode: Text.WordWrap + } + } + + Row { + id: editorActions + width: parent.width + spacing: Theme.spacingS + + Item { + id: buttonSpacer + width: Math.max(0, parent.width - cancelButton.width - saveButton.width - Theme.spacingS) + height: 1 + } + + DankButton { + id: cancelButton + text: I18n.tr("Cancel") + backgroundColor: Theme.surfaceContainerHigh + textColor: Theme.surfaceText + onClicked: modal.mode = "history" + } + + Item { + id: saveButton + + readonly property int buttonHeight: cancelButton.buttonHeight + readonly property int arrowWidth: Theme.iconSizeLarge + + width: cancelButton.width + height: buttonHeight + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: Theme.primary + } + + Item { + id: saveMainArea + anchors.left: parent.left + anchors.right: saveArrowArea.left + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + StyledText { + text: I18n.tr("Save") + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.onPrimary + anchors.centerIn: saveMainArea + } + + Item { + id: saveArrowArea + width: saveButton.arrowWidth + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + Rectangle { + width: 1 + height: parent.height - cancelButton.horizontalPadding + color: Theme.withAlpha(Theme.onPrimary, 0.2) + anchors.right: saveArrowArea.left + anchors.verticalCenter: parent.verticalCenter + } + + DankIcon { + name: saveMenu.visible ? "expand_less" : "expand_more" + size: Theme.iconSizeSmall + color: Theme.onPrimary + anchors.centerIn: saveArrowArea + } + + StateLayer { + z: 1 + anchors.fill: saveMainArea + stateColor: Theme.onPrimary + onClicked: root.saveEntry("history") + } + + StateLayer { + z: 1 + anchors.fill: saveArrowArea + stateColor: Theme.onPrimary + onClicked: root.toggleSaveMenu() + } + } + } + + Popup { + id: saveMenu + parent: Overlay.overlay + padding: Theme.spacingM + modal: true + dim: false + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + background: StyledRect { + radius: Theme.cornerRadius + color: Theme.surfaceContainer + border.color: Theme.outlineMedium + border.width: 1 + } + + contentItem: Column { + id: saveMenuColumn + spacing: Theme.spacingXS + + StyledRect { + implicitWidth: saveMenuRow.implicitWidth + Theme.spacingS * 2 + implicitHeight: saveMenuRow.implicitHeight + Theme.spacingS * 2 + radius: Theme.cornerRadius + color: saveMenuSaveArea.containsMouse ? Theme.surfaceVariant : "transparent" + + Row { + id: saveMenuRow + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "save" + size: Theme.iconSizeSmall + color: Theme.surfaceText + } + + StyledText { + text: I18n.tr("Save") + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + } + } + + MouseArea { + id: saveMenuSaveArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + saveMenu.close(); + root.saveEntry("history"); + } + } + } + + StyledRect { + implicitWidth: saveMenuCloseRow.implicitWidth + Theme.spacingS * 2 + implicitHeight: saveMenuCloseRow.implicitHeight + Theme.spacingS * 2 + radius: Theme.cornerRadius + color: saveMenuCloseArea.containsMouse ? Theme.surfaceVariant : "transparent" + + Row { + id: saveMenuCloseRow + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "close" + size: Theme.iconSizeSmall + color: Theme.surfaceText + } + + StyledText { + text: I18n.tr("Save and close") + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + } + } + + MouseArea { + id: saveMenuCloseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + saveMenu.close(); + root.saveEntry("close"); + } + } + } + + StyledRect { + implicitWidth: saveMenuPasteRow.implicitWidth + Theme.spacingS * 2 + implicitHeight: saveMenuPasteRow.implicitHeight + Theme.spacingS * 2 + radius: Theme.cornerRadius + color: saveMenuPasteArea.containsMouse ? Theme.surfaceVariant : "transparent" + opacity: modal.wtypeAvailable ? 1 : 0.5 + + Row { + id: saveMenuPasteRow + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "content_paste" + size: Theme.iconSizeSmall + color: Theme.surfaceText + } + + StyledText { + text: I18n.tr("Save and paste") + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + } + } + + MouseArea { + id: saveMenuPasteArea + anchors.fill: parent + hoverEnabled: true + enabled: modal.wtypeAvailable + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + saveMenu.close(); + root.saveEntry("paste"); + } + } + } + } + } + } +} diff --git a/quickshell/Modals/Clipboard/ClipboardEntry.qml b/quickshell/Modals/Clipboard/ClipboardEntry.qml index 53a2ecec..d0383ff9 100644 --- a/quickshell/Modals/Clipboard/ClipboardEntry.qml +++ b/quickshell/Modals/Clipboard/ClipboardEntry.qml @@ -17,6 +17,7 @@ Rectangle { signal deleteRequested signal pinRequested signal unpinRequested + signal editRequested readonly property string entryType: modal ? modal.getEntryType(entry) : "text" readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : "" @@ -70,6 +71,20 @@ Rectangle { onClicked: entry.pinned ? unpinRequested() : pinRequested() } + DankActionButton { + iconName: "edit" + iconSize: Theme.iconSize - 6 + iconColor: Theme.surfaceText + + onClicked: { + if (entryType === "image") { + // TODO - forward to editing software + } else { + editRequested(); + } + } + } + DankActionButton { iconName: "close" iconSize: Theme.iconSize - 6 @@ -142,8 +157,11 @@ Rectangle { MouseArea { id: mouseArea - anchors.fill: parent - anchors.rightMargin: 80 + anchors.left: parent.left + anchors.right: actionButtons.left + anchors.rightMargin: Theme.spacingS + anchors.top: parent.top + anchors.bottom: parent.bottom hoverEnabled: true cursorShape: Qt.PointingHandCursor onPressed: mouse => { diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml index 2d50403e..176e5925 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml @@ -43,6 +43,18 @@ DankModal { service: ClipboardService } + property string mode: "history" + onModeChanged: { + if (mode !== "history") { + return; + } + Qt.callLater(function () { + if (contentLoader.item?.searchField) { + contentLoader.item.searchField.forceActiveFocus(); + } + }); + } + function updateFilteredModel() { ClipboardService.updateFilteredModel(); } @@ -61,6 +73,7 @@ DankModal { function show() { open(); + mode = "history"; activeImageLoads = 0; shouldHaveFocus = true; ClipboardService.reset(); @@ -130,6 +143,21 @@ DankModal { return ClipboardService.getEntryType(entry); } + function editEntry(entry) { + if (!entry) { + return; + } + if (entry.isImage) { + return; + } + const editor = contentLoader.item?.editorView; + if (!editor) { + return; + } + editor.setEntry(entry); + mode = "editor"; + } + visible: false modalWidth: ClipboardConstants.modalWidth modalHeight: ClipboardConstants.modalHeight @@ -138,6 +166,7 @@ DankModal { borderColor: Theme.outlineMedium borderWidth: 1 enableShadow: true + closeOnEscapeKey: mode !== "editor" onBackgroundClicked: hide() modalFocusScope.Keys.onPressed: function (event) { keyboardController.handleKey(event); @@ -174,9 +203,109 @@ DankModal { property var confirmDialog: clearConfirmDialog clipboardContent: Component { - ClipboardContent { - modal: clipboardHistoryModal - clearConfirmDialog: clipboardHistoryModal.confirmDialog + Item { + id: viewContainer + + property alias editorView: editorView + property alias searchField: historyContent.searchField + + anchors.fill: parent + + Item { + id: historyView + + anchors.fill: parent + opacity: 1 + scale: 1 + visible: opacity > 0.01 + enabled: clipboardHistoryModal.mode === "history" + + ClipboardContent { + id: historyContent + anchors.fill: parent + modal: clipboardHistoryModal + clearConfirmDialog: clipboardHistoryModal.confirmDialog + } + } + + ClipboardEditor { + id: editorView + + anchors.fill: parent + opacity: 0 + scale: 0.98 + visible: opacity > 0.01 + enabled: clipboardHistoryModal.mode === "editor" + focus: clipboardHistoryModal.mode === "editor" + modal: clipboardHistoryModal + keyController: keyboardController + } + + states: [ + State { + name: "history" + when: clipboardHistoryModal.mode === "history" + PropertyChanges { + target: historyView + opacity: 1 + scale: 1 + } + PropertyChanges { + target: editorView + opacity: 0 + scale: 0.98 + } + }, + State { + name: "editor" + when: clipboardHistoryModal.mode === "editor" + PropertyChanges { + target: historyView + opacity: 0 + scale: 0.98 + } + PropertyChanges { + target: editorView + opacity: 1 + scale: 1 + } + } + ] + + transitions: [ + Transition { + from: "history" + to: "editor" + ParallelAnimation { + NumberAnimation { + property: "opacity" + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + NumberAnimation { + property: "scale" + duration: Theme.shortDuration + easing.type: Theme.emphasizedEasing + } + } + }, + Transition { + from: "editor" + to: "history" + ParallelAnimation { + NumberAnimation { + property: "opacity" + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + NumberAnimation { + property: "scale" + duration: Theme.shortDuration + easing.type: Theme.emphasizedEasing + } + } + } + ] } } } diff --git a/quickshell/Modals/Clipboard/ClipboardKeyboardController.qml b/quickshell/Modals/Clipboard/ClipboardKeyboardController.qml index 699f0dec..1798426b 100644 --- a/quickshell/Modals/Clipboard/ClipboardKeyboardController.qml +++ b/quickshell/Modals/Clipboard/ClipboardKeyboardController.qml @@ -66,7 +66,24 @@ QtObject { } } + function editSelected() { + const entries = modal.activeTab === "saved" ? ClipboardService.pinnedEntries : ClipboardService.unpinnedEntries; + if (!entries || entries.length === 0) { + return; + } + const index = ClipboardService.selectedIndex >= 0 && ClipboardService.selectedIndex < entries.length ? ClipboardService.selectedIndex : 0; + modal.editEntry(entries[index]); + } + function handleKey(event) { + if (modal.mode === "editor") { + if (event.key === Qt.Key_Escape) { + modal.mode = "history"; + event.accepted = true; + } + return; + } + switch (event.key) { case Qt.Key_Escape: if (ClipboardService.keyboardNavigationActive) { @@ -152,6 +169,10 @@ QtObject { event.accepted = true; } return; + case Qt.Key_E: + editSelected(); + event.accepted = true; + return; } } diff --git a/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml b/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml index c9891eb7..a637d1e6 100644 --- a/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml +++ b/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml @@ -10,7 +10,7 @@ Rectangle { readonly property string hintsText: { if (!wtypeAvailable) return I18n.tr("Ctrl+Tab: Switch Tab • Ctrl+S: Pin/Unpin • Shift+Del: Clear All • Esc: Close"); - return enterToPaste ? I18n.tr("Ctrl+Tab: Switch Tab • Ctrl+S: Pin/Unpin • Shift+Enter: Copy • Shift+Del: Clear All • Esc: Close", "Keyboard hints when enter-to-paste is enabled") : I18n.tr("Ctrl+Tab: Switch Tab • Ctrl+S: Pin/Unpin • Shift+Enter: Paste • Shift+Del: Clear All • Esc: Close"); + return enterToPaste ? I18n.tr("Ctrl+Tab: Switch Tabs • Ctrl+S: Pin/Unpin • Shift+Enter: Copy • Shift+Del: Clear All • F10: Help • Esc: Close", "Keyboard hints when enter-to-paste is enabled") : I18n.tr("Ctrl+Tab: Switch Tabs • Ctrl+S: Pin/Unpin • Shift+Enter: Paste • Shift+Del: Clear All • F10: Help • Esc: Close"); } height: ClipboardConstants.keyboardHintsHeight @@ -22,13 +22,17 @@ Rectangle { z: 100 Column { + width: parent.width - Theme.spacingL * 2 anchors.centerIn: parent spacing: 2 StyledText { - text: keyboardHints.enterToPaste ? I18n.tr("↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help", "Keyboard hints when enter-to-paste is enabled") : I18n.tr("↑/↓: Navigate • Enter/Ctrl+C: Copy • Del: Delete • F10: Help") + text: keyboardHints.enterToPaste ? I18n.tr("↑/↓: Navigate • Enter: Paste • Ctrl+C: Copy • Del: Delete • Ctrl+E: Edit • Ctrl+S: Pin/Unpin • F10: Help", "Keyboard hints when enter-to-paste is enabled") : I18n.tr("↑/↓: Navigate • Enter/Ctrl+C: Copy • Del: Delete • Ctrl+E: Edit • Ctrl+S: Pin/Unpin • F10: Help") font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceText + width: parent.width + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } @@ -36,6 +40,9 @@ Rectangle { text: keyboardHints.hintsText font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceText + width: parent.width + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter } } diff --git a/quickshell/Services/ClipboardService.qml b/quickshell/Services/ClipboardService.qml index 3a45ebce..c132b703 100644 --- a/quickshell/Services/ClipboardService.qml +++ b/quickshell/Services/ClipboardService.qml @@ -240,6 +240,17 @@ Singleton { }); } + function pasteClipboard(closeCallback) { + if (!wtypeAvailable) { + ToastService.showError(I18n.tr("wtype not available - install wtype for paste support")); + return; + } + if (closeCallback) { + closeCallback(); + } + pasteTimer.start(); + } + function pasteEntry(entry, closeCallback) { if (!wtypeAvailable) { ToastService.showError(I18n.tr("wtype not available - install wtype for paste support"));