From d157d9d30db929508c4f7f9755ed53c933eb72ce Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 16 Oct 2026 22:11:52 +0200 Subject: [PATCH] Working ui demo --- .gitignore | 5 + .vscode/tasks.json | 49 +++++ Makefile | 65 ++++++ README.md | 124 +++++++++++ assets/icon.png | Bin 0 -> 6010 bytes assets/icon.svg | 13 ++ data/gkeymapper.desktop | 11 + po/de.po | 138 ++++++++++++ po/de.po~ | 102 +++++++++ po/es.po | 136 ++++++++++++ po/es.po~ | 100 +++++++++ po/fr.po | 138 ++++++++++++ po/fr.po~ | 100 +++++++++ po/gkeymapper.pot | 140 ++++++++++++ src/main.cpp | 471 ++++++++++++++++++++++++++++++++++++++++ 15 files changed, 1592 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/tasks.json create mode 100644 Makefile create mode 100644 README.md create mode 100644 assets/icon.png create mode 100644 assets/icon.svg create mode 100644 data/gkeymapper.desktop create mode 100644 po/de.po create mode 100644 po/de.po~ create mode 100644 po/es.po create mode 100644 po/es.po~ create mode 100644 po/fr.po create mode 100644 po/fr.po~ create mode 100644 po/gkeymapper.pot create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d22442a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +locale/ +gkeymapper +*.o +*.so diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..3a59124 --- /dev/null +++ b/.vscode/tasks.json @@ -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" + } + ] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fdac805 --- /dev/null +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..11ca5fd --- /dev/null +++ b/README.md @@ -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. diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..50135cc409321d714756c77a0c475b69d97aebfe GIT binary patch literal 6010 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%0A+LJ~OmiG z{78(xpx3ffN?0gL#*gMrwXEbFbAa`s{GWUX`_O2{mh!N#*~kAn7`5e2(|t*WKux(W z&Hq6X94U3gDqeuO(K#H!0!0r6L2hGryr3Hv=yn~zDrotRB$%Fa;ps%phzx7~&q0(xqdAh7e`0*YwB{&L#m zQ)?J#_ccCTD=3QIR9vD<3Os0HMd!r922m4CrX5fgAn@q3#5U9Vx{>c5pR~E~AX&*s zk{pNy={wRn6R_9$B|MRhEujm9)(CM4jEHxm-YV&9b>5dbKrMLtKwydQX>ye0vxSR` zS51LQz;J=dlNa_b6t>~0{*>lA{?Gban%GzI@aq`&1E z2W~cVYX=^tT)G4lHSyx80%LJB&deR%UAgzE_xsal0QNE#qE)hRmvqAfYzknk%Zc$R z>+P0)?=x37amg{*jbhLkjrivm9PaNHdvJnsZFa>!b#KtL#= z7r5quAZLit2RM*nv-2m*U*KywR6tu9rveujZ$LdJ$fTT?sR&T|y z8Gx=Oe}**Y=woSIHDf1*ue_b>`WF2pScFI5efQkLE+4KRNa2!F9Jo{88O6K>+^u*U z6bK-?bEl2iF#!Wu=hr=LP}zaa2Snc`QN`DyU^xXz zItN6a;de(E2kci6vVbKPoLM`(yzL1s(qlpH_wYq+-hqrQ^ZR$o?|DKNEJP3BO*~Np zr)~(dpD7~_1FC+-?3)v#_P;@}Q{ER2E<5fI58w%W1VCGZGI;NbAU_tP_dmv&Z@C3L zok#$*r5L38UilvY;;J*k!KKILA2_y9zjh?O?%6Y$1|-P>6#Uy%;Rm0iXvVz2_1_va z8hI3ooTra%k6(fHXfkJNFGLqmQ9qcLODBxl&+7Z``G1(HK*P7D z+>ft60%V}oKXTkv^sDmnwXyd>qYRCTse^qA&{AsFd>p;d?e}H6R$C>9K`EUC6c;$p zS?bbigVUm1!wfsgz5WE$yrrtSF`1&D=s>-_Pzo0T+7>?C8hE=RYd+CKfpK7AGG6>J z^fEZw8IP!7!CdxlnqK!KPURjgfknnq04!wRV`LtU3%p@SAJ{txYhIA$fD zOxk=rSk8w3^5ywu6%8)ad=^413072yrQy + + + diff --git a/data/gkeymapper.desktop b/data/gkeymapper.desktop new file mode 100644 index 0000000..7afbf03 --- /dev/null +++ b/data/gkeymapper.desktop @@ -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 diff --git a/po/de.po b/po/de.po new file mode 100644 index 0000000..ec32863 --- /dev/null +++ b/po/de.po @@ -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 "" +msgstr "" + +#: 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" diff --git a/po/de.po~ b/po/de.po~ new file mode 100644 index 0000000..48da094 --- /dev/null +++ b/po/de.po~ @@ -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 "" +msgstr "" + +#, 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" diff --git a/po/es.po b/po/es.po new file mode 100644 index 0000000..94cc2a3 --- /dev/null +++ b/po/es.po @@ -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 "" +msgstr "" + +#: 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" diff --git a/po/es.po~ b/po/es.po~ new file mode 100644 index 0000000..2b4933c --- /dev/null +++ b/po/es.po~ @@ -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 "" +msgstr "" + +#, 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" diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000..55e0b78 --- /dev/null +++ b/po/fr.po @@ -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 "" +msgstr "" + +#: 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" diff --git a/po/fr.po~ b/po/fr.po~ new file mode 100644 index 0000000..4d27816 --- /dev/null +++ b/po/fr.po~ @@ -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 "" +msgstr "" + +#, 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" diff --git a/po/gkeymapper.pot b/po/gkeymapper.pot new file mode 100644 index 0000000..adcf269 --- /dev/null +++ b/po/gkeymapper.pot @@ -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 , 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 \n" +"Language-Team: LANGUAGE \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 "" +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 "" diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1535bc4 --- /dev/null +++ b/src/main.cpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include + +#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 rows; + +static void on_pick_file_response(GtkDialog* dialog, int response, gpointer userData) { + if (response == GTK_RESPONSE_ACCEPT) { + KeyRow* row = static_cast(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(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), _("")); + 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(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), "G-Key Mapper"); + 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("%s", 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("G%d", 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; +}