Files
dotfiles/.config/hypr/scripts/help.py

260 lines
9.5 KiB
Python
Executable File

#!/usr/bin/env python3
# This script is used to display help information for Hyprland keybinds.
import os
import sys
import re
import subprocess
HARDCODED_VARIABLES = {
"exec, ": "",
"VoidSymbol": "CAPS",
"brave --proxy-pac-url=\"https://pac.cn01.woodburn.au/proxy.pac\" --enable-features=UseOzonePlatform --ozone-platform=wayland --use-gl=angle --ignore-gpu-blocklist --enable-features=VaapiVideoEncoder,VaapiVideoDecoder,CanvasOopRasterization,VaapiIgnoreDriverChecks,VaapiVideoDecodeLinuxGL,AcceleratedVideoEncoder,Vulkan,DefaultANGLEVulkan,VulkanFromANGLE --disable-gpu-memory-buffer-video-frames": "brave",
"~/.config/hypr/scripts/":"󰯂 ",
".sh": "",
".py": "",
"alacritty --config-file ~/dotfiles/.alacritty-nozellij.toml": "Terminal (No Zellij)",
"ydotool key 56:1 105:1 105:0 56:0":"Back",
"ydotool key 56:1 106:1 106:0 56:0":"Forward",
"mouse_left":"Left Tilt",
"mouse_right":"Right Tilt",
"[float] kitty --class float-80 -e":"",
"alacritty --class float-80 -e":"",
"alacritty --class float -e":"",
"$":"",
"mouse:272": "Left Drag",
"mouse:273": "Right Drag",
"hyprctl": "",
"dispatch ": "",
"vdesk,": "󰧨",
"vdeskreset": "󰧨 Reset",
"movetodesk": "󰶭 󰧨",
"movetoworkspace": "󰶭 󰧨",
"togglespecialworkspace": "󰔡 󰧨",
"special:":"",
"lastdesk": "󰧨 Last Active",
}
IGNORED_ACTIONS = [
"hyprctl notify",
"submap, reset",
"hyprpanel -q"
]
TYPE_MAP = {
'bind': ' ',
'bindl': '',
'bindle': 'Lock',
'bindm': '󰍽',
}
SUBMAP_MAP ={
"main": "",
"capsmode": "󰚟󰌎",
"opacity": "󰚟󱡔",
}
KEYCODES = {
20: "-"
}
# Read the keybinds.conf file
def read_keybinds(file_path = os.path.expanduser('~/.config/hypr/keybinds.conf')):
if not os.path.exists(file_path):
print(f"Error: The file {file_path} does not exist.")
sys.exit(1)
with open(file_path, 'r') as file:
lines = file.readlines()
return lines
def parse_keybinds(lines):
"""Parse keybinds from the configuration file and return a list of structured keybind information."""
keybinds = []
variables = {}
current_submap = "main"
for line_num, line in enumerate(lines, 1):
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('#'):
continue
# Handle variable definitions
if line.startswith('$'):
var_match = re.match(r'\$(\w+)\s*=\s*(.+)', line)
if var_match:
variables[var_match.group(1)] = var_match.group(2)
continue
# Handle submap declarations
if line.startswith('submap ='):
submap_match = re.match(r'submap\s*=\s*(\w+)', line)
if submap_match:
current_submap = submap_match.group(1)
continue
# Handle bind declarations
if line.startswith('bind') or line.startswith('bindle') or line.startswith('bindl') or line.startswith('bindm'):
bind_match = re.match(r'(bind[lme]?)\s*=\s*([^,]+),\s*([^,]+),\s*(.+)', line)
if bind_match:
bind_type = bind_match.group(1)
modifiers = bind_match.group(2).strip()
key = bind_match.group(3).strip()
action = bind_match.group(4).strip()
# Ignore certain actions
if any(ignored in action for ignored in IGNORED_ACTIONS):
continue
# Replace variables in the key, modifiers, and action
for var_name, var_value in variables.items():
modifiers = modifiers.replace(f'${var_name}', var_value)
key = key.replace(f'${var_name}', var_value)
# action = action.replace(f'${var_name}', var_value)
# Update for HARDCODED
for var_name, var_value in HARDCODED_VARIABLES.items():
modifiers = modifiers.replace(f'{var_name}', var_value)
key = key.replace(f'{var_name}', var_value)
action = action.replace(f'{var_name}', var_value)
if action.startswith('submap,'):
smap = action.split(',')[1].strip()
action = SUBMAP_MAP.get(smap, smap)
# Convert any 'code:<number>' in key to the corresponding Unicode character
code_match = re.match(r'code:(\d+)', key)
if code_match:
code_num = int(code_match.group(1))
key = KEYCODES[code_num] if code_num in KEYCODES else f"code:{code_num}"
# Add icon for logging
if ">>" in action:
log_action = action.split('>>')[0].strip()
action = f"{log_action}"
# Cleanup action
action = action.strip(',')
action = action.replace(',', '')
# Format the keybind description
key_combo = f"{modifiers} + {key}" if modifiers else key
keybind_info = {
'type': bind_type,
'submap': current_submap,
'key_combo': key_combo,
'action': action,
'line_num': line_num,
'raw_line': line
}
keybinds.append(keybind_info)
return keybinds
def format_keybind_for_display(keybind):
"""Format a keybind for display in fzf or terminal."""
submap_prefix = f"{SUBMAP_MAP.get(keybind['submap'], keybind['submap'].upper())} " if keybind['submap'] in SUBMAP_MAP else f"{keybind['submap'].upper()} "
type_prefix = f"{TYPE_MAP.get(keybind['type'], 'Unknown')} " if keybind['type'] in TYPE_MAP else f"{keybind['type']} "
# Clean up the action for display
action = keybind['action']
return f"{submap_prefix:3}{type_prefix}{keybind['key_combo']:30}{action}"
def run_fzf_search(keybinds):
"""Run fzf with the keybinds for interactive searching."""
try:
# Prepare the input for fzf
fzf_input = []
for keybind in keybinds:
formatted = format_keybind_for_display(keybind)
fzf_input.append(formatted)
# Run fzf
process = subprocess.Popen(
['fzf', '--reverse', '--border', '--preview-window=up:3',
'--header=Hyprland Keybinds - Press Enter or Esc to exit',
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = process.communicate(input='\n'.join(fzf_input))
if process.returncode == 0 and stdout.strip():
# Find the selected keybind
selected_line = stdout.strip()
for keybind in keybinds:
if format_keybind_for_display(keybind) == selected_line:
print(f"\nSelected Keybind Details:")
print(f"Key Combination: {keybind['key_combo']}")
print(f"Action: {keybind['action']}")
print(f"Type: {keybind['type']}")
print(f"Submap: {keybind['submap']}")
print(f"Line: {keybind['line_num']}")
print(f"Raw: {keybind['raw_line']}")
break
except FileNotFoundError:
print("Error: fzf is not installed. Please install fzf to use interactive search.")
print("On most distributions: sudo pacman -S fzf # or apt install fzf")
return False
except Exception as e:
print(f"Error running fzf: {e}")
return False
return True
if __name__ == "__main__":
# Parse command line arguments - default to fzf unless --no-fzf is specified
use_fzf = not (len(sys.argv) > 1 and sys.argv[1] in ['--no-fzf', '-n', 'no-fzf'])
# Read and parse keybinds
raw_lines = read_keybinds()
parsed_keybinds = parse_keybinds(raw_lines)
if use_fzf:
# Use fzf for interactive searching
if not run_fzf_search(parsed_keybinds):
# Fallback to regular display if fzf fails
use_fzf = False
if not use_fzf:
print("Hyprland Keybinds Help:")
print("=" * 50)
print(f"Found {len(parsed_keybinds)} keybinds\n")
# Group by submap for better organization
submaps = {}
for keybind in parsed_keybinds:
submap = keybind['submap']
if submap not in submaps:
submaps[submap] = []
submaps[submap].append(keybind)
# Display keybinds grouped by submap
for submap_name, submap_keybinds in submaps.items():
if submap_name != 'main':
print(f"\n[{submap_name.upper()} SUBMAP]")
print("-" * 30)
else:
print("[MAIN KEYBINDS]")
print("-" * 30)
for keybind in submap_keybinds:
print(format_keybind_for_display(keybind))
print(f"\n{'-' * 50}")
print("Usage: python help.py [--no-fzf] to disable interactive search")
print("For more information, refer to the Hyprland documentation.")
print("You can also customize your keybinds in the keybinds.conf file.")
# Wait for user input before exiting
input("\nPress Enter to exit...")