Skip to content

feat(linux): launch_at_login + input device access permission check#172

Open
cserby wants to merge 7 commits into
AprilNEA:masterfrom
cserby:story/linux-packaging/udev-install
Open

feat(linux): launch_at_login + input device access permission check#172
cserby wants to merge 7 commits into
AprilNEA:masterfrom
cserby:story/linux-packaging/udev-install

Conversation

@cserby

@cserby cserby commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • launch_at_login on Linux — writes/removes a systemd user unit at $XDG_CONFIG_HOME/systemd/user/openlogi-agent.service. Mirrors the macOS LaunchAgent behaviour exactly: Restart=on-failure (crash respawns; clean exit(0) stays stopped), WantedBy=graphical-session.target (takes effect at next login). Unit content is compared before writing (idempotent). systemctl --user enable/disable is best-effort — failures are logged, not propagated.
  • Input device access permission check on LinuxSettings → Permissions now shows a Linux-specific row ("Input device access") backed by probes of /dev/uinput (write) and /dev/hidraw* (read/write, Logitech vendor verified numerically from sysfs HID_ID). Returns Granted / Denied / Unknown (unknown = uinput accessible but no Logitech device connected yet). Description is shown only when access is not yet granted.
  • macOS-only UI hidden on Linux — accessibility footer, permission "Open" button, and "log in to macOS" copy are all gated to macOS.

Test plan

  • cargo test -p openlogi-agent -p openlogi-gui passes on Linux (7 new unit tests in launch_agent.rs, 4 in permissions.rs)
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo fmt --check clean
  • Toggle "Launch at login" → ~/.config/systemd/user/openlogi-agent.service appears; systemctl --user is-enabled openlogi-agent reports enabled; toggle off → file removed, disabled
  • Settings → Permissions shows "Granted" with udev rules installed; "Not granted" without
  • No macOS-specific text or buttons visible in Linux GUI build

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Linux support for two features: launch_at_login (via a systemd user unit at $XDG_CONFIG_HOME/systemd/user/openlogi-agent.service) and an input-device access permission check (probing /dev/uinput write access and Logitech /dev/hidraw* read/write access via sysfs vendor ID). macOS-only UI elements (accessibility footer, permission "Open" button, login copy) are correctly gated behind #[cfg(target_os = "macos")].

  • launch_agent.rs: Adds reconcile_linux that writes/removes the unit file and calls systemctl --user enable/disable; includes escape_systemd_exec for %/$/space/quote handling and 7 unit tests.
  • permissions.rs: Adds probe_uinput, probe_logitech_hidraw, and a pure classify function with 4 unit tests; input_device_access() is the new Linux-facing API.
  • settings.rs / app.rs: Refactors the permissions page to build platform-specific groups at compile time; Linux shows a single "Input device access" row with contextual hint text.

Confidence Score: 4/5

Safe to merge for new installs; existing Linux users who have manually run systemctl disable may find the launch-at-login setting silently ineffective after restart.

The reconcile_linux function is called at agent startup (main.rs:92) as well as on setting change. When the unit file already has the correct content, it skips both daemon-reload and systemctl enable. On Linux a service file's presence alone does not mean the unit is enabled — the wants/ symlink must also exist. A user who manually disables the unit (without deleting the file) will have the app setting silently ignored on every subsequent restart until they toggle the setting off and on in the UI.

crates/openlogi-agent/src/launch_agent.rs — the want == have arm of reconcile_linux and the backslash handling in escape_systemd_exec

Important Files Changed

Filename Overview
crates/openlogi-agent/src/launch_agent.rs Adds Linux systemd user-unit reconcile with write/remove, daemon-reload, and enable/disable. The content-match shortcut skips systemctl enable, which can leave the unit disabled after a manual systemctl disable since reconcile is also called at startup. Also missing backslash escaping in the quoted ExecStart path.
crates/openlogi-core/src/paths.rs Extracts xdg_config_home() from the existing xdg_base logic; refactors config_dir() to call it. Functionally equivalent to the previous implementation and correctly rejects relative XDG_CONFIG_HOME values per spec.
crates/openlogi-gui/src/platform/permissions.rs Adds Linux permission probes for /dev/uinput and /dev/hidraw* with Logitech VID sysfs check. Logic is clean and well-tested; classify is correctly exhaustive. Minor: input_device_access() is called per render frame in settings.rs.
crates/openlogi-gui/src/windows/settings.rs Gates macOS-only UI (permission items, "Open" button, accessibility footer) behind #[cfg(target_os = "macos")]; adds Linux input-device-access row with inline hint text. The permission probe runs on every render pass rather than being cached.
crates/openlogi-gui/src/app.rs Gates accessibility_status function and its call site behind #[cfg(target_os = "macos")]; non-macOS builds render an empty div instead. Clean and straightforward platform guard.
crates/openlogi-gui/locales/en.yml Adds "Input device access" translation key for the new Linux permissions row.

