gKeyMapper
GTK4 application (C++) for remapping the G keys G1–G9 of a Logitech G910 to other keys or commands (Linux Mint / Ubuntu).
The GUI is complete. A background daemon (gkeymapper-daemon) that reads the
G-keys from the keyboard is in progress — see docs/daemon-plan.md.
Documentation and source comments are written in English.
Requirements
- Linux Mint / Ubuntu
- g++ (C++17)
- pkg-config
- gettext (for translations,
msgfmt) - GTK 4 development files
- libusb 1.0 development files (for the daemon)
- libxkbcommon development files (for the daemon's layout handling)
Install dependencies
sudo apt update
sudo apt install -y build-essential gettext libgtk-4-dev libusb-1.0-0-dev libxkbcommon-dev
Afterwards pkg-config --modversion gtk4 and
pkg-config --modversion libusb-1.0 should print versions.
Setting up the project
Change to the project directory:
cd /home/trager/Nextcloud/Projekte/C++/gKeyMapper
(Alternatively clone the repository or copy the project folder to your own location.)
Build
make
This produces the gkeymapper GUI binary, the gkeymapper-daemon binary and
the .mo translations under locale/. The daemon can also be built on its own
with make daemon.
Rebuild translations
make translations
Run
Normal:
./gkeymapper
With explicit language files (useful for testing from the build directory):
LANG=de_DE.UTF-8 GKEYMAPPER_LOCALEDIR=$PWD/locale ./gkeymapper
Supported languages: German, English, Spanish, French.
Running the daemon
The daemon reads G1–G9 from the keyboard and replays the configured action
(key through a virtual keyboard, command by launching the program):
./gkeymapper-daemon # uses ~/.config/gKeyMapper/mappings.conf
./gkeymapper-daemon /path/to.conf # or an explicit file
-
It reaches the keyboard through libusb (raw USB HID, vendor interface 1) and creates a virtual keyboard via
/dev/uinput. Both need access for your user;make installships a udev rule (TAG+="uaccess") for that. On Linux Mint the device already has such an ACL, otherwise install the rule or add your user to theinput/uinputgroup. -
While it runs, the kernel driver is detached from the vendor interface, so the media keys (mute / volume / play) may stop working until it exits — it reattaches the driver on shutdown.
-
The mappings file is watched: save in the GUI and the daemon picks the change up by itself (within about half a second), no restart and no signal.
SIGHUP/SIGUSR1reload it on demand, which is also the fallback on systems without inotify:kill -HUP $(pgrep -x gkeymapper-daem)Note: the kernel truncates the process name to 15 characters, so
pgrep -x gkeymapper-daemondoes not match. -
A failed reload keeps the previous mappings, so a transient read error never silently disables every key.
-
Key names are layout-independent:
key:ctrl+zmeans the key that produceszon your layout. The layout is read from/etc/default/keyboard(XKBLAYOUT); without it, US key positions are used. Because the name is symbolic, the same entry keeps its meaning if the layout changes (as long as the layout has that key); a name the layout does not provide is skipped. -
altgr+...presses AltGr (level 3), e.g.key:altgr+7types{on a German layout. -
command:...is run through the shell, so arguments work (command:/usr/bin/firefox --new-window); a plain program path is also fine.
Stop it with Ctrl+C or kill; it reattaches the kernel driver on the way out.
Install (optional)
sudo make install
Installs, under PREFIX (default /usr/local):
| Path | What |
|---|---|
bin/gkeymapper, bin/gkeymapper-daemon |
the GUI and the daemon |
share/locale/*/LC_MESSAGES/gkeymapper.mo |
translations |
share/gkeymapper/assets/, share/applications/, share/icons/ |
icon, .desktop file, icons |
lib/udev/rules.d/60-gkeymapper.rules |
lets your user reach the keyboard and /dev/uinput |
lib/modules-load.d/gkeymapper.conf |
loads the uinput module at boot |
lib/systemd/user/gkeymapper-daemon.service |
systemd user unit (binary path filled in from PREFIX) |
Then enable the daemon for your session — it then starts automatically at login:
systemctl --user daemon-reload
systemctl --user enable --now gkeymapper-daemon
systemctl --user status gkeymapper-daemon
It is a user service on purpose: it needs your session for /dev/uinput and
so that a command action inherits DISPLAY/WAYLAND_DISPLAY. The unit does
not run as root. make install reloads the udev rules for you; on a fresh
machine replug the keyboard (or reboot) once so the ACL applies.
Clean
make clean
Removes the binary and the generated locale/ directory.
Capturing key sequences
For rows of type Key, the action button opens a capture dialog:
- Press keys one after another; each press becomes one step.
- Modifier keys (Ctrl, Alt, Shift, Super) held during a press are recorded as part of that step.
- Each step stores the base key (the unshifted key), so
Shift+ß is recorded asshift+ssharpand not as the shifted symbolquestion. - AltGr (level 3) is recorded as the
altgrmodifier plus the base key, soAltGr+7 becomesaltgr+7. - The live preview shows the sequence.
Clearresets it,Donewrites it into the row,Canceldiscards it.
The sequence uses the same format as
g910-gkey-macro-support:
modifiers are joined with +, steps are separated with ,
(e.g. ctrl+c,ctrl+v or shift+f4,super+t). The modifier order is
ctrl, alt, shift, super, altgr; parsing accepts any order.
Configuration format
Save mappings writes ~/.config/gKeyMapper/mappings.conf:
# gKeyMapper mappings (G1..G9)
G1=key:ctrl+c,ctrl+v
G2=command:/usr/bin/firefox
G3=disabled:
The same format is used by Export/Import. key holds a captured shortcut
sequence, command a program path or command line, disabled ignores the
G key. These map to the shortcut / run / nothing hotkey types of the
reference project; the native C++ daemon (gkeymapper-daemon) reads this file
and replays the actions — no Python involved.
Save mappings writes atomically (temporary file + rename), so the daemon
never sees a half-written file and the watch reloads exactly once per save.
Project structure
src/main.cpp # app actions + GUI
src/keyrow.h # KeyRow (one per G key)
src/mappings.{h,cpp} # GUI glue: config file <-> row widgets
src/capturedialog.{h,cpp} # key-capture dialog
shared/mapping.{h,cpp} # mappings format + shortcut parsing (GUI & daemon)
shared/keys.{h,cpp} # canonical key names -> Linux key codes
shared/keymap.{h,cpp} # layout-aware key lookups (xkbcommon), GUI + daemon
daemon/main.cpp # event loop: read G-keys, run the action
daemon/device_tables.{h,cpp} # per-model raw HID report tables (G910)
daemon/g910_device.{h,cpp} # libusb access to the vendor interface
daemon/uinput_keyboard.{h,cpp}# virtual keyboard for "key" mappings
daemon/action.{h,cpp} # dispatch "key" / "command" / "disabled"
daemon/config_watch.{h,cpp} # inotify watch on mappings.conf (auto-reload)
data/60-gkeymapper.rules # udev rule (uaccess for device + /dev/uinput)
data/gkeymapper-uinput.conf # modules-load.d fragment for uinput
data/gkeymapper-daemon.service# systemd user unit (installed by make install)
docs/daemon-plan.md # daemon plan (reference: g910-gkey-macro-support)
po/ # Translations (pot/po/mo targets)
Makefile # Build via pkg-config + gettext
.vscode/tasks.json # VS Code tasks for make
README.md
Troubleshooting
Package gtk4 was not found
The GTK 4 development files are missing. Install them:
sudo apt install -y libgtk-4-dev
Then verify:
pkg-config --modversion gtk4
pkg-config cannot find .pc files (wrong search path)
In some environments PKG_CONFIG_PATH must be set explicitly. Standard path on
Ubuntu/Mint:
export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig:/usr/lib/pkgconfig
Planned next steps
- Daemon ↔ GUI channel (D-Bus): reload on save and show the daemon status.
- Support the media keys and the M1–M3 / MR keys of the G910.
- udev rule and systemd user service so the daemon can autostart.
make installfor the daemon binary, the udev rule and the service file.