2026-10-17 02:42:09 +02:00
|
|
|
|
# AGENTS.md
|
|
|
|
|
|
|
|
|
|
|
|
## What this is
|
|
|
|
|
|
A pure GTK4 C++ GUI for remapping the G1–G9 keys of a Logitech G910. Today it
|
|
|
|
|
|
only edits and persists mappings: there is **no** hardware G-key detection and
|
|
|
|
|
|
**no** command execution yet (a native daemon is the planned next step). The
|
2026-10-17 02:57:21 +02:00
|
|
|
|
code is split into a few small translation units under `src/` (see below).
|
2026-10-17 02:42:09 +02:00
|
|
|
|
|
|
|
|
|
|
## Build / run / verify
|
|
|
|
|
|
- `make` — builds the `gkeymapper` binary **and** the `.mo` catalogs.
|
|
|
|
|
|
Needs `g++` (C++17), `make`, `gettext`, `libgtk-4-dev` (see README for
|
|
|
|
|
|
`apt install` on a fresh machine).
|
|
|
|
|
|
- `make translations` — only runs `msgfmt` on the existing `.po` files.
|
|
|
|
|
|
- `make run` — builds, then runs with `GKEYMAPPER_LOCALEDIR=$PWD/locale` and
|
|
|
|
|
|
`GKEYMAPPER_ASSETS_DIR=$PWD/assets` so the working-tree translations and
|
|
|
|
|
|
assets are used. Plain `./gkeymapper` works too (assets fall back to
|
|
|
|
|
|
`./assets`).
|
|
|
|
|
|
- `make clean` — deletes the binary and the generated `locale/` tree.
|
|
|
|
|
|
- No tests and no CI. Verify changes by building and running the app.
|
|
|
|
|
|
|
|
|
|
|
|
## i18n — easy to get wrong
|
|
|
|
|
|
- English is the source language (the `msgid`) **and** also has an explicit
|
|
|
|
|
|
identity catalog `po/en.po`. Languages: `de`, `en`, `es`, `fr`.
|
|
|
|
|
|
- `make translations` does **not** regenerate the `.pot`. When you add or change
|
|
|
|
|
|
a UI string, edit `po/gkeymapper.pot` and `po/*.po` by hand, then run
|
|
|
|
|
|
`make translations`; validate with `msgfmt --check -o /dev/null po/<lang>.po`.
|
|
|
|
|
|
- Never edit the generated `.mo` files under `locale/` (gitignored). Delete any
|
|
|
|
|
|
`po/*.po~` backups before committing.
|
|
|
|
|
|
- All documentation and source comments are English. German appears only inside
|
|
|
|
|
|
`po/de.po`.
|
|
|
|
|
|
|
|
|
|
|
|
## Code layout and conventions
|
2026-10-17 02:57:21 +02:00
|
|
|
|
- Modules under `src/` (shared API lives in `namespace gkeymapper`):
|
|
|
|
|
|
`keyrow.h` (`KeyRow`, `kNumKeys`), `mappings.{h,cpp}` (config file I/O),
|
|
|
|
|
|
`capturedialog.{h,cpp}` (key-capture dialog), `main.cpp` (app actions + UI).
|
|
|
|
|
|
- The Makefile globs sources (`$(wildcard src/*.cpp)`): a new `.cpp` is compiled
|
|
|
|
|
|
automatically, and `src/*.h` are prerequisites of the target.
|
|
|
|
|
|
- `main.cpp` keeps the `rows` vector (one `KeyRow` per G key, widget pointers)
|
|
|
|
|
|
in an anonymous namespace; mappings functions receive it as a parameter.
|
|
|
|
|
|
`on_activate` registers the app actions `app.export-config`,
|
|
|
|
|
|
`app.import-config`, `app.about`; `build_ui` constructs the window. App id:
|
|
|
|
|
|
`com.example.gkeymapper`.
|
2026-10-17 02:42:09 +02:00
|
|
|
|
- The Type dropdown index is meaningful everywhere: `0=Key`, `1=Command`,
|
|
|
|
|
|
`2=Disabled`. Keep that order — import/export map against it.
|
|
|
|
|
|
- The capture dialog's `CaptureSession` is heap-allocated and freed only by the
|
|
|
|
|
|
window's `"destroy"` handler. Always close the dialog with
|
|
|
|
|
|
`gtk_window_destroy` so the session is released.
|
|
|
|
|
|
- Use GTK4 API only: `gtk_editable_set_text` / `gtk_editable_get_text` (there is
|
|
|
|
|
|
no `gtk_entry_set_text`), `gtk_window_destroy` (no `gtk_dialog_run`),
|
|
|
|
|
|
`G_APPLICATION_FLAGS_NONE`.
|
|
|
|
|
|
- The main window is intentionally **non-resizable** and has **no**
|
|
|
|
|
|
`GtkScrolledWindow` (one collapsed the rows earlier). Keep the fixed widths
|
|
|
|
|
|
(Type dropdown 150 px, target entry 440 px min, action button 180 px) so rows
|
|
|
|
|
|
do not jump when the language changes.
|
|
|
|
|
|
- Because it is pure GTK4 it follows the system theme (Mint-Y ships a
|
|
|
|
|
|
`gtk-4.0` theme). Do **not** add the `suggested-action` CSS class — Mint-Y
|
|
|
|
|
|
tints it green and it clashes with the rest of the layout.
|
|
|
|
|
|
|
|
|
|
|
|
## Config file format
|
|
|
|
|
|
`Save mappings` writes `~/.config/gKeyMapper/mappings.conf`; Export/Import use
|
|
|
|
|
|
the same format:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
G1=key:ctrl+c,ctrl+v
|
|
|
|
|
|
G2=command:/usr/bin/firefox
|
|
|
|
|
|
G3=disabled:
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`<type>:<target>`, one line per key. `key` is a captured sequence (lowercase
|
|
|
|
|
|
`gdk_keyval_name` output; modifiers joined with `+`, steps joined with `,`),
|
|
|
|
|
|
`command` is a program/file path, `disabled` is inactive.
|
|
|
|
|
|
|
|
|
|
|
|
## Assets
|
|
|
|
|
|
`find_asset()` resolves in order: `$GKEYMAPPER_ASSETS_DIR` → `./assets` →
|
|
|
|
|
|
compiled-in `ASSETS_DIR` (`/usr/local/share/gkeymapper/assets`). The icon
|
|
|
|
|
|
(`assets/icon.png`) is used in the header bar and the About dialog.
|
|
|
|
|
|
|
|
|
|
|
|
## Driving the GUI for manual checks
|
|
|
|
|
|
There is no `xdotool`/`wtype` on this machine, and the capture-dialog buttons
|
|
|
|
|
|
(`Clear`/`Cancel`/`Done`) expose an empty AT-SPI accessible name — the text is in
|
|
|
|
|
|
a child `label` node, so target the button by its child label. `pyatspi`
|
|
|
|
|
|
(`queryAction().doAction(0)`) plus `Xlib`'s `xtest.fake_input` can click buttons
|
|
|
|
|
|
and inject keys. Warn the user before synthesizing keystrokes or stealing window
|
|
|
|
|
|
focus.
|