.pragma library // Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // hyprctl binds -j into display groups. Pure, so node can check it: // see test_binds.js. // Modmask bits, in the order they are written in keybindings.lua. // ponytail: CAPS/MOD2/MOD3/MOD5 bits are not decoded and vanish from the // combo; add them here if a bind ever uses one. // SUPER shows as Win, the key it is on. const MODS = [[64, "Win"], [4, "CTRL"], [8, "ALT"], [1, "SHIFT"]]; function mods(mask) { return MODS.filter(m => mask & m[0]).map(m => m[1]); } // Display names for keys whose Hyprland name is hard to remember. A key not // listed shows as Hyprland names it. The Fn combos are where the XF86 keys // sit on this keyboard, which is how they are pressed. const NAMES = Object.assign(Object.create(null), { "mouse:272": "LMB", "mouse:273": "RMB", "mouse_up": "Mouse wheel up", "mouse_down": "Mouse wheel down", "XF86AudioRaiseVolume": "Volume +", "XF86AudioLowerVolume": "Volume -", "XF86AudioMute": "Mute", "XF86AudioPlay": "Play/Pause", "XF86AudioPrev": "Previous song", "XF86AudioNext": "Next song", "XF86Calculator": "Fn+F9", "XF86Mail": "Fn+F10", "XF86Search": "Fn+F11", }); function keys(bind) { return mods(bind.modmask).concat([NAMES[bind.key] ?? bind.key]).join(" + "); } // 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) { const groups = []; const byName = Object.create(null); const undescribed = []; for (const b of binds) { const d = b.description || ""; if (!d) { undescribed.push({ keys: keys(b), label: "no description" }); continue; } const i = d.indexOf(": "); const name = i < 0 ? "Other" : d.slice(0, i); const label = i < 0 ? d : d.slice(i + 2); if (!byName[name]) { byName[name] = { name: name, rows: [] }; groups.push(byName[name]); } byName[name].rows.push({ keys: keys(b), label: label }); } if (undescribed.length) groups.push({ name: "Undescribed", rows: undescribed }); return groups; }