Working ui demo

This commit is contained in:
2026-10-16 22:11:52 +02:00
commit d157d9d30d
15 changed files with 1592 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
build/
locale/
gkeymapper
*.o
*.so

49
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,49 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "make: build",
"type": "shell",
"command": "make",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": "$gcc"
},
{
"label": "make: clean",
"type": "shell",
"command": "make clean",
"problemMatcher": []
},
{
"label": "make: translations",
"type": "shell",
"command": "make translations",
"problemMatcher": []
},
{
"label": "make: run",
"type": "shell",
"command": "make run",
"dependsOn": [
"make: build",
"make: translations"
],
"problemMatcher": []
},
{
"label": "make: install",
"type": "shell",
"command": "make install",
"problemMatcher": []
},
{
"label": "make: clean build",
"type": "shell",
"command": "make clean && make",
"problemMatcher": "$gcc"
}
]
}

65
Makefile Normal file
View File

@@ -0,0 +1,65 @@
# gKeyMapper - Build
# Dependencies: g++, pkg-config, msgfmt, libgtk-4-dev
# Installation: sudo apt install build-essential gettext libgtk-4-dev
CXX ?= g++
CXXFLAGS ?= -std=c++17 -O2 -Wall -Wextra -Wpedantic
PKG_CONFIG ?= pkg-config
MSGFMT ?= msgfmt
GTK_CFLAGS := $(shell $(PKG_CONFIG) --cflags gtk4)
GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4)
PACKAGE := gkeymapper
TARGET := gkeymapper
SRC_DIR := src
SOURCES := $(SRC_DIR)/main.cpp
PREFIX ?= /usr/local
LOCALEDIR := $(PREFIX)/share/locale
ASSETS_DIR := $(PREFIX)/share/$(PACKAGE)/assets
CXXFLAGS += -DPACKAGE=\"$(PACKAGE)\" -DLOCALEDIR=\"$(LOCALEDIR)\" -DASSETS_DIR=\"$(ASSETS_DIR)\"
# Translations
PO_FILES := $(wildcard po/*.po)
LANGS := $(patsubst po/%.po,%,$(PO_FILES))
MO_FILES := $(patsubst %,locale/%/LC_MESSAGES/$(PACKAGE).mo,$(LANGS))
.PHONY: all clean run install translations
all: $(TARGET) translations
$(TARGET): $(SOURCES)
$(CXX) $(CXXFLAGS) $(GTK_CFLAGS) -o $@ $(SOURCES) $(GTK_LIBS)
translations: $(MO_FILES)
locale/%/LC_MESSAGES/$(PACKAGE).mo: po/%.po
@mkdir -p $(@D)
$(MSGFMT) -o $@ $<
run: $(TARGET) translations
GKEYMAPPER_LOCALEDIR=$(CURDIR)/locale GKEYMAPPER_ASSETS_DIR=$(CURDIR)/assets ./$(TARGET)
clean:
rm -f $(TARGET)
rm -rf locale
install: $(TARGET) translations
install -d $(DESTDIR)$(PREFIX)/bin
install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(TARGET)
install -d $(DESTDIR)$(ASSETS_DIR)
install -m 644 assets/* $(DESTDIR)$(ASSETS_DIR)/
install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps
install -m 644 assets/icon.png $(DESTDIR)$(PREFIX)/share/icons/hicolor/256x256/apps/$(PACKAGE).png
install -d $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps
install -m 644 assets/icon.svg $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/$(PACKAGE).svg
-command -v gtk-update-icon-cache >/dev/null && gtk-update-icon-cache -q -t $(DESTDIR)$(PREFIX)/share/icons/hicolor 2>/dev/null || true
install -d $(DESTDIR)$(PREFIX)/share/applications
install -m 644 data/gkeymapper.desktop $(DESTDIR)$(PREFIX)/share/applications/$(PACKAGE).desktop
-command -v update-desktop-database >/dev/null && update-desktop-database $(DESTDIR)$(PREFIX)/share/applications 2>/dev/null || true
for lang in $(LANGS); do \
install -d $(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES; \
install -m 644 locale/$$lang/LC_MESSAGES/$(PACKAGE).mo \
$(DESTDIR)$(LOCALEDIR)/$$lang/LC_MESSAGES/$(PACKAGE).mo; \
done

124
README.md Normal file
View File

@@ -0,0 +1,124 @@
# gKeyMapper
GTK4 application (C++) for remapping the G keys **G1–G9** of a Logitech **G910**
to other keys or commands (Linux Mint / Ubuntu).
The current focus is on the **GUI** only (no actual key capture/command execution yet).
Documentation and source comments are written in English.
## Requirements
- Linux Mint / Ubuntu
- g++ (C++17)
- pkg-config
- gettext (for translations, `msgfmt`)
- GTK 4 development files
### Install dependencies
```bash
sudo apt update
sudo apt install -y build-essential gettext libgtk-4-dev
```
Afterwards `pkg-config --modversion gtk4` should print a version (e.g. `4.6.x`).
## Setting up the project
Change to the project directory:
```bash
cd /home/trager/Nextcloud/Projekte/C++/gKeyMapper
```
(Alternatively clone the repository or copy the project folder to your own location.)
## Build
```bash
make
```
This produces the `gkeymapper` binary and the `.mo` translations under `locale/`.
## Rebuild translations
```bash
make translations
```
## Run
Normal:
```bash
./gkeymapper
```
With explicit language files (useful for testing from the build directory):
```bash
LANG=de_DE.UTF-8 GKEYMAPPER_LOCALEDIR=$PWD/locale ./gkeymapper
```
Supported languages: German, English, Spanish, French.
## Install (optional)
```bash
sudo make install
```
Installs `gkeymapper` to `/usr/local/bin` and the `.mo` files to
`/usr/local/share/locale`.
## Clean
```bash
make clean
```
Removes the binary and the generated `locale/` directory.
## Project structure
```
src/main.cpp # GTK4 GUI
po/ # Translations (pot/po/mo targets)
Makefile # Build via pkg-config + gettext
.vscode/tasks.json # VS Code tasks for make
README.md
```
## Troubleshooting
### `Package gtk4 was not found`
The GTK 4 development files are missing. Install them:
```bash
sudo apt install -y libgtk-4-dev
```
Then verify:
```bash
pkg-config --modversion gtk4
```
### `pkg-config` cannot find `.pc` files (wrong search path)
In some environments `PKG_CONFIG_PATH` must be set explicitly. Standard path on
Ubuntu/Mint:
```bash
export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig:/usr/lib/pkgconfig
```
## Planned next steps
- Real capture of key presses (evdev/uinput or libinput).
- Execution of commands when a G key is pressed.
- Loading and saving profiles.
- Autostart service / background daemon.

BIN
assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

13
assets/icon.svg Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" viewBox="0 0 2000 2000" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g id="Logo" transform="matrix(3.86383,0,0,3.86383,-2875.29,-2663.36)">
<g>
<g transform="matrix(1,0,0,1,198.741,171.259)">
<path d="M799,699L736,569L632,569L680,669L570,669L680,949L789,949L679,698L799,699Z" style="fill:#00ff66;"/>
</g>
<g id="Logo-Komponent" transform="matrix(-1,0,0,1,1807.19,171.259)">
<path d="M799,699L736,569L632,569L680,669L570,669L680,949L789,949L679,698L799,699Z" style="fill:#00ff66;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 832 B

11
data/gkeymapper.desktop Normal file
View File

@@ -0,0 +1,11 @@
[Desktop Entry]
Type=Application
Name=gKeyMapper
GenericName=G-Key Mapper
Comment=Remap the G keys (G1-G9) of a Logitech G910
Exec=gkeymapper
Icon=gkeymapper
Terminal=false
Categories=Utility;
StartupNotify=true
StartupWMClass=gkeymapper

138
po/de.po Normal file
View File

@@ -0,0 +1,138 @@
# 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:53+0200\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"
#: 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...>"
#: src/main.cpp:87
msgid "Choose file..."
msgstr "Datei auswählen..."
#: src/main.cpp:89 src/main.cpp:364
msgid "Capture"
msgstr "Aufnehmen"
#: src/main.cpp:125
#, 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~ Normal file
View File

@@ -0,0 +1,102 @@
# 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"

136
po/es.po Normal file
View File

@@ -0,0 +1,136 @@
# 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:53+0200\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"
#: 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...>"
#: src/main.cpp:87
msgid "Choose file..."
msgstr "Elegir archivo..."
#: src/main.cpp:89 src/main.cpp:364
msgid "Capture"
msgstr "Capturar"
#: src/main.cpp:125
#, 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~ Normal file
View File

@@ -0,0 +1,100 @@
# 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"

138
po/fr.po Normal file
View File

@@ -0,0 +1,138 @@
# 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:53+0200\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"
#: 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...>"
#: src/main.cpp:87
msgid "Choose file..."
msgstr "Choisir un fichier..."
#: src/main.cpp:89 src/main.cpp:364
msgid "Capture"
msgstr "Capturer"
#: src/main.cpp:125
#, 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"
#: 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~ Normal file
View File

@@ -0,0 +1,100 @@
# 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"

140
po/gkeymapper.pot Normal file
View File

@@ -0,0 +1,140 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-16 21:53+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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/main.cpp:53
msgid "Choose file"
msgstr ""
#: src/main.cpp:56 src/main.cpp:224 src/main.cpp:236
msgid "_Cancel"
msgstr ""
#: src/main.cpp:57 src/main.cpp:237
msgid "_Open"
msgstr ""
#: src/main.cpp:75
msgid "<Press a key...>"
msgstr ""
#: src/main.cpp:87
msgid "Choose file..."
msgstr ""
#: src/main.cpp:89 src/main.cpp:364
msgid "Capture"
msgstr ""
#: src/main.cpp:125
#, c-format
msgid "Could not save %s.\n"
msgstr ""
#: src/main.cpp:129
msgid "Mappings saved to "
msgstr ""
#: src/main.cpp:135
#, c-format
msgid "Could not open %s.\n"
msgstr ""
#: src/main.cpp:187
msgid "Could not export mappings.\n"
msgstr ""
#: src/main.cpp:210 src/main.cpp:273
msgid "gKeyMapper"
msgstr ""
#: src/main.cpp:213
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr ""
#: src/main.cpp:221
msgid "Export configuration"
msgstr ""
#: src/main.cpp:225
msgid "_Save"
msgstr ""
#: src/main.cpp:233
msgid "Import configuration"
msgstr ""
#: src/main.cpp:269
msgid "gKeyMapper – Logitech G910"
msgstr ""
#: src/main.cpp:277
msgid "Export configuration..."
msgstr ""
#: src/main.cpp:278
msgid "Import configuration..."
msgstr ""
#: src/main.cpp:281
msgid "About"
msgstr ""
#: src/main.cpp:305
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr ""
#: src/main.cpp:327
msgid "G-Key"
msgstr ""
#: src/main.cpp:327
msgid "Type"
msgstr ""
#: src/main.cpp:327
msgid "Target (Key/Command)"
msgstr ""
#: src/main.cpp:327
msgid "Action"
msgstr ""
#: src/main.cpp:335
msgid "Key"
msgstr ""
#: src/main.cpp:335
msgid "Command"
msgstr ""
#: src/main.cpp:335
msgid "Disabled"
msgstr ""
#: src/main.cpp:358
msgid "e.g. ctrl+c or ls -la"
msgstr ""
#: src/main.cpp:378
msgid "Save mappings"
msgstr ""
#: src/main.cpp:383
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr ""

471
src/main.cpp Normal file
View File

@@ -0,0 +1,471 @@
// gKeyMapper - GTK4 GUI for remapping the G keys (G1-G9) of the Logitech G910
// GUI only for now. Mappings are stored as a simple text file.
#include <gtk/gtk.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <locale.h>
#include <libintl.h>
#include <string>
#include <vector>
#ifndef PACKAGE
#define PACKAGE "gkeymapper"
#endif
#ifndef LOCALEDIR
#define LOCALEDIR "/usr/local/share/locale"
#endif
#define _(s) gettext(s)
#define N_(s) (s)
namespace {
constexpr int NUM_KEYS = 9;
struct KeyRow {
int index; // 0..8 -> G1..G9
GtkWidget* keyLabel = nullptr;
GtkWidget* typeDrop = nullptr;
GtkWidget* targetEntry = nullptr;
GtkWidget* captureButton = nullptr;
};
std::vector<KeyRow> rows;
static void on_pick_file_response(GtkDialog* dialog, int response, gpointer userData) {
if (response == GTK_RESPONSE_ACCEPT) {
KeyRow* row = static_cast<KeyRow*>(userData);
GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog);
g_autoptr(GFile) file = gtk_file_chooser_get_file(chooser);
char* path = g_file_get_path(file);
if (path) {
gtk_editable_set_text(GTK_EDITABLE(row->targetEntry), path);
g_free(path);
}
}
gtk_window_destroy(GTK_WINDOW(dialog));
}
static void pick_command_file(KeyRow* row, GtkWindow* parent) {
GtkWidget* dialog = gtk_file_chooser_dialog_new(_("Choose file"),
parent,
GTK_FILE_CHOOSER_ACTION_OPEN,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Open"), GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect(dialog, "response", G_CALLBACK(on_pick_file_response), row);
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));
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";
}
}
static void update_row_state(KeyRow* row) {
guint sel = gtk_drop_down_get_selected(GTK_DROP_DOWN(row->typeDrop));
gboolean enabled = (sel != 2);
gtk_widget_set_sensitive(row->targetEntry, enabled);
gtk_widget_set_sensitive(row->captureButton, enabled);
if (sel == 1) {
gtk_button_set_label(GTK_BUTTON(row->captureButton), _("Choose file..."));
} else {
gtk_button_set_label(GTK_BUTTON(row->captureButton), _("Capture"));
}
gtk_widget_set_size_request(row->captureButton, 180, -1);
}
static void on_type_changed(GObject* /*object*/, GParamSpec* /*pspec*/, gpointer userData) {
update_row_state(static_cast<KeyRow*>(userData));
}
static bool write_mappings_to_file(const std::string& path) {
std::ofstream out(path);
if (!out) {
return false;
}
out << "# gKeyMapper mappings (G1..G9)\n";
for (const auto& row : rows) {
const char* type = "disabled";
switch (gtk_drop_down_get_selected(GTK_DROP_DOWN(row.typeDrop))) {
case 0: type = "key"; break;
case 1: type = "command"; break;
default: type = "disabled"; break;
}
const char* target = gtk_editable_get_text(GTK_EDITABLE(row.targetEntry));
out << "G" << (row.index + 1) << "=" << type << ":" << (target ? target : "") << "\n";
}
out.close();
return true;
}
void on_save_clicked(GtkButton* /*btn*/, gpointer /*userData*/) {
const char* home = g_get_home_dir();
std::string dir = std::string(home) + "/.config/gKeyMapper";
g_mkdir_with_parents(dir.c_str(), 0755);
std::string path = dir + "/mappings.conf";
if (!write_mappings_to_file(path)) {
g_printerr(_("Could not save %s.\n"), path.c_str());
return;
}
std::cout << _("Mappings saved to ") << path << "\n";
}
static void import_mappings_from_file(const std::string& path) {
std::ifstream in(path);
if (!in) {
g_printerr(_("Could not open %s.\n"), path.c_str());
return;
}
std::string line;
while (std::getline(in, line)) {
if (line.empty() || line[0] == '#') {
continue;
}
auto eq = line.find('=');
if (eq == std::string::npos) {
continue;
}
std::string key = line.substr(0, eq);
if (key.size() < 2 || key[0] != 'G') {
continue;
}
int n = 0;
try {
n = std::stoi(key.substr(1)) - 1;
} catch (...) {
continue;
}
if (n < 0 || n >= (int)rows.size()) {
continue;
}
std::string rest = line.substr(eq + 1);
auto col = rest.find(':');
std::string typeToken = (col == std::string::npos) ? rest : rest.substr(0, col);
std::string target = (col == std::string::npos) ? std::string() : rest.substr(col + 1);
guint idx = 2;
if (typeToken == "key") {
idx = 0;
} else if (typeToken == "command") {
idx = 1;
} else if (typeToken == "disabled") {
idx = 2;
}
gtk_drop_down_set_selected(GTK_DROP_DOWN(rows[n].typeDrop), idx);
gtk_editable_set_text(GTK_EDITABLE(rows[n].targetEntry), target.c_str());
}
}
static void on_export_response(GtkDialog* dialog, int response, gpointer /*userData*/) {
if (response == GTK_RESPONSE_ACCEPT) {
GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog);
g_autoptr(GFile) file = gtk_file_chooser_get_file(chooser);
char* path = g_file_get_path(file);
if (path) {
if (!write_mappings_to_file(path)) {
g_printerr(_("Could not export mappings.\n"));
}
g_free(path);
}
}
gtk_window_destroy(GTK_WINDOW(dialog));
}
static void on_import_response(GtkDialog* dialog, int response, gpointer /*userData*/) {
if (response == GTK_RESPONSE_ACCEPT) {
GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog);
g_autoptr(GFile) file = gtk_file_chooser_get_file(chooser);
char* path = g_file_get_path(file);
if (path) {
import_mappings_from_file(path);
g_free(path);
}
}
gtk_window_destroy(GTK_WINDOW(dialog));
}
static std::string find_asset(const std::string& name);
static void show_about_dialog(GtkWindow* parent) {
GtkWidget* dialog = gtk_about_dialog_new();
gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(dialog), _("gKeyMapper"));
gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "0.1.0");
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");
std::string iconPath = find_asset("icon.png");
GError* error = nullptr;
GdkTexture* texture = gdk_texture_new_from_filename(iconPath.c_str(), &error);
if (texture) {
gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), GDK_PAINTABLE(texture));
g_object_unref(texture);
} else if (error) {
g_printerr("Could not load icon: %s\n", error->message);
g_error_free(error);
}
gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
g_signal_connect(dialog, "response", G_CALLBACK(gtk_window_destroy), NULL);
gtk_window_present(GTK_WINDOW(dialog));
}
static void do_export(GtkWindow* parent) {
GtkWidget* dialog = gtk_file_chooser_dialog_new(_("Export configuration"),
parent,
GTK_FILE_CHOOSER_ACTION_SAVE,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Save"), GTK_RESPONSE_ACCEPT,
NULL);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "gkeymapper.conf");
g_signal_connect(dialog, "response", G_CALLBACK(on_export_response), NULL);
gtk_window_present(GTK_WINDOW(dialog));
}
static void do_import(GtkWindow* parent) {
GtkWidget* dialog = gtk_file_chooser_dialog_new(_("Import configuration"),
parent,
GTK_FILE_CHOOSER_ACTION_OPEN,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Open"), GTK_RESPONSE_ACCEPT,
NULL);
g_signal_connect(dialog, "response", G_CALLBACK(on_import_response), NULL);
gtk_window_present(GTK_WINDOW(dialog));
}
static void action_export(GSimpleAction* /*action*/, GVariant* /*parameter*/, gpointer userData) {
GtkApplication* app = GTK_APPLICATION(userData);
GtkWindow* window = gtk_application_get_active_window(app);
if (window) {
do_export(window);
}
}
static void action_import(GSimpleAction* /*action*/, GVariant* /*parameter*/, gpointer userData) {
GtkApplication* app = GTK_APPLICATION(userData);
GtkWindow* window = gtk_application_get_active_window(app);
if (window) {
do_import(window);
}
}
static void action_about(GSimpleAction* /*action*/, GVariant* /*parameter*/, gpointer userData) {
GtkApplication* app = GTK_APPLICATION(userData);
GtkWindow* window = gtk_application_get_active_window(app);
if (window) {
show_about_dialog(window);
}
}
static std::string find_asset(const std::string& name) {
const char* env = g_getenv("GKEYMAPPER_ASSETS_DIR");
if (env && *env) {
std::string path = std::string(env) + "/" + name;
if (g_file_test(path.c_str(), G_FILE_TEST_EXISTS)) {
return path;
}
}
std::string local = std::string("./assets/") + name;
if (g_file_test(local.c_str(), G_FILE_TEST_EXISTS)) {
return local;
}
#ifndef ASSETS_DIR
#define ASSETS_DIR "/usr/local/share/gkeymapper/assets"
#endif
std::string installed = std::string(ASSETS_DIR) + "/" + name;
if (g_file_test(installed.c_str(), G_FILE_TEST_EXISTS)) {
return installed;
}
return local;
}
void build_ui(GtkApplication* app) {
GtkWidget* window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), _("gKeyMapper – Logitech G910"));
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
GtkWidget* headerBar = gtk_header_bar_new();
gtk_window_set_titlebar(GTK_WINDOW(window), headerBar);
gtk_window_set_icon_name(GTK_WINDOW(window), "gkeymapper");
GtkWidget* titleBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
GtkWidget* iconImage = gtk_image_new_from_file(find_asset("icon.png").c_str());
gtk_image_set_pixel_size(GTK_IMAGE(iconImage), 24);
gtk_box_append(GTK_BOX(titleBox), iconImage);
gtk_box_append(GTK_BOX(titleBox), gtk_label_new(_("gKeyMapper")));
gtk_header_bar_set_title_widget(GTK_HEADER_BAR(headerBar), titleBox);
GMenu* menu = g_menu_new();
g_menu_append(menu, _("Export configuration..."), "app.export-config");
g_menu_append(menu, _("Import configuration..."), "app.import-config");
GMenu* helpSection = g_menu_new();
g_menu_append(helpSection, _("About"), "app.about");
g_menu_append_section(menu, NULL, G_MENU_MODEL(helpSection));
GtkWidget* menuButton = gtk_menu_button_new();
gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menuButton), "open-menu-symbolic");
gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menuButton), G_MENU_MODEL(menu));
gtk_header_bar_pack_end(GTK_HEADER_BAR(headerBar), menuButton);
GtkWidget* outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_window_set_child(GTK_WINDOW(window), outer);
// Title / subtitle
GtkWidget* headerBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
gtk_widget_set_margin_top(headerBox, 16);
gtk_widget_set_margin_bottom(headerBox, 8);
gtk_widget_set_margin_start(headerBox, 16);
gtk_widget_set_margin_end(headerBox, 16);
gtk_box_append(GTK_BOX(outer), headerBox);
GtkWidget* title = gtk_label_new(nullptr);
gtk_label_set_markup(GTK_LABEL(title), "<span size='x-large' weight='bold'>G-Key Mapper</span>");
gtk_label_set_xalign(GTK_LABEL(title), 0.0f);
gtk_box_append(GTK_BOX(headerBox), title);
GtkWidget* subtitle = gtk_label_new(_("G1 to G9 of the Logitech G910 remapped to keys or commands"));
gtk_label_set_xalign(GTK_LABEL(subtitle), 0.0f);
gtk_widget_add_css_class(subtitle, "dim-label");
gtk_box_append(GTK_BOX(headerBox), subtitle);
// Table with all nine G keys (no scrolling needed, everything stays visible)
GtkWidget* grid = gtk_grid_new();
gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
gtk_grid_set_column_spacing(GTK_GRID(grid), 8);
gtk_widget_set_margin_top(grid, 8);
gtk_widget_set_margin_bottom(grid, 4);
gtk_widget_set_margin_start(grid, 16);
gtk_widget_set_margin_end(grid, 16);
gtk_widget_set_hexpand(grid, TRUE);
gtk_box_append(GTK_BOX(outer), grid);
// Column headers
const char* headers[] = { _("G-Key"), _("Type"), _("Target (Key/Command)"), _("Action") };
for (int c = 0; c < 4; ++c) {
GtkWidget* h = gtk_label_new(headers[c]);
gtk_label_set_markup(GTK_LABEL(h), g_strdup_printf("<b>%s</b>", headers[c]));
gtk_widget_add_css_class(h, "dim-label");
gtk_grid_attach(GTK_GRID(grid), h, c, 0, 1, 1);
}
const char* typeOptions[] = { _("Key"), _("Command"), _("Disabled"), nullptr };
rows.resize(NUM_KEYS);
for (int i = 0; i < NUM_KEYS; ++i) {
rows[i].index = i;
// G key label
GtkWidget* keyLabel = gtk_label_new(g_strdup_printf("G%d", i + 1));
gtk_label_set_markup(GTK_LABEL(keyLabel), g_strdup_printf("<b>G%d</b>", i + 1));
gtk_grid_attach(GTK_GRID(grid), keyLabel, 0, i + 1, 1, 1);
rows[i].keyLabel = keyLabel;
// Type dropdown (fixed width so rows don't jump between languages)
GtkWidget* drop = gtk_drop_down_new_from_strings(typeOptions);
gtk_drop_down_set_selected(GTK_DROP_DOWN(drop), 0);
gtk_widget_set_size_request(drop, 150, -1);
gtk_grid_attach(GTK_GRID(grid), drop, 1, i + 1, 1, 1);
rows[i].typeDrop = drop;
g_signal_connect(drop, "notify::selected", G_CALLBACK(on_type_changed), &rows[i]);
// 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_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);
rows[i].targetEntry = entry;
// Capture button
GtkWidget* capture = gtk_button_new_with_label(_("Capture"));
gtk_widget_set_size_request(capture, 180, -1);
g_signal_connect(capture, "clicked", G_CALLBACK(on_capture_clicked), &rows[i]);
gtk_grid_attach(GTK_GRID(grid), capture, 3, i + 1, 1, 1);
rows[i].captureButton = capture;
update_row_state(&rows[i]);
}
// Footer with save button
GtkWidget* footer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_set_margin_top(footer, 8);
gtk_widget_set_margin_bottom(footer, 16);
gtk_widget_set_margin_start(footer, 16);
gtk_widget_set_margin_end(footer, 16);
gtk_box_append(GTK_BOX(outer), footer);
GtkWidget* saveBtn = gtk_button_new_with_label(_("Save mappings"));
gtk_widget_add_css_class(saveBtn, "suggested-action");
g_signal_connect(saveBtn, "clicked", G_CALLBACK(on_save_clicked), nullptr);
gtk_box_append(GTK_BOX(footer), saveBtn);
GtkWidget* hint = gtk_label_new(_("Saved to ~/.config/gKeyMapper/mappings.conf"));
gtk_widget_add_css_class(hint, "dim-label");
gtk_widget_set_halign(hint, GTK_ALIGN_END);
gtk_widget_set_hexpand(hint, TRUE);
gtk_box_append(GTK_BOX(footer), hint);
gtk_window_present(GTK_WINDOW(window));
}
static void on_activate(GtkApplication* app, gpointer /*user_data*/) {
GSimpleAction* exportAction = g_simple_action_new("export-config", NULL);
g_signal_connect(exportAction, "activate", G_CALLBACK(action_export), app);
g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(exportAction));
GSimpleAction* importAction = g_simple_action_new("import-config", NULL);
g_signal_connect(importAction, "activate", G_CALLBACK(action_import), app);
g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(importAction));
GSimpleAction* aboutAction = g_simple_action_new("about", NULL);
g_signal_connect(aboutAction, "activate", G_CALLBACK(action_about), app);
g_action_map_add_action(G_ACTION_MAP(app), G_ACTION(aboutAction));
build_ui(app);
}
} // namespace
int main(int argc, char** argv) {
setlocale(LC_ALL, "");
const char* localedir = g_getenv("GKEYMAPPER_LOCALEDIR");
if (!localedir || !*localedir) {
localedir = LOCALEDIR;
}
bindtextdomain(PACKAGE, localedir);
bind_textdomain_codeset(PACKAGE, "UTF-8");
textdomain(PACKAGE);
GtkApplication* app = gtk_application_new("com.example.gkeymapper",
G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), nullptr);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}