New locale

This commit is contained in:
2026-10-17 02:42:09 +02:00
parent d157d9d30d
commit 99146577d4
11 changed files with 614 additions and 458 deletions

78
AGENTS.md Normal file
View File

@@ -0,0 +1,78 @@
# 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.

View File

@@ -81,6 +81,38 @@ make clean
Removes the binary and the generated `locale/` directory.
## Capturing key sequences
For rows of type **Key**, the action button opens a capture dialog:
- Press keys one after another; each press becomes one step.
- Modifier keys (Ctrl, Alt, Shift, Super) held during a press are recorded
as part of that step.
- The live preview shows the sequence. `Clear` resets it, `Done` writes it
into the row, `Cancel` discards it.
The sequence uses the same format as
[g910-gkey-macro-support](https://github.com/JSubelj/g910-gkey-macro-support):
modifiers are joined with `+`, steps are separated with `,`
(e.g. `ctrl+c,ctrl+v` or `shift+f4,super+t`).
## Configuration format
`Save mappings` writes `~/.config/gKeyMapper/mappings.conf`:
```
# gKeyMapper mappings (G1..G9)
G1=key:ctrl+c,ctrl+v
G2=command:/usr/bin/firefox
G3=disabled:
```
The same format is used by Export/Import. `key` holds a captured shortcut
sequence, `command` a program or file path, `disabled` ignores the G key
(similar to the `shortcut` / `run` / `nothing` hotkey types of the
reference project above; a native C++ daemon for G-key detection and
replay is planned next, no Python involved).
## Project structure
```

View File

@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:53+0200\n"
"PO-Revision-Date: 2026-10-16 21:00+0000\n"
"POT-Creation-Date: 2026-10-16 22:12+0200\n"
"PO-Revision-Date: 2026-10-16 22:12+0200\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de\n"
@@ -15,124 +15,116 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/main.cpp:53
msgid "Choose file"
msgstr "Datei auswählen"
#: src/main.cpp:56 src/main.cpp:224 src/main.cpp:236
msgid "_Cancel"
msgstr "_Abbrechen"
#: src/main.cpp:57 src/main.cpp:237
msgid "_Open"
msgstr "_Öffnen"
#: src/main.cpp:75
msgid "<Press a key...>"
msgstr "<Taste drücken...>"
msgid "No keys recorded yet"
msgstr "Noch keine Tasten aufgezeichnet"
msgid "Capture key sequence"
msgstr "Tastensequenz aufnehmen"
msgid "Clear"
msgstr "Leeren"
msgid "Cancel"
msgstr "Abbrechen"
msgid "Done"
msgstr "Fertig"
msgid ""
"Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded "
"too). Click Done to apply or Cancel to discard."
msgstr ""
"Tasten der Reihe nach drücken (Modifikatoren wie Strg, Alt, Shift und Super "
"werden mit aufgezeichnet). Mit Fertig übernehmen oder mit Abbrechen verwerfen."
#: src/main.cpp:87
msgid "Choose file..."
msgstr "Datei auswählen..."
#: src/main.cpp:89 src/main.cpp:364
msgid "e.g. /usr/bin/firefox or custom command"
msgstr "z. B. /usr/bin/firefox oder eigener Befehl"
msgid "Capture"
msgstr "Aufnehmen"
#: src/main.cpp:125
msgid "e.g. ctrl+c, ctrl+v"
msgstr "z. B. Strg+C, Strg+V"
#, c-format
msgid "Could not save %s.\n"
msgstr "Konnte %s nicht speichern.\n"
#: src/main.cpp:129
msgid "Mappings saved to "
msgstr "Mappings gespeichert nach "
#: src/main.cpp:135
#, c-format
msgid "Could not open %s.\n"
msgstr "Konnte %s nicht öffnen.\n"
#: src/main.cpp:187
msgid "Could not export mappings.\n"
msgstr "Mappings konnten nicht exportiert werden.\n"
#: src/main.cpp:210 src/main.cpp:273
msgid "gKeyMapper"
msgstr "gKeyMapper"
#: src/main.cpp:213
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "G-Key-Mapper für die Logitech G910 (GTK4)."
#: src/main.cpp:221
msgid "Export configuration"
msgstr "Konfiguration exportieren"
#: src/main.cpp:225
msgid "_Save"
msgstr "_Speichern"
#: src/main.cpp:233
msgid "Import configuration"
msgstr "Konfiguration importieren"
#: src/main.cpp:269
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
#: src/main.cpp:277
msgid "Export configuration..."
msgstr "Konfiguration exportieren..."
#: src/main.cpp:278
msgid "Import configuration..."
msgstr "Konfiguration importieren..."
#: src/main.cpp:281
msgid "About"
msgstr "Über"
#: src/main.cpp:305
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "G1 bis G9 der Logitech G910 auf Tasten oder Befehle ummappen"
#: src/main.cpp:327
msgid "G-Key"
msgstr "G-Taste"
#: src/main.cpp:327
msgid "Type"
msgstr "Typ"
#: src/main.cpp:327
msgid "Target (Key/Command)"
msgstr "Ziel (Taste/Befehl)"
#: src/main.cpp:327
msgid "Action"
msgstr "Aktion"
#: src/main.cpp:335
msgid "Key"
msgstr "Taste"
#: src/main.cpp:335
msgid "Command"
msgstr "Befehl"
#: src/main.cpp:335
msgid "Disabled"
msgstr "Deaktiviert"
#: src/main.cpp:358
msgid "e.g. ctrl+c or ls -la"
msgstr "z. B. Strg+C oder ls -la"
#: src/main.cpp:378
msgid "Save mappings"
msgstr "Mappings speichern"
#: src/main.cpp:383
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Gespeichert in ~/.config/gKeyMapper/mappings.conf"

102
po/de.po~
View File

@@ -1,102 +0,0 @@
# German translation for gkeymapper.
# Copyright (C) 2026
# This file is distributed under the same license as the gkeymapper package.
#
msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:00+0000\n"
"PO-Revision-Date: 2026-10-16 21:00+0000\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "<Press a key...>"
msgstr "<Taste drücken...>"
#, c-format
msgid "Could not save %s.\n"
msgstr "Konnte %s nicht speichern.\n"
msgid "Mappings saved to "
msgstr "Mappings gespeichert nach "
#, c-format
msgid "Could not open %s.\n"
msgstr "Konnte %s nicht öffnen.\n"
msgid "Could not export mappings.\n"
msgstr "Mappings konnten nicht exportiert werden.\n"
msgid "gKeyMapper"
msgstr "gKeyMapper"
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "G-Key-Mapper für die Logitech G910 (GTK4)."
msgid "Export configuration"
msgstr "Konfiguration exportieren"
msgid "_Cancel"
msgstr "_Abbrechen"
msgid "_Save"
msgstr "_Speichern"
msgid "Import configuration"
msgstr "Konfiguration importieren"
msgid "_Open"
msgstr "_Öffnen"
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
msgid "Export configuration..."
msgstr "Konfiguration exportieren..."
msgid "Import configuration..."
msgstr "Konfiguration importieren..."
msgid "About"
msgstr "Über"
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "G1 bis G9 der Logitech G910 auf Tasten oder Befehle ummappen"
msgid "G-Key"
msgstr "G-Taste"
msgid "Type"
msgstr "Typ"
msgid "Target (Key/Command)"
msgstr "Ziel (Taste/Befehl)"
msgid "Action"
msgstr "Aktion"
msgid "Key"
msgstr "Taste"
msgid "Command"
msgstr "Befehl"
msgid "Disabled"
msgstr "Deaktiviert"
msgid "e.g. ctrl+c or ls -la"
msgstr "z. B. Strg+C oder ls -la"
msgid "Capture"
msgstr "Aufnehmen"
msgid "Save mappings"
msgstr "Mappings speichern"
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Gespeichert in ~/.config/gKeyMapper/mappings.conf"

131
po/en.po Normal file
View File

@@ -0,0 +1,131 @@
# English translation for gkeymapper.
# Copyright (C) 2026
# This file is distributed under the same license as the gkeymapper package.
# English is the source language; this catalog keeps the strings explicit.
#
msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 22:12+0200\n"
"PO-Revision-Date: 2026-10-16 22:12+0200\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Choose file"
msgstr "Choose file"
msgid "_Cancel"
msgstr "_Cancel"
msgid "_Open"
msgstr "_Open"
msgid "No keys recorded yet"
msgstr "No keys recorded yet"
msgid "Capture key sequence"
msgstr "Capture key sequence"
msgid "Clear"
msgstr "Clear"
msgid "Cancel"
msgstr "Cancel"
msgid "Done"
msgstr "Done"
msgid ""
"Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded "
"too). Click Done to apply or Cancel to discard."
msgstr ""
"Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded "
"too). Click Done to apply or Cancel to discard."
msgid "Choose file..."
msgstr "Choose file..."
msgid "e.g. /usr/bin/firefox or custom command"
msgstr "e.g. /usr/bin/firefox or custom command"
msgid "Capture"
msgstr "Capture"
msgid "e.g. ctrl+c, ctrl+v"
msgstr "e.g. ctrl+c, ctrl+v"
#, c-format
msgid "Could not save %s.\n"
msgstr "Could not save %s.\n"
msgid "Mappings saved to "
msgstr "Mappings saved to "
#, c-format
msgid "Could not open %s.\n"
msgstr "Could not open %s.\n"
msgid "Could not export mappings.\n"
msgstr "Could not export mappings.\n"
msgid "gKeyMapper"
msgstr "gKeyMapper"
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "G-Key mapper for the Logitech G910 (GTK4)."
msgid "Export configuration"
msgstr "Export configuration"
msgid "_Save"
msgstr "_Save"
msgid "Import configuration"
msgstr "Import configuration"
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
msgid "Export configuration..."
msgstr "Export configuration..."
msgid "Import configuration..."
msgstr "Import configuration..."
msgid "About"
msgstr "About"
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgid "G-Key"
msgstr "G-Key"
msgid "Type"
msgstr "Type"
msgid "Target (Key/Command)"
msgstr "Target (Key/Command)"
msgid "Action"
msgstr "Action"
msgid "Key"
msgstr "Key"
msgid "Command"
msgstr "Command"
msgid "Disabled"
msgstr "Disabled"
msgid "Save mappings"
msgstr "Save mappings"
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Saved to ~/.config/gKeyMapper/mappings.conf"

View File

@@ -4,8 +4,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:53+0200\n"
"PO-Revision-Date: 2026-10-16 21:00+0000\n"
"POT-Creation-Date: 2026-10-16 22:12+0200\n"
"PO-Revision-Date: 2026-10-16 22:12+0200\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es\n"
@@ -13,124 +13,117 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/main.cpp:53
msgid "Choose file"
msgstr "Elegir archivo"
#: src/main.cpp:56 src/main.cpp:224 src/main.cpp:236
msgid "_Cancel"
msgstr "_Cancelar"
#: src/main.cpp:57 src/main.cpp:237
msgid "_Open"
msgstr "_Abrir"
#: src/main.cpp:75
msgid "<Press a key...>"
msgstr "<Pulsa una tecla...>"
msgid "No keys recorded yet"
msgstr "Aún no se ha registrado ninguna tecla"
msgid "Capture key sequence"
msgstr "Capturar secuencia de teclas"
msgid "Clear"
msgstr "Borrar"
msgid "Cancel"
msgstr "Cancelar"
msgid "Done"
msgstr "Aceptar"
msgid ""
"Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded "
"too). Click Done to apply or Cancel to discard."
msgstr ""
"Pulsa las teclas en orden (los modificadores como Ctrl, Alt, Mayús y Super "
"también se registran). Haz clic en Aceptar para aplicar o en Cancelar para "
"descartar."
#: src/main.cpp:87
msgid "Choose file..."
msgstr "Elegir archivo..."
#: src/main.cpp:89 src/main.cpp:364
msgid "e.g. /usr/bin/firefox or custom command"
msgstr "p. ej. /usr/bin/firefox o comando personalizado"
msgid "Capture"
msgstr "Capturar"
#: src/main.cpp:125
msgid "e.g. ctrl+c, ctrl+v"
msgstr "p. ej. ctrl+c, ctrl+v"
#, c-format
msgid "Could not save %s.\n"
msgstr "No se pudo guardar %s.\n"
#: src/main.cpp:129
msgid "Mappings saved to "
msgstr "Asignaciones guardadas en "
#: src/main.cpp:135
#, c-format
msgid "Could not open %s.\n"
msgstr "No se pudo abrir %s.\n"
#: src/main.cpp:187
msgid "Could not export mappings.\n"
msgstr "No se pudieron exportar las asignaciones.\n"
#: src/main.cpp:210 src/main.cpp:273
msgid "gKeyMapper"
msgstr "gKeyMapper"
#: src/main.cpp:213
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "Asignador de teclas G para el Logitech G910 (GTK4)."
#: src/main.cpp:221
msgid "Export configuration"
msgstr "Exportar configuración"
#: src/main.cpp:225
msgid "_Save"
msgstr "_Guardar"
#: src/main.cpp:233
msgid "Import configuration"
msgstr "Importar configuración"
#: src/main.cpp:269
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
#: src/main.cpp:277
msgid "Export configuration..."
msgstr "Exportar configuración..."
#: src/main.cpp:278
msgid "Import configuration..."
msgstr "Importar configuración..."
#: src/main.cpp:281
msgid "About"
msgstr "Acerca de"
#: src/main.cpp:305
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "Reasigna las G1 a G9 del Logitech G910 a teclas o comandos"
#: src/main.cpp:327
msgid "G-Key"
msgstr "Tecla G"
#: src/main.cpp:327
msgid "Type"
msgstr "Tipo"
#: src/main.cpp:327
msgid "Target (Key/Command)"
msgstr "Destino (Tecla/Comando)"
#: src/main.cpp:327
msgid "Action"
msgstr "Acción"
#: src/main.cpp:335
msgid "Key"
msgstr "Tecla"
#: src/main.cpp:335
msgid "Command"
msgstr "Comando"
#: src/main.cpp:335
msgid "Disabled"
msgstr "Desactivado"
#: src/main.cpp:358
msgid "e.g. ctrl+c or ls -la"
msgstr "p. ej. ctrl+c o ls -la"
#: src/main.cpp:378
msgid "Save mappings"
msgstr "Guardar asignaciones"
#: src/main.cpp:383
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Guardado en ~/.config/gKeyMapper/mappings.conf"

100
po/es.po~
View File

@@ -1,100 +0,0 @@
# Spanish translation for gkeymapper.
#
msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:00+0000\n"
"PO-Revision-Date: 2026-10-16 21:00+0000\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "<Press a key...>"
msgstr "<Pulsa una tecla...>"
#, c-format
msgid "Could not save %s.\n"
msgstr "No se pudo guardar %s.\n"
msgid "Mappings saved to "
msgstr "Asignaciones guardadas en "
#, c-format
msgid "Could not open %s.\n"
msgstr "No se pudo abrir %s.\n"
msgid "Could not export mappings.\n"
msgstr "No se pudieron exportar las asignaciones.\n"
msgid "gKeyMapper"
msgstr "gKeyMapper"
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "Asignador de teclas G para el Logitech G910 (GTK4)."
msgid "Export configuration"
msgstr "Exportar configuración"
msgid "_Cancel"
msgstr "_Cancelar"
msgid "_Save"
msgstr "_Guardar"
msgid "Import configuration"
msgstr "Importar configuración"
msgid "_Open"
msgstr "_Abrir"
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
msgid "Export configuration..."
msgstr "Exportar configuración..."
msgid "Import configuration..."
msgstr "Importar configuración..."
msgid "About"
msgstr "Acerca de"
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "Reasigna las G1 a G9 del Logitech G910 a teclas o comandos"
msgid "G-Key"
msgstr "Tecla G"
msgid "Type"
msgstr "Tipo"
msgid "Target (Key/Command)"
msgstr "Destino (Tecla/Comando)"
msgid "Action"
msgstr "Acción"
msgid "Key"
msgstr "Tecla"
msgid "Command"
msgstr "Comando"
msgid "Disabled"
msgstr "Desactivado"
msgid "e.g. ctrl+c or ls -la"
msgstr "p. ej. ctrl+c o ls -la"
msgid "Capture"
msgstr "Capturar"
msgid "Save mappings"
msgstr "Guardar asignaciones"
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Guardado en ~/.config/gKeyMapper/mappings.conf"

View File

@@ -4,8 +4,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:53+0200\n"
"PO-Revision-Date: 2026-10-16 21:00+0000\n"
"POT-Creation-Date: 2026-10-16 22:12+0200\n"
"PO-Revision-Date: 2026-10-16 22:12+0200\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr\n"
@@ -13,126 +13,117 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/main.cpp:53
msgid "Choose file"
msgstr "Choisir un fichier"
#: src/main.cpp:56 src/main.cpp:224 src/main.cpp:236
msgid "_Cancel"
msgstr "_Annuler"
#: src/main.cpp:57 src/main.cpp:237
msgid "_Open"
msgstr "_Ouvrir"
#: src/main.cpp:75
msgid "<Press a key...>"
msgstr "<Appuyez sur une touche...>"
msgid "No keys recorded yet"
msgstr "Aucune touche enregistrée pour le moment"
msgid "Capture key sequence"
msgstr "Capturer une séquence de touches"
msgid "Clear"
msgstr "Effacer"
msgid "Cancel"
msgstr "Annuler"
msgid "Done"
msgstr "Terminer"
msgid ""
"Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded "
"too). Click Done to apply or Cancel to discard."
msgstr ""
"Appuyez sur les touches dans l'ordre (les modificateurs comme Ctrl, Alt, "
"Maj et Super sont également enregistrés). Cliquez sur Terminer pour "
"appliquer ou sur Annuler pour abandonner."
#: src/main.cpp:87
msgid "Choose file..."
msgstr "Choisir un fichier..."
#: src/main.cpp:89 src/main.cpp:364
msgid "e.g. /usr/bin/firefox or custom command"
msgstr "ex. /usr/bin/firefox ou commande personnalisée"
msgid "Capture"
msgstr "Capturer"
#: src/main.cpp:125
msgid "e.g. ctrl+c, ctrl+v"
msgstr "ex. ctrl+c, ctrl+v"
#, c-format
msgid "Could not save %s.\n"
msgstr "Impossible d'enregistrer %s.\n"
#: src/main.cpp:129
msgid "Mappings saved to "
msgstr "Attributions enregistrées dans "
#: src/main.cpp:135
#, c-format
msgid "Could not open %s.\n"
msgstr "Impossible d'ouvrir %s.\n"
#: src/main.cpp:187
msgid "Could not export mappings.\n"
msgstr "Impossible d'exporter les attributions.\n"
#: src/main.cpp:210 src/main.cpp:273
msgid "gKeyMapper"
msgstr "gKeyMapper"
#: src/main.cpp:213
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "Assignateur de touches G pour le Logitech G910 (GTK4)."
#: src/main.cpp:221
msgid "Export configuration"
msgstr "Exporter la configuration"
#: src/main.cpp:225
msgid "_Save"
msgstr "_Enregistrer"
#: src/main.cpp:233
msgid "Import configuration"
msgstr "Importer la configuration"
#: src/main.cpp:269
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
#: src/main.cpp:277
msgid "Export configuration..."
msgstr "Exporter la configuration..."
#: src/main.cpp:278
msgid "Import configuration..."
msgstr "Importer la configuration..."
#: src/main.cpp:281
msgid "About"
msgstr "À propos"
#: src/main.cpp:305
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr ""
"Réassignation des touches G1 à G9 du Logitech G910 vers des touches ou des "
"commandes"
msgstr "Réassignation des touches G1 à G9 du Logitech G910 vers des touches ou des commandes"
#: src/main.cpp:327
msgid "G-Key"
msgstr "Touche G"
#: src/main.cpp:327
msgid "Type"
msgstr "Type"
#: src/main.cpp:327
msgid "Target (Key/Command)"
msgstr "Cible (Touche/Commande)"
#: src/main.cpp:327
msgid "Action"
msgstr "Action"
#: src/main.cpp:335
msgid "Key"
msgstr "Touche"
#: src/main.cpp:335
msgid "Command"
msgstr "Commande"
#: src/main.cpp:335
msgid "Disabled"
msgstr "Désactivé"
#: src/main.cpp:358
msgid "e.g. ctrl+c or ls -la"
msgstr "ex. ctrl+c ou ls -la"
#: src/main.cpp:378
msgid "Save mappings"
msgstr "Enregistrer les attributions"
#: src/main.cpp:383
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Enregistré dans ~/.config/gKeyMapper/mappings.conf"

100
po/fr.po~
View File

@@ -1,100 +0,0 @@
# French translation for gkeymapper.
#
msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:00+0000\n"
"PO-Revision-Date: 2026-10-16 21:00+0000\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "<Press a key...>"
msgstr "<Appuyez sur une touche...>"
#, c-format
msgid "Could not save %s.\n"
msgstr "Impossible d'enregistrer %s.\n"
msgid "Mappings saved to "
msgstr "Attributions enregistrées dans "
#, c-format
msgid "Could not open %s.\n"
msgstr "Impossible d'ouvrir %s.\n"
msgid "Could not export mappings.\n"
msgstr "Impossible d'exporter les attributions.\n"
msgid "gKeyMapper"
msgstr "gKeyMapper"
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "Assignateur de touches G pour le Logitech G910 (GTK4)."
msgid "Export configuration"
msgstr "Exporter la configuration"
msgid "_Cancel"
msgstr "_Annuler"
msgid "_Save"
msgstr "_Enregistrer"
msgid "Import configuration"
msgstr "Importer la configuration"
msgid "_Open"
msgstr "_Ouvrir"
msgid "gKeyMapper – Logitech G910"
msgstr "gKeyMapper – Logitech G910"
msgid "Export configuration..."
msgstr "Exporter la configuration..."
msgid "Import configuration..."
msgstr "Importer la configuration..."
msgid "About"
msgstr "À propos"
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "Réassignation des touches G1 à G9 du Logitech G910 vers des touches ou des commandes"
msgid "G-Key"
msgstr "Touche G"
msgid "Type"
msgstr "Type"
msgid "Target (Key/Command)"
msgstr "Cible (Touche/Commande)"
msgid "Action"
msgstr "Action"
msgid "Key"
msgstr "Touche"
msgid "Command"
msgstr "Commande"
msgid "Disabled"
msgstr "Désactivé"
msgid "e.g. ctrl+c or ls -la"
msgstr "ex. ctrl+c ou ls -la"
msgid "Capture"
msgstr "Capturer"
msgid "Save mappings"
msgstr "Enregistrer les attributions"
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "Enregistré dans ~/.config/gKeyMapper/mappings.conf"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:53+0200\n"
"POT-Creation-Date: 2026-10-16 22:12+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,124 +17,150 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/main.cpp:53
#: src/main.cpp:55
msgid "Choose file"
msgstr ""
#: src/main.cpp:56 src/main.cpp:224 src/main.cpp:236
#: src/main.cpp:58 src/main.cpp:419 src/main.cpp:431
msgid "_Cancel"
msgstr ""
#: src/main.cpp:57 src/main.cpp:237
#: src/main.cpp:59 src/main.cpp:432
msgid "_Open"
msgstr ""
#: src/main.cpp:75
msgid "<Press a key...>"
#: src/main.cpp:129 src/main.cpp:218
msgid "No keys recorded yet"
msgstr ""
#: src/main.cpp:87
#: src/main.cpp:187
msgid "Capture key sequence"
msgstr ""
#: src/main.cpp:191
msgid "Clear"
msgstr ""
#: src/main.cpp:192
msgid "Cancel"
msgstr ""
#: src/main.cpp:193
msgid "Done"
msgstr ""
#: src/main.cpp:213
msgid ""
"Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded "
"too). Click Done to apply or Cancel to discard."
msgstr ""
#: src/main.cpp:263
msgid "Choose file..."
msgstr ""
#: src/main.cpp:89 src/main.cpp:364
#: src/main.cpp:265
msgid "e.g. /usr/bin/firefox or custom command"
msgstr ""
#: src/main.cpp:267 src/main.cpp:586
msgid "Capture"
msgstr ""
#: src/main.cpp:125
#: src/main.cpp:269 src/main.cpp:579
msgid "e.g. ctrl+c, ctrl+v"
msgstr ""
#: src/main.cpp:306
#, c-format
msgid "Could not save %s.\n"
msgstr ""
#: src/main.cpp:129
#: src/main.cpp:310
msgid "Mappings saved to "
msgstr ""
#: src/main.cpp:135
#: src/main.cpp:316
#, c-format
msgid "Could not open %s.\n"
msgstr ""
#: src/main.cpp:187
#: src/main.cpp:368
msgid "Could not export mappings.\n"
msgstr ""
#: src/main.cpp:210 src/main.cpp:273
#: src/main.cpp:393 src/main.cpp:499
msgid "gKeyMapper"
msgstr ""
#: src/main.cpp:213
#: src/main.cpp:396
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr ""
#: src/main.cpp:221
#: src/main.cpp:416
msgid "Export configuration"
msgstr ""
#: src/main.cpp:225
#: src/main.cpp:420
msgid "_Save"
msgstr ""
#: src/main.cpp:233
#: src/main.cpp:428
msgid "Import configuration"
msgstr ""
#: src/main.cpp:269
#: src/main.cpp:488
msgid "gKeyMapper – Logitech G910"
msgstr ""
#: src/main.cpp:277
#: src/main.cpp:503
msgid "Export configuration..."
msgstr ""
#: src/main.cpp:278
#: src/main.cpp:504
msgid "Import configuration..."
msgstr ""
#: src/main.cpp:281
#: src/main.cpp:507
msgid "About"
msgstr ""
#: src/main.cpp:305
#: src/main.cpp:531
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr ""
#: src/main.cpp:327
#: src/main.cpp:548
msgid "G-Key"
msgstr ""
#: src/main.cpp:327
#: src/main.cpp:548
msgid "Type"
msgstr ""
#: src/main.cpp:327
#: src/main.cpp:548
msgid "Target (Key/Command)"
msgstr ""
#: src/main.cpp:327
#: src/main.cpp:548
msgid "Action"
msgstr ""
#: src/main.cpp:335
#: src/main.cpp:556
msgid "Key"
msgstr ""
#: src/main.cpp:335
#: src/main.cpp:556
msgid "Command"
msgstr ""
#: src/main.cpp:335
#: src/main.cpp:556
msgid "Disabled"
msgstr ""
#: src/main.cpp:358
msgid "e.g. ctrl+c or ls -la"
msgstr ""
#: src/main.cpp:378
#: src/main.cpp:603
msgid "Save mappings"
msgstr ""
#: src/main.cpp:383
#: src/main.cpp:608
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr ""

View File

@@ -4,10 +4,12 @@
#include <gtk/gtk.h>
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <locale.h>
#include <libintl.h>
#include <set>
#include <string>
#include <vector>
@@ -60,20 +62,227 @@ static void pick_command_file(KeyRow* row, GtkWindow* parent) {
gtk_window_present(GTK_WINDOW(dialog));
}
struct CaptureSession {
KeyRow* row = nullptr;
GtkWidget* displayLabel = nullptr;
std::vector<std::string> steps;
std::set<guint> heldKeys; // non-modifier keyvals currently held (ignores auto-repeat)
};
static bool is_modifier_keyval(guint keyval) {
switch (keyval) {
case GDK_KEY_Control_L:
case GDK_KEY_Control_R:
case GDK_KEY_Alt_L:
case GDK_KEY_Alt_R:
case GDK_KEY_Shift_L:
case GDK_KEY_Shift_R:
case GDK_KEY_Super_L:
case GDK_KEY_Super_R:
case GDK_KEY_Meta_L:
case GDK_KEY_Meta_R:
case GDK_KEY_ISO_Level3_Shift: // AltGr
return true;
default:
return false;
}
}
// Format one step like the reference project: modifiers joined with '+',
// e.g. "ctrl+shift+t". Multiple steps are joined with ',' ("ctrl+c,ctrl+v").
static std::string format_capture_step(GdkModifierType state, guint keyval) {
std::string step;
if (state & GDK_CONTROL_MASK) {
step += "ctrl+";
}
if (state & GDK_ALT_MASK) {
step += "alt+";
}
if (state & GDK_SHIFT_MASK) {
step += "shift+";
}
if (state & (GDK_SUPER_MASK | GDK_META_MASK)) {
step += "super+";
}
const char* name = gdk_keyval_name(keyval);
std::string key = name ? name : "?";
std::transform(key.begin(), key.end(), key.begin(),
[](unsigned char c) { return std::tolower(c); });
step += key;
return step;
}
static std::string join_capture_steps(const std::vector<std::string>& steps) {
std::string out;
for (size_t i = 0; i < steps.size(); ++i) {
if (i > 0) {
out += ",";
}
out += steps[i];
}
return out;
}
static void update_capture_display(CaptureSession* session) {
std::string text = join_capture_steps(session->steps);
if (text.empty()) {
text = _("No keys recorded yet");
}
gtk_label_set_text(GTK_LABEL(session->displayLabel), text.c_str());
}
static gboolean on_capture_key_pressed(GtkEventControllerKey* /*controller*/,
guint keyval, guint /*keycode*/,
GdkModifierType state, gpointer userData) {
auto* session = static_cast<CaptureSession*>(userData);
if (is_modifier_keyval(keyval)) {
return TRUE; // modifiers are recorded as part of the next real key
}
if (session->heldKeys.count(keyval)) {
return TRUE; // ignore auto-repeat while the key is held down
}
session->heldKeys.insert(keyval);
constexpr GdkModifierType kModMask = static_cast<GdkModifierType>(
GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_ALT_MASK |
GDK_SUPER_MASK | GDK_META_MASK);
session->steps.push_back(format_capture_step(
static_cast<GdkModifierType>(state & kModMask), keyval));
update_capture_display(session);
return TRUE;
}
static gboolean on_capture_key_released(GtkEventControllerKey* /*controller*/,
guint keyval, guint /*keycode*/,
GdkModifierType /*state*/, gpointer userData) {
auto* session = static_cast<CaptureSession*>(userData);
session->heldKeys.erase(keyval);
return TRUE;
}
static void on_capture_session_destroy(GtkWidget* /*widget*/, gpointer userData) {
delete static_cast<CaptureSession*>(userData);
}
static void on_capture_done_clicked(GtkButton* btn, gpointer userData) {
auto* session = static_cast<CaptureSession*>(userData);
if (!session->steps.empty()) {
gtk_editable_set_text(GTK_EDITABLE(session->row->targetEntry),
join_capture_steps(session->steps).c_str());
}
GtkWindow* window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(btn)));
gtk_window_destroy(window); // triggers session cleanup via "destroy"
}
static void on_capture_cancel_clicked(GtkButton* btn, gpointer /*userData*/) {
GtkWindow* window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(btn)));
gtk_window_destroy(window); // triggers session cleanup via "destroy"
}
static void on_capture_clear_clicked(GtkButton* /*btn*/, gpointer userData) {
auto* session = static_cast<CaptureSession*>(userData);
session->steps.clear();
session->heldKeys.clear();
update_capture_display(session);
}
static void open_key_capture_dialog(KeyRow* row, GtkWindow* parent) {
auto* session = new CaptureSession();
session->row = row;
GtkWidget* dialog = gtk_window_new();
gtk_window_set_title(GTK_WINDOW(dialog), _("Capture key sequence"));
gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
gtk_window_set_default_size(GTK_WINDOW(dialog), 540, 300);
g_signal_connect(dialog, "destroy", G_CALLBACK(on_capture_session_destroy), session);
GtkWidget* outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
gtk_widget_set_margin_top(outer, 20);
gtk_widget_set_margin_bottom(outer, 20);
gtk_widget_set_margin_start(outer, 20);
gtk_widget_set_margin_end(outer, 20);
gtk_window_set_child(GTK_WINDOW(dialog), outer);
GtkWidget* title = gtk_label_new(nullptr);
gtk_label_set_markup(GTK_LABEL(title),
g_strdup_printf("<span size='large' weight='bold'>%s</span>",
_("Capture key sequence")));
gtk_label_set_xalign(GTK_LABEL(title), 0.0f);
gtk_box_append(GTK_BOX(outer), title);
GtkWidget* info = gtk_label_new(_("Press keys in order (modifiers like Ctrl, Alt, Shift and Super are recorded too). Click Done to apply or Cancel to discard."));
gtk_label_set_wrap(GTK_LABEL(info), TRUE);
gtk_label_set_xalign(GTK_LABEL(info), 0.0f);
gtk_widget_add_css_class(info, "dim-label");
gtk_box_append(GTK_BOX(outer), info);
session->displayLabel = gtk_label_new(_("No keys recorded yet"));
gtk_label_set_xalign(GTK_LABEL(session->displayLabel), 0.0f);
gtk_label_set_ellipsize(GTK_LABEL(session->displayLabel), PANGO_ELLIPSIZE_MIDDLE);
gtk_widget_add_css_class(session->displayLabel, "monospace");
gtk_widget_set_margin_top(session->displayLabel, 12);
gtk_widget_set_margin_bottom(session->displayLabel, 12);
gtk_widget_set_margin_start(session->displayLabel, 12);
gtk_widget_set_margin_end(session->displayLabel, 12);
GtkWidget* frame = gtk_frame_new(nullptr);
gtk_frame_set_child(GTK_FRAME(frame), session->displayLabel);
gtk_widget_set_size_request(frame, -1, 64);
gtk_widget_set_vexpand(frame, TRUE);
gtk_box_append(GTK_BOX(outer), frame);
gtk_box_append(GTK_BOX(outer), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
// Buttons: Clear on the left, Cancel/Done grouped on the right.
GtkWidget* buttonRow = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_append(GTK_BOX(outer), buttonRow);
GtkWidget* clearBtn = gtk_button_new_with_label(_("Clear"));
gtk_widget_set_size_request(clearBtn, 110, -1);
g_signal_connect(clearBtn, "clicked", G_CALLBACK(on_capture_clear_clicked), session);
gtk_box_append(GTK_BOX(buttonRow), clearBtn);
GtkWidget* spacer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_hexpand(spacer, TRUE);
gtk_box_append(GTK_BOX(buttonRow), spacer);
GtkWidget* endBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_box_append(GTK_BOX(buttonRow), endBox);
GtkWidget* cancelBtn = gtk_button_new_with_label(_("Cancel"));
gtk_widget_set_size_request(cancelBtn, 110, -1);
g_signal_connect(cancelBtn, "clicked", G_CALLBACK(on_capture_cancel_clicked), nullptr);
gtk_box_append(GTK_BOX(endBox), cancelBtn);
GtkWidget* doneBtn = gtk_button_new_with_label(_("Done"));
gtk_widget_set_size_request(doneBtn, 110, -1);
g_signal_connect(doneBtn, "clicked", G_CALLBACK(on_capture_done_clicked), session);
gtk_box_append(GTK_BOX(endBox), doneBtn);
// Key events go to the dialog; buttons must not steal Space/Return.
GtkEventController* keyCtrl = gtk_event_controller_key_new();
g_signal_connect(keyCtrl, "key-pressed", G_CALLBACK(on_capture_key_pressed), session);
g_signal_connect(keyCtrl, "key-released", G_CALLBACK(on_capture_key_released), session);
gtk_widget_add_controller(dialog, keyCtrl);
gtk_widget_set_focusable(clearBtn, FALSE);
gtk_widget_set_focusable(cancelBtn, FALSE);
gtk_widget_set_focusable(doneBtn, FALSE);
gtk_window_present(GTK_WINDOW(dialog));
}
void on_capture_clicked(GtkButton* btn, gpointer userData) {
auto* row = static_cast<KeyRow*>(userData);
guint sel = gtk_drop_down_get_selected(GTK_DROP_DOWN(row->typeDrop));
GtkWindow* window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(btn)));
if (sel == 1) {
GtkWindow* window = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(btn)));
pick_command_file(row, window);
return;
}
if (sel == 0) {
// Placeholder: real key capture will be added in a later version.
gtk_editable_set_text(GTK_EDITABLE(row->targetEntry), _("<Press a key...>"));
std::cout << "G" << (row->index + 1) << ": waiting for key...\n";
open_key_capture_dialog(row, window);
}
}
@@ -85,8 +294,12 @@ static void update_row_state(KeyRow* row) {
if (sel == 1) {
gtk_button_set_label(GTK_BUTTON(row->captureButton), _("Choose file..."));
gtk_entry_set_placeholder_text(GTK_ENTRY(row->targetEntry),
_("e.g. /usr/bin/firefox or custom command"));
} else {
gtk_button_set_label(GTK_BUTTON(row->captureButton), _("Capture"));
gtk_entry_set_placeholder_text(GTK_ENTRY(row->targetEntry),
_("e.g. ctrl+c, ctrl+v"));
}
gtk_widget_set_size_request(row->captureButton, 180, -1);
}
@@ -215,7 +428,7 @@ static void show_about_dialog(GtkWindow* parent) {
gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog),
_("G-Key mapper for the Logitech G910 (GTK4)."));
gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), "https://rager.dev");
gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(dialog), "rager.dev");
gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(dialog), "Thomas Rager - rager.dev");
std::string iconPath = find_asset("icon.png");
GError* error = nullptr;
@@ -396,7 +609,7 @@ void build_ui(GtkApplication* app) {
// Target input (wide: commands and file paths need room)
GtkWidget* entry = gtk_entry_new();
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), _("e.g. ctrl+c or ls -la"));
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), _("e.g. ctrl+c, ctrl+v"));
gtk_widget_set_size_request(entry, 440, -1);
gtk_widget_set_hexpand(entry, TRUE);
gtk_grid_attach(GTK_GRID(grid), entry, 2, i + 1, 1, 1);
@@ -421,7 +634,9 @@ void build_ui(GtkApplication* app) {
gtk_box_append(GTK_BOX(outer), footer);
GtkWidget* saveBtn = gtk_button_new_with_label(_("Save mappings"));
gtk_widget_add_css_class(saveBtn, "suggested-action");
// No "suggested-action" class on purpose: it gets a strong accent color
// (e.g. Mint-Y green) that clashes with the rest of the layout. The plain
// button follows the system theme like everything else.
g_signal_connect(saveBtn, "clicked", G_CALLBACK(on_save_clicked), nullptr);
gtk_box_append(GTK_BOX(footer), saveBtn);