From 7b2d490ba7f12fba05044754b07da7f4d26f5d28 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Wed, 3 Sep 2025 10:25:44 -0400 Subject: [PATCH] Launcher: clipboard, prevent unecessary refresh while browsing --- Modules/Launcher/Launcher.qml | 16 +++- .../Launcher/Plugins/ApplicationsPlugin.qml | 2 +- Modules/Launcher/Plugins/CalculatorPlugin.qml | 2 +- Modules/Launcher/Plugins/ClipboardPlugin.qml | 88 ++++++++++++++----- Services/ClipboardService.qml | 18 ++-- Services/NotificationService.qml | 2 +- 6 files changed, 91 insertions(+), 37 deletions(-) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 7f57b53..9317675 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -11,8 +11,18 @@ NPanel { id: root // Panel configuration - panelWidth: Math.min(700 * scaling, screen?.width * 0.75) - panelHeight: Math.min(600 * scaling, screen?.height * 0.8) + panelWidth: { + var w = Math.round(Math.max(screen?.width * 0.3, 500) * scaling) + w = Math.min(w, screen?.width - Style.marginL * 2) + return w + } + panelHeight: { + var h = Math.round(Math.max(screen?.height * 0.5, 600) * scaling) + h = Math.min(h, screen?.height - Style.barHeight * scaling - Style.marginL * 2) + return h + } + + panelKeyboardFocus: true panelBackgroundColor: Qt.rgba(Color.mSurface.r, Color.mSurface.g, Color.mSurface.b, Settings.data.appLauncher.backgroundOpacity) @@ -246,7 +256,7 @@ NPanel { id: entry property bool isSelected: mouseArea.containsMouse || (index === selectedIndex) - property int badgeSize: Style.baseWidgetSize * 1.75 * scaling + property int badgeSize: Style.baseWidgetSize * 1.6 * scaling // Property to reliably track the current item's ID. // This changes whenever the delegate is recycled for a new item. diff --git a/Modules/Launcher/Plugins/ApplicationsPlugin.qml b/Modules/Launcher/Plugins/ApplicationsPlugin.qml index 2bf45a9..dcc6f6c 100644 --- a/Modules/Launcher/Plugins/ApplicationsPlugin.qml +++ b/Modules/Launcher/Plugins/ApplicationsPlugin.qml @@ -5,7 +5,7 @@ import qs.Commons import qs.Services import "../../../Helpers/FuzzySort.js" as Fuzzysort -QtObject { +Item { property var launcher: null property string name: "Applications" property bool handleSearch: true diff --git a/Modules/Launcher/Plugins/CalculatorPlugin.qml b/Modules/Launcher/Plugins/CalculatorPlugin.qml index ee13c70..292517f 100644 --- a/Modules/Launcher/Plugins/CalculatorPlugin.qml +++ b/Modules/Launcher/Plugins/CalculatorPlugin.qml @@ -2,7 +2,7 @@ import QtQuick import qs.Services import "../../../Helpers/AdvancedMath.js" as AdvancedMath -QtObject { +Item { property var launcher: null property string name: "Calculator" diff --git a/Modules/Launcher/Plugins/ClipboardPlugin.qml b/Modules/Launcher/Plugins/ClipboardPlugin.qml index 586473b..26087e0 100644 --- a/Modules/Launcher/Plugins/ClipboardPlugin.qml +++ b/Modules/Launcher/Plugins/ClipboardPlugin.qml @@ -3,7 +3,7 @@ import Quickshell import qs.Commons import qs.Services -QtObject { +Item { id: root // Plugin metadata @@ -13,26 +13,47 @@ QtObject { // Plugin capabilities property bool handleSearch: false // Don't handle regular search + // Internal state + property bool isWaitingForData: false + property bool gotResults: false + property string lastSearchText: "" + // Listen for clipboard data updates - // Connections { - // target: ClipboardService - // function onListCompleted() { - // // Only refresh if the clipboard plugin is active - // if (launcher && launcher.activePlugin === root) { - // launcher.updateResults() - // } - // } - // } + Connections { + target: ClipboardService + function onListCompleted() { + if (gotResults) { + // Do not update results after the first fetch. + // This will avoid the list resetting every 2seconds when the service updates. + return + } + // Refresh results if we're waiting for data or if clipboard plugin is active + if (isWaitingForData || (launcher && launcher.searchText.startsWith(">clip"))) { + isWaitingForData = false + gotResults = true + if (launcher) { + launcher.updateResults() + } + } + } + } // Initialize plugin function init() { Logger.log("ClipboardPlugin", "Initialized") + // Pre-load clipboard data if service is active + if (ClipboardService.active) { + ClipboardService.list(100) + } } // Called when launcher opens function onOpened() { // Refresh clipboard history when launcher opens - ClipboardService.list(100) + if (ClipboardService.active) { + isWaitingForData = true + ClipboardService.list(100) + } } // Check if this plugin handles the command @@ -45,7 +66,7 @@ QtObject { return [{ "name": ">clip", "description": "Search clipboard history", - "icon": "content_paste", + "icon": "text-x-generic", "isImage": false, "onActivate": function () { launcher.setSearchText(">clip ") @@ -53,7 +74,7 @@ QtObject { }, { "name": ">clip clear", "description": "Clear all clipboard history", - "icon": "delete_sweep", + "icon": "text-x-generic", "isImage": false, "onActivate": function () { ClipboardService.wipeAll() @@ -68,15 +89,28 @@ QtObject { return [] } + lastSearchText = searchText const results = [] const query = searchText.slice(5).trim() + // Check if clipboard service is not active + if (!ClipboardService.active) { + return [{ + "name": "Clipboard History Disabled", + "description": "Enable clipboard history in settings or install cliphist", + "icon": "view-refresh", + "isImage": false, + "onActivate": function () {} + }] + } + // Special command: clear if (query === "clear") { return [{ "name": "Clear Clipboard History", "description": "Remove all items from clipboard history", "icon": "delete_sweep", + "isImage": false, "onActivate": function () { ClipboardService.wipeAll() launcher.close() @@ -84,8 +118,24 @@ QtObject { }] } - // Show loading state if data isn't ready yet - if (ClipboardService.loading) { + // Show loading state if data is being loaded + if (ClipboardService.loading || isWaitingForData) { + return [{ + "name": "Loading clipboard history...", + "description": "Please wait", + "icon": "view-refresh", + "isImage": false, + "onActivate": function () {} + }] + } + + // Get clipboard items + const items = ClipboardService.items || [] + + // If no items and we haven't tried loading yet, trigger a load + if (items.length === 0 && !ClipboardService.loading) { + isWaitingForData = true + ClipboardService.list(100) return [{ "name": "Loading clipboard history...", "description": "Please wait", @@ -98,10 +148,6 @@ QtObject { // Search clipboard items const searchTerm = query.toLowerCase() - // Force dependency update - const _rev = ClipboardService.revision - const items = ClipboardService.items || [] - // Filter and format results items.forEach(function (item) { const preview = (item.preview || "").toLowerCase() @@ -140,7 +186,7 @@ QtObject { }) } - Logger.log("ClipboardPlugin", `Returning ${results.length} results`) + //Logger.log("ClipboardPlugin", `Returning ${results.length} results for query: "${query}"`) return results } @@ -216,4 +262,4 @@ QtObject { function getImageForItem(clipboardId) { return ClipboardService.getImageData ? ClipboardService.getImageData(clipboardId) : null } -} \ No newline at end of file +} diff --git a/Services/ClipboardService.qml b/Services/ClipboardService.qml index e521369..8e2df62 100644 --- a/Services/ClipboardService.qml +++ b/Services/ClipboardService.qml @@ -10,10 +10,9 @@ Singleton { id: root // Public API - property var items: [] // [{id, preview, mime, isImage}] + property bool active: Settings.isLoaded && Settings.data.appLauncher.enableClipboardHistory && cliphistAvailable property bool loading: false - // Active only when feature is enabled, settings have finished initial load, and cliphist is available - property bool active: Settings.data.appLauncher.enableClipboardHistory && Settings.isLoaded && cliphistAvailable + property var items: [] // [{id, preview, mime, isImage}] // Check if cliphist is available on the system property bool cliphistAvailable: false @@ -39,7 +38,7 @@ Singleton { property string _b64CurrentMime: "" property string _b64CurrentId: "" - signal listCompleted() + signal listCompleted // Check if cliphist is available Component.onCompleted: { @@ -82,7 +81,7 @@ Singleton { // Start/stop watchers when enabled changes onActiveChanged: { - if (root.active && root.cliphistAvailable) { + if (root.active) { startWatchers() } else { stopWatchers() @@ -96,7 +95,7 @@ Singleton { Timer { interval: 5000 repeat: true - running: root.active && root.cliphistAvailable + running: root.active onTriggered: list() } @@ -185,9 +184,9 @@ Singleton { const url = `data:${root._b64CurrentMime};base64,${b64}` try { root._b64CurrentCb(url) - } finally { + } catch (e) { - /* noop */ } + } } if (root._b64CurrentId !== "") { root.imageDataById[root._b64CurrentId] = `data:${root._b64CurrentMime};base64,${b64}` @@ -321,6 +320,7 @@ Singleton { return } Quickshell.execDetached(["cliphist", "delete", id]) + revision++ Qt.callLater(() => list()) } @@ -330,9 +330,7 @@ Singleton { } Quickshell.execDetached(["cliphist", "wipe"]) - revision++ - Qt.callLater(() => list()) } } diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index 0354df5..71ffe13 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -7,7 +7,7 @@ import qs.Commons import qs.Services import Quickshell.Services.Notifications -QtObject { +Singleton { id: root // Notification server instance