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.
Download the latest release for your architecture:
skhd-arm64-macos.tar.gz - For Apple Silicon Macs
skhd-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 symbols
skhd-ReleaseSafe - Release build with safety checks and runtime safety
skhd-ReleaseFast - Optimized for performance (recommended for daily use)
skhd-ReleaseSmall - Optimized for binary size
# 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/
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/skhdrc or ~/.skhdrc
Automatically reload on config changes
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")
--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
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 .load directive
Comprehensive error reporting: Detailed error messages with line numbers
# 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
Default Configuration Locations
skhd.zig looks for configuration files in the following order:
Path specified with -c flag
~/.config/skhd/skhdrc
~/.skhdrc
The configuration syntax is fully compatible with the original skhd. See SYNTAX.md for the complete syntax reference and grammar.
# 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 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"
# Basic modifiers
cmd # Command key
ctrl # Control key
alt # Alt/Option keyshift# 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
# 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"
]
# 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
# 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
]
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/--verbose flag
Profiling (-P/--profile) is disabled - all tracing code is compiled out for maximum performance
ReleaseSafe builds (built with -Doptimize=ReleaseSafe):
Show errors, warnings, and info messages with -V/--verbose flag
Profiling (-P/--profile) is available for production debugging
Debug builds (default zig build):
Show all log levels including debug messages with -V/--verbose flag
Profiling (-P/--profile) is available with full trace details
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.
# 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
Fork the repository
Create a feature branch
Make your changes
Run tests: zig build test
Submit a pull request
This project maintains compatibility with the original skhd license.