feat(releng): add fws-hypr-keys script to display hyprland keyboard shortcuts
Add a new Python utility script that displays actual Hyprland keyboard shortcuts from hyprctl binds output. The script: - Queries hyprctl binds to get loaded key bindings from the compositor - Formats keybinds with human-readable modifiers and key labels (French localization) - Displays descriptions from bindd config entries or falls back to dispatcher actions - Opens results in wofi dmenu for incremental search and filtering - Handles special keys like mouse buttons, arrow keys, and common special keys - Deduplicates repeated bindings (e.g., from resize actions) No additional dependencies required beyond python3 and wofi (already in FWS + Hyprland packages).
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
# FWS — affiche les raccourcis clavier RÉELS de Hyprland.
|
||||
#
|
||||
# Source de vérité : « hyprctl binds -j » = les binds effectivement CHARGÉS
|
||||
# par le compositeur (ceux de ~/.config/hypr/hyprland.conf, y compris ceux
|
||||
# ajoutés par l'utilisateur après l'installation). Les descriptions viennent
|
||||
# des « bindd » de la config ; un bind sans description affiche son action
|
||||
# brute (dispatcher + argument). Affichage : wofi en mode dmenu (recherche
|
||||
# incrémentale intégrée) — déclenché par le bouton ⌨ de la waybar ou SUPER+K.
|
||||
#
|
||||
# python3 et wofi sont déjà sur le système (image FWS + paquets Hyprland) :
|
||||
# aucune dépendance supplémentaire.
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Bits de modmask (masques X11) dans l'ordre d'affichage voulu.
|
||||
_MODS = [
|
||||
(64, "SUPER"),
|
||||
(4, "CTRL"),
|
||||
(8, "ALT"),
|
||||
(1, "SHIFT"),
|
||||
]
|
||||
|
||||
# Libellés humains pour les « touches » spéciales.
|
||||
_KEY_LABELS = {
|
||||
"mouse:272": "clic gauche",
|
||||
"mouse:273": "clic droit",
|
||||
"mouse:274": "clic molette",
|
||||
"mouse_down": "molette bas",
|
||||
"mouse_up": "molette haut",
|
||||
"return": "Entrée",
|
||||
"space": "Espace",
|
||||
"escape": "Échap",
|
||||
"tab": "Tab",
|
||||
"left": "←",
|
||||
"right": "→",
|
||||
"up": "↑",
|
||||
"down": "↓",
|
||||
}
|
||||
|
||||
|
||||
def _mods(mask):
|
||||
return [name for bit, name in _MODS if mask & bit]
|
||||
|
||||
|
||||
def _combo(bind):
|
||||
parts = _mods(bind.get("modmask", 0))
|
||||
key = bind.get("key") or ""
|
||||
if not key and bind.get("keycode"):
|
||||
key = f"code {bind['keycode']}"
|
||||
parts.append(_KEY_LABELS.get(key.lower(), key))
|
||||
return " + ".join(p for p in parts if p)
|
||||
|
||||
|
||||
def _action(bind):
|
||||
desc = (bind.get("description") or "").strip()
|
||||
if desc:
|
||||
return desc
|
||||
arg = (bind.get("arg") or "").strip()
|
||||
return f"{bind.get('dispatcher', '?')} {arg}".strip()
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
raw = subprocess.check_output(["hyprctl", "binds", "-j"], text=True)
|
||||
binds = json.loads(raw)
|
||||
except (OSError, subprocess.CalledProcessError, ValueError) as exc:
|
||||
subprocess.run(["notify-send", "Raccourcis FWS",
|
||||
f"hyprctl binds a échoué : {exc}"], check=False)
|
||||
return 1
|
||||
|
||||
lines = []
|
||||
seen = set()
|
||||
for bind in binds:
|
||||
combo = _combo(bind)
|
||||
if not combo or combo in seen: # binde répétés (resize) : une ligne
|
||||
continue
|
||||
seen.add(combo)
|
||||
lines.append(f"{combo:<24} {_action(bind)}")
|
||||
|
||||
if not lines:
|
||||
lines = ["(aucun raccourci déclaré)"]
|
||||
|
||||
# dmenu = liste + filtre incrémental ; la sélection n'a pas d'effet.
|
||||
subprocess.run(
|
||||
["wofi", "--dmenu", "--prompt", "Raccourcis clavier — taper pour filtrer",
|
||||
"--width", "780", "--height", "560", "--insensitive", "--cache-file", "/dev/null"],
|
||||
input="\n".join(lines), text=True, check=False)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user