From bd20dd8a9b363e2a9257a4e19253efb9140b3b96 Mon Sep 17 00:00:00 2001 From: felixzsh Date: Tue, 11 Aug 2026 11:06:37 -0500 Subject: [PATCH] Add bar widget to pause and resume the key display A keyboard glyph in the bar (WidgetButton, right section by default) toggles a pause flag at $XDG_RUNTIME_DIR/omarchy-key-visualizer.paused that the panel and every bar widget instance watch, so a click on any monitor updates all of them. While paused the panel ignores key state and hides; the button dims. Also exposes pause/resume/toggle over the key-visualizer IPC target. --- BarWidget.qml | 61 +++++++++++++++++++++++++++++++++++++++++++++++ KeyVisualizer.qml | 58 +++++++++++++++++++++++++++++++++++++------- manifest.json | 14 ++++++++--- 3 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 BarWidget.qml diff --git a/BarWidget.qml b/BarWidget.qml new file mode 100644 index 0000000..ecea3d0 --- /dev/null +++ b/BarWidget.qml @@ -0,0 +1,61 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Ui +import qs.Commons + +// Bar toggle for the Key Visualizer: a keyboard glyph that pauses/resumes the +// on-screen key display. Pause state is a flag file in the runtime dir +// ($XDG_RUNTIME_DIR/omarchy-key-visualizer.paused) that the panel and every bar +// widget instance watch, so one click updates all of them. The widget is +// dimmed while paused. +BarWidget { + id: root + moduleName: "felixzsh.key-visualizer" + + property bool paused: false + + readonly property string pausePath: { + var runtime = Quickshell.env("XDG_RUNTIME_DIR") + return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.paused" + } + + function toggle() { + var cmd = root.paused + ? "rm -f " + Util.shellQuote(root.pausePath) + : "printf 1 > " + Util.shellQuote(root.pausePath) + toggleProc.command = ["sh", "-c", cmd] + toggleProc.running = true + } + + Process { + id: toggleProc + } + + FileView { + id: pauseFlag + path: root.pausePath + watchChanges: true + printErrors: false + onLoaded: root.paused = (text() === "1") + onFileChanged: reload() + } + + implicitWidth: button.implicitWidth + implicitHeight: button.implicitHeight + + WidgetButton { + id: button + anchors.fill: parent + bar: root.bar + text: "\uF030C" // nf-md-keyboard_outline + fontSize: Style.font.icon + horizontalMargin: 6 + active: !root.paused + dimmed: root.paused + tooltipText: root.paused + ? "Key Visualizer — paused (click to resume)" + : "Key Visualizer — click to pause" + onPressed: function() { root.toggle() } + } +} diff --git a/KeyVisualizer.qml b/KeyVisualizer.qml index 4af4e8d..17d7e5c 100644 --- a/KeyVisualizer.qml +++ b/KeyVisualizer.qml @@ -34,6 +34,15 @@ Item { return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.json" } + // Pause flag shared with the bar widget: while the file holds "1" the + // display is frozen (keys are ignored). The bar button writes/removes the + // file; both sides watch it, so a click on any monitor updates all of them. + property bool paused: false + readonly property string pausePath: { + var runtime = Quickshell.env("XDG_RUNTIME_DIR") + return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.paused" + } + // ------------------------------------------------------------- layout readonly property int cardPad: Style.space(10) @@ -73,14 +82,15 @@ Item { function apply() { var next = [] - try { - var parsed = JSON.parse(stateFile.text()) - if (parsed && Array.isArray(parsed.keys)) { - var age = Math.floor(Date.now() / 1000) - (parsed.t || 0) - if (age <= Math.ceil(root.maxStateAgeMs / 1000)) next = parsed.keys - } - } catch (e) {} - if (next.length === 0) { + if (!root.paused) { + try { + var parsed = JSON.parse(stateFile.text()) + if (parsed && Array.isArray(parsed.keys)) { + var age = Math.floor(Date.now() / 1000) - (parsed.t || 0) + if (age <= Math.ceil(root.maxStateAgeMs / 1000)) next = parsed.keys + } + } catch (e) {} + } if (next.length === 0) { // Keys were released: keep the last combo on screen for the linger // window, then clear it. A fresh press restarts the timer below. hideTimer.restart() @@ -109,6 +119,34 @@ Item { onFileChanged: reload() } + FileView { + id: pauseFile + path: root.pausePath + watchChanges: true + printErrors: false + onLoaded: root.paused = (text() === "1") + onFileChanged: reload() + } + + onPausedChanged: if (root.paused) { + hideTimer.stop() + root.keys = [] + root.opened = false + } + + function setPaused(p) { + if (p === root.paused) return + var cmd = p + ? "printf 1 > " + Util.shellQuote(root.pausePath) + : "rm -f " + Util.shellQuote(root.pausePath) + pauseToggleProc.command = ["sh", "-c", cmd] + pauseToggleProc.running = true + } + + Process { + id: pauseToggleProc + } + // ------------------------------------------------- capture hook injection // // The capture script (key-visualizer.lua) must run inside Hyprland's Lua @@ -177,6 +215,10 @@ Item { target: "key-visualizer" function ping(): string { return "ok" } function state(): string { return root.opened ? "open" : "closed" } + function paused(): string { return root.paused ? "true" : "false" } + function pause(): string { root.setPaused(true); return "ok" } + function resume(): string { root.setPaused(false); return "ok" } + function toggle(): string { root.setPaused(!root.paused); return "ok" } } // ------------------------------------------------------------- display diff --git a/manifest.json b/manifest.json index 5de976f..dddf3b6 100644 --- a/manifest.json +++ b/manifest.json @@ -2,11 +2,19 @@ "schemaVersion": 1, "id": "felixzsh.key-visualizer", "name": "Key Visualizer", - "version": "1.1.0", + "version": "1.2.0", "description": "Shows the keys you press on screen. Great for keybinding tutorials, demos, and screencasts.", - "kinds": ["panel"], + "kinds": ["panel", "bar-widget"], "keepLoaded": true, "entryPoints": { - "panel": "KeyVisualizer.qml" + "panel": "KeyVisualizer.qml", + "barWidget": "BarWidget.qml" + }, + "barWidget": { + "displayName": "Key Visualizer", + "description": "Pause or resume the on-screen key display", + "category": "Tools", + "allowMultiple": false, + "defaultSection": "right" } }