Files
gKeyMapper/AGENTS.md
2026-10-17 02:42:09 +02:00

3.9 KiB
Raw Blame History

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.