a28a64310a
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).
96 lines
2.8 KiB
Python
Executable File
96 lines
2.8 KiB
Python
Executable File
#!/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())
|