# 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 ```bash 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: ```bash cd /home/trager/Nextcloud/Projekte/C++/gKeyMapper ``` (Alternatively clone the repository or copy the project folder to your own location.) ## Build ```bash 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 ```bash make translations ``` ## Run Normal: ```bash ./gkeymapper ``` With explicit language files (useful for testing from the build directory): ```bash 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): ```bash ./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 install` ships 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 the `input`/`uinput` group. - 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`/`SIGUSR1` reload it on demand, which is also the fallback on systems without inotify: ```bash kill -HUP $(pgrep -x gkeymapper-daem) ``` Note: the kernel truncates the process name to 15 characters, so `pgrep -x gkeymapper-daemon` does **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+z` means the key that produces `z` **on 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+7` types `{` 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) ```bash 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: ```bash 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 ```bash 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 as `shift+ssharp` and not as the shifted symbol `question`. - AltGr (level 3) is recorded as the `altgr` modifier plus the base key, so `AltGr`+7 becomes `altgr+7`. - The live preview shows the sequence. `Clear` resets it, `Done` writes it into the row, `Cancel` discards it. The sequence uses the same format as [g910-gkey-macro-support](https://github.com/JSubelj/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: ```bash sudo apt install -y libgtk-4-dev ``` Then verify: ```bash 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: ```bash 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 install` for the daemon binary, the udev rule and the service file.