Per-application profile manager for the Logitech MX Creative Keypad on Linux.
Switch what the keypad's 9 LCD keys display and which keyboard shortcuts they fire, automatically, based on the application currently focused on your desktop. Author profiles in a small Tk editor; a background daemon handles the rest.
logimap-demo-reduced-size.mp4
Disclaimer: This is not an official Logitech™ application. This project is not affiliated with, endorsed by, or sponsored by Logitech.
Note
This is the NPPprojects community fork of abishekmuthian/logimap, focused on native Hyprland support while retaining KDE Plasma support and upstream attribution.
| Need | Supported |
|---|---|
| Hardware | Logitech MX Creative Keypad (USB 046d:c354) |
| OS | Linux (kernel ≥ 5.10 for uinput) |
| Desktop | KDE Plasma 6 or Hyprland on Wayland |
| Keystroke injection | Built-in /dev/uinput (no third-party daemon) |
The separate MX Dialpad is not supported yet.
- Automatically selects a profile by focused-window class and optional title regex on KDE Plasma 6 or Hyprland.
- Fills all nine MX Creative Keypad LCD keys with generated text labels using per-profile foreground and background colors.
- Sends single-chord and multi-chord keyboard shortcuts through
/dev/uinput. - Dispatches numbered workspaces directly through Hyprland IPC. Workspace profiles can show a fixed range or a compact, live list containing only occupied workspaces, with the active workspace highlighted.
- Includes a Tk profile editor and an on-device preview action.
- Hot-reloads
profiles.jsonand automatically reconnects after keypad or compositor restarts. - Can run as a
systemd --userservice and writes rotating diagnostic logs.
Current keypad limits: labels are generated from text rather than custom images. General shortcut profiles have one page; arrow pagination applies to fixed or occupied Hyprland workspace layouts.
git clone --recursive https://github.com/NPPprojects/logimap-hyprland.git
cd logimap-hyprland
./install.sh
logimap install-kwin # KDE only; one-time per Plasma session
logimap gui # author your first profile
logimap run # auto-detect KWin or HyprlandIf you're adding yourself to the input group for the first time you'll
need to log out and back in once before the daemon can write /dev/uinput
across reboots. install.sh will tell you.
It's a plain bash script, so read it before running. In order, it:
- Refuses to proceed unless you're on Linux with KDE Plasma 6 Wayland or
Hyprland (pass
--forceto override). - Detects your distro (Fedora, Debian/Ubuntu, or Arch) and installs the
build/runtime deps (
cmake, a C++ compiler, Python, Tk, Pillow, dbus-next,pybind11, DejaVu Sans,git,acl). - Creates
~/.local/share/logimap/venvand installs the Python runtime dependencies. git submodule update --init, which pulls the project's LogiLinux core compatibility fork and the upstreamlogilinux-sdk/.- Builds the C++ core (
logilinux/) with CMake. - Symlinks the SDK's empty
logilinux-driverstub to../logilinuxso the pybind11 build resolves headers. It then pip-installspybind11,logilinux-sdk(editable), andlogimap(editable). The venv uses--system-site-packagesso it inherits Tk and Pillow-tk from the distro. - Drops two udev rules into
/etc/udev/rules.d/:99-logitech-creator.rules:GROUP=input MODE=0660for HID PID046d:c354(MX Creative Keypad).99-logimap-uinput.rules:GROUP=input MODE=0660for/dev/uinput. It also loads theuinputkernel module now and at boot through/etc/modules-load.d/logimap-uinput.conf.
- Adds your user to the
inputgroup if you're not already a member, plus a temporary ACL bridge for the current boot. - Drops a launcher at
~/.local/bin/logimapand a.desktopentry under~/.local/share/applications/. - Drops a
systemd --userunit at~/.config/systemd/user/logimap.service(installed but not enabled; you opt in).
You'll be prompted for sudo at the udev / usermod steps.
-
logimap guiopens the Tk editor. -
Click Add, pick a profile name.
-
In the Match section, set
wm_classto the focused window class reported by your compositor. The easy way to discover this: withlogimap runactive, alt-tab to the app you care about and tail the log:tail -F ~/.local/state/logimap/logimap.log | grep 'focus ->'
Copy the logged
wm_classstring verbatim. Plasma often uses a reverse-DNS class such asorg.mozilla.firefox; Hyprland commonly reportsfirefox. -
Click a key in the 3×3 grid (e.g.
GRID_0). -
Type a Title (what the LCD will show) and assign a shortcut. Two ways:
- Capture: click the button, press the chord (e.g.
Ctrl+T). Works for app-level shortcuts the compositor doesn't intercept. Capture records a single chord — for multi-chord shortcuts like tmux prefixes, type directly (see below). - Type directly into the Shortcut field: required for chords
KWin grabs globally before they reach the editor (e.g.
Meta+Lfor lock screen,Meta+Dfor show desktop). Use the canonical+-joined form:logo+l,ctrl+shift+t,alt+f4. Modifier names:ctrl,shift,alt,logo(also acceptsmeta/super/control). The daemon synthesizes these via uinput, and KWin's global-shortcut handler honors them on Plasma 6. - Multi-chord shortcuts (tmux, screen, etc.): separate chords
with a comma. For example, tmux's vertical split (
Ctrl+Bthen") is entered asctrl+b,"(the"auto-expands toshift+'). The daemon injects each chord with a 50 ms pause between them so the receiving application can distinguish the prefix from the action. Other examples:ctrl+b,%— tmux horizontal splitctrl+b,o— tmux switch panectrl+b,x— tmux close pane
- Fixed Hyprland workspaces: leave Workspace layout set to
fixedand enterworkspace:1throughworkspace:9. These actions dispatch directly through Hyprland IPC instead of synthesizing a modifier chord. Set Last workspace on the profile (for example,15) to enable the hardware arrow buttons. Right shows workspaces 10–15; left returns to 1–9. - Occupied Hyprland workspaces: set Workspace layout to
occupied. All nine keys are generated from live Hyprland state, sorted and packed without gaps. Empty and special workspaces are hidden, and the active occupied workspace is highlighted. Last workspace still limits the highest workspace shown. Configured per-key bindings are retained but ignored while this mode is active.
- Capture: click the button, press the chord (e.g.
-
Save. The daemon hot-reloads the config file (mtime watcher;
SIGHUPalso works).
~/.config/logimap/profiles.json:
{
"version": 1,
"default_profile": "default",
"profiles": {
"firefox": {
"match": {"wm_class": "org.mozilla.firefox", "title_regex": null},
"bg_color": [30, 30, 30],
"fg_color": [255, 255, 255],
"keys": {
"GRID_0": {"title": "New Tab", "shortcut": "ctrl+t"},
"GRID_1": {"title": "Close Tab", "shortcut": "ctrl+w"},
"GRID_2": {"title": "Reopen Tab", "shortcut": "ctrl+shift+t"},
"GRID_3": {"title": "Split V", "shortcut": "ctrl+b,\""},
"GRID_4": {"title": "Split H", "shortcut": "ctrl+b,%"}
}
},
"default": {
"match": {"wm_class": "*", "title_regex": null},
"workspace_limit": 15,
"workspace_mode": "occupied",
"keys": {}
}
}
}Layout:
GRID_0 GRID_1 GRID_2
GRID_3 GRID_4 GRID_5
GRID_6 GRID_7 GRID_8
Profile selection: first profile whose wm_class matches (with
title_regex checked if set), otherwise default_profile.
Start the installed service immediately:
systemctl --user start logimap
journalctl --user -u logimap -f # follow the daemon's logsOn KDE, enable it for future graphical sessions:
systemctl --user enable logimapFor a Hyprland session not managed by UWSM, add this to hyprland.conf instead:
exec-once = systemctl --user start logimap.serviceFor a Lua-based Hyprland configuration, put the equivalent command in its
startup hook, for example hl.exec_cmd("systemctl --user start logimap.service").
UWSM-managed Hyprland sessions start graphical-session.target, so
systemctl --user enable logimap is sufficient there.
KWin JS ──D-Bus───────┐
├──▶ daemon ──JPEG bytes──▶ MX Keypad
Hyprland ──Unix IPC───┘ ▲ │
└──── ButtonEvent ──────┘
│
└─ /dev/uinput EV_KEY ─▶ focused app
logimap/focus/kwin_dbus.pyownsorg.logimap.Focus1on the session bus.kwin_script/logimap-focus.jssubscribes toworkspace.windowActivatedand pings us viacallDBus.logimap/focus/hyprland_ipc.pylistens to Hyprland's event socket and reads structuredactivewindowdata from its request socket. It reconnects and rediscovers the compositor instance after a Hyprland restart.workspace:Nactions use the request socket's workspace dispatcher directly.logimap/device.pywrapslogilinux.MXKeypadDevicewith auto-reconnect.logimap/inject/uinput.pyopens/dev/uinput, registers a virtual keyboard, writesEV_KEYevents for the chord.wtype/ydotool/xdotoolare auto-probed fallbacks (rarely needed on KWin 6; see Troubleshooting).logimap/render.pyproduces a 118×118 JPEG from a title string to fill the LCD region programmed byMXKeypadDevice.set_key_image().
Logs are in ~/.local/state/logimap/logimap.log (rotating, 1 MB × 3
backups). Pass -v for DEBUG.
tail -F ~/.local/state/logimap/logimap.log
# Verify D-Bus owner is the live daemon:
busctl --user status org.logimap.Focus1| Symptom | First thing to check |
|---|---|
| LCDs don't change on alt-tab | grep 'focus ->' ~/.local/state/logimap/logimap.log. On KDE, run logimap install-kwin; on Hyprland, check for connected to Hyprland IPC in the log. |
| Wrong compositor was selected | Run logimap run --focus-source hyprland or logimap run --focus-source kwin. LOGIMAP_FOCUS_SOURCE provides the same override for services. |
| Key press does nothing | Look for press GRID_N has no binding in profile '…'. The resolved profile name shows whether your wm_class matched. Use the exact class logged by your compositor; Plasma often uses reverse-DNS names while Hyprland commonly uses names such as firefox. |
Capture button doesn't react to Meta+L, Meta+D, etc. |
KWin grabs those globally before the editor sees them. Type the canonical form (e.g. logo+l) directly into the Shortcut field instead. |
No usable keystroke injector found |
Your user can't open /dev/uinput. Re-login after install.sh (the input group needs a fresh session) or sudo setfacl -m u:$USER:rw /dev/uinput for a one-shot. |
Daemon falls back to xdotool on Arch |
Check lsmod | grep uinput and /dev/uinput. Re-run ./install.sh; it now loads uinput and installs a persistent modules-load entry. Direct workspace:N actions do not depend on an injector. |
wtype failed for ... exit 1 |
KWin 6 hides zwp_virtual_keyboard_v1 from non-IME clients. The probe should have skipped wtype and selected uinput; check the startup log line using built-in uinput injector. |
MX Keypad not found |
lsusb | grep 046d:c354 should show the device. Confirm the matching /dev/hidraw* node belongs to the input group and your user is a member; try sudo udevadm trigger --subsystem-match=hidraw and replug. |
RequestNameReply.IN_QUEUE in startup log |
A second daemon is fighting for the D-Bus name. pkill -f "logimap run" and restart. |
ModuleNotFoundError: _logilinux_native |
The SDK was built against a different Python version than the one running the daemon. Re-run ./install.sh; it picks an interpreter that matches the compiled .so. |
| systemd unit fails to start | journalctl --user -u logimap -n 50 and inspect. Most often: not in input group yet (re-login), or ~/.local/bin not on the systemd user PATH. |
| Service does not start with Hyprland | Non-UWSM Hyprland does not start graphical-session.target; add exec-once = systemctl --user start logimap.service to hyprland.conf. |
VENV="$HOME/.local/share/logimap/venv"
"$VENV/bin/python" -m pip install -e ".[dev]"
PYTHONPATH="$PWD:$PWD/logilinux-sdk" \
LD_LIBRARY_PATH="$PWD/logilinux/build/lib" \
"$VENV/bin/python" -m pytest tests/ -qThe test suite covers the shortcut parser, config round-trip, profile resolver, workspace paging, JPEG rendering, Hyprland IPC parsing/selection, and uinput event-sequence logic. Physical keypad and full compositor behavior still require manual integration testing.
Contributions to Hyprland support are welcome through NPPprojects/logimap-hyprland. Changes that are also useful upstream should be proposed to abishekmuthian/logimap.
logimap is MIT-licensed. See LICENSE.
- General pagination: Extend arrow paging beyond numbered workspace actions to arbitrary multi-page shortcut bindings.
Built on top of the LogiLinux project:
logilinux: C++ core library and hardware abstractionlogilinux-sdk: pybind11 Python bindings
The core compatibility fork at
abishekmuthian/logilinux and
the upstream logilinux-sdk are pulled in as git submodules.