A KDE Plasma Wayland tray application for mapping macros to extra buttons on gaming peripherals. Supports multiple devices simultaneously and automatically switches macro profiles based on whichever application is in focus.
v0.9.4 — Fix plasmashell memory leak caused by tray notifications. QSystemTrayIcon.showMessage() was sending notifications without proper KDE metadata, triggering QML TypeErrors in plasmashell (~25–30 MB/min leak). Replaced with notify-send subprocess for correct D-Bus notification delivery. Error/debug messages are now suppressed from desktop popups and remain in the status bar only.
v0.9.3 — App now starts minimized to the system tray (no window on startup). Launcher moved to ~/scripts/keymacro, set read-only.
v0.9.1 — Live button press visualization on device canvases.
- Macro assignment — bind any button on a supported device to a macro: key sequences, mouse clicks, holds, releases, waits, or combinations
- Profiles — create multiple macro profiles and switch between them manually or automatically
- Auto-switching — watches the active window via KWin and switches to the matching profile automatically (e.g. one profile for your browser, another for your game)
- Multi-device — multiple devices can be active at the same time, all sharing a single profile set
- Device feedback — supported devices display the active profile name on their hardware display (e.g. G13 LCD)
- System tray — lives in the tray, out of your way; open the editor only when you need it
| Plugin | Device | Features | Extra dependencies |
|---|---|---|---|
g13 |
Logitech G13 Advanced Gameboard | Button macros, LCD profile display | pyusb, evdev, Pillow |
g600 |
Logitech G600 Gaming Mouse | Button macros | evdev |
Additional devices can be added by writing a plugin — see plugins/README.md.
keymacro uses a pluggable device architecture. Each plugin lives in its own directory under plugins/ and carries everything it needs:
plugins/
├── g13/
│ ├── plugin.py # DevicePlugin subclass
│ ├── raw_capture.py # USB HID capture thread
│ ├── lcd.py # G13 LCD renderer (Pillow-based)
│ ├── 99-keymacro-g13.rules # udev rules (installed by install.sh)
│ └── requirements.txt # pyusb, evdev, Pillow
└── g600/
├── plugin.py # DevicePlugin subclass
├── raw_capture.py # evdev capture thread
├── 99-keymacro-g600.rules # udev rules (installed by install.sh)
└── requirements.txt # evdev
The core (core/, ui/) has zero device-specific imports. Plugins are discovered and loaded at runtime via importlib.
Device → Core raw button events → macro execution (always present)
Core → Device profile name → device feedback (optional, per plugin)
The core→device feedback channel is used by the G13 to update its LCD whenever the active profile changes. The same framework is ready for future use — e.g. G600 LED colour switching.
Macros are sequences of space-separated tokens:
| Token | Meaning |
|---|---|
A, Enter, F5 |
Tap key (press + release) |
+LeftCtrl |
Hold key down |
-LeftCtrl |
Release held key |
+LeftCtrl C -LeftCtrl |
Ctrl+C |
t50 |
Wait 50 ms |
BTN_LEFT, LeftButton |
Mouse button tap |
- KDE Plasma on Wayland
- Python 3.13+
- PyQt6 ≥ 6.3
All other dependencies are installed automatically by scripts/install.sh.
git clone https://github.com/otoa-kiyori/keymacro.git
cd keymacrobash scripts/install.shThe installer handles everything in order:
| Step | What it does |
|---|---|
| 1 | Verifies sudo access (prompts once, caches for the session) |
| 2 | Installs Python dependencies via apt (PyQt6, PyYAML, pyusb, evdev, Pillow) |
| 3 | Installs udev rules for core and all device plugins |
| 4 | Loads the uinput kernel module and makes it persist across reboots |
| 5 | Adds your user to the input and plugdev groups |
| 6 | Creates a launcher script at ~/scripts/keymacro |
| 7 | Installs a KDE autostart entry (keymacro starts with your desktop) |
| 8 | Adds KeyMacro to the KDE application menu |
Group membership (input, plugdev) takes effect on the next login.
After logging back in, keymacro starts automatically with KDE and appears in the system tray. You can also launch it manually:
keymacroOr find KeyMacro in the KDE application menu under Utilities / Games.
bash scripts/uninstall.shThe uninstaller:
| Step | What it does |
|---|---|
| 1 | Verifies sudo access |
| 2 | Removes the KDE autostart and application menu entries |
| 3 | Removes the ~/scripts/keymacro launcher |
| 4 | Removes all keymacro udev rules and reloads udev |
| 5 | Removes /etc/modules-load.d/keymacro.conf |
| 6 | Intelligently removes Python packages — queries apt-cache rdepends at runtime and only removes packages that nothing else on your system needs |
Note on groups: The uninstaller does not remove you from the
inputandplugdevgroups, as other hardware may need them. Manual removal instructions are printed at the end if needed.
To also remove your saved profiles and configuration:
rm -rf ~/.config/keymacrokeymacro/
├── keymacro.py # Entry point
├── requirements.txt # Core deps: PyQt6, PyYAML
├── core/ # Framework: signals, profiles, macro library,
│ │ # plugin manager, feedback thread
│ ├── app.py # Top-level controller
│ ├── feedback_thread.py # Reusable core→device feedback thread base
│ ├── plugin_manager.py # DevicePlugin ABC + plugin discovery
│ ├── macro_library.py # Named macro store
│ ├── macro_queue.py # Single worker thread for macro execution
│ ├── profile_store.py # Profile persistence
│ ├── signals.py # Central Qt signal bus
│ └── window_watcher.py # KWin D-Bus integration for auto-switching
├── ui/ # Qt widgets: tray, main window, macro editor
├── plugins/
│ ├── g13/ # Logitech G13 plugin (USB HID + LCD feedback)
│ └── g600/ # Logitech G600 plugin (evdev)
├── scripts/
│ ├── install.sh # Comprehensive installer
│ ├── uninstall.sh # Intelligent uninstaller
│ └── 99-keymacro.rules # Core udev rule (uinput)
└── storage/ # .gitkeep — runtime data at ~/.config/keymacro/
- No runtime sudo — keymacro runs entirely as a normal user after installation. Device access is granted via udev rules and group membership.
- Config privacy —
~/.config/keymacro/is created with mode0700(owner-only). - Safe YAML — profiles are loaded with
yaml.safe_load(no code execution). - Named system files — every file keymacro installs into system directories is prefixed with
keymacroso you always know what belongs to this app vs the OS.
keymacro is public domain — fork it, modify it, do whatever you want with it.
There is no issue tracker, no PR reviews, and no maintenance commitments. If something doesn't work for you, fork it and make it yours. That's the spirit of The Unlicense.
KeyMacro was conceived and directed by its author.
The codebase — architecture, implementation, all device plugins, the installer, the uninstaller, documentation, and this README — was written by Claude Sonnet 4.6 by Anthropic, who contributed approximately 99.99% of all work. The remaining 0.01% was the author saying "yes, do that", "make it better", and "you super smart Claude Sonnet-san!!"
Claude Sonnet 4.6 did all the work. Otoa just bossed around like a backseat driver. 🚗
Built with Claude Code — Anthropic's agentic coding assistant.