3.9 KiB
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 thegkeymapperbinary and the.mocatalogs. Needsg++(C++17),make,gettext,libgtk-4-dev(see README forapt installon a fresh machine).make translations— only runsmsgfmton the existing.pofiles.make run— builds, then runs withGKEYMAPPER_LOCALEDIR=$PWD/localeandGKEYMAPPER_ASSETS_DIR=$PWD/assetsso the working-tree translations and assets are used. Plain./gkeymapperworks too (assets fall back to./assets).make clean— deletes the binary and the generatedlocale/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 catalogpo/en.po. Languages:de,en,es,fr. make translationsdoes not regenerate the.pot. When you add or change a UI string, editpo/gkeymapper.potandpo/*.poby hand, then runmake translations; validate withmsgfmt --check -o /dev/null po/<lang>.po.- Never edit the generated
.mofiles underlocale/(gitignored). Delete anypo/*.po~backups before committing. - All documentation and source comments are English. German appears only inside
po/de.po.
Code layout and conventions
src/main.cppholds everything in an anonymous namespace. The globalrowsvector keeps oneKeyRow(widget pointers) per G key.on_activateregisters the app actionsapp.export-config,app.import-config,app.about;build_uiconstructs 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
CaptureSessionis heap-allocated and freed only by the window's"destroy"handler. Always close the dialog withgtk_window_destroyso the session is released. - Use GTK4 API only:
gtk_editable_set_text/gtk_editable_get_text(there is nogtk_entry_set_text),gtk_window_destroy(nogtk_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.0theme). Do not add thesuggested-actionCSS 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.