Embedd assets icons

This commit is contained in:
2026-10-18 02:57:29 +02:00
parent b77b4a3fc6
commit 726a7ae92d
19 changed files with 1202 additions and 126 deletions

View File

@@ -3,8 +3,10 @@
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`.
The GUI is complete. The background daemon (`gkeymapper-daemon`) that reads the
G-keys from the keyboard and replays the actions is implemented as well; the
GUI and the daemon talk over a small D-Bus channel (status, reload, test) — see
`docs/daemon-plan.md`.
Documentation and source comments are written in English.
@@ -17,16 +19,18 @@ Documentation and source comments are written in English.
- GTK 4 development files
- libusb 1.0 development files (for the daemon)
- libxkbcommon development files (for the daemon's layout handling)
- GLib/GIO development files (for the GUI ↔ daemon D-Bus channel; `libgtk-4-dev`
already pulls them in, but they are needed for `make daemon` on its own)
### Install dependencies
```bash
sudo apt update
sudo apt install -y build-essential gettext libgtk-4-dev libusb-1.0-0-dev libxkbcommon-dev
sudo apt install -y build-essential gettext libgtk-4-dev libusb-1.0-0-dev libxkbcommon-dev libglib2.0-dev
```
Afterwards `pkg-config --modversion gtk4` and
`pkg-config --modversion libusb-1.0` should print versions.
Afterwards `pkg-config --modversion gtk4`, `pkg-config --modversion libusb-1.0`
and `pkg-config --modversion gio-2.0` should print versions.
## Setting up the project
@@ -46,7 +50,9 @@ 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`.
with `make daemon`. The GUI has its icons (`assets/icon.png`, `assets/icon.svg`)
compiled in (`data/icons.gresource.xml` → generated `build/gresources.c`), so the
window icon works without an installed `assets/` directory.
## Rebuild translations
@@ -70,6 +76,10 @@ LANG=de_DE.UTF-8 GKEYMAPPER_LOCALEDIR=$PWD/locale ./gkeymapper
Supported languages: German, English, Spanish, French.
The footer of the window shows the daemon state (`Daemon: running · Device: …`
plus the last G key that fired, or `Daemon: not reachable`) and follows it live:
start or stop the daemon while the GUI is open and the line updates itself.
## Running the daemon
The daemon reads G1–G9 from the keyboard and replays the configured action
@@ -101,6 +111,9 @@ The daemon reads G1–G9 from the keyboard and replays the configured action
`pgrep -x gkeymapper-daemon` does **not** match.
- A failed reload keeps the previous mappings, so a transient read error never
silently disables every key.
- Only one instance may run: a second one prints why it cannot get the bus name
and exits, instead of fighting over the keyboard and creating a second virtual
keyboard.
- 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
@@ -113,6 +126,34 @@ The daemon reads G1–G9 from the keyboard and replays the configured action
Stop it with Ctrl+C or `kill`; it reattaches the kernel driver on the way out.
### Status and control over D-Bus
The daemon owns the name `dev.rager.gkeymapper.Daemon` on the session bus. The
GUI uses that channel for its footer line ("Daemon: running / not reachable",
the device and the last G key that fired) and to apply a save immediately.
Nothing on this channel is on the keypress path, so a GUI that is slow or absent
never delays an action.
```bash
D=dev.rager.gkeymapper.Daemon
O=/dev/rager/gkeymapper/Daemon
gdbus call --session --dest $D --object-path $O --method $D.GetStatus
gdbus call --session --dest $D --object-path $O --method $D.Reload
gdbus call --session --dest $D --object-path $O --method $D.TestMapping 0
gdbus monitor --session --dest $D # KeyPressed / ConfigReloaded / DeviceChanged
```
- `GetStatus` → `(version, running, device, configPath)`.
- `Reload` re-reads the mappings file; `ConfigReloaded(ok)` reports the result.
- `TestMapping` fires a mapping without touching the keyboard (useful to check a
sequence). The index is **0-based**: `0` = G1 … `8` = G9. It runs the same code
path as a real keypress.
- `KeyPressed(index, action)` is emitted for every G key, real or tested.
If no session bus is available the daemon still works — it just runs without the
GUI channel and logs that.
## Install (optional)
```bash
@@ -129,6 +170,7 @@ Installs, under `PREFIX` (default `/usr/local`):
| `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`) |
| `share/dbus-1/services/dev.rager.gkeymapper.Daemon.service` | D-Bus activation: the GUI can start the daemon on demand |
Then enable the daemon for your session — it then starts automatically at login:
@@ -138,6 +180,10 @@ systemctl --user enable --now gkeymapper-daemon
systemctl --user status gkeymapper-daemon
```
Enabling it is optional: the D-Bus activation file above lets the bus start the
daemon as soon as the GUI asks for its status. With both in place the unit is
used either way, so the daemon never runs twice.
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
@@ -149,7 +195,7 @@ machine replug the keyboard (or reboot) once so the ACL applies.
make clean
```
Removes the binary and the generated `locale/` directory.
Removes the binaries and the generated `locale/` and `build/` directories.
## Capturing key sequences
@@ -194,22 +240,26 @@ never sees a half-written file and the watch reloads exactly once per save.
## Project structure
```
src/main.cpp # app actions + GUI
src/main.cpp # app actions + GUI + daemon status footer
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
src/daemonclient.{h,cpp} # D-Bus client for the daemon (status/reload)
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
shared/ipc_ids.h # app id + D-Bus names shared by GUI and 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)
daemon/ipc_dbus.{h,cpp} # D-Bus service (status/reload/test/signals)
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)
data/dev.rager.gkeymapper.Daemon.service # D-Bus activation (session bus)
docs/daemon-plan.md # daemon plan (reference: g910-gkey-macro-support)
po/ # Translations (pot/po/mo targets)
Makefile # Build via pkg-config + gettext
@@ -244,7 +294,7 @@ export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/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.
- Profiles: swap mapping sets on the fly (the G910's M keys), which is what the
reserved `SetProfile` D-Bus method was meant for.
- Key sequence timing: per-step delays for recorded macros.