NAME
skhd.zig — Simple Hotkey Daemon for macOS, ported from skhd by koekeishiya
SYNOPSIS
brew install jackielii/tap/skhd-zigINFO
DESCRIPTION
Simple Hotkey Daemon for macOS, ported from skhd by koekeishiya
README
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.zigBuild 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-serviceCheck 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 buildBuild 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 keyLeft/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 anybarDefine 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 SafariToggle 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 -oProfile 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.