diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-25 20:05:27 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-25 20:05:27 +0200 |
| commit | 85206cf2939109b2b28044b35b37be510ad2b3fa (patch) | |
| tree | 1f6544bb28ab2e7b8c401a22dbee73d524f04d53 /keybinds | |
| parent | a7725f296102a6f4f69d4488a0b94f3b0f337471 (diff) | |
| download | quickshell-85206cf2939109b2b28044b35b37be510ad2b3fa.tar.gz quickshell-85206cf2939109b2b28044b35b37be510ad2b3fa.zip | |
feat(keybinds): show where a bind sits on the macropad
The macropad's firmware sends ordinary combos, so the overlay listed
them as keyboard shortcuts nobody presses. A PAD table in Binds.js maps
each combo to its key (1-12) or encoder (top or bottom, turned or
pressed) and what is held with it, and the row draws the pad small with
that key lit. The workspace row is built in a loop, as it is in
keybindings.lua. Every entry was checked against hyprctl binds -j.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'keybinds')
| -rw-r--r-- | keybinds/Binds.js | 41 | ||||
| -rw-r--r-- | keybinds/Keybinds.qml | 64 | ||||
| -rw-r--r-- | keybinds/README.md | 5 | ||||
| -rw-r--r-- | keybinds/test_binds.js | 15 |
4 files changed, 120 insertions, 5 deletions
diff --git a/keybinds/Binds.js b/keybinds/Binds.js index 2029f36..d02c714 100644 --- a/keybinds/Binds.js +++ b/keybinds/Binds.js @@ -46,6 +46,43 @@ function keys(bind) { return mods(bind.modmask).concat([NAMES[bind.key] ?? bind.key]).join(" + "); } +// Where a bind sits on the macropad, keyed by the combo as keys() writes it, +// so a renamed key above must be renamed here too. key is 1-12, left to +// right and top to bottom; knob is the top or bottom encoder with how it is +// turned; mod is what is held with it. The pad's firmware sends these combos. +const PAD = Object.assign(Object.create(null), { + "Win + w": { key: 1 }, + "Win + SHIFT + w": { key: 1, mod: "Shift" }, + "Win + r": { key: 2 }, + "Win + SHIFT + r": { key: 2, mod: "Shift" }, + "Win + g": { key: 3 }, + "CTRL + SHIFT + l": { key: 4 }, + "CTRL + ALT + SHIFT + l": { key: 4, mod: "Alt" }, + "ALT + Return": { key: 5 }, + "ALT + SHIFT + Return": { key: 5, mod: "Shift" }, + "ALT + h": { key: 6 }, + "Win + CTRL + r": { key: 7 }, + "ALT + w": { key: 8 }, + "Volume -": { knob: "top", turn: "↺" }, + "Volume +": { knob: "top", turn: "↻" }, + "Mute": { knob: "top", turn: "●" }, + "Win + Volume -": { knob: "top", turn: "↺", mod: "Win" }, + "Win + Volume +": { knob: "top", turn: "↻", mod: "Win" }, + "Previous song": { knob: "bottom", turn: "↺" }, + "Next song": { knob: "bottom", turn: "↻" }, + "Play/Pause": { knob: "bottom", turn: "●" }, + "Win + Previous song": { knob: "bottom", turn: "↺", mod: "Win" }, + "Win + Next song": { knob: "bottom", turn: "↻", mod: "Win" }, +}); +// The bottom row is workspaces 1-4, 5-8 with Shift, and moves the window +// there with Win held on the keyboard. +for (let i = 1; i <= 4; i++) { + PAD["CTRL + F" + i] = { key: 8 + i }; + PAD["CTRL + SHIFT + F" + i] = { key: 8 + i, mod: "Shift" }; + PAD["Win + CTRL + F" + i] = { key: 8 + i, mod: "Win" }; + PAD["Win + CTRL + SHIFT + F" + i] = { key: 8 + i, mod: "Win Shift" }; +} + // A description reads "Group: Label". A bind without one still shows, last, // so a gap in the config is visible rather than silently missing. function group(binds) { @@ -65,7 +102,9 @@ function group(binds) { byName[name] = { name: name, rows: [] }; groups.push(byName[name]); } - byName[name].rows.push({ keys: keys(b), label: label }); + const row = { keys: keys(b), label: label }; + if (PAD[row.keys]) row.pad = PAD[row.keys]; + byName[name].rows.push(row); } if (undescribed.length) groups.push({ name: "Undescribed", rows: undescribed }); return groups; diff --git a/keybinds/Keybinds.qml b/keybinds/Keybinds.qml index be28e2f..532ae86 100644 --- a/keybinds/Keybinds.qml +++ b/keybinds/Keybinds.qml @@ -122,7 +122,7 @@ Scope { Column { required property var modelData - width: 440 + width: 552 spacing: 4 Text { @@ -159,6 +159,68 @@ Scope { font.family: Theme.fontFamily font.pixelSize: 14 } + + // The macropad, drawn small: its 4x3 + // keys and two encoders, the one to + // use lit. Empty but still 100 wide + // on a row the pad has no part in, so + // the column keeps its width. + Item { + id: padGlyph + readonly property var pad: modelData.pad ?? null + width: 100 + height: 17 + anchors.verticalCenter: parent.verticalCenter + + Grid { + visible: padGlyph.pad !== null + columns: 4 + spacing: 1 + + Repeater { + model: 12 + + Rectangle { + required property int index + width: 5 + height: 5 + radius: 1 + color: padGlyph.pad?.key === index + 1 + ? Theme.accent : Theme.surfaceAlt + } + } + } + + Column { + x: 26 + visible: padGlyph.pad !== null + spacing: 3 + + Repeater { + model: ["top", "bottom"] + + Rectangle { + required property string modelData + width: 7 + height: 7 + radius: 3.5 + color: padGlyph.pad?.knob === modelData + ? Theme.accent : Theme.surfaceAlt + } + } + } + + Text { + x: 38 + anchors.verticalCenter: parent.verticalCenter + visible: padGlyph.pad !== null + text: [padGlyph.pad?.turn, padGlyph.pad?.mod] + .filter(Boolean).join(" ") + color: Theme.subtext + font.family: Theme.fontFamily + font.pixelSize: 12 + } + } } } } diff --git a/keybinds/README.md b/keybinds/README.md index 2184f6a..c4fa64f 100644 --- a/keybinds/README.md +++ b/keybinds/README.md @@ -20,6 +20,11 @@ Key names are Hyprland's unless `NAMES` in `Binds.js` gives a friendlier one (`mouse:272` shows as `LMB`, `XF86Calculator` as `Fn+F9`), and SUPER shows as Win. Add a line there to rename another. +A bind the macropad sends shows the pad beside it: its 4x3 keys and two +encoders drawn small, the one to press lit, with the turn direction and any +key held. `PAD` in `Binds.js` maps each combo to its place, keyed by the combo +as displayed, so renaming a key in `NAMES` means renaming it there too. + ## Running qs -p ./keybinds diff --git a/keybinds/test_binds.js b/keybinds/test_binds.js index 9acb071..08be66a 100644 --- a/keybinds/test_binds.js +++ b/keybinds/test_binds.js @@ -18,7 +18,7 @@ const assert = require("assert"); const src = fs.readFileSync(__dirname + "/Binds.js", "utf8") .replace(/^\.pragma library$/m, ""); -const B = new Function(src + "\nreturn { mods, keys, group };")(); +const B = new Function(src + "\nreturn { mods, keys, group, PAD };")(); assert.deepStrictEqual(B.mods(0), []); assert.deepStrictEqual(B.mods(65), ["Win", "SHIFT"]); @@ -38,10 +38,10 @@ assert.deepStrictEqual(B.group([ { modmask: 8, key: "x", description: "No colon here" }, ]), [ { name: "Apps", rows: [ - { keys: "Win + w", label: "Browser" }, + { keys: "Win + w", label: "Browser", pad: { key: 1 } }, { keys: "Win + e", label: "Files" }, ] }, - { name: "Media", rows: [{ keys: "Mute", label: "Mute" }] }, + { name: "Media", rows: [{ keys: "Mute", label: "Mute", pad: { knob: "top", turn: "●" } }] }, { name: "Other", rows: [{ keys: "ALT + x", label: "No colon here" }] }, { name: "Undescribed", rows: [{ keys: "Win + s", label: "no description" }] }, ]); @@ -52,4 +52,13 @@ assert.deepStrictEqual(B.group([ { modmask: 64, key: "z", description: "toString: Weird" }, ]), [{ name: "toString", rows: [{ keys: "Win + z", label: "Weird" }] }]); +// The macropad: shifted keys, the workspace row built in a loop, and an +// encoder turned with Win held. +assert.deepStrictEqual(B.PAD["Win + SHIFT + r"], { key: 2, mod: "Shift" }); +assert.deepStrictEqual(B.PAD["CTRL + ALT + SHIFT + l"], { key: 4, mod: "Alt" }); +assert.deepStrictEqual(B.PAD["CTRL + F1"], { key: 9 }); +assert.deepStrictEqual(B.PAD["Win + CTRL + SHIFT + F4"], { key: 12, mod: "Win Shift" }); +assert.deepStrictEqual(B.PAD["Win + Previous song"], { knob: "bottom", turn: "↺", mod: "Win" }); +assert.strictEqual(B.PAD["toString"], undefined); + console.log("ok"); |
