Skip to content

Repository files navigation

omarchy-keypad

Turns a cheap USB macropad into a programmable control surface on Omarchy.

The keypad editor — the pad drawn on screen, click a key or a knob to bind it

Built for a 12-key, 2-knob macropad (USB 1189:8840) — sold under the Sikai name, and by plenty of sellers unbranded as a "12 key macro keyboard with two knobs". The device identity is in the config, so any pad that reports plain keycodes will work. The pad is matched on any bus: plugged in over USB, or paired over Bluetooth, where it shows up as MINI_KEYBOARD with a borrowed Apple id (05ac:022c). Both can be live at once; each is grabbed.

Why it exists

These pads advertise three hardware layers. On Linux the layers are a fiction: every layer sends the same keycodes, and pressing the layer button sends nothing at all — verified by reading the raw HID reports on both the standard and the vendor-specific interface, where the vendor channel stayed completely silent. Only the manufacturer's Windows tool can write different keycodes into layers 2 and 3.

So the layers here are ours. The daemon holds the layer, the bar widget shows it, and any control can be made the switch. Name each layer after an app — "Resolve", "Signal", "Blender" — and the same twelve keys become a different set of keys for each.

The keys also send plain letters al, which cannot be used as global hotkeys without breaking typing. The daemon therefore grabs the pad exclusively, so its keys stop reaching anything else.

Install

Two commands. The second one is the whole setup.

omarchy plugin add https://github.com/mfilm77/omarchy-keypad --enable --yes
~/.config/omarchy/plugins/io.github.mfilm77.keypad/bin/keypad-setup

keypad-setup creates your bindings file, installs and starts the background service, and — after printing them — runs the three steps that need root. It asks for sudo once. It never overwrites a bindings file, a service file or a udev rule you already have: anything already in place is reported and left alone, so running it twice is safe.

Then press a key on the pad.

Checking, later

keypad-setup --check

lists every prerequisite and what to do about each one:

