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).
This commit is contained in:
felixzsh
2026-08-11 13:26:32 -05:00
parent 1b2128e6f1
commit 949f41a6be
3 changed files with 247 additions and 64 deletions
-60
View File
@@ -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() }
}
}
+240
View File
@@ -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" })
}
}
}
}
}
}
+7 -4
View File
@@ -2,17 +2,20 @@
"schemaVersion": 1, "schemaVersion": 1,
"id": "felixzsh.key-visualizer", "id": "felixzsh.key-visualizer",
"name": "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.", "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, "keepLoaded": true,
"entryPoints": { "entryPoints": {
"panel": "KeyVisualizer.qml", "panel": "KeyVisualizer.qml",
"barWidget": "BarWidget.qml" "barWidget": "Panel.qml"
}, },
"barWidget": { "barWidget": {
"displayName": "Key Visualizer", "displayName": "Key Visualizer",
"description": "Pause or resume the on-screen key display", "description": "Toggle the key display and its options",
"category": "Tools", "category": "Tools",
"allowMultiple": false, "allowMultiple": false,
"defaultSection": "right" "defaultSection": "right"