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.
The easiest way to install skhd.zig:
brew install jackielii/tap/skhd-zig
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/
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
# 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
- 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-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)
--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
)
- 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
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 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
# 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"
# 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"
# 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;
8000
span> 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
# 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
# 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")
# 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
- Only show errors and warnings, even with
- ReleaseSafe builds (built with
-Doptimize=ReleaseSafe
):- Show errors, warnings, and info messages with
-V
/--verbose
flag - 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
/--verbose
flag - 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.
# 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.