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 gkeymapper-daemon
*.o *.o
*.so *.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. icon itself is compiled into the binary, so it never depends on these.
- `make clean` — deletes the binaries and the generated `locale/` and `build/` - `make clean` — deletes the binaries and the generated `locale/` and `build/`
trees. 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. - No tests and no CI. Verify changes by building and running the app.
## i18n — easy to get wrong ## 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 := build/gresources.c
GUI_RES_SRC := data/icons.gresource.xml GUI_RES_SRC := data/icons.gresource.xml
PREFIX ?= /usr/local PREFIX ?= /usr/local
DEB_VERSION ?= 0.1.0
DEB_ARCH := $(shell dpkg --print-architecture 2>/dev/null || echo amd64)
BINDIR := $(PREFIX)/bin BINDIR := $(PREFIX)/bin
LOCALEDIR := $(PREFIX)/share/locale LOCALEDIR := $(PREFIX)/share/locale
ASSETS_DIR := $(PREFIX)/share/$(PACKAGE)/assets 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)) MO_FILES := $(patsubst %,locale/%/LC_MESSAGES/$(PACKAGE).mo,$(LANGS))
I18N := $(CURDIR)/scripts/i18n.sh 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) all: $(TARGET) translations $(DAEMON)
@@ -94,6 +96,10 @@ i18n-status:
run: $(TARGET) translations run: $(TARGET) translations
GKEYMAPPER_LOCALEDIR=$(CURDIR)/locale GKEYMAPPER_ASSETS_DIR=$(CURDIR)/assets ./$(TARGET) GKEYMAPPER_LOCALEDIR=$(CURDIR)/locale GKEYMAPPER_ASSETS_DIR=$(CURDIR)/assets ./$(TARGET)
deb:
$(CURDIR)/scripts/make-deb.sh $(DEB_VERSION) $(DEB_ARCH)
clean: clean:
rm -f $(TARGET) $(DAEMON) rm -f $(TARGET) $(DAEMON)
rm -rf locale build 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 compiled in (`data/icons.gresource.xml` → generated `build/gresources.c`), so the
window icon works without an installed `assets/` directory. 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 ## Rebuild translations
```bash ```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 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. 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 ## Clean
```bash ```bash

