QMK-inspired keyboard customization for Linux - A blazing-fast, zero-latency keyboard middleware daemon that brings advanced QMK features to any keyboard
- QMK-Style Mod-Tap (MT): Advanced tap/hold with permissive hold, roll detection, chord detection, adaptive timing
- Adaptive Timing: Per-key personalized thresholds that learn your typing patterns
- Custom Layers: Define unlimited layers (navigation, numpad, symbols, etc.)
- Game Mode: Automatic detection via Steam/Gamescope with SOCD support
- SOCD Cleaner: Last-input-priority for FPS games (eliminates W+S conflicts)
- Command Runner: Execute arbitrary shell commands on key press
- Per-Keyboard Configs: Different keymaps for different keyboards
- Hot-Reload: Automatic config reload on file save with desktop notifications
- Full Keyboard Tracking: Collects tap statistics for 100% of keyboard keys
- Zero Input Lag: Direct evdev access with non-blocking I/O
- Multi-Keyboard Support: Handle multiple keyboards simultaneously
- Hotplug Detection: Automatically detect keyboard connect/disconnect
- Hardware-Based IDs: Keyboards identified by USB properties, not device paths
- Adaptive Statistics: View real-time typing analytics with
adaptive-statscommand - Shell Completions: Bash, Zsh, Fish support built-in
- Systemd Service: Runs as user service with automatic startup
- Desktop Notifications: Config reload success/error notifications
- IPC Architecture: Manage keyboards without restarting daemon
- RON Configuration: Human-readable config with extensive comments
Precompiled binary (default, fast):
curl -fsSL https://raw.githubusercontent.com/fibsussy/keymux/main/install.sh | bashOr build from source:
curl -fsSL https://raw.githubusercontent.com/fibsussy/keymux/main/install.sh | bash -s localNote: For security, inspect the install script before running it. View it here.
Add yourself to the input group:
sudo usermod -a -G input $USER
# Log out and log back in for changes to take effect# Clone and build
git clone https://github.com/fibsussy/keymux.git
cd keymux
cargo build --release
# Install
sudo cp target/release/keymux /usr/bin/
sudo cp keymux.service /usr/lib/systemd/system/
sudo cp keymux-niri.service /usr/lib/systemd/user/
sudo cp config.example.ron /usr/share/doc/keymux/
# Enable and start root daemon
sudo systemctl enable --now keymux.service- Copy the example config:
mkdir -p ~/.config/keymux
cp /usr/share/doc/keymux/config.example.ron ~/.config/keymux/config.ron- Edit your config:
$EDITOR ~/.config/keymux/config.ron- Select which keyboards to enable:
keymux toggle- (Optional) Enable Niri watcher for automatic game mode:
systemctl --user enable --now keymux-niri.serviceRoot daemon (required): Manages keyboard devices
- Path:
/usr/lib/systemd/system/keymux.service - Enable:
sudo systemctl enable --now keymux.service
User service (optional): Watches Niri windows for automatic game mode
- Path:
/usr/lib/systemd/user/keymux-niri.service - Enable:
systemctl --user enable --now keymux-niri.service
~/.config/keymux/config.ron
(
tapping_term_ms: 130,
enabled_keyboards: [
"*", // enable all by default
// "6913", // enable keyboard with 6913 in ID
// "Keychron K2", // enable by keyboard name
// "1234": Enable, // explicit enable (map syntax)
// "event3": Disable, // disable by event path
// ("1234", "Enable"), // explicit enable (tuple syntax)
],
remaps: { /* base layer keymaps */ },
layers: { /* additional layers */ },
game_mode: ( remaps: { /* game mode keymaps */ } ),
keyboard_overrides: { /* per-keyboard configs */ },
// MT configuration (all optional, shown with defaults)
mt_config: (
permissive_hold: true,
enable_roll_detection: true,
enable_chord_detection: true,
enable_multi_mod_detection: true,
enable_cross_hand_unwrap: true,
enable_double_tap_hold: true,
enable_adaptive_timing: true,
pause_learning_in_game_mode: true,
roll_threshold_ms: 100,
chord_threshold_ms: 100,
target_margin_ms: 30,
ema_alpha: 0.02,
auto_save_interval_secs: 30,
),
)-
tapping_term_ms (default: 130)
- Base time threshold for tap vs hold decision
- When adaptive timing is enabled, this serves as the initial/fallback threshold
- 100-130ms: More sensitive to holds
- 150-200ms: More sensitive to taps
-
enabled_keyboards - Which keyboards to process:
- Absent/omitted: Enable all keyboards (equivalent to
["*"]) None: Disable all (no keyboards enabled)["*"]: Enable all keyboards["1234", "5678"]: Enable keyboards matching these patterns["*", "1234": Disable]: Enable all except 1234
Syntax options:
- Map style:
"pattern": Enable,"pattern": Disable - Tuple style:
("pattern", "Enable"),("pattern", "Disable") - Bare string:
"1234"= enable keyboard matching 1234
- Absent/omitted: Enable all keyboards (equivalent to
-
mt_config - Configuration for Mod-Tap (MT) keys
- permissive_hold (default: true): When another key is pressed while MT is held, resolve immediately to hold
- enable_roll_detection (default: true): Fast same-hand rolls favor tap (prevents accidental holds during typing)
- enable_chord_detection (default: true): Opposite-hand chords favor hold (e.g., Ctrl+C with home row mods)
- enable_multi_mod_detection (default: true): Multiple mods on same hand promote to hold
- enable_cross_hand_unwrap (default: true): Holding opposite-hand mod unwraps MT keys to tap (prevents Shift+Shift)
- enable_double_tap_hold (default: true): Double-tap then hold to hold the tap key instead of modifier
- enable_adaptive_timing (default: true): Learn per-key personalized thresholds from your typing patterns
- pause_learning_in_game_mode (default: true): Disable adaptive learning when game mode is active
- roll_threshold_ms (default: 100): Max time between keys to count as roll
- chord_threshold_ms (default: 100): Max time between keys to count as chord
- target_margin_ms (default: 30): Safety margin added to average tap duration for adaptive threshold
- ema_alpha (default: 0.02): Exponential moving average smoothing factor (~100 sample window)
- auto_save_interval_secs (default: 30): How often to save adaptive statistics to disk
Letters: KC_A through KC_Z
Numbers: KC_1 through KC_0
Modifiers: KC_LCTL, KC_LSFT, KC_LALT, KC_LGUI, KC_RCTL, KC_RSFT, KC_RALT, KC_RGUI
Special: KC_ESC, KC_CAPS, KC_TAB, KC_SPC, KC_ENT, KC_BSPC, KC_DEL
Function: KC_F1 through KC_F12
Arrows: KC_LEFT, KC_DOWN, KC_UP, KC_RGHT
Direct key mapping.
KC_CAPS: Key(KC_ESC), // Caps Lock becomes EscapeQMK-style Mod-Tap with advanced features:
- Tap: Quick press emits tap_key (base key)
- Hold: Long press emits hold_key (modifier)
- Permissive Hold: Pressing another key while MT is held resolves immediately to hold
- Roll Detection: Fast same-hand rolls favor tap (prevents accidental holds during typing)
- Chord Detection: Opposite-hand chords favor hold (e.g., Ctrl+C with home row mods)
- Cross-Hand Unwrap: Holding opposite-hand mod unwraps MT keys to tap (prevents Shift+Shift conflicts)
- Double-Tap-Hold: Press twice quickly then hold to hold the tap key instead of modifier
- Adaptive Timing: Learns per-key personalized thresholds from your typing patterns
// Home row mods - left hand
KC_A: MT(KC_A, KC_LGUI), // Tap 'a', hold for Super/Win/Cmd
KC_S: MT(KC_S, KC_LALT), // Tap 's', hold for Alt
KC_D: MT(KC_D, KC_LCTL), // Tap 'd', hold for Ctrl
KC_F: MT(KC_F, KC_LSFT), // Tap 'f', hold for Shift
// Space/Control overload
KC_SPC: MT(KC_SPC, KC_LCTL), // Tap Space, hold Ctrl
// Examples of MT behavior:
// - Quick press A = 'a'
// - Hold A (130ms+) = Super/Cmd held
// - Hold A + press J = Super+J (permissive hold)
// - Type "as" quickly (roll) = "as" (not Alt+s)
// - A on left + J on right quickly = 'a' then 'j' (chord detection)
// - Hold D (Ctrl) + press A = Ctrl+A (cross-hand unwrap prevents Cmd+A)
// - Double-tap-hold SPC = hold Space key (not Ctrl)All MT features are configurable via mt_config (see Behavior Settings section).
Switch to a different layer while held.
KC_LALT: TO("nav"), // Hold Left Alt to activate navigation layerSOCD cleaner for gaming (last-input-priority).
KC_W: SOCD(KC_W, [KC_S]), // Pressing W then S = S, release S = W againExecute arbitrary shell commands on key press.
KC_F1: CMD("/usr/bin/notify-send 'Hello from keyboard!'"),
KC_F2: CMD("/usr/bin/playerctl play-pause"),(
tapping_term_ms: 130,
enabled_keyboards: [
"*", // enable all keyboards
],
remaps: {
// Escape/Caps swap
KC_CAPS: KC_ESC,
KC_ESC: KC_GRV,
// Mod-Tap - left hand
KC_A: MT(KC_A, KC_LGUI),
KC_S: MT(KC_S, KC_LALT),
KC_D: MT(KC_D, KC_LCTL),
KC_F: MT(KC_F, KC_LSFT),
// Mod-Tap - right hand
KC_J: MT(KC_J, KC_RSFT),
KC_K: MT(KC_K, KC_RCTL),
KC_L: MT(KC_L, KC_RALT),
KC_SCLN: MT(KC_SCLN, KC_RGUI),
},
layers: {},
game_mode: (remaps: {}),
keyboard_overrides: {},
)(
tapping_term_ms: 130,
enabled_keyboards: [
"2e3c:c365:0110:0003", // enable specific keyboard by ID
],
remaps: {
KC_CAPS: KC_ESC,
KC_ESC: KC_GRV,
KC_LALT: TO("nav"),
KC_A: MT(KC_A, KC_LGUI),
KC_S: MT(KC_S, KC_LALT),
KC_D: MT(KC_D, KC_LCTL),
KC_F: MT(KC_F, KC_LSFT),
KC_J: MT(KC_J, KC_RSFT),
KC_K: MT(KC_K, KC_RCTL),
KC_L: MT(KC_L, KC_RALT),
KC_SCLN: MT(KC_SCLN, KC_RGUI),
},
layers: {
"nav": (
remaps: {
// Keep modifiers accessible
KC_A: Key(KC_LGUI),
KC_S: Key(KC_LALT),
KC_D: Key(KC_LCTL),
KC_F: Key(KC_LSFT),
// Vim-style navigation
KC_H: Key(KC_LEFT),
KC_J: Key(KC_DOWN),
KC_K: Key(KC_UP),
KC_L: Key(KC_RGHT),
},
),
},
game_mode: (
remaps: {
KC_CAPS: KC_ESC,
KC_ESC: KC_GRV,
// SOCD for FPS gaming
KC_W: SOCD(KC_W, [KC_S]),
KC_S: SOCD(KC_S, [KC_W]),
KC_A: SOCD(KC_A, [KC_D]),
KC_D: SOCD(KC_D, [KC_A]),
},
),
keyboard_overrides: {},
// Optional: Customize MT behavior
mt_config: (
permissive_hold: true,
enable_roll_detection: true,
enable_chord_detection: true,
enable_adaptive_timing: true,
target_margin_ms: 40, // More conservative margin (default: 30)
ema_alpha: 0.01, // Slower learning (default: 0.02)
),
)- tapping_term_ms: Base time threshold for tap vs hold (130-200ms recommended)
- When adaptive timing is enabled, this serves as the initial/fallback threshold
- Lower value = more sensitive to holds, higher = more sensitive to taps
- Recommended: 130ms for mechanical keyboards, 150-180ms for laptops
The MT system can learn your typing patterns and personalize thresholds per key:
- How it works: Records tap durations when keys resolve to TAP (below threshold)
- Formula:
adaptive_threshold = average_tap_duration + target_margin_ms - Tracks all keys: Collects statistics for 100% of keyboard (A-Z, numbers, F-keys, etc.)
- Exponential moving average: Uses α=0.02 (~100 sample window) for smooth adaptation
- Starts immediately: Begins learning after first tap (no minimum sample requirement)
- Game mode aware: Pauses learning during games to avoid skewing data
View statistics: keymux adaptive-stats
Clear statistics: keymux clear-stats
Game mode activates automatically when:
- Steam games: Process tree contains
steam+ game executable - Gamescope: Window manager reports gamescope app ID
- IS_GAME env var: Process has
IS_GAME=1environment variable
Game mode is controlled automatically via the Niri daemon or can be toggled via IPC
# Start daemon (automatically started by systemd)
keymux daemon
# Check status
systemctl status keymux
# View live logs
journalctl -u keymux -f
# Restart daemon
systemctl restart keymux# List all detected keyboards
keymux list
# Toggle which keyboards are enabled (interactive menu)
keymux toggle
# Enable specific keyboards by ID or name
keymux enable 6912 # by partial ID
keymux enable "Keychron" # by name
keymux enable "*" # enable all
# Disable specific keyboards by ID or name
keymux disable 6912 # by partial ID
keymux disable "Built-in" # by name
keymux disable "*" # disable all
# Open multi-select menu to choose keyboards
keymux toggle --multi
keymux enable --multi
keymux disable --multi
# Validate your config
keymux validate
# Reload config (automatic on file save, but manual trigger available)
keymux reload
# Game mode is automatically detected when running Steam/Gamescope
# Manual toggle requires sending IPC requests to the daemon
# View adaptive timing statistics
keymux adaptive-stats
# Clear adaptive timing statistics (prompts for confirmation)
keymux clear-stats
# Debug mode (show all keyboard events in real-time)
keymux debug# Bash
keymux completion bash | sudo tee /usr/share/bash-completion/completions/keymux
# Zsh
keymux completion zsh | sudo tee /usr/share/zsh/site-functions/_keymux
# Fish
keymux completion fish > ~/.config/fish/completions/keymux.fishAdd yourself to the input group:
sudo usermod -a -G input $USERThen log out and back in.
Another process is grabbing your keyboard. Check for:
# Kill any existing instances
pkill -f keymux
# Check for other remapping tools
ps aux | grep -E "kmonad|keyd|xremap"Watch the logs when editing config:
journalctl -u keymux -fConfig errors show desktop notifications and keep the previous working config.
Ensure the daemon is running:
systemctl status keymuxCheck file watcher is working (should see "Config reloaded" in logs when you save).
Option 1: Adjust base threshold
- 100-130ms: More sensitive to holds
- 150-200ms: More sensitive to taps
- Recommended: 130ms for mechanical keyboards, 150-180ms for laptops
Option 2: Enable adaptive timing (recommended)
- Set
mt_config.enable_adaptive_timing: true(enabled by default) - The system learns your typing patterns and personalizes thresholds per key
- View statistics:
keymux adaptive-stats - Increase safety margin:
mt_config.target_margin_ms: 40(default: 30)
Option 3: Adjust detection features
- Disable roll detection:
mt_config.enable_roll_detection: false - Disable chord detection:
mt_config.enable_chord_detection: false - Disable permissive hold:
mt_config.permissive_hold: false
Use SOCD in game_mode (see configuration examples above).
Alternative keyboard remapping tools with different approaches:
- kmonad - Haskell-based, cross-platform, mature codebase, S-expression config
- keyd - C-based, key remapping via config files, active development
- kanata - Rust-based, cross-platform, programmable key remapper
- xremap - Python-based, X11 only, older project
Each has different strengths - choose based on your platform, performance needs, and configuration preferences.
- QMK Documentation - Inspiration for this project
- Home Row Mods Guide - Deep dive into HRM techniques
- Colemak Mod-DH - Alternative keyboard layout with better HRM placement
Contributions are welcome! Feel free to open issues or submit pull requests.
MIT License - See LICENSE for details.
- Inspired by QMK Firmware
- Built with Rust and evdev