[  ok  ] udev rule (lets you read the pad without being root)
[  ok  ] uinput (how the pad's shortcuts are typed)
[  ok  ] your bindings file
[  ok  ] the background service that reads the pad
[  ok  ] the keypad itself
         found at /dev/input/event23

The panel shows the same thing in plain words when something is missing, so a pad that is not working tells you why rather than leaving you to guess.

What needs root, and why

Reading a keyboard is a privileged act. The udev rule grants access to this one pad rather than adding you to the input group, which would mean read access to every keyboard on the machine — a keylogger for anything running as you. The same rule grants /dev/uinput, which is where shortcut bindings are typed from, and the uinput module has to be loaded for that node to exist.

Those three steps are printed in full before they run, and they run in a single sudo invocation you can see. Nothing is escalated silently. If you would rather do it yourself, keypad-setup --check names each file, and the commands are:

sudo install -m 644 share/70-omarchy-keypad.rules /etc/udev/rules.d/70-omarchy-keypad.rules
printf 'uinput\n' | sudo tee /etc/modules-load.d/omarchy-keypad.conf
sudo modprobe uinput && sudo udevadm control --reload && sudo udevadm trigger

If the pad is paired over Bluetooth

A rule installed while the pad is already connected reaches it on the next re-trigger, which keypad-setup does for you. If the pad still is not read, switch it off and on once so it re-attaches under the new rule, then keypad-setup --check.

Requirements

Omarchy 4 (Quattro) with the Quickshell-based shell. No pip packages — the daemon reads evdev directly from the Python standard library. It shells out to hyprctl (window/workspace actions and the recording submap), systemctl --user (its own unit), qs (the shell IPC) and, only if present, bluetoothctl for the Bluetooth battery reading. The uinput kernel module is needed for shortcut bindings.

Removal

systemctl --user disable --now omarchy-keypad.service
rm -f ~/.config/systemd/user/omarchy-keypad.service && systemctl --user daemon-reload
omarchy plugin remove io.github.mfilm77.keypad
sudo rm -f /etc/udev/rules.d/70-omarchy-keypad.rules /etc/modules-load.d/omarchy-keypad.conf
sudo udevadm control --reload && sudo udevadm trigger

Your bindings are left alone at ~/.config/omarchy-keypad/config.json — delete that directory too if you want them gone. Nothing outside these paths is touched, and no existing configuration is overwritten at install: the udev rule and the systemd unit are new files under this plugin's own name, and the recording submap is registered at runtime rather than written into your Hyprland config.

Use

Click the bar widget. Click a key or a knob in the picture, give it a name, and choose what it does:

  • Run a command — any shell command, e.g. hyprctl dispatch 'hl.dsp.focus({ workspace = "3" })' (Hyprland 0.56+ takes Lua; older setups use hyprctl dispatch workspace 3). Pick from the Preset list to fill the field with one of the commands the plugin ships — menus, capture, sound, windows, workspaces, apps, system — or type your own. A preset fills the field in; it does not lock it.
  • Press a shortcut — click the box, press the chord on your keyboard, and that is what the key will press. Recorded, not typed, so there is nothing to spell.
  • Open an app — search the same launcher entries SUPER + SPACE offers.

A recorded shortcut can be tapped or held. Tap is the default and is right for nearly everything. Hold keeps the chord down for exactly as long as the pad key is down, which is what anything that watches key-down and key-up separately needs — push-to-talk dictation being the usual one. (For dictation specifically, the command voxtype record toggle is often nicer: press to start, press to stop, and your hand is free in between.)

Save. Changes are live — the daemon rereads on SIGHUP and never drops the grab, so no keypress escapes as a stray letter mid-edit.

What you get out of the box

share/config.default.json ships three worked layers, so the pad does something useful the moment it is installed. Rebind anything you don't want.

Layer 1 — Main Layer 2 — System Layer 3 — Media
Keys 1–4 Workspaces 1–4 Gaps · Transparency · Fullscreen · Layout Play/pause · Prev · Next · Spotify
Keys 5–8 Terminal · Browser · Files · Screenshot Record screen · Scan QR · Grab text · Keybindings Copy · Paste · Undo · Redo
Keys 9–12 Emoji · Clipboard · Omarchy menu · Screensaver Share · Input · Edit config · About
Knob 1 Volume, press to pause all Brightness, press to mute Volume, press to pause all
Knob 2 Press for the next layer Press for the next layer Press to go back to Main

Pressing knob 1 pauses, it does not mute. Muting leaves the video running and the podcast talking to nobody; keypad-media-pause-all stops the playback itself — whichever player is actually making the sound, be it a YouTube tab, a video player, or the Plex Music plugin. Press it again and exactly those players resume, not every idle tab that happens to still be open. It speaks MPRIS over the session bus with busctl, so it needs nothing installed, and it does nothing quietly when nothing is playing. Mute is still there whenever you want it — on layer 2, and in the editor's Sound presets.

Layers 1 and 2 are all Omarchy commands. Layer 3 shows the other binding type: Copy, Paste, Undo and Redo are recorded shortcuts, pressed on the virtual keyboard rather than shelled out.

To rename the layer you are on, type in the Rename field next to the tabs.

How shortcut recording works

Hyprland runs its binds before a key reaches any window, and a consumed key never arrives at all — press SUPER+1 in a plain recorder and it would see SUPER go down, then the workspace change. While recording, the panel therefore enters a Hyprland submap that has no useful binds (bin/keypad-record-mode), so every chord falls through to it. The submap is registered at runtime the first time it is needed; nothing is added to your Hyprland config. It is left again the moment a chord lands, on Escape, on close, or after 20 seconds regardless.

How shortcuts are pressed

The daemon creates a virtual keyboard through uinput and presses the recorded keys on it. The compositor sees a real keyboard with your own keymap, so binds written by keycode (Omarchy binds workspaces as code:10…) fire exactly as they do from the physical keyboard, and app shortcuts arrive as ordinary key events. That is why uinput rather than wtype: a wtype keymap carries its own keycodes and misses every code: bind.

Would you want a keypad built for this?

This plugin exists because the hardware got in the way. The pad is fine, but its layers are a fiction on Linux, its keys type letters into whatever has focus, and the only tool that can change any of that runs on Windows. All of that is worked around here rather than fixed.

It could be fixed. If enough people want one, I would look at making a keypad properly — designed for this instead of adapted to it: different colours, different knobs, and a key layout chosen rather than inherited.

Nothing is for sale and there is nothing to pre-order. This is a straight question about whether the interest exists, and the answer decides whether it goes any further.

If you would want one, say so here — Would you want a keypad built properly for this? — what you would use it for, how many keys and knobs you would actually want, and what it should look like. A 👍 counts if you have nothing to add. Numbers are what decide it.

Files

Path What
bin/keypadd.py Reads the pad, holds the layer, runs commands, presses shortcuts
bin/keypad-record-mode Enters/leaves the Hyprland submap used while recording
bin/keypad-media-pause-all Pauses every playing MPRIS player, and resumes those same ones
Service.qml Owns the config and the running state
Panel.qml The editor
PadGraphic.qml / KnobGraphic.qml The drawing of the pad
BarWidget.qml Current layer in the bar
share/presets.json The ready-made commands the editor offers
~/.config/omarchy-keypad/config.json Your bindings

A binding in config.json is one of:

{ "type": "command",  "label": "Workspace 3", "run": "hyprctl dispatch 'hl.dsp.focus({ workspace = \"3\" })'" }
{ "type": "shortcut", "label": "Undo", "keys": "CTRL + Z", "codes": [29, 44] }
{ "type": "hold",     "label": "Talk", "keys": "F9", "codes": [67] }
{ "type": "layer",    "label": "Next layer" }
{ "type": "layer",    "label": "Resolve keys", "to": 1 }

devices lists what to grab; each entry is any subset of bus (usb or bluetooth), vendor, product, name, uniq (the Bluetooth address):

"devices": [
  { "bus": "usb", "vendor": "1189", "product": "8840" },
  { "bus": "bluetooth", "name": "MINI_KEYBOARD", "vendor": "05ac", "product": "022c" }
]

codes are Linux evdev keycodes, pressed in that order and released in reverse. to on a layer binding jumps to that layer (0-based) instead of cycling.

About

Turn a cheap USB macropad into a programmable control surface on Omarchy. Software layers, recorded shortcuts, and a graphical editor that draws your actual pad.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages