0.1.0 fixes

This commit is contained in:
2026-10-18 22:53:39 +02:00
parent 673af1ae43
commit ff020d0eb9
12 changed files with 260 additions and 72 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ gkeymapper
gkeymapper-daemon
*.o
*.so
*.deb

View File

@@ -22,6 +22,9 @@ live in the daemon, not the GUI. Code is split across `src/` (GUI), `shared/`
icon itself is compiled into the binary, so it never depends on these.
- `make clean` — deletes the binaries and the generated `locale/` and `build/`
trees.
- `make deb` — builds a `.deb` package (`gkeymapper_<version>_<arch>.deb`) via
`scripts/make-deb.sh`; on install it runs `systemctl --user daemon-reload` and
`systemctl --user enable --now gkeymapper-daemon` for the installing user.
- No tests and no CI. Verify changes by building and running the app.
## i18n — easy to get wrong

View File

@@ -32,6 +32,8 @@ DAEMON_HEADERS := $(wildcard $(DAEMON_DIR)/*.h) $(wildcard $(SHARED_DIR)/*.h)
GUI_RES := build/gresources.c
GUI_RES_SRC := data/icons.gresource.xml
PREFIX ?= /usr/local
DEB_VERSION ?= 0.1.0
DEB_ARCH := $(shell dpkg --print-architecture 2>/dev/null || echo amd64)
BINDIR := $(PREFIX)/bin
LOCALEDIR := $(PREFIX)/share/locale
ASSETS_DIR := $(PREFIX)/share/$(PACKAGE)/assets
@@ -51,7 +53,7 @@ LANGS := $(patsubst po/%.po,%,$(PO_FILES))
MO_FILES := $(patsubst %,locale/%/LC_MESSAGES/$(PACKAGE).mo,$(LANGS))
I18N := $(CURDIR)/scripts/i18n.sh
.PHONY: all clean run install translations daemon pot i18n-check i18n-status
.PHONY: all clean run install translations daemon pot i18n-check i18n-status deb
all: $(TARGET) translations $(DAEMON)
@@ -94,6 +96,10 @@ i18n-status:
run: $(TARGET) translations
GKEYMAPPER_LOCALEDIR=$(CURDIR)/locale GKEYMAPPER_ASSETS_DIR=$(CURDIR)/assets ./$(TARGET)
deb:
$(CURDIR)/scripts/make-deb.sh $(DEB_VERSION) $(DEB_ARCH)
clean:
rm -f $(TARGET) $(DAEMON)
rm -rf locale build

View File

@@ -69,6 +69,16 @@ with `make daemon`. The GUI has its icons (`assets/icon.png`, `assets/icon.svg`)
compiled in (`data/icons.gresource.xml` → generated `build/gresources.c`), so the
window icon works without an installed `assets/` directory.
A `.deb` package can be built with
```bash
make deb
```
which produces `gkeymapper_<version>_<arch>.deb`. Installing it runs
`systemctl --user daemon-reload` and `systemctl --user enable --now gkeymapper-daemon`
for the installing user.
## Rebuild translations
```bash
@@ -296,6 +306,20 @@ so that a `command` action inherits `DISPLAY`/`WAYLAND_DISPLAY`. The unit does
not run as root. `make install` reloads the udev rules for you; on a fresh
machine replug the keyboard (or reboot) once so the ACL applies.
## After installation
If the daemon is not running (the GUI footer says "Daemon: not reachable"):
```bash
systemctl --user daemon-reload
systemctl --user enable --now gkeymapper-daemon
```
The daemon needs the user's session for `/dev/uinput` and for the `DISPLAY` a
`command` action inherits, so it runs as a **user** service, not a system
service. If the commands above fail, check that your user session has systemd
running (`systemctl --user status`).
## Clean
```bash

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
"POT-Creation-Date: 2026-10-18 22:01+0200\n"
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
"Last-Translator: \n"
"Language-Team: German\n"
@@ -131,6 +131,9 @@ msgstr "Fenster schließen"
msgid "Daemon: not reachable"
msgstr "Daemon: nicht erreichbar"
msgid "Run: systemctl --user enable --now gkeymapper-daemon"
msgstr "Ausführen: systemctl --user enable --now gkeymapper-daemon"
msgid "Daemon: running"
msgstr "Daemon: läuft"

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
"POT-Creation-Date: 2026-10-18 22:01+0200\n"
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -128,6 +128,9 @@ msgstr "Close window"
msgid "Daemon: not reachable"
msgstr "Daemon: not reachable"
msgid "Run: systemctl --user enable --now gkeymapper-daemon"
msgstr "Run: systemctl --user enable --now gkeymapper-daemon"
msgid "Daemon: running"
msgstr "Daemon: running"

View File

@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
"POT-Creation-Date: 2026-10-18 22:01+0200\n"
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
@@ -128,6 +128,9 @@ msgstr "Cerrar ventana"
msgid "Daemon: not reachable"
msgstr "Demonio: no accesible"
msgid "Run: systemctl --user enable --now gkeymapper-daemon"
msgstr "Ejecutar: systemctl --user enable --now gkeymapper-daemon"
msgid "Daemon: running"
msgstr "Demonio: en ejecución"

View File

@@ -4,7 +4,7 @@ msgid ""
msgstr ""
"Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
"POT-Creation-Date: 2026-10-18 22:01+0200\n"
"PO-Revision-Date: 2026-10-18 03:08+0200\n"
"Last-Translator: \n"
"Language-Team: French\n"
@@ -131,6 +131,9 @@ msgstr "Fermer la fenêtre"
msgid "Daemon: not reachable"
msgstr "Démon : injoignable"
msgid "Run: systemctl --user enable --now gkeymapper-daemon"
msgstr "Exécuter : systemctl --user enable --now gkeymapper-daemon"
msgid "Daemon: running"
msgstr "Démon : en cours d'exécution"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-18 20:18+0200\n"
"POT-Creation-Date: 2026-10-18 22:01+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,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: src/autostart.cpp:97 src/main.cpp:280 src/trayicon.cpp:371
#: src/autostart.cpp:97 src/main.cpp:285 src/trayicon.cpp:371
msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr ""
@@ -111,11 +111,11 @@ msgid ""
"along."
msgstr ""
#: src/help.cpp:67 src/help.cpp:86 src/main.cpp:635
#: src/help.cpp:67 src/help.cpp:86 src/main.cpp:640
msgid "Help"
msgstr ""
#: src/help.cpp:95 src/optionsdialog.cpp:177
#: src/help.cpp:95 src/optionsdialog.cpp:178
msgid "Close window"
msgstr ""
@@ -123,158 +123,162 @@ msgstr ""
msgid "Daemon: not reachable"
msgstr ""
#: src/main.cpp:78
msgid "Daemon: running"
#: src/main.cpp:80
msgid "Run: systemctl --user enable --now gkeymapper-daemon"
msgstr ""
#: src/main.cpp:83
msgid "Daemon: running"
msgstr ""
#: src/main.cpp:88
msgid "Device: not found"
msgstr ""
#: src/main.cpp:86
#: src/main.cpp:91
#, c-format
msgid "Device: %s"
msgstr ""
#: src/main.cpp:91 src/optionsdialog.cpp:228
#: src/main.cpp:96 src/optionsdialog.cpp:229
msgid "Configuration error"
msgstr ""
#: src/main.cpp:96
#: src/main.cpp:101
#, c-format
msgid "Last key: %s"
msgstr ""
#: src/main.cpp:139
#: src/main.cpp:144
msgid "Choose file"
msgstr ""
#: src/main.cpp:142 src/main.cpp:313 src/main.cpp:325
#: src/main.cpp:147 src/main.cpp:318 src/main.cpp:330
msgid "_Cancel"
msgstr ""
#: src/main.cpp:143 src/main.cpp:326
#: src/main.cpp:148 src/main.cpp:331
msgid "_Open"
msgstr ""
#: src/main.cpp:173
#: src/main.cpp:178
msgid "Choose file..."
msgstr ""
#: src/main.cpp:175
#: src/main.cpp:180
msgid "e.g. /usr/bin/firefox or custom command"
msgstr ""
#: src/main.cpp:177 src/main.cpp:181 src/main.cpp:726
#: src/main.cpp:182 src/main.cpp:186 src/main.cpp:733
msgid "Capture"
msgstr ""
#: src/main.cpp:179
#: src/main.cpp:184
msgid "e.g. ctrl+w (held while the G key is held)"
msgstr ""
#: src/main.cpp:183 src/main.cpp:719
#: src/main.cpp:188 src/main.cpp:726
msgid "e.g. ctrl+c, ctrl+v"
msgstr ""
#: src/main.cpp:198
#: src/main.cpp:203
#, c-format
msgid "Could not save %s.\n"
msgstr ""
#: src/main.cpp:206
#: src/main.cpp:211
msgid "Mappings saved to "
msgstr ""
#: src/main.cpp:216
#: src/main.cpp:221
msgid "Could not export mappings.\n"
msgstr ""
#: src/main.cpp:277 src/main.cpp:623 src/trayicon.cpp:370 src/trayicon.cpp:500
#: src/main.cpp:282 src/main.cpp:628 src/trayicon.cpp:370 src/trayicon.cpp:500
msgid "gKeyMapper"
msgstr ""
#: src/main.cpp:310
#: src/main.cpp:315
msgid "Export configuration"
msgstr ""
#: src/main.cpp:314
#: src/main.cpp:319
msgid "_Save"
msgstr ""
#: src/main.cpp:322
#: src/main.cpp:327
msgid "Import configuration"
msgstr ""
#: src/main.cpp:608
#: src/main.cpp:613
msgid "gKeyMapper – Logitech G910"
msgstr ""
#: src/main.cpp:627
#: src/main.cpp:632
msgid "Export configuration..."
msgstr ""
#: src/main.cpp:628
#: src/main.cpp:633
msgid "Import configuration..."
msgstr ""
#: src/main.cpp:631 src/optionsdialog.cpp:83
#: src/main.cpp:636 src/optionsdialog.cpp:83
msgid "Options"
msgstr ""
#: src/main.cpp:636
#: src/main.cpp:641
msgid "About"
msgstr ""
#: src/main.cpp:642
#: src/main.cpp:647
msgid "Quit"
msgstr ""
#: src/main.cpp:656
#: src/main.cpp:661
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr ""
#: src/main.cpp:677
#: src/main.cpp:682
msgid "G-Key"
msgstr ""
#: src/main.cpp:677
#: src/main.cpp:682
msgid "Type"
msgstr ""
#: src/main.cpp:677
#: src/main.cpp:682
msgid "Target (Key/Command)"
msgstr ""
#: src/main.cpp:677
#: src/main.cpp:682
msgid "Action"
msgstr ""
#: src/main.cpp:689
#: src/main.cpp:696
msgid "Key"
msgstr ""
#: src/main.cpp:689
#: src/main.cpp:696
msgid "Command"
msgstr ""
#: src/main.cpp:689
#: src/main.cpp:696
msgid "Disabled"
msgstr ""
#: src/main.cpp:690
#: src/main.cpp:697
msgid "Key (hold)"
msgstr ""
#: src/main.cpp:761
#: src/main.cpp:769
msgid "Save mappings"
msgstr ""
#: src/main.cpp:768
#: src/main.cpp:776
msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr ""
#: src/main.cpp:779
#: src/main.cpp:787
msgid "Daemon: checking..."
msgstr ""
@@ -301,7 +305,7 @@ msgid ""
"G keys - the daemon does the work and is the only part that has to run."
msgstr ""
#: src/optionsdialog.cpp:131 src/optionsdialog.cpp:149
#: src/optionsdialog.cpp:131 src/optionsdialog.cpp:150
msgid "Language"
msgstr ""
@@ -309,65 +313,65 @@ msgstr ""
msgid "System language"
msgstr ""
#: src/optionsdialog.cpp:154
#: src/optionsdialog.cpp:155
msgid "The interface switches right away, including the tray menu."
msgstr ""
#: src/optionsdialog.cpp:157
#: src/optionsdialog.cpp:158
msgid "Daemon status"
msgstr ""
#: src/optionsdialog.cpp:170
#: src/optionsdialog.cpp:171
msgid "Tray icon"
msgstr ""
#: src/optionsdialog.cpp:171
#: src/optionsdialog.cpp:172
msgid "State"
msgstr ""
#: src/optionsdialog.cpp:172
#: src/optionsdialog.cpp:173
msgid "Version"
msgstr ""
#: src/optionsdialog.cpp:173
#: src/optionsdialog.cpp:174
msgid "Device"
msgstr ""
#: src/optionsdialog.cpp:174
#: src/optionsdialog.cpp:175
msgid "Configuration file"
msgstr ""
#: src/optionsdialog.cpp:175
#: src/optionsdialog.cpp:176
msgid "Last key"
msgstr ""
#: src/optionsdialog.cpp:216
#: src/optionsdialog.cpp:217
msgid ""
"Closing or minimizing the window hides it instead of quitting. The tray icon "
"shows it again (Show) and quits the program (Close)."
msgstr ""
#: src/optionsdialog.cpp:218
#: src/optionsdialog.cpp:219
msgid "No tray is available on this desktop, so the window cannot be hidden."
msgstr ""
#: src/optionsdialog.cpp:221
#: src/optionsdialog.cpp:222
msgid "active"
msgstr ""
#: src/optionsdialog.cpp:221
#: src/optionsdialog.cpp:222
msgid "not available"
msgstr ""
#: src/optionsdialog.cpp:222
#: src/optionsdialog.cpp:223
msgid "running"
msgstr ""
#: src/optionsdialog.cpp:222
#: src/optionsdialog.cpp:223
msgid "not reachable"
msgstr ""
#: src/optionsdialog.cpp:224
#: src/optionsdialog.cpp:225
msgid "not found"
msgstr ""

129
scripts/make-deb.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/bin/sh
# Build a .deb package for gKeyMapper.
# Usage: scripts/make-deb.sh [version] [arch]
set -e
VERSION="${1:-0.1.0}"
ARCH="${2:-$(dpkg --print-architecture 2>/dev/null || echo amd64)}"
PACKAGE="gkeymapper"
STAGING="build/deb"
DEB_NAME="${PACKAGE}_${VERSION}_${ARCH}.deb"
# Build everything first.
make all translations
rm -rf "$STAGING"
mkdir -p "$STAGING/DEBIAN"
# Stage files with Debian layout (PREFIX=/usr).
make install DESTDIR="$STAGING" PREFIX=/usr
# Calculate installed size (in KB) for the control file.
INSTALLED_SIZE=$(du -sk "$STAGING" | cut -f1)
# Control file.
cat > "$STAGING/DEBIAN/control" <<EOF
Package: $PACKAGE
Version: $VERSION
Section: utils
Installed-Size: $INSTALLED_SIZE
Priority: optional
Architecture: $ARCH
Depends: libc6, libgtk-4-1, libusb-1.0-0, libxkbcommon0, libglib2.0-0, systemd, udev
Maintainer: gKeyMapper <gkeymapper@rager.dev>
Description: Remap G1-G9 keys of the Logitech G910
A GTK4 GUI plus a background daemon for remapping the G1-G9 keys
of a Logitech G910 keyboard to keys or commands.
EOF
# postinst: reload udev/systemd and enable the user daemon.
cat > "$STAGING/DEBIAN/postinst" <<'EOF'
#!/bin/sh
set -e
case "$1" in
configure)
if command -v udevadm >/dev/null 2>&1; then
udevadm control --reload-rules 2>/dev/null || true
udevadm trigger --subsystem-match=usb 2>/dev/null || true
udevadm trigger --subsystem-match=input 2>/dev/null || true
fi
# Helper: run a command as a given user with the D-Bus session bus
# available. "systemctl --user" needs DBUS_SESSION_BUS_ADDRESS, which
# is not set in a plain "su -" login shell.
run_as_user() {
_user="$1"; shift
_uid=$(id -u "$_user" 2>/dev/null) || return 1
_bus="unix:path=/run/user/$_uid/bus"
runuser -u "$_user" -- env DBUS_SESSION_BUS_ADDRESS="$_bus" "$@" 2>&1
}
_target_user=""
if [ -n "$SUDO_USER" ]; then
_target_user="$SUDO_USER"
fi
if [ -n "$_target_user" ]; then
echo "gkeymapper: enabling daemon for user $_target_user ..."
if run_as_user "$_target_user" systemctl --user daemon-reload; then
echo " daemon-reload: OK"
else
echo " daemon-reload: FAILED (is the user session running?)"
fi
if run_as_user "$_target_user" systemctl --user enable --now gkeymapper-daemon; then
echo " enable --now gkeymapper-daemon: OK"
else
echo " enable --now gkeymapper-daemon: FAILED"
echo " -> run manually: systemctl --user enable --now gkeymapper-daemon"
fi
else
echo "gkeymapper: no installing user detected (SUDO_USER empty)."
echo " Enable the daemon manually with:"
echo " systemctl --user daemon-reload"
echo " systemctl --user enable --now gkeymapper-daemon"
for _user in $(loginctl list-users --no-legend 2>/dev/null | awk '{print $2}'); do
_uid=$(id -u "$_user" 2>/dev/null) || continue
echo " trying user $_user ..."
run_as_user "$_user" systemctl --user daemon-reload >/dev/null 2>&1 || true
run_as_user "$_user" systemctl --user enable --now gkeymapper-daemon >/dev/null 2>&1 || true
done
fi
;;
esac
exit 0
EOF
chmod 755 "$STAGING/DEBIAN/postinst"
# postrm: disable the user daemon on removal.
cat > "$STAGING/DEBIAN/postrm" <<'EOF'
#!/bin/sh
set -e
case "$1" in
remove|purge)
if [ -n "$SUDO_USER" ]; then
su - "$SUDO_USER" -c "systemctl --user disable --now gkeymapper-daemon" >/dev/null 2>&1 || true
su - "$SUDO_USER" -c "systemctl --user daemon-reload" >/dev/null 2>&1 || true
fi
;;
esac
exit 0
EOF
chmod 755 "$STAGING/DEBIAN/postrm"
# Fix ownership (files must be owned by root in a .deb).
# Strip debug symbols from binaries.
strip "$STAGING/usr/bin/gkeymapper" "$STAGING/usr/bin/gkeymapper-daemon" 2>/dev/null || true
# Remove generated cache files (they are regenerated on the target).
rm -f "$STAGING/usr/share/applications/mimeinfo.cache"
rm -f "$STAGING/usr/share/icons/hicolor/icon-theme.cache"
# Fix file permissions.
chmod 644 "$STAGING"/usr/share/applications/* 2>/dev/null || true
chmod 755 "$STAGING"/usr/bin/* 2>/dev/null || true
# Build the .deb.
dpkg-deb --root-owner-group --build "$STAGING" "$DEB_NAME"
echo ""
echo "Built: $DEB_NAME"

View File

@@ -74,7 +74,12 @@ static void refresh_footer() {
std::string text;
if (!daemonView.reachable) {
text = _("Daemon: not reachable");
// Hint for users who installed the package but did not (or could not)
// enable the daemon service during installation.
gtk_widget_set_tooltip_text(daemonLabel,
_("Run: systemctl --user enable --now gkeymapper-daemon"));
} else {
gtk_widget_set_tooltip_text(daemonLabel, nullptr);
text = _("Daemon: running");
text += " · ";
if (daemonView.device.empty()) {
@@ -685,14 +690,16 @@ void build_ui(GtkApplication* app, bool show) {
// GTK draws the check mark of the selected entry right behind the text of a
// dropdown row, so every entry carries a trailing space. The catalogs keep
// the bare words; the space is added here, where the model is built.
// the bare words; the space is added here, where the model is built. The
// strings are copied by gtk_drop_down_new_from_strings, so the array can be
// freed once every dropdown has been created.
const char* typeOptions[] = { _("Key"), _("Command"), _("Disabled"),
_("Key (hold)"), nullptr };
g_autoptr(GtkStringList) typeModel = gtk_string_list_new(nullptr);
char** typeShown = g_new(char*, 5);
for (int i = 0; typeOptions[i] != nullptr; ++i) {
g_autofree gchar* shown = g_strconcat(typeOptions[i], " ", nullptr);
gtk_string_list_append(typeModel, shown);
typeShown[i] = g_strconcat(typeOptions[i], " ", nullptr);
}
typeShown[4] = nullptr;
rows.resize(kNumKeys);
for (int i = 0; i < kNumKeys; ++i) {
@@ -706,7 +713,7 @@ void build_ui(GtkApplication* app, bool show) {
rows[i].keyLabel = keyLabel;
// Type dropdown (fixed width so rows don't jump between languages)
GtkWidget* drop = gtk_drop_down_new(G_LIST_MODEL(typeModel), nullptr);
GtkWidget* drop = gtk_drop_down_new_from_strings((const char* const*)typeShown);
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);
@@ -731,6 +738,7 @@ void build_ui(GtkApplication* app, bool show) {
update_row_state(&rows[i]);
}
g_strfreev(typeShown);
// Start from the rows the replaced instance had on screen, or - when this is
// the only instance - from the saved mappings. Without either the rows are

View File

@@ -136,12 +136,13 @@ void OptionsDialog::show(GtkWindow* parent, const OptionsState& state, ToggleFn
// check mark of the selected entry right behind the text.
const char* languageOptions[] = { _("System language"), "Deutsch", "English", "Español",
"Français", nullptr };
g_autoptr(GtkStringList) languageModel = gtk_string_list_new(nullptr);
char** languageShown = g_new(char*, 6);
for (int i = 0; languageOptions[i] != nullptr; ++i) {
g_autofree gchar* shown = g_strconcat(languageOptions[i], " ", nullptr);
gtk_string_list_append(languageModel, shown);
languageShown[i] = g_strconcat(languageOptions[i], " ", nullptr);
}
languageDrop_ = gtk_drop_down_new(G_LIST_MODEL(languageModel), nullptr);
languageShown[5] = nullptr;
languageDrop_ = gtk_drop_down_new_from_strings((const char* const*)languageShown);
g_strfreev(languageShown);
// Right-aligned like the switches above: the GUI's own controls sit on one
// line along the right edge, the text explains them on the left.
gtk_widget_set_halign(languageDrop_, GTK_ALIGN_END);