Make display options configurable via config.json
The panel reads config.json from its plugin folder (seeded with defaults on first run, hot-reloaded on save): mode (all | bindings — the bindings mode only shows combos with a modifier, ideal for keybinding tutorials), position (keyviz-style alignment, bottom-center by default) and margin, plus the existing lingerMs. Replaces the hardcoded bottom anchors with x/y computed from position and margin.
This commit is contained in:
+81
-4
@@ -29,6 +29,25 @@ Item {
|
|||||||
// session after a restart) so a stale combo never sticks on screen.
|
// session after a restart) so a stale combo never sticks on screen.
|
||||||
readonly property int maxStateAgeMs: 1500
|
readonly property int maxStateAgeMs: 1500
|
||||||
|
|
||||||
|
// Options read from config.json in the plugin folder (created with
|
||||||
|
// defaults on first run, hot-reloaded on save):
|
||||||
|
// mode "all" | "bindings" — bindings only shows combos with a
|
||||||
|
// modifier, ignoring plain typing (tutorial mode).
|
||||||
|
// position bottom-center (default) or any top/bottom + left/center/
|
||||||
|
// right combination, keyviz-style.
|
||||||
|
// margin distance from the screen edge in px (default 67).
|
||||||
|
// lingerMs how long a released combo stays (default 1000).
|
||||||
|
property string mode: "all"
|
||||||
|
property string position: "bottom-center"
|
||||||
|
property int margin: Style.space(67)
|
||||||
|
readonly property var modLabels: ["Super", "Ctrl", "Alt", "Shift", "Menu", "AltGr"]
|
||||||
|
|
||||||
|
property var manifest: ({})
|
||||||
|
readonly property string configPath: {
|
||||||
|
var dir = root.manifest && root.manifest.__sourceDir ? root.manifest.__sourceDir : ""
|
||||||
|
return dir ? dir + "/config.json" : ""
|
||||||
|
}
|
||||||
|
|
||||||
readonly property string statePath: {
|
readonly property string statePath: {
|
||||||
var runtime = Quickshell.env("XDG_RUNTIME_DIR")
|
var runtime = Quickshell.env("XDG_RUNTIME_DIR")
|
||||||
return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.json"
|
return (runtime && runtime.length > 0 ? runtime : "/tmp") + "/omarchy-key-visualizer.json"
|
||||||
@@ -90,7 +109,14 @@ Item {
|
|||||||
if (age <= Math.ceil(root.maxStateAgeMs / 1000)) next = parsed.keys
|
if (age <= Math.ceil(root.maxStateAgeMs / 1000)) next = parsed.keys
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
} if (next.length === 0) {
|
} if (next.length > 0 && root.mode === "bindings") {
|
||||||
|
var hasMod = false
|
||||||
|
for (var i = 0; i < next.length; i++) {
|
||||||
|
if (root.modLabels.indexOf(next[i]) !== -1) { hasMod = true; break }
|
||||||
|
}
|
||||||
|
if (!hasMod) next = []
|
||||||
|
}
|
||||||
|
if (next.length === 0) {
|
||||||
// Keys were released: keep the last combo on screen for the linger
|
// Keys were released: keep the last combo on screen for the linger
|
||||||
// window, then clear it. A fresh press restarts the timer below.
|
// window, then clear it. A fresh press restarts the timer below.
|
||||||
hideTimer.restart()
|
hideTimer.restart()
|
||||||
@@ -147,6 +173,48 @@ Item {
|
|||||||
id: pauseToggleProc
|
id: pauseToggleProc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------- options
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
function seedConfig() {
|
||||||
|
var defaults = '{"mode": "all", "position": "bottom-center", "margin": 67, "lingerMs": 1000}'
|
||||||
|
seedConfigProc.command = ["sh", "-c",
|
||||||
|
"printf '%s\\n' '" + defaults + "' > " + Util.shellQuote(root.configPath)]
|
||||||
|
seedConfigProc.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: seedConfigProc
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool configSeeded: false
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
id: configFile
|
||||||
|
path: root.configPath
|
||||||
|
watchChanges: true
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: {
|
||||||
|
root.applyConfig(text())
|
||||||
|
// First run: write the defaults so the file is discoverable and the
|
||||||
|
// options can be tuned without hunting for them. Guarded because the
|
||||||
|
// shell injects `manifest` after instantiation, which re-fires this.
|
||||||
|
if (!root.configSeeded) {
|
||||||
|
root.configSeeded = true
|
||||||
|
if (root.configPath !== "" && text() === "") root.seedConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onFileChanged: reload()
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------- capture hook injection
|
// ------------------------------------------------- capture hook injection
|
||||||
//
|
//
|
||||||
// The capture script (key-visualizer.lua) must run inside Hyprland's Lua
|
// The capture script (key-visualizer.lua) must run inside Hyprland's Lua
|
||||||
@@ -240,9 +308,18 @@ Item {
|
|||||||
visible: root.keys.length > 0
|
visible: root.keys.length > 0
|
||||||
width: card.borderLeft + root.cardPad + root.contentWidth() + root.cardPad + card.borderRight
|
width: card.borderLeft + root.cardPad + root.contentWidth() + root.cardPad + card.borderRight
|
||||||
height: card.borderTop + root.cardPad + root.chipHeight + root.cardPad + card.borderBottom
|
height: card.borderTop + root.cardPad + root.chipHeight + root.cardPad + card.borderBottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
x: {
|
||||||
anchors.bottom: parent.bottom
|
var p = root.position
|
||||||
anchors.bottomMargin: Style.space(67)
|
if (p.indexOf("left") !== -1) return root.margin
|
||||||
|
if (p.indexOf("right") !== -1) return parent.width - width - root.margin
|
||||||
|
return Math.round((parent.width - width) / 2)
|
||||||
|
}
|
||||||
|
y: {
|
||||||
|
var p = root.position
|
||||||
|
if (p.indexOf("top") !== -1) return root.margin
|
||||||
|
if (p.indexOf("bottom") !== -1) return parent.height - height - root.margin
|
||||||
|
return Math.round((parent.height - height) / 2)
|
||||||
|
}
|
||||||
color: Util.alpha(Color.popups.background, 0.97)
|
color: Util.alpha(Color.popups.background, 0.97)
|
||||||
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
|
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2)))
|
||||||
radius: Style.cornerRadius
|
radius: Style.cornerRadius
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
A tiny [Omarchy](https://omarchy.org/) plugin that shows the keys you press
|
A tiny [Omarchy](https://omarchy.org/) plugin that shows the keys you press
|
||||||
on screen. No keycap images, no animations — just the characters and
|
on screen. No keycap images, no animations — just the characters and
|
||||||
combinations, rendered with the shell's own styles, appearing while a key is
|
combinations, rendered with the shell's own styles, appearing while a key is
|
||||||
held and disappearing the moment it's released.
|
held and lingering briefly after release.
|
||||||
|
|
||||||
Built for keybinding tutorials and screencasts: Omarchy is a keybinding-heavy
|
Built for keybinding tutorials and screencasts: Omarchy is a keybinding-heavy
|
||||||
desktop, so a combo like `Super + Shift + G` shows up as three small chips
|
desktop, so a combo like `Super + Shift + G` shows up as three small chips
|
||||||
@@ -15,6 +15,7 @@ at the bottom of the screen.
|
|||||||
|----------------|--------------------------------------------|--------------|
|
|----------------|--------------------------------------------|--------------|
|
||||||
| `key-visualizer.lua`| Hyprland Lua config (inside the compositor)| Listens to `input.keyboard.key`, tracks the pressed combo, writes it to `$XDG_RUNTIME_DIR/omarchy-key-visualizer.json` |
|
| `key-visualizer.lua`| Hyprland Lua config (inside the compositor)| Listens to `input.keyboard.key`, tracks the pressed combo, writes it to `$XDG_RUNTIME_DIR/omarchy-key-visualizer.json` |
|
||||||
| `KeyVisualizer.qml`| Quickshell `omarchy-shell` (keep-loaded panel)| Watches the state file and renders the chips |
|
| `KeyVisualizer.qml`| Quickshell `omarchy-shell` (keep-loaded panel)| Watches the state file and renders the chips |
|
||||||
|
| `BarWidget.qml`| Quickshell bar (per monitor) | Keyboard glyph that pauses/resumes the display |
|
||||||
|
|
||||||
No background daemons, no special permissions, no extra packages: the
|
No background daemons, no special permissions, no extra packages: the
|
||||||
compositor is already the source of truth for keys, and the shell is already
|
compositor is already the source of truth for keys, and the shell is already
|
||||||
@@ -38,30 +39,40 @@ omarchy plugin add /path/to/omarchy-keycaster --enable
|
|||||||
```
|
```
|
||||||
|
|
||||||
`--enable` enables it right away; without it, enable later with
|
`--enable` enables it right away; without it, enable later with
|
||||||
`omarchy plugin enable felixzsh.key-visualizer`. If you added from a local path,
|
`omarchy plugin enable felixzsh.key-visualizer`. The plugin is a panel and a bar
|
||||||
`omarchy plugin update felixzsh.key-visualizer` pulls your local changes into the
|
widget: the panel shows the keys, and the bar widget is a toggle — enable
|
||||||
installed copy.
|
prompts for its bar section (or pass `--section right`). If you added from a
|
||||||
|
local path, `omarchy plugin update felixzsh.key-visualizer` pulls your local
|
||||||
|
changes into the installed copy.
|
||||||
|
|
||||||
Then load the capture script into Hyprland's Lua config. Add this line at the
|
That's the whole install: on first load the panel appends a small guarded
|
||||||
bottom of `~/.config/hypr/hyprland.lua`:
|
block to `~/.config/hypr/hyprland.lua` that loads the capture script into
|
||||||
|
Hyprland's Lua config, and Hyprland auto-reloads on save. Nothing else to
|
||||||
```lua
|
do — no manual edits, no `hyprctl reload`.
|
||||||
dofile(os.getenv("HOME") .. "/.config/omarchy/plugins/felixzsh.key-visualizer/key-visualizer.lua")
|
|
||||||
```
|
|
||||||
|
|
||||||
Reload Hyprland:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
hyprctl reload
|
|
||||||
```
|
|
||||||
|
|
||||||
The panel starts with the shell, so it works after the next login too.
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Hyprland with Lua config support (0.56+), as shipped by Omarchy.
|
- Hyprland with Lua config support (0.56+), as shipped by Omarchy.
|
||||||
- The plugin enabled in the shell (done above).
|
- The plugin enabled in the shell (done above).
|
||||||
|
|
||||||
|
## Bar widget
|
||||||
|
|
||||||
|
The keyboard glyph in the bar (right section by default) toggles the
|
||||||
|
display: click to pause, click again to resume. While paused the glyph dims
|
||||||
|
and keys stay off the screen — handy for presentations. The state is a flag
|
||||||
|
file both the bar and the panel watch, so it stays in sync across monitors
|
||||||
|
and survives shell restarts. You can also drive it over IPC:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
omarchy-shell key-visualizer toggle
|
||||||
|
omarchy-shell key-visualizer pause
|
||||||
|
omarchy-shell key-visualizer resume
|
||||||
|
omarchy-shell key-visualizer paused # true | false
|
||||||
|
```
|
||||||
|
|
||||||
|
Move it with `omarchy bar move felixzsh.key-visualizer --section left` (or
|
||||||
|
right/center).
|
||||||
|
|
||||||
## Behavior
|
## Behavior
|
||||||
|
|
||||||
- **Typing** — shows just the character: `g`, `G`, `!`, `5`.
|
- **Typing** — shows just the character: `g`, `G`, `!`, `5`.
|
||||||
@@ -73,16 +84,35 @@ The panel starts with the shell, so it works after the next login too.
|
|||||||
- Key repeat is ignored; a held key shows once.
|
- Key repeat is ignored; a held key shows once.
|
||||||
- After the last key is released the combo lingers briefly (1s by default,
|
- After the last key is released the combo lingers briefly (1s by default,
|
||||||
keyviz-style "Duration") and then vanishes in one frame — no fade.
|
keyviz-style "Duration") and then vanishes in one frame — no fade.
|
||||||
|
- In `mode: "bindings"` only combos with a modifier are shown; plain typing
|
||||||
|
stays off screen.
|
||||||
|
|
||||||
## Customize
|
## Customize
|
||||||
|
|
||||||
Everything lives in the plugin directory
|
On first load the panel writes `config.json` into the plugin directory
|
||||||
(`~/.config/omarchy/plugins/felixzsh.key-visualizer/`); saved changes reload
|
(`~/.config/omarchy/plugins/felixzsh.key-visualizer/config.json`) with these
|
||||||
automatically.
|
defaults; edit it and the display updates on save:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mode": "all",
|
||||||
|
"position": "bottom-center",
|
||||||
|
"margin": 67,
|
||||||
|
"lingerMs": 1000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Values | Default |
|
||||||
|
|-------------|----------------------------------------------------|-----------------|
|
||||||
|
| `mode` | `all` or `bindings` (only combos with a modifier) | `all` |
|
||||||
|
| `position` | `bottom-center`, `top-center`, `center`, `top-left`, `bottom-right`, … | `bottom-center` |
|
||||||
|
| `margin` | px from the screen edge | `67` |
|
||||||
|
| `lingerMs` | ms a released combo stays (keyviz defaults to `5000`) | `1000` |
|
||||||
|
|
||||||
|
Deeper tweaks still live in the QML/Lua sources:
|
||||||
|
|
||||||
| Want to change... | Edit |
|
| Want to change... | Edit |
|
||||||
|-----------------------------------|-----------------------------------------|
|
|-----------------------------------|-----------------------------------------|
|
||||||
| How long a released combo lingers | `lingerMs` in `KeyVisualizer.qml` (default `1000`; keyviz defaults to `5000`) |
|
|
||||||
| Chip font / padding | `chipFont`, `chipPadX/Y` in `KeyVisualizer.qml` |
|
| Chip font / padding | `chipFont`, `chipPadX/Y` in `KeyVisualizer.qml` |
|
||||||
| Key labels or layout mapping | `KEYS` / `CHARS` / `SHIFTED` tables in `key-visualizer.lua` |
|
| Key labels or layout mapping | `KEYS` / `CHARS` / `SHIFTED` tables in `key-visualizer.lua` |
|
||||||
|
|
||||||
@@ -94,12 +124,13 @@ not expanded.
|
|||||||
## Status
|
## Status
|
||||||
|
|
||||||
`omarchy-shell key-visualizer ping` — health check.
|
`omarchy-shell key-visualizer ping` — health check.
|
||||||
|
`omarchy-shell key-visualizer state` — `open` while a combo is on screen.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- Layout-aware keysyms via `xkbcommon` instead of the static US table.
|
- Layout-aware keysyms via `xkbcommon` instead of the static US table.
|
||||||
- Mouse button display.
|
- Mouse button display.
|
||||||
- A pause toggle for presentations.
|
- Per-monitor placement.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user