UInput Macropad is a lightweight, background daemon that intercepts events from a specific /dev/input/event device and remaps them into macros, custom key combos, or system commands. Since it operates via uinput at the Linux kernel level, it works universally across X11, Wayland, and the TTY console.
The program is based on sebastiansam55/uinput-keyboard-mapper and is designed to gracefully handle device disconnections and reconnections automatically.
- Universal Compatibility: Works under X11, Wayland, and virtual console/TTY.
- Multilayer Support: Define multiple layers of key mappings and swap between them on the fly.
- Layer Transition Commands: Trigger external shell commands automatically when swapping layers (e.g., triggering a desktop notification).
- Comprehensive Macro Types:
cmd: Execute system commands or scripts.key: Remap single keys.keylist: Send sequences of keys in rapid succession.keycomb: Construct complex key down/up sequences with fine-grained control and delay timers.button: Custom handler for relative scroll wheels/axes (e.g., Logitech MX Master 3 thumb wheels).dispose: Intercept and completely discard specific keystrokes.
- Input Grabbing: Fully absorb original device inputs (using
EVIOCGRAB) so only remapped outputs reach the system. - Robust Connection Handling: Daemon keeps running and automatically re-grabs your device if it is unplugged and plugged back in.
- Python: Version 3.9 or higher
- Dependencies:
python-evdevlibrary
- Install the required
evdevPython package:pip3 install evdev
- Copy macropad.py to a location in your
PATH(e.g.,/usr/local/bin/or~/.local/bin/) and mark it as executable:chmod +x macropad.py
To run the daemon as a non-root user (which is highly recommended for security), you need access to the input and uinput subsystems.
Add your user to the input group:
sudo usermod -a -G input $USERIf the virtual keyboard creation still fails, create a dedicated uinput group and udev rules:
- Create the
uinputgroup:sudo groupadd -f uinput
- Add your user to the
uinputgroup:sudo gpasswd -a $USER uinput - Create a new udev rule file at
/etc/udev/rules.d/99-uinput.rulescontaining:KERNEL=="uinput", GROUP="uinput", MODE="0660" - Reboot your system to apply permissions.
Warning
While you can run this script using sudo, any command-based macros (cmd) will run as root, presenting a severe security risk. Always configure non-root permissions.
Run the program from the terminal or daemon launcher:
macropad.py [options]-c,--config-file <path>: Path to a custom config file (default:~/.config/uinput-macropad/config.json).-v,--verbose: Enable verbose logging for debugging and setup (default:False).--full-grab/--no-full-grab: Fully absorb all keystrokes from the grabbed device (default:True).--only-defined/--no-only-defined: If true, key events not mapped to a macro are discarded rather than passed through (default:False).--clone/--no-clone: If true, the virtual output keyboard copies the exact capabilities of the grabbed device (default:True).
The config file uses JSON format. Below are structured templates and options:
See config.json for reference:
{
"macros": {
"layer_name_1": [
["macro_name", [hotkey_code_list], "macro_type", macro_info_payload],
["copy_shortcut", [275], "keycomb", [29, 46, -29, -46]]
],
"layer_name_2": [
["paste_shortcut", [276], "keycomb", [29, 47, -29, -47]]
]
},
"layers": [
["layer_name_1", [layer_trigger_keys]],
["layer_name_2", [layer_trigger_keys]]
],
"dev_name": "Device Name or Event Path",
"only_defined": false,
"full_grab": true,
"clone": true
}You can specify a command to run whenever a layer becomes active. This is useful for displaying notifications, changing indicator LEDs, or updating status bars. See layers-commands.json for reference:
"layers": [
["test1", [59], ["zenity", "--info", "--title=Layer swap", "--text=Layer 'test1'", "--timeout=2"]],
["test2", [60], ["zenity", "--info", "--title=Layer swap", "--text=Layer 'test2'", "--timeout=2"]]
]The daemon supports six macro engines:
Executes external shell commands or scripts using a list formatting similar to Python's subprocess.Popen.
- Example:
["open_browser", [2], "cmd", ["xdg-open", "https://google.com"]]
Sends a single virtual keycode.
- Example:
["map_to_one", [3], "key", [2]](sends keycode 2, which corresponds to the "1" key)
Sends a sequence of keys in rapid succession (note: this is sequential typing, not a simultaneous combination).
- Example:
["type_hello", [4], "keylist", [35, 18, 38, 38, 24]]
Enables custom macro recipes using a sequence of commands matching these data types:
- Integer (
int): Keydown events are positive integers, keyup events are negative integers. (e.g.29presses Left Ctrl,-29releases it). - Float (
float): Pause execution for the specified duration in seconds (e.g.,0.25sleeps for 250ms). - List (
list): Typings sent inside the combo.
- Example (Ctrl + A):
["select_all", [5], "keycomb", [29, 30, -29, -30]] - Example (Capitalized letters "AS!"):
["type_as", [6], "keycomb", [42, 30, -30, 31, -31, 2, -2, -42]]
Tailored for scroll wheels and analog inputs. Maps relative movements (like -1 or 1) to corresponding keyboard outputs.
- Example:
["hscroll", [6], "button", {"1": 105, "-1": 106}]
Absorbs and silences the specified keys. Useful when combining mappings so that the host OS doesn't receive standard key events.
- Example:
["disable_wheel", [12], "dispose", []]
To find the names of your devices and keycode integers:
- Run
evtestin your terminal. - Select your device from the listed options to monitor real-time events.
- Press keys on your keyboard/macropad.
evtestwill print statements containing values like(KEY_LEFTCTRL), code 29. - Use the printed integer
codevalues in your configuration. - Refer to input-event-codes.h for a complete system header list of Linux keycode definitions.
This macro automates triggering a LiveReload browser sync in Sublime Text, saving keystrokes and context switches:
| Step | Details | Combo Array Element |
|---|---|---|
| 1 | Open command palette (Ctrl + Shift + P) | 29, 42, 25, -29, -42, -25, 0.25, |
| 2 | Type "livereload" | [38, 23, 47, 18, 19, 18, 38, 24, 30, 32], 0.25, |
| 3 | Move cursor down 5 times | [108, 108, 108, 108, 108], 0.25, |
| 4 | Press Enter | [28], 0.25, |
| 5 | Move cursor down 4 times | [108, 108, 108, 108], 0.25, |
| 6 | Press Enter | [28], 0.25, |
| 7 | Reopen command palette | 29, 42, 25, -29, -42, -25, 0.25, |
| 8 | Type "browser" | [25, 19, 18, 47, 23, 18, 17], 0.25, |
| 9 | Press Enter | [28], 0.25, |
| 10 | Press Enter | [28], 0.25 |
A pre-packaged profile is available in logimxmaster3.json. It provides the following remaps:
- Copy: Maps the mouse "Back" button to send
Ctrl + C. - Paste: Maps the mouse "Forward" button to send
Ctrl + V. - Thumb Scroll (HScroll): Translates horizontal side wheel scrolls to left/right arrow key events, allowing horizontal scrolling to work out of the box in VirtualBox, RDP clients, and unsupported Linux applications.
If editing JSON configuration files is too complex, consider trying:
- AutoKey (easy-to-use GUI utility)
- Input Remapper (GUI-based, versatile remapper)
Note: AutoKey relies on X11 and does not work natively under Wayland or TTY consoles. Because uinput-macropad runs at the uinput kernel subsystem level, it works universally in all environments.
For planned features, refactoring tasks, and upcoming bug fixes, please consult TODO.md. Suggestions and Pull Requests are welcome!