Skip to content

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

Open
cserby wants to merge 10 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 10 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

Adds Linux support for two previously macOS-only features: launch_at_login (writes/removes a systemd user unit and calls systemctl --user enable/disable) and an input-device permission probe (/dev/uinput write + /dev/hidraw* read/write via sysfs vendor check). macOS-only UI elements (accessibility footer, "Open" button, platform copy) are correctly gated behind #[cfg(target_os = \"macos\")].

  • launch_agent.rs: New reconcile_linux path generates an idiomatic systemd unit file with correct Rust string-continuation formatting, idempotent content comparison, and best-effort systemctl calls. The escape_systemd_exec helper handles %, $, spaces, and \", but omits \\ (backslash), which systemd processes as an escape character in ExecStart.
  • permissions.rs: probe_uinput + probe_logitech_hidraw implement the tri-state Granted/Denied/Unknown classification; the pure classify() function is extracted and covered by four unit tests. The probes are called inside a SettingField::render callback, matching the existing macOS pattern but at higher per-frame syscall cost.
  • Locale files: 22 locales updated; most non-CJK languages carry the English fallback for "Input device access" (intentional placeholder).

Confidence Score: 5/5

Safe to merge; all changes are well-structured Linux additions with no regressions to existing macOS/Windows paths.

The Linux systemd and permission-probe implementations are logically correct and covered by 11 new unit tests. The two observations (incomplete backslash escaping in escape_systemd_exec and per-render filesystem work in probe_logitech_hidraw) are quality improvements rather than current defects — backslash in an executable path is essentially impossible in practice, and the performance concern only manifests while the Settings window is open.

crates/openlogi-agent/src/launch_agent.rs — the escape_systemd_exec helper

Important Files Changed

Filename Overview
crates/openlogi-agent/src/launch_agent.rs Adds Linux systemd user-unit reconcile path alongside the existing macOS/Windows implementations; escaping handles %, $, spaces and quotes but omits backslash, which systemd interprets as an escape character in ExecStart
crates/openlogi-gui/src/platform/permissions.rs Adds Linux-specific input device access probe (uinput + hidraw); classify() logic is correct and well-tested; probe_logitech_hidraw does filesystem work per render call which matches the existing macOS pattern
crates/openlogi-gui/src/windows/settings.rs Gates macOS-only UI (accessibility footer, Open button, platform copy) behind cfg, adds Linux-specific permissions item; clean split from the original shared function
crates/openlogi-core/src/paths.rs Extracts xdg_config_home() so systemd unit path can reuse the XDG_CONFIG_HOME resolution without the openlogi-specific subdirectory; correct refactor
crates/openlogi-gui/src/app.rs Correctly gates accessibility_status() behind #[cfg(target_os = "macos")] so the footer renders an empty div on Linux

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([reconcile enabled]) --> B{unit_path OK?}
    B -- Err --> Z([return Err])
    B -- Ok --> C{current_exe OK?}
    C -- Err --> Z
    C -- Ok --> D[render desired unit content\nor None if !enabled]
    D --> E[read current unit file\nok = Some, missing = None]
    E --> F{desired vs current}
    F -- Same content --> G([debug: already current])
    F -- Want to write --> H[create_dir_all parent]
    H --> I[fs::write unit file]
    I --> J[systemctl --user daemon-reload\nbest-effort]
    J --> K[systemctl --user enable\nbest-effort]
    K --> G
    F -- Want to remove --> L[systemctl --user disable\nbest-effort]
    L --> M[fs::remove_file]
    M -- Err --> Z
    M -- Ok --> N[systemctl --user daemon-reload\nbest-effort]
    N --> G
    F -- Both absent --> O([debug: already absent])
Loading

Reviews (10): Last reviewed commit: "fix(gui): gate platform-specific imports..." | 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 added a commit to cserby/OpenLogi that referenced this pull request Jun 10, 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 10, 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 cserby force-pushed the story/linux-packaging/udev-install branch 3 times, most recently from c9bc3df to 811a693 Compare June 11, 2026 05:31
cserby added a commit to cserby/OpenLogi that referenced this pull request Jun 11, 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 11, 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 9 commits June 11, 2026 09:24
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>
… 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>
- Add StatefulInteractiveElement as _ to unconditional gpui import so
  on_click() resolves on macOS (fixes E0599)
- Gate status_badge with any(macos, linux) since both platforms call it
  (fixes Windows dead_code warning)
- Remove unreachable input_device_access stub for non-mac/non-linux
  targets; nothing calls it there (fixes Windows dead_code warning)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Upstream added Option<bool> accessibility_granted with a 3-state match.
Merge that with our Linux cfg structure:
- Take upstream's Some(true)/Some(false)/None match for accessibility
- Restore #[cfg(target_os = "macos")] on permission_field, permission_item,
  Permission import, and StatefulInteractiveElement import — all are
  macOS-only; gating them unconditionally caused dead_code / unused-import
  errors on Linux

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cserby cserby force-pushed the story/linux-packaging/udev-install branch from bee3c3f to aa95e08 Compare June 11, 2026 07:30
On Windows, PermissionStatus, classify, rgb, and the permissions module
are all dead — they only exist for macOS/Linux permission dialogs.
Gate each with the appropriate cfg so Windows clippy -D warnings passes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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