#!/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())