View File

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

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gkeymapper 0.1\n" "Project-Id-Version: gkeymapper 0.1\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2026-10-18 03:08+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: English\n" "Language-Team: English\n"
@@ -128,6 +128,9 @@ msgstr "Close window"
msgid "Daemon: not reachable" msgid "Daemon: not reachable"
msgstr "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" msgid "Daemon: running"
msgstr "Daemon: running" msgstr "Daemon: running"

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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)." msgid "G-Key mapper for the Logitech G910 (GTK4)."
msgstr "" msgstr ""
@@ -111,11 +111,11 @@ msgid ""
"along." "along."
msgstr "" 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" msgid "Help"
msgstr "" msgstr ""
#: src/help.cpp:95 src/optionsdialog.cpp:177 #: src/help.cpp:95 src/optionsdialog.cpp:178
msgid "Close window" msgid "Close window"
msgstr "" msgstr ""
@@ -123,158 +123,162 @@ msgstr ""
msgid "Daemon: not reachable" msgid "Daemon: not reachable"
msgstr "" msgstr ""
#: src/main.cpp:78 #: src/main.cpp:80
msgid "Daemon: running" msgid "Run: systemctl --user enable --now gkeymapper-daemon"
msgstr "" msgstr ""
#: src/main.cpp:83 #: src/main.cpp:83
msgid "Daemon: running"
msgstr ""
#: src/main.cpp:88
msgid "Device: not found" msgid "Device: not found"
msgstr "" msgstr ""
#: src/main.cpp:86 #: src/main.cpp:91
#, c-format #, c-format
msgid "Device: %s" msgid "Device: %s"
msgstr "" msgstr ""
#: src/main.cpp:91 src/optionsdialog.cpp:228 #: src/main.cpp:96 src/optionsdialog.cpp:229
msgid "Configuration error" msgid "Configuration error"
msgstr "" msgstr ""
#: src/main.cpp:96 #: src/main.cpp:101
#, c-format #, c-format
msgid "Last key: %s" msgid "Last key: %s"
msgstr "" msgstr ""
#: src/main.cpp:139 #: src/main.cpp:144
msgid "Choose file" msgid "Choose file"
msgstr "" 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" msgid "_Cancel"
msgstr "" msgstr ""
#: src/main.cpp:143 src/main.cpp:326 #: src/main.cpp:148 src/main.cpp:331
msgid "_Open" msgid "_Open"
msgstr "" msgstr ""
#: src/main.cpp:173 #: src/main.cpp:178
msgid "Choose file..." msgid "Choose file..."
msgstr "" msgstr ""
#: src/main.cpp:175 #: src/main.cpp:180
msgid "e.g. /usr/bin/firefox or custom command" msgid "e.g. /usr/bin/firefox or custom command"
msgstr "" 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" msgid "Capture"
msgstr "" msgstr ""
#: src/main.cpp:179 #: src/main.cpp:184
msgid "e.g. ctrl+w (held while the G key is held)" msgid "e.g. ctrl+w (held while the G key is held)"
msgstr "" msgstr ""
#: src/main.cpp:183 src/main.cpp:719 #: src/main.cpp:188 src/main.cpp:726
msgid "e.g. ctrl+c, ctrl+v" msgid "e.g. ctrl+c, ctrl+v"
msgstr "" msgstr ""
#: src/main.cpp:198 #: src/main.cpp:203
#, c-format #, c-format
msgid "Could not save %s.\n" msgid "Could not save %s.\n"
msgstr "" msgstr ""
#: src/main.cpp:206 #: src/main.cpp:211
msgid "Mappings saved to " msgid "Mappings saved to "
msgstr "" msgstr ""
#: src/main.cpp:216 #: src/main.cpp:221
msgid "Could not export mappings.\n" msgid "Could not export mappings.\n"
msgstr "" 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" msgid "gKeyMapper"
msgstr "" msgstr ""
#: src/main.cpp:310 #: src/main.cpp:315
msgid "Export configuration" msgid "Export configuration"
msgstr "" msgstr ""
#: src/main.cpp:314 #: src/main.cpp:319
msgid "_Save" msgid "_Save"
msgstr "" msgstr ""
#: src/main.cpp:322 #: src/main.cpp:327
msgid "Import configuration" msgid "Import configuration"
msgstr "" msgstr ""
#: src/main.cpp:608 #: src/main.cpp:613
msgid "gKeyMapper – Logitech G910" msgid "gKeyMapper – Logitech G910"
msgstr "" msgstr ""
#: src/main.cpp:627 #: src/main.cpp:632
msgid "Export configuration..." msgid "Export configuration..."
msgstr "" msgstr ""
#: src/main.cpp:628 #: src/main.cpp:633
msgid "Import configuration..." msgid "Import configuration..."
msgstr "" msgstr ""
#: src/main.cpp:631 src/optionsdialog.cpp:83 #: src/main.cpp:636 src/optionsdialog.cpp:83
msgid "Options" msgid "Options"
msgstr "" msgstr ""
#: src/main.cpp:636 #: src/main.cpp:641
msgid "About" msgid "About"
msgstr "" msgstr ""
#: src/main.cpp:642 #: src/main.cpp:647
msgid "Quit" msgid "Quit"
msgstr "" msgstr ""
#: src/main.cpp:656 #: src/main.cpp:661
msgid "G1 to G9 of the Logitech G910 remapped to keys or commands" msgid "G1 to G9 of the Logitech G910 remapped to keys or commands"
msgstr "" msgstr ""
#: src/main.cpp:677 #: src/main.cpp:682
msgid "G-Key" msgid "G-Key"
msgstr "" msgstr ""
#: src/main.cpp:677 #: src/main.cpp:682
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#: src/main.cpp:677 #: src/main.cpp:682
msgid "Target (Key/Command)" msgid "Target (Key/Command)"
msgstr "" msgstr ""
#: src/main.cpp:677 #: src/main.cpp:682
msgid "Action" msgid "Action"
msgstr "" msgstr ""
#: src/main.cpp:689 #: src/main.cpp:696
msgid "Key" msgid "Key"
msgstr "" msgstr ""
#: src/main.cpp:689 #: src/main.cpp:696
msgid "Command" msgid "Command"
msgstr "" msgstr ""
#: src/main.cpp:689 #: src/main.cpp:696
msgid "Disabled" msgid "Disabled"
msgstr "" msgstr ""
#: src/main.cpp:690 #: src/main.cpp:697
msgid "Key (hold)" msgid "Key (hold)"
msgstr "" msgstr ""
#: src/main.cpp:761 #: src/main.cpp:769
msgid "Save mappings" msgid "Save mappings"
msgstr "" msgstr ""
#: src/main.cpp:768 #: src/main.cpp:776
msgid "Saved to ~/.config/gKeyMapper/mappings.conf" msgid "Saved to ~/.config/gKeyMapper/mappings.conf"
msgstr "" msgstr ""
#: src/main.cpp:779 #: src/main.cpp:787
msgid "Daemon: checking..." msgid "Daemon: checking..."
msgstr "" msgstr ""
@@ -301,7 +305,7 @@ msgid ""
"G keys - the daemon does the work and is the only part that has to run." "G keys - the daemon does the work and is the only part that has to run."
msgstr "" msgstr ""
#: src/optionsdialog.cpp:131 src/optionsdialog.cpp:149 #: src/optionsdialog.cpp:131 src/optionsdialog.cpp:150
msgid "Language" msgid "Language"
msgstr "" msgstr ""
@@ -309,65 +313,65 @@ msgstr ""
msgid "System language" msgid "System language"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:154 #: src/optionsdialog.cpp:155
msgid "The interface switches right away, including the tray menu." msgid "The interface switches right away, including the tray menu."
msgstr "" msgstr ""
#: src/optionsdialog.cpp:157 #: src/optionsdialog.cpp:158
msgid "Daemon status" msgid "Daemon status"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:170 #: src/optionsdialog.cpp:171
msgid "Tray icon" msgid "Tray icon"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:171 #: src/optionsdialog.cpp:172
msgid "State" msgid "State"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:172 #: src/optionsdialog.cpp:173
msgid "Version" msgid "Version"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:173 #: src/optionsdialog.cpp:174
msgid "Device" msgid "Device"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:174 #: src/optionsdialog.cpp:175
msgid "Configuration file" msgid "Configuration file"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:175 #: src/optionsdialog.cpp:176
msgid "Last key" msgid "Last key"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:216 #: src/optionsdialog.cpp:217
msgid "" msgid ""
"Closing or minimizing the window hides it instead of quitting. The tray icon " "Closing or minimizing the window hides it instead of quitting. The tray icon "
"shows it again (Show) and quits the program (Close)." "shows it again (Show) and quits the program (Close)."
msgstr "" msgstr ""
#: src/optionsdialog.cpp:218 #: src/optionsdialog.cpp:219
msgid "No tray is available on this desktop, so the window cannot be hidden." msgid "No tray is available on this desktop, so the window cannot be hidden."
msgstr "" msgstr ""
#: src/optionsdialog.cpp:221 #: src/optionsdialog.cpp:222
msgid "active" msgid "active"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:221 #: src/optionsdialog.cpp:222
msgid "not available" msgid "not available"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:222 #: src/optionsdialog.cpp:223
msgid "running" msgid "running"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:222 #: src/optionsdialog.cpp:223
msgid "not reachable" msgid "not reachable"
msgstr "" msgstr ""
#: src/optionsdialog.cpp:224 #: src/optionsdialog.cpp:225
msgid "not found" msgid "not found"
msgstr "" 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; std::string text;
if (!daemonView.reachable) { if (!daemonView.reachable) {
text = _("Daemon: not 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 { } else {
gtk_widget_set_tooltip_text(daemonLabel, nullptr);
text = _("Daemon: running"); text = _("Daemon: running");
text += " · "; text += " · ";
if (daemonView.device.empty()) { 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 // 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 // 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"), const char* typeOptions[] = { _("Key"), _("Command"), _("Disabled"),
_("Key (hold)"), nullptr }; _("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) { for (int i = 0; typeOptions[i] != nullptr; ++i) {
g_autofree gchar* shown = g_strconcat(typeOptions[i], " ", nullptr); typeShown[i] = g_strconcat(typeOptions[i], " ", nullptr);
gtk_string_list_append(typeModel, shown);
} }
typeShown[4] = nullptr;
rows.resize(kNumKeys); rows.resize(kNumKeys);
for (int i = 0; i < kNumKeys; ++i) { for (int i = 0; i < kNumKeys; ++i) {
@@ -706,7 +713,7 @@ void build_ui(GtkApplication* app, bool show) {
rows[i].keyLabel = keyLabel; rows[i].keyLabel = keyLabel;
// Type dropdown (fixed width so rows don't jump between languages) // 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_drop_down_set_selected(GTK_DROP_DOWN(drop), 0);
gtk_widget_set_size_request(drop, 150, -1); gtk_widget_set_size_request(drop, 150, -1);
gtk_grid_attach(GTK_GRID(grid), drop, 1, i + 1, 1, 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]); update_row_state(&rows[i]);
} }
g_strfreev(typeShown);
// Start from the rows the replaced instance had on screen, or - when this is // 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 // 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. // check mark of the selected entry right behind the text.
const char* languageOptions[] = { _("System language"), "Deutsch", "English", "Español", const char* languageOptions[] = { _("System language"), "Deutsch", "English", "Español",
"Français", nullptr }; "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) { for (int i = 0; languageOptions[i] != nullptr; ++i) {
g_autofree gchar* shown = g_strconcat(languageOptions[i], " ", nullptr); languageShown[i] = g_strconcat(languageOptions[i], " ", nullptr);
gtk_string_list_append(languageModel, shown);
} }
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 // 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. // line along the right edge, the text explains them on the left.
gtk_widget_set_halign(languageDrop_, GTK_ALIGN_END); gtk_widget_set_halign(languageDrop_, GTK_ALIGN_END);