SKHD in Zig
Simple Hotkey Daemon for macOS, ported from skhd to Zig.
This implementation is fully compatible with the original skhd configuration format - your existing .skhdrc files will work without modification. Additionally, it includes new features like process groups and command definitions (.define) for cleaner configs, key forwarding/remapping, and improved error reporting.
Installation
Homebrew
The easiest way to install skhd.zig:
brew install jackielii/tap/skhd-zig
Pre-built Binaries
Download the latest release for your architecture:
skhd-arm64-macos.tar.gz- For Apple Silicon Macsskhd-x86_64-macos.tar.gz- For Intel Macs
Extract and install:
tar -xzf skhd-*.tar.gz
sudo cp skhd /usr/local/bin/Development Builds from GitHub Actions
If you need builds with different optimization levels (Debug, ReleaseSafe, ReleaseFast, ReleaseSmall), you can download them directly from GitHub Actions:
- Go to the CI workflow in Actions tab. Filter by branch
main. - Click on the latest successful run
- Scroll down to the "Artifacts" section
- Download the build artifact for your desired optimization level:
skhd-Debug- Debug build with full debugging symbolsskhd-ReleaseSafe- Release build with safety checks and runtime safetyskhd-ReleaseFast- Optimized for performance (recommended for daily use)skhd-ReleaseSmall- Optimized for binary size
Build from Source
# Clone the repository git clone https://github.com/jackielii/skhd.zig cd skhd.zig # Build in release mode zig build -Doptimize=ReleaseFast # Install (copy to /usr/local/bin) sudo cp zig-out/bin/skhd /usr/local/bin/
Running as Service
After installation, run skhd as a service for automatic startup:
# Install and start the service skhd --install-service skhd --start-service # Check if skhd is running properly skhd --status # Restart service (useful for restarting after giving accessibility permissions) skhd --restart-service # Stop service skhd --stop-service # Uninstall service skhd --uninstall-service
The service will:
- Start automatically on login
- Create logs at
/tmp/skhd_$USER.log - Use your config from
~/.config/skhd/skhdrcor~/.skhdrc - Automatically reload on config changes
Features
Core Functionality
- Event capturing: Uses macOS Core Graphics Event Tap for system-wide keyboard event interception
- Hotkey mapping: Maps key combinations to shell commands with full modifier support
- Process-specific bindings: Different commands for different applications
- Key forwarding/remapping: Remap keys to other key combinations
- Modal system: Multi-level modal hotkey system with capture modes
- Configuration file: Compatible with original skhd configuration format
- Hot reloading: Automatic config reload on file changes
Additional Features (New in skhd.zig!)
- Process groups: Define named groups of applications for cleaner configs
- Command definitions: Define reusable commands with placeholders to reduce repetition
- Key Forwarding: Forward / remap key binding to another key binding
- Mode activation with command: Execute a command when switching modes (e.g.,
cmd - w ; window : echo "Window mode")
Command-Line Interface
--version/-v- Display version information--help- Show usage information-c/--config- Specify config file location-o/--observe- Observe mode (echo keycodes and modifiers)-V/--verbose- Debug output with detailed logging-k/--key- Synthesize keypress for testing-t/--text- Synthesize text input-r/--reload- Signal reload to running instance-h/--no-hotload- Disable hotloading-P/--profile- Profile event handling (Debug and ReleaseSafe builds only)
Service Management
--install-service- Install launchd service--uninstall-service- Remove launchd service--start-service- Start as service--restart-service- Restart service--stop-service- Stop service- PID file management (
/tmp/skhd_$USER.pid) - Service logging (
/tmp/skhd_$USER.log)
Advanced Features
- Blacklisting: Exclude applications from hotkey processing
- Shell customization: Use custom shell for command execution
- Left/right modifier distinction: Support for lcmd, rcmd, lalt, ralt, etc.
- Special key support: Function keys, media keys, arrow keys
- Passthrough mode: Execute command but still send keypress to application
- Config includes: Load additional config files with
.loaddirective - Comprehensive error reporting: Detailed error messages with line numbers
Build Commands
# Build the project (creates executable in zig-out/bin/) zig build # Build in release mode with optimizations zig build -Doptimize=ReleaseFast # Run the application zig build run # Run with arguments zig build run -- -V -c ~/.config/skhd/skhdrc # Run tests zig build test
Configuration & Usage
Default Configuration Locations
skhd.zig looks for configuration files in the following order:
- Path specified with
-cflag ~/.config/skhd/skhdrc~/.skhdrc
The configuration syntax is fully compatible with the original skhd. See SYNTAX.md for the complete syntax reference and grammar.
Configuration Directives
# Use custom shell (skips interactive shell overhead) .shell "/bin/dash" # Blacklist applications (skip hotkey processing) .blacklist [ "dota2" "Microsoft Remote Desktop" "VMware Fusion" ] # Load additional config files .load "~/.config/skhd/extra.skhdrc" # Define process groups for reuse (New in skhd.zig!) .define terminal_apps ["kitty", "wezterm", "terminal"] .define native_apps ["kitty", "wezterm", "chrome", "whatsapp"] .define browser_apps ["chrome", "safari", "firefox", "edge"] # Define reusable commands with placeholders (New in skhd.zig!) .define yabai_focus : yabai -m window --focus {{1}} || yabai -m display --focus {{1}} .define yabai_swap : yabai -m window --swap {{1}} || (yabai -m window --display {{1}} && yabai -m display --focus {{1}}) .define toggle_app : open -a "{{1}}" || osascript -e 'tell app "{{1}}" to quit' .define resize_window : yabai -m window --resize {{1}}:{{2}}:{{3}} .define toggle_scratchpad : yabai -m window --toggle {{1}} || open -a "{{2}}"
Basic Hotkey Syntax
# Basic format: modifier - key : command cmd - a : echo "Command+A pressed" # Multiple modifiers cmd + shift - t : open -a Terminal # Different modifier combinations ctrl - h : echo "Control+H" alt - space : echo "Alt+Space" shift - f1 : echo "Shift+F1"
Supported Modifiers
# Basic modifiers cmd # Command key ctrl # Control key alt # Alt/Option key shift # Shift key fn # Function key # Left/right specific modifiers lcmd, rcmd # Left/right Command lctrl, rctrl # Left/right Control lalt, ralt # Left/right Alt lshift, rshift # Left/right Shift # Special modifier combinations hyper # cmd + shift + alt + ctrl meh # shift + alt + ctrl
Special Keys
# Navigation keys cmd - left : echo "Left arrow" cmd - right : echo "Right arrow" cmd - up : echo "Up arrow" cmd - down : echo "Down arrow" # Special keys cmd - space : echo "Space" cmd - return : echo "Return/Enter" cmd - tab : echo "Tab" cmd - escape : echo "Escape" cmd - delete : echo "Delete/Backspace" cmd - home : echo "Home" cmd - end : echo "End" cmd - pageup : echo "Page Up" cmd - pagedown : echo "Page Down" # Function keys cmd - f1 : echo "F1" cmd - f12 : echo "F12" # Media keys sound_up : echo "Volume Up" sound_down : echo "Volume Down" mute : echo "Mute" brightness_up : echo "Brightness Up" brightness_down : echo "Brightness Down"
Process-Specific Bindings
# Different commands for different applications cmd - n [ "terminal" : echo "New terminal window" "safari" : echo "New safari window" "finder" : echo "New finder window" * : echo "New window in other apps" ]
Key Forwarding/Remapping
# Keyboard layout fixes 0xa | 0x32 # UK keyboard § to ` shift - 0xa | shift - 0x32 # shift - § to ~ # Function key navigation (for laptop keyboards) fn - j | down fn - k | up fn - h | left fn - l | right # When you have cmd - number for yabai spaces, # and you still want the cmd - number to work in applications ctrl - 1 | cmd - 1 ctrl - 2 | cmd - 2 ctrl - 3 | cmd - 3
Passthrough Mode
# Execute command but still send keypress to application cmd - p -> : echo "This runs but Cmd+P still goes to app"
Modal Workflow with Visual Indicators
# Window management mode with anybar visual indicator # Install anybar: brew install --cask anybar # Define window management mode for warp/stack operations # Use anybar to indicate the mode: https://github.com/tonsky/AnyBar :: winmode @ : echo -n "red" | nc -4u -w0 localhost 1738 :: default : echo -n "hollow" | nc -4u -w0 localhost 1738 # Enter window mode with meh + m (shift + alt + ctrl + m) meh - w ; winmode winmode < escape ; default winmode < meh - w ; default # Alternative: Enter window mode AND show notification (New in skhd.zig!) # This executes the command when switching to the mode # It allows for different commands to execute and switch to another mode meh - w ; winmode : osascript -e 'display notification "Window mode active" with title "skhd"' winmode < escape ; default : osascript -e 'display notification "Normal mode" with title "skhd"' # Focus operations - basic hjkl for focus winmode < h : yabai -m window --focus west || yabai -m display --focus west winmode < j : yabai -m window --focus south || yabai -m display --focus south winmode < k : yabai -m window --focus north || yabai -m display --focus north winmode < l : yabai -m window --focus east || yabai -m display --focus east # Move operations - shift + hjkl for moving winmode < shift - h : yabai -m window --move rel:-80:0 winmode < shift - j : yabai -m window --move rel:0:80 winmode < shift - k : yabai -m window --move rel:0:-80 winmode < shift - l : yabai -m window --move rel:80:0 # Warp operations - alt + shift + hjkl for warping winmode < alt + shift - h : yabai -m window --warp west winmode < alt + shift - j : yabai -m window --warp south winmode < alt + shift - k : yabai -m window --warp north winmode < alt + shift - l : yabai -m window --warp east # Stack operations - ctrl + shift + hjkl for stacking winmode < ctrl + shift - h : yabai -m window --stack west winmode < ctrl + shift - j : yabai -m window --stack south winmode < ctrl + shift - k : yabai -m window --stack north winmode < ctrl + shift - l : yabai -m window --stack east # Stack management shortcuts winmode < s : yabai -m window --insert stack # Toggle stack mode winmode < u : yabai -m window --toggle float; yabai -m window --toggle float # Unstack window winmode < n : yabai -m window --focus stack.next # Navigate stack next winmode < p : yabai -m window --focus stack.prev # Navigate stack prev # Resize submode winmode < r ; resize :: resize @ : echo -n "orange" | nc -4u -w0 localhost 1738 resize < h : yabai -m window --resize left:-20:0 resize < j : yabai -m window --resize bottom:0:20 resize < k : yabai -m window --resize top:0:-20 resize < l : yabai -m window --resize right:20:0 resize < escape ; winmode
Window Management Example
# Focus windows using command definitions (New in skhd.zig!) cmd - h : @yabai_focus("west") cmd - j : @yabai_focus("south") cmd - k : @yabai_focus("north") cmd - l : @yabai_focus("east") # Move/swap windows using command definitions cmd + shift - h : @yabai_swap("west") cmd + shift - j : @yabai_swap("south") cmd + shift - k : @yabai_swap("north") cmd + shift - l : @yabai_swap("east") # Resize windows using command definitions cmd + ctrl - h : @resize_window("left", "-20", "0") cmd + ctrl - l : @resize_window("right", "20", "0") # Switch spaces cmd - 1 : yabai -m space --focus 1 cmd - 2 : yabai -m space --focus 2
Application Launching Example
# Quick app launching (traditional way) alt - return : open -a Terminal alt - b : open -a Safari # Toggle apps using command definitions (New in skhd.zig!) alt - f : @toggle_app("Finder") alt - c : @toggle_app("Visual Studio Code") # Scratchpad apps with yabai (New in skhd.zig!) # In yabairc: yabai -m rule --add app="^YouTube Music$" scratchpad=music grid=11:11:1:1:9:9 alt - m : @toggle_scratchpad("music", "YouTube Music") alt - n : @toggle_scratchpad("notes", "Notes")
Text Editing Enhancements Example
# Linux-style word navigation and deletion ctrl - backspace [ @native_apps ~ # Terminal apps handle natively * | alt - backspace # Other apps: delete word ] ctrl - left [ @native_apps ~ # Terminal apps handle natively * | alt - left # Other apps: move word left ] ctrl - right [ @native_apps ~ # Terminal apps handle natively * | alt - right # Other apps: move word right ] # Home/End key behavior (with shift for selection) home [ @native_apps ~ # Terminal apps handle natively * | cmd - left # Other apps: line start ] shift - home [ @native_apps ~ # Terminal apps handle natively * | cmd + shift - left # Other apps: select to line start ] # Ctrl+Home/End for document navigation ctrl - home [ @native_apps ~ # Terminal apps handle natively * | cmd - up # Other apps: document start ] ctrl - end [ @native_apps ~ # Terminal apps handle natively * | cmd - down # Other apps: document end ]
Testing and Debugging
Debug vs Release Builds
Important: The logging and profiling behavior differs between build modes:
- ReleaseFast builds (installed via Homebrew or built with
-Doptimize=ReleaseFast):- Only show errors and warnings, even with
-V/--verboseflag - Profiling (
-P/--profile) is disabled - all tracing code is compiled out for maximum performance
- Only show errors and warnings, even with
- ReleaseSafe builds (built with
-Doptimize=ReleaseSafe):- Show errors, warnings, and info messages with
-V/--verboseflag - Profiling (
-P/--profile) is available for production debugging
- Show errors, warnings, and info messages with
- Debug builds (default
zig build):- Show all log levels including debug messages with
-V/--verboseflag - Profiling (
-P/--profile) is available with full trace details
- Show all log levels including debug messages with
However, command output will be shown if verbose flag is specified in release builds.
This is a trade-off between convenience and performance:
- Performance mode (default): Command output is discarded for faster execution
- Verbose mode (
-V): Command output is preserved, which may add slight overhead but helps with trouble shooting
To debug hotkey events and see detailed logging:
# Verbose logging for troubleshooting config issues # Note: In release builds, verbose mode only shows errors and warnings. # To see debug/info logs, use a debug build: zig build run -- -V
Performance: The event loop is allocation-free in release builds, ensuring consistent low-latency hotkey processing.
Testing Commands
# Test key combinations and hex code (observe mode) skhd -o # Profile event handling (show after CTRL+C) # Note: Profiling works in Debug and ReleaseSafe builds only zig build && ./zig-out/bin/skhd -P # or for production debugging: zig build -Doptimize=ReleaseSafe && ./zig-out/bin/skhd -P # Test specific keypress skhd -k "cmd + shift - t" # Test text synthesis skhd -t "hello world" # Reload config of running instance skhd -r # Debug memory allocations with real-time tracking zig build alloc -- -V
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
zig build test - Submit a pull request
License
This project maintains compatibility with the original skhd license.