diff --git a/packaging/linux/install.sh b/packaging/linux/install.sh index fb71f8c19..451612376 100755 --- a/packaging/linux/install.sh +++ b/packaging/linux/install.sh @@ -87,12 +87,28 @@ echo "Installing udev rules …" sudo install -Dm644 "${SCRIPT_DIR}/udev/70-openlogi.rules" \ /etc/udev/rules.d/70-openlogi.rules +# The rule grants /dev/uinput with TAG+="uaccess", which logind applies in +# response to a device event. A host that has never loaded the uinput module has +# no such device, so the trigger below matches nothing and the node stays +# root-owned — the hook then cannot create its virtual device and button +# remapping silently does nothing. Load it now, and register it for every boot. +echo "Loading the uinput module …" +sudo install -Dm644 "${SCRIPT_DIR}/modules-load/openlogi.conf" \ + /etc/modules-load.d/openlogi.conf +if command -v modprobe >/dev/null 2>&1; then + sudo modprobe uinput || true +fi + if command -v udevadm >/dev/null 2>&1; then echo "Reloading udev rules …" sudo udevadm control --reload-rules sudo udevadm trigger --subsystem-match=hidraw sudo udevadm trigger --subsystem-match=input sudo udevadm trigger --subsystem-match=misc --attr-match=name=uinput 2>/dev/null || true + # trigger only queues the events. Without settle the script can return before + # udev has applied the uaccess ACLs, so the next thing the user does — start + # the agent — still hits EACCES on a correctly configured system. + sudo udevadm settle 2>/dev/null || true fi # ── systemd user unit ───────────────────────────────────────────────────────── diff --git a/packaging/linux/modules-load/openlogi.conf b/packaging/linux/modules-load/openlogi.conf new file mode 100644 index 000000000..1407af725 --- /dev/null +++ b/packaging/linux/modules-load/openlogi.conf @@ -0,0 +1,11 @@ +# Load uinput at boot for OpenLogi. +# +# 70-openlogi.rules grants access to /dev/uinput with TAG+="uaccess", which +# systemd-logind applies in response to a device event. A machine that has never +# loaded the uinput module has no such device: OPTIONS+="static_node=uinput" +# creates the node so that opening it can autoload the module, but the node is +# created with default permissions and no ACL, and opening it is precisely what +# the missing permission forbids. +# +# Loading the module makes the device appear, which lets the rule tag it. +uinput diff --git a/packaging/linux/nfpm-scripts/postinstall.sh b/packaging/linux/nfpm-scripts/postinstall.sh index 07dc65a83..09dfc1d29 100755 --- a/packaging/linux/nfpm-scripts/postinstall.sh +++ b/packaging/linux/nfpm-scripts/postinstall.sh @@ -1,11 +1,20 @@ #!/bin/sh set -eu +# /etc/modules-load.d/openlogi.conf loads uinput from the next boot on; load it +# now so the first session after installing works too. Without the module there +# is no uinput device for the rule's uaccess tag to apply to — the node exists +# via static_node= but stays root-owned, so the agent's hook cannot open it and +# button remapping silently does nothing while HID++ keeps working. +if command -v modprobe >/dev/null 2>&1; then + modprobe uinput || true +fi + # Reload udev rules and wait for the new uaccess tags to be applied. # udevadm trigger is asynchronous — settle ensures the tags are in place -# before the script exits so the agent can open /dev/hidraw* and the mouse's -# /dev/input/event* node immediately, even for a device connected before the -# install. +# before the script exits so the agent can open /dev/hidraw*, /dev/uinput and +# the mouse's /dev/input/event* node immediately, even for a device connected +# before the install. if command -v udevadm >/dev/null 2>&1; then udevadm control --reload-rules udevadm trigger --subsystem-match=hidraw diff --git a/packaging/linux/nfpm.yaml b/packaging/linux/nfpm.yaml index bb4ddb510..0fe7ebf54 100644 --- a/packaging/linux/nfpm.yaml +++ b/packaging/linux/nfpm.yaml @@ -47,6 +47,14 @@ contents: file_info: mode: 0644 + # ── uinput module load ────────────────────────────────────────────────────── + # The udev rule alone cannot grant /dev/uinput on a machine that has never + # loaded the module; see the file's own comment. + - src: packaging/linux/modules-load/openlogi.conf + dst: /etc/modules-load.d/openlogi.conf + file_info: + mode: 0644 + # ── systemd user unit ─────────────────────────────────────────────────────── - src: packaging/linux/systemd/openlogi-agent.service dst: /usr/lib/systemd/user/openlogi-agent.service diff --git a/packaging/linux/nixos-module.nix b/packaging/linux/nixos-module.nix index b10e1f4d9..c2a875a55 100644 --- a/packaging/linux/nixos-module.nix +++ b/packaging/linux/nixos-module.nix @@ -25,6 +25,14 @@ in environment.systemPackages = [ cfg.package ]; services.udev.packages = [ cfg.package ]; + # The udev rule grants /dev/uinput with TAG+="uaccess", which logind applies + # in response to a device event — and a host that has never loaded the + # uinput module has no such device, so the node stays root-owned and the + # agent cannot create the virtual device button remapping needs. The other + # packaging paths ship /etc/modules-load.d/openlogi.conf for this; on NixOS + # the equivalent is asking for the module here. + boot.kernelModules = [ "uinput" ]; + systemd.user.services.openlogi-agent = { description = "OpenLogi background agent"; wantedBy = lib.optionals cfg.launchAtLogin [ "graphical-session.target" ]; diff --git a/packaging/linux/uninstall.sh b/packaging/linux/uninstall.sh index 6e6749821..d2e623e9e 100755 --- a/packaging/linux/uninstall.sh +++ b/packaging/linux/uninstall.sh @@ -52,6 +52,9 @@ sudo rm -f "${BINDIR}/openlogi" "${BINDIR}/openlogi-desktop" \ echo "Removing udev rules …" sudo rm -f /etc/udev/rules.d/70-openlogi.rules +# Leave the module loaded: other software may be using it by now. Only stop +# asking for it at boot. +sudo rm -f /etc/modules-load.d/openlogi.conf if command -v udevadm >/dev/null 2>&1; then sudo udevadm control --reload-rules sudo udevadm trigger --subsystem-match=hidraw