From 949f41a6beb632f9a6344be16742ee1b95b3a634 Mon Sep 17 00:00:00 2001 From: felixzsh Date: Tue, 11 Aug 2026 13:26:32 -0500 Subject: [PATCH] Turn the bar widget into a menu with toggle and display options Clicking the keyboard glyph now opens a KeyboardPanel popup (the native bar pattern, e.g. omarchy.audio) instead of toggling pause directly: Show keys toggle, Mode (all | bindings), and Position (bottom/top/ center). The toggle writes the pause flag and the options write config.json, both watched by the display panel, so menu and overlay stay in sync across monitors. Replaces BarWidget.qml with a Panel-based widget (Panel + BarIconButton + KeyboardPanel). --- BarWidget.qml | 60 ------------- Panel.qml | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++ manifest.json | 11 ++- 3 files changed, 247 insertions(+), 64 deletions(-) delete mode 100644 BarWidget.qml create mode 100644 Panel.qml diff --git a/BarWidget.qml b/BarWidget.qml deleted file mode 100644 index 4b60892..0000000 --- a/BarWidget.qml +++ /dev/null @@ -1,60 +0,0 @@ -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 - dimmed: root.paused - tooltipText: root.paused - ? "Key Visualizer — paused (click to resume)" - : "Key Visualizer — click to pause" - onPressed: function() { root.toggle() } - } -} diff --git a/Panel.qml b/Panel.qml new file mode 100644 index 0000000..04810ae --- /dev/null +++ b/Panel.qml @@ -0,0 +1,240 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Ui +import qs.Commons + +// Bar control for the Key Visualizer. The keyboard glyph opens a small menu +// (KeyboardPanel, the native bar popup) with: +// - Show keys toggle that pauses/resumes the on-screen display +// - Mode all keys, or bindings only (combos with a modifier) +// - Position bottom / top / center of the screen +// +// The toggle writes a pause flag and the options write config.json — both +// files are watched by the display panel (KeyVisualizer.qml), so the menu and +// the overlay always agree, on every monitor. Follows the native bar-widget +// pattern (Panel + BarIconButton + KeyboardPanel), e.g. omarchy.audio. +Panel { + id: root + moduleName: "felixzsh.key-visualizer" + // No IPC target: the display panel (KeyVisualizer.qml) owns the "key-visualizer" + // target, so omarchy-shell key-visualizer toggle|pause|resume keeps working. + + // Mirrors of the files the display panel watches; these stay in sync via + // the FileViews below. + property bool paused: false + property string mode: "all" + property string position: "bottom-center" + property int margin: 67 + property int lingerMs: 1000 + + readonly property string pluginDir: Quickshell.env("HOME") + "/.config/omarchy/plugins/felixzsh.key-visualizer" + readonly property string pausePath: { + var runtime = Quickshell.env("XDG_RUNTIME_DIR") + return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.paused" + } + readonly property string configPath: root.pluginDir + "/config.json" + + // ------------------------------------------------------------- pause + + function setPaused(p) { + if (p === root.paused) return + var cmd = p + ? "printf 1 > " + Util.shellQuote(root.pausePath) + : "rm -f " + 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() + } + + // ------------------------------------------------------------- config + + function applyConfig(raw) { + var cfg = {} + try { cfg = JSON.parse(raw || "{}") } catch (e) {} + root.mode = cfg.mode === "bindings" ? "bindings" : "all" + if (typeof cfg.position === "string" && cfg.position.length > 0) root.position = cfg.position + if (isFinite(cfg.margin) && cfg.margin >= 0) root.margin = Math.round(cfg.margin) + if (isFinite(cfg.lingerMs) && cfg.lingerMs >= 0) root.lingerMs = Math.round(cfg.lingerMs) + } + + // Round-trips every option so a menu edit never drops values the display + // panel or the user set directly in config.json. + function writeConfig(update) { + var cfg = { + mode: root.mode, + position: root.position, + margin: root.margin, + lingerMs: root.lingerMs + } + for (var k in update) cfg[k] = update[k] + if (update.mode !== undefined) root.mode = update.mode + if (update.position !== undefined) root.position = update.position + writeProc.command = ["sh", "-c", + "printf '%s\\n' '" + JSON.stringify(cfg) + "' > " + Util.shellQuote(root.configPath)] + writeProc.running = true + } + + Process { + id: writeProc + } + + FileView { + id: configFile + path: root.configPath + watchChanges: true + printErrors: false + onLoaded: root.applyConfig(text()) + onFileChanged: reload() + } + + // ------------------------------------------------------------- bar + + implicitWidth: button.implicitWidth + implicitHeight: button.implicitHeight + + BarIconButton { + id: button + anchors.fill: parent + bar: root.bar + text: "\uF030C" // nf-md-keyboard_outline + onPressed: function(b) { + if (b === Qt.RightButton) return + root.toggle() + } + } + + KeyboardPanel { + id: panel + anchorItem: button + owner: root + bar: root.bar + open: root.opened + contentWidth: Style.space(250) + contentHeight: menuColumn.implicitHeight + panel.padding * 2 + + Column { + id: menuColumn + width: parent.width + spacing: Style.spacing.lg + + // Show keys ----------------------------------------------------- + Item { + width: parent.width + height: showSwitch.implicitHeight + + Text { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: "Show keys" + font.family: Style.font.family + font.pixelSize: Style.font.body + color: Color.popups.text + } + + ToggleSwitch { + id: showSwitch + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + checked: !root.paused + foreground: Color.popups.text + accent: Color.accent + onToggled: root.setPaused(!checked) + } + } + + // Mode ---------------------------------------------------------- + Item { + width: parent.width + height: modeButtons.height + + Text { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: "Mode" + font.family: Style.font.family + font.pixelSize: Style.font.body + color: Color.popups.text + } + + Row { + id: modeButtons + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Style.spacing.xs + + Button { + text: "All keys" + selected: root.mode !== "bindings" + foreground: Color.popups.text + accent: Color.accent + onClicked: root.writeConfig({ mode: "all" }) + } + Button { + text: "Bindings" + selected: root.mode === "bindings" + foreground: Color.popups.text + accent: Color.accent + onClicked: root.writeConfig({ mode: "bindings" }) + } + } + } + + // Position ------------------------------------------------------ + Item { + width: parent.width + height: positionButtons.height + + Text { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: "Position" + font.family: Style.font.family + font.pixelSize: Style.font.body + color: Color.popups.text + } + + Row { + id: positionButtons + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Style.spacing.xs + + Button { + text: "Bottom" + selected: root.position.indexOf("bottom") !== -1 + foreground: Color.popups.text + accent: Color.accent + onClicked: root.writeConfig({ position: "bottom-center" }) + } + Button { + text: "Top" + selected: root.position.indexOf("top") !== -1 + foreground: Color.popups.text + accent: Color.accent + onClicked: root.writeConfig({ position: "top-center" }) + } + Button { + text: "Center" + selected: root.position === "center" + foreground: Color.popups.text + accent: Color.accent + onClicked: root.writeConfig({ position: "center" }) + } + } + } + } + } +} diff --git a/manifest.json b/manifest.json index dddf3b6..1733b83 100644 --- a/manifest.json +++ b/manifest.json @@ -2,17 +2,20 @@ "schemaVersion": 1, "id": "felixzsh.key-visualizer", "name": "Key Visualizer", - "version": "1.2.0", + "version": "1.3.0", "description": "Shows the keys you press on screen. Great for keybinding tutorials, demos, and screencasts.", - "kinds": ["panel", "bar-widget"], + "kinds": [ + "panel", + "bar-widget" + ], "keepLoaded": true, "entryPoints": { "panel": "KeyVisualizer.qml", - "barWidget": "BarWidget.qml" + "barWidget": "Panel.qml" }, "barWidget": { "displayName": "Key Visualizer", - "description": "Pause or resume the on-screen key display", + "description": "Toggle the key display and its options", "category": "Tools", "allowMultiple": false, "defaultSection": "right"