260 lines
9.5 KiB
Python
Executable File
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...") |