Deamon management

This commit is contained in:
2026-10-17 03:53:51 +02:00
parent 335a4674d1
commit b77b4a3fc6
35 changed files with 2001 additions and 172 deletions

View File

@@ -4,6 +4,33 @@ Status: draft / proposal
Scope: a native C/C++ background service that detects G1–G9 (and later M1–M3/MR
and media keys) on the Logitech G910 and replays the mappings stored by the GUI.
## Implementation status
- **M0 done** — `shared/mapping.{h,cpp}` (file format + shortcut parsing) and
`shared/keys.{h,cpp}` (canonical names → Linux codes); the GUI uses them.
Round-trip and shortcut parsing are covered by a small standalone test.
- **M1 done** — `daemon/` builds (`gkeymapper-daemon`): device discovery,
kernel-driver detach, F-key binding disable, 64-byte interrupt reads and G1–G9
decoding. Verified on hardware: all nine keys decode correctly (press and
release), in order.
- **M2 done** — action execution: `key` shortcuts are replayed through a
`/dev/uinput` virtual keyboard, `command` runs via fork/exec (with a shell
fallback so arguments work), `disabled` does nothing. Verified on hardware.
- **M3 done** — config watching and lifecycle: `daemon/config_watch` watches the
config *directory* with inotify, so a save in the GUI reloads within about
half a second; `SIGHUP`/`SIGUSR1` stay as the fallback. `Save mappings` now
writes atomically (temp file + `rename`), so the daemon never reads a
half-written file. `make install` ships the udev rule, the `modules-load.d`
fragment and the systemd **user** unit (`data/`).
- **Layout handling (added during M2)** — key names are resolved through the
active layout (`XKBLAYOUT` from `/etc/default/keyboard`, via xkbcommon). This
matters because the US-position code `KEY_Z` (44) is `y` on a German keyboard;
`z` is now correctly resolved to evdev 21.
- **AltGr (added after M2)** — level-3 combinations are representable as
`altgr+7`; the base (level 0) key is stored plus the `altgr` modifier.
- **Next: M4 (D-Bus GUI channel)** — status, explicit `Reload`, `TestMapping`
and signals. The reload side of it is already covered by the M3 file watch.
## 1. Goals and non-goals
Goals
@@ -243,24 +270,16 @@ Start-up orders:
```
src/ # GUI (existing)
daemon/
main.cpp # event loop, wiring
device_libusb.{h,cpp} # discovery, detach/attach, read, disable-binding
decoder.{h,cpp} # report bytes -> action id (compile-time tables)
uinput_keyboard.{h,cpp}# virtual keyboard, emit / combo / press-release stack
actions.{h,cpp} # key / command / disabled execution, child reaping
config.{h,cpp} # mappings.conf parse + watch
ipc_dbus.{h,cpp} # GDBus service
generated/devices.inc # ported from supported_devices.py
daemon/ # as implemented (see the list in §6 above)
shared/
mapping.{h,cpp} # parse/format mappings.conf (used by GUI and daemon)
keys.{h,cpp} # canonical names <-> gdk keyval <-> linux KEY_*
ipc_ids.h # D-Bus name/path/interface constants
keymap.{h,cpp} # layout-aware name <-> input code (xkbcommon)
data/
60-gkeymapper.rules
gkeymapper-daemon.service
uinput.conf # modules-load.d
po/ # daemon strings, reuse the existing catalogs
60-gkeymapper.rules # udev: uaccess for the device and /dev/uinput
gkeymapper-uinput.conf # modules-load.d fragment (uinput)
gkeymapper-daemon.service # systemd user unit (@BINDIR@ filled in on install)
po/ # shared catalogs (GUI strings)
```
Build: the Makefile gets a second target `gkeymapper-daemon` and install rules
@@ -289,8 +308,9 @@ for the unit, udev rule and modules-load fragment. New build dependency:
F-key binding, logs `G1..G9` presses. No output yet.
- **M2 – Action layer.** uinput keyboard; `key` (shortcut, incl. release) and
`command` work end-to-end. `disabled` no-op.
- **M3 – Config & lifecycle.** config parse/reload (inotify, SIGUSR1), signals,
systemd --user unit + udev rule + modules-load.
- **M3 – Config & lifecycle (done).** config reload via inotify (`config_watch`,
directory watch + atomic save) and `SIGHUP`/`SIGUSR1`; systemd --user unit +
udev rule + modules-load, installed by `make install`.
- **M4 – IPC.** GDBus service (`Reload`, `GetStatus`, `TestMapping`, signals);
GUI status line + Reload-on-save + optional Test button.
- **M5 – Polish.** Packaging/install rules, docs, translations, hardening
@@ -333,3 +353,33 @@ for the unit, udev rule and modules-load fragment. New build dependency:
dependency). Recommendation: D-Bus, with signals as a fallback.
4. Scope of v1: keep `key`/`command`/`disabled` only, or also port
`typeout`/`media`?
### Resolved
- **Event loop (as implemented, deviates from §4.2).** The daemon is
single-threaded with a blocking `libusb_interrupt_transfer` and a 500 ms
timeout; the config watch is drained once per iteration. That is ~2 idle
wakeups/s at 0 % CPU (the "simple variant" of §4.2) rather than the preferred
async libusb + GMainLoop design. Switching to libusb's async API with its poll
fds in a `poll()` loop stays open if idle wakeups ever become a concern — the
config watch would fold into the same set.
- **Layout handling (done in M2).** Key names in the file are layout
independent; the daemon resolves them through the active layout (`XKBLAYOUT`
from `/etc/default/keyboard`, via xkbcommon) and only falls back to the US
positions from `shared/keys`. This closes the vocabulary-drift risk for non-US
layouts — a German `z` is evdev 21, not the US position 44.
- **Reload without restart (done in M2).** `SIGHUP` re-reads the config file
(the full D-Bus `Reload` comes with M4).
- **Config watching (done in M3).** The daemon watches the config **directory**
with inotify rather than the file, so an atomic save (temp file + `rename`) and
a recreated file are caught too. The loop iteration (≤ 500 ms) is the
coalescing window, so one save yields exactly one reload; `SIGHUP`/`SIGUSR1`
stay as the fallback and there is no extra fd in the loop. `Save mappings` in
the GUI now writes atomically, so the daemon can never read a partial file. A
failed reload keeps the previous mappings instead of disabling every key.
- **Lifecycle (done in M3).** A systemd **user** unit (not a system service): the
daemon needs the session for `/dev/uinput` and for the `DISPLAY` a `command`
action inherits. It is `Type=simple` for now; it becomes `Type=dbus` with a
`BusName` once M4 adds the D-Bus service. The udev rule and the
`modules-load.d` fragment live in `data/` and are installed by `make install`
(under `PREFIX/lib/...`, which udev and systemd both search).