Files
gKeyMapper/AGENTS.md

79 lines
3.9 KiB
Markdown
Raw Normal View History

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
whole program is one translation unit, `src/main.cpp`.
## 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
- `src/main.cpp` holds everything in an anonymous namespace. The global `rows`
vector keeps one `KeyRow` (widget pointers) per G key. `on_activate` registers
the app actions `app.export-config`, `app.import-config`, `app.about`;
`build_ui` constructs the window. App id: `com.example.gkeymapper`.
- 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.