A year ago I switched to Linux, and it turned out to be the perfect fit for me. Faster, quieter, and mine to shape. But one thing kept nagging me: I missed the PowerToys-style tools I'd used to supercharge my workflow. I lean on shortcuts for everything, and above all I missed touchpad gestures. I wanted to slide three fingers to change the volume without hunting for a slider.
So I built Knobu: five small power tools for
Ubuntu and Kubuntu, each one switchable from a GTK4 settings app.
- Touchpad Gestures: slide three fingers up or down and the volume follows. Tap to mute or play/pause.
- Keyboard Manager: remap keys (CapsLock → Ctrl) and shortcuts (Super+E → terminal) in every app.
- Awake: keep the computer awake for a set time without touching power settings.
- Color Picker: Super+Shift+C, click a pixel, and its HEX/RGB/HSL is copied.
- Text Extractor: Super+Shift+T, drag over text on screen, and it's copied (Tesseract OCR).
The part I found most interesting to build was the keyboard remapper, so that's what this post is about.
The problem: Wayland doesn't let apps grab your keys
On X11, remapping tools could ask the X server to intercept keys. On Wayland, that's deliberately impossible: an app only sees keys while its own window is focused. Each compositor (KWin, Mutter, ...) has its own way of configuring keys, if it has one.
I wanted one remapper that works the same on KDE, GNOME, Wayland and X11. The trick is to work below the compositor, at the kernel's input layer.
Grab the real keyboard, re-type from a virtual one
Linux exposes every input device as /dev/input/event* (evdev). A process can grab a device, which means nobody else (including the compositor) receives its events. And through /dev/uinput, a process can create a virtual keyboard that the compositor treats like any other keyboard.
So Knobu's remapper:
- Creates a virtual keyboard with python-evdev's
UInput. - Grabs each physical keyboard.
- Reads every key event, runs it through the remap rules, and writes the result to the virtual keyboard.
ui = evdev.UInput({e.EV_KEY: codes}, name="Knobu virtual keyboard")
dev.grab()
for ev in dev.read():
if ev.type == e.EV_KEY:
for code, value in remapper.handle(ev.code, ev.value):
ui.write(e.EV_KEY, code, value)
ui.syn()
The compositor never knows the difference, which is why it behaves the same everywhere.
The gotchas
Never grab while a key is down. If you start the daemon from a terminal, Enter is still held when the grab happens. The compositor saw Enter go down but never sees it come up, so Enter is stuck. Knobu waits until dev.active_keys() is empty before grabbing.
Key remaps and shortcut remaps are different things. CapsLock → Ctrl should change the key everywhere, so CapsLock+C is a real copy. But Super+E → Ctrl+Alt+T should only fire on that exact combination. So plain key→key remaps are a lookup table applied to every event, and shortcuts are matched separately on key press, against the modifiers currently held.
Shortcuts have to lift modifiers you're holding. If you press Super+E and the target is Ctrl+Alt+T, the desktop must not see Super+Ctrl+Alt+T. The remapper briefly releases the held modifiers the target doesn't use, taps the target, then presses them again.
Super opens the app launcher. Desktops open the launcher when Super is pressed and released on its own. After a Super+E remap fires, releasing Super would open it. The fix is a little odd: tap an unused key (F24) before Super goes up, so the desktop thinks
Super was part of a shortcut.
Don't grab anything you don't need. With no remaps configured, Knobu grabs nothing. And if anything goes wrong, the grab is released, so you can always still type.
The remap logic is a plain class that takes (code, value) and returns the events to send, with no devices involved. That made all of the above easy to unit test.
The touchpad volume knob
Gestures use the same layer, but read-only: the touchpad is never grabbed, so normal clicking and scrolling are untouched. Knobu follows the multitouch slots (ABS_MT_SLOT, ABS_MT_POSITION_Y) and averages the movement of the fingers present in consecutive reports. That way fingers landing a few milliseconds apart don't make the volume jump. A small deadzone separates a slide from a tap. A drag only counts if the finger count never went above three, so a 4-finger tap never turns into a volume change.
Volume goes through PipeWire's wpctl (falling back to pactl). On KDE, Knobu calls Plasma's own OSD over D-Bus, so you get the native volume pop-up.
The cost: permissions
Reading /dev/input needs your user in the input group, and creating a virtual keyboard needs a udev rule for /dev/uinput. The .deb installs the rule. It's a real trade-off, since any process running as you can then read your keyboard, and I'd rather say so up front.
Try it
Knobu is GPL-3.0. It's fully supported on Kubuntu (Plasma 6) and in beta on Ubuntu (GNOME), and Xfce, LXQt and MATE are next.
👉 https://github.com/Saad-1719/knobu
I'd love to hear which PowerToys tool you miss most on Linux. That's how I'll pick the next module.

Top comments (0)