Deamon management
This commit is contained in:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user