Sequence Diagram

sequenceDiagram
    participant Agent as openlogi-agent (startup)
    participant RL as reconcile_linux(enabled)
    participant FS as Filesystem
    participant SC as systemctl --user

    Agent->>RL: reconcile(launch_at_login)
    RL->>FS: read unit file (current)
    RL->>RL: render_unit(current_exe)

    alt content differs or file absent
        RL->>FS: create_dir_all + write unit file
        RL->>SC: daemon-reload
        RL->>SC: enable openlogi-agent.service
    else content matches (idempotent skip)
        RL-->>Agent: no-op (unit may still be disabled!)
    end

    Note over RL,SC: disable path
    alt "enabled=false, file exists"
        RL->>SC: disable openlogi-agent.service
        RL->>FS: remove unit file
        RL->>SC: daemon-reload
    end
Loading

Fix All in Codex Fix All in Claude Code

Reviews (6): Last reviewed commit: "fix(gui,i18n): add missing locale keys; ..." | Re-trigger Greptile

Comment thread crates/openlogi-gui/src/platform/permissions.rs Outdated
Comment thread crates/openlogi-gui/src/windows/settings.rs Outdated
Comment thread crates/openlogi-gui/src/windows/settings.rs Outdated
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 8, 2026
- Remove misplaced sysfs comment above the open() call in
  probe_logitech_hidraw (the sysfs check is in is_logitech_hidraw)
- Remove dead #[cfg(not(target_os = "macos"))] suppressor inside the
  already-macOS-gated permission_field function
- Split Denied/Unknown description text: Unknown means uinput is
  accessible but no Logitech device is connected, so point the user
  at the device rather than the udev install guide

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 8, 2026
- escape_systemd_exec: double $ → $$ to prevent systemd variable
  substitution in ExecStart paths containing a literal dollar sign
- paths: add pub xdg_config_home() that returns the raw XDG config base
  without the openlogi sub-directory; refactor config_dir() to call it
- unit_path: use xdg_config_home() directly instead of relying on the
  fragile .parent() traversal from config_dir()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cserby and others added 6 commits June 10, 2026 08:42
Reconcile the agent's autostart state on Linux by writing/removing a
systemd user unit at $XDG_CONFIG_HOME/systemd/user/openlogi-agent.service.
Mirrors the macOS LaunchAgent semantics exactly:

- Restart=on-failure (crash respawns; clean exit(0) stays stopped)
- WantedBy=graphical-session.target (takes effect at next login)
- ExecStart escaped for systemd (% doubled, spaces quoted)
- Idempotent write/remove — only touches disk when content changes
- systemctl --user daemon-reload + enable/disable best-effort

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a Linux-specific permission probe and settings page row:

- probe_uinput(): checks write access to /dev/uinput
- probe_logitech_hidraw(): iterates /dev/hidraw*, confirms Logitech
  vendor (HID_ID sysfs field parsed numerically — 0000046D matches 046d)
- classify(uinput_ok, hidraw_ok): pure function → Granted/Denied/Unknown
- Settings → Permissions shows one "Input device access" row on Linux
  with description only when access is not yet granted (no noise when
  everything works)
- macOS permission rows and helpers gated #[cfg(target_os = "macos")]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Accessibility footer in the main window hidden on Linux
- "Open" button in permission rows gated to macOS only
- Launch-at-login description no longer says "log in to macOS"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove misplaced sysfs comment above the open() call in
  probe_logitech_hidraw (the sysfs check is in is_logitech_hidraw)
- Remove dead #[cfg(not(target_os = "macos"))] suppressor inside the
  already-macOS-gated permission_field function
- Split Denied/Unknown description text: Unknown means uinput is
  accessible but no Logitech device is connected, so point the user
  at the device rather than the udev install guide

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- escape_systemd_exec: double $ → $$ to prevent systemd variable
  substitution in ExecStart paths containing a literal dollar sign
- paths: add pub xdg_config_home() that returns the raw XDG config base
  without the openlogi sub-directory; refactor config_dir() to call it
- unit_path: use xdg_config_home() directly instead of relying on the
  fragile .parent() traversal from config_dir()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added missing translations for the Linux permission row label introduced
in this PR. Follows the same pattern as 'Input Monitoring'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cserby cserby force-pushed the story/linux-packaging/udev-install branch from 92efd48 to 3d04d64 Compare June 10, 2026 06:48
… scope

- Add 'Unifying receiver' and 'Input device access' keys to en.yml
  (all locale files must match en.yml for the i18n test)
- Add 'Ricevitore Unifying' to it.yml for key parity
- Move InteractiveElement import outside #[cfg(target_os = "macos")]
  so on_action() calls for CloseWindow/Minimize/Zoom work on Linux
- Fix rustfmt line-wrap in launch_agent::unit_path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cserby cserby force-pushed the story/linux-packaging/udev-install branch from 414f761 to c9bc3df Compare June 10, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant