aboutsummaryrefslogtreecommitdiffstats
path: root/keybinds
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-25 20:05:27 +0200
committerDanilo M. <danix@danix.xyz>2026-09-25 20:05:27 +0200
commit85206cf2939109b2b28044b35b37be510ad2b3fa (patch)
tree1f6544bb28ab2e7b8c401a22dbee73d524f04d53 /keybinds
parenta7725f296102a6f4f69d4488a0b94f3b0f337471 (diff)
downloadquickshell-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.js41
-rw-r--r--keybinds/Keybinds.qml64
-rw-r--r--keybinds/README.md5
-rw-r--r--keybinds/test_binds.js15
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");