Commit Graph

8 Commits

Author SHA1 Message Date
Jon Kinney
e5862e10e3 style: apply cargo fmt
No behavior changes. Brings three files back in line with the
project's `style_edition = "2024"` rustfmt config so subsequent
edits don't carry unrelated formatting in their diffs.
2026-04-29 22:59:43 +02:00
Jon Kinney
10fd728804 macos: default to showing the window on every launch
Replaces the narrow LAN_MOUSE_RELAUNCHED signal with an inverted
opt-out: present the main window on every macOS launch unless
LAN_MOUSE_HIDDEN=1 is in the environment.

User feedback: on any manual launch (Dock, Finder, `open`, post-
grant relaunch) the window should come up so the user has a visible
confirmation the app is alive and can see its current state. A
hidden-to-menu-bar-only launch should be opt-in for a LaunchAgent /
login-item configuration, not the default.

LaunchAgent plists can set the env via `EnvironmentVariables` and
`RunAtLoad=true` for a quiet boot launch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
99344a3104 macos: present the window on the post-grant relaunch
After the user clicks Relaunch on the warning row, the new instance
started hidden in the menu bar — no visible confirmation that the
relaunch actually worked. Present the window on that specific
launch so the user sees the app come up healthy.

Mechanism: relaunch_bundle() sets LAN_MOUSE_RELAUNCHED=1 via
`open --env` when spawning the new instance. build_ui reads the
env var and calls window.present() only when it's set. Normal
fresh launches (from Finder / Dock / Launchpad / any other
Launch Services path) continue to start hidden in the menu bar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
94e9301e9c macos: quit immediately when Accessibility is revoked mid-session
Even with the earlier event-tap-callback cleanup fix, revoking
Accessibility in System Settings while Lan Mouse was running with
an active capture tap could still leave system input wedged — clicks
and keypresses silently dropped until the app was force-quit.

The reliable fix is to not rely on in-process tap teardown at all.
When AX is revoked:

- The kernel guarantees an active CGEventTap is dismantled when the
  owning process exits.
- SIGINT+wait on the daemon child (main.rs already does this on
  GUI exit) drops the daemon's tap and restores the cursor.

So: continuously poll AX state (1-second GLib timer, replacing the
one-shot grant watcher), and on a revoke transition call
`app.quit()`. Input is restored within ~1-2 seconds regardless of
capture state — no force-quit required, no stuck cursor, no silently
consumed events beyond the brief window until the poller fires.

The grant-transition case is preserved: on a 0→1 flip the warning
row swaps to its "relaunch required" state, same as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
5d7d14fbf7 macos: fold relaunch prompt into the warning row instead of a toast
The cut-off toast UX ("Accessibility granted. Relaunch Lan Mouse so
capture and emulat…") was unreadable in a compact window and split
the "grant" and "relaunch" flows into two disconnected surfaces. Fold
everything into the existing warning row with state-dependent content:

- AX missing:
    title    = "input capture is disabled"
    subtitle = "grant Accessibility permission to enable"
    button   = "Grant"   → opens System Settings → Accessibility

- AX granted, daemon still bailed:
    title    = "relaunch required"
    subtitle = "Accessibility granted — restart to activate capture
                and emulation"
    button   = "Relaunch" → spawns a fresh bundle via `open` after
                            a 1s delay, then quits.

- Both active: row hidden.

The emulation_status_row is kept hidden on macOS because capture and
emulation share the same TCC gate — a single row is sufficient and
two identical-looking warnings were noisy. `handle_emulation` still
exists for the non-macOS platforms where the rows are distinct.

Side effects:
- `relaunch_bundle` moved from lib.rs to macos_privacy so imp.rs can
  call it from the row button handler.
- AX watcher callback shrinks to `window.present()` +
  `refresh_capture_emulation_status()`; the toast-based dialog is
  gone along with its helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
8a444f98dd macos: drop the CapturePane / EmulationPane enums
Now that we always route Reenable clicks to the Accessibility pane on
macOS 13+ (AX transitively covers Input Monitoring listen-only and
Post Event, and the bundle isn't listed in the separate panes anyway),
the CapturePane / EmulationPane enums and their non-Accessibility
variants are dead weight. Remove them along with:

- `missing_capture_pane` / `missing_emulation_pane`
- `open_input_monitoring_settings` / `open_post_event_settings`
- `input_monitoring_granted` / `post_event_granted` preflight wrappers
- the `CGPreflightListenEventAccess` / `CGPreflightPostEventAccess`
  FFI declarations in lan-mouse-gtk (the daemon crates keep their own)

`handle_capture` / `handle_emulation` collapse to a single helper that
opens the Accessibility pane if AX is missing, otherwise just retries.
`ensure_listed_in_input_monitoring` is kept because it still has a
side effect on macOS 13/14, where Input Monitoring is a separately-
granted category.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
b3cade9bac macos: prompt to relaunch after live Accessibility grant
The daemon subprocess initializes at startup and bails immediately if
Accessibility is missing ("accessibility permission is required"). If
the user then grants AX mid-session via the system prompt, the daemon
has no way to retry from its bailed state — capture and emulation
stay dead until the next restart. Make the GUI watch for the AX
transition and surface a toast with a "Relaunch" button that quits
the app and spawns a fresh instance via Launch Services.

While here:
- Route capture/emulation "missing pane" fallback to the Accessibility
  pane instead of the Input Monitoring / Post Event panes when AX is
  already granted. On macOS 13+ those separate grants auto-confer via
  Accessibility and the bundle typically isn't listed in the IM pane
  at all, so the old navigation was a dead end.
- Reword the status-row subtitles so the action is clearer: the user
  now sees "click Reenable to grant permission" instead of a generic
  "required for outgoing connections".
- Bump libadwaita feature flag to v1_2 for AdwToast button signals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
5e79743bd0 macos: per-pane TCC navigation and Sequoia-tolerant permission flow
On macOS the three TCC grants (Accessibility, Input Monitoring, Post
Event) live in separate Privacy panes. Before this change the
"Reenable" row sent the user to Accessibility regardless of which
grant was actually missing, and the daemon's own permission checks
re-fired the Accessibility prompt on every retry.

- lan-mouse-gtk/src/macos_privacy.rs: new module that exposes silent
  preflight checks (AXIsProcessTrusted, CGPreflightListenEventAccess,
  CGPreflightPostEventAccess), per-pane URL-scheme navigation, and
  a Once-guarded fire_initial_prompts() called from build_ui. The
  initial-prompt path only fires the Accessibility prompt if AX is
  missing and then returns; secondary registrations run only after
  AX is granted, which prevents a double Accessibility alert on
  Sequoia where Post Event is nested under Accessibility.
- Input Monitoring registration attempts CGEventTapCreate at
  kCGSessionEventTap (not kCGHIDEventTap) so a failure surfaces as
  an Input Monitoring signal rather than triggering an Accessibility
  prompt as a side effect.
- lan-mouse-gtk/src/window/imp.rs: handle_capture / handle_emulation
  switch on the missing-pane enum and navigate to the specific pane
  via x-apple.systempreferences:... URLs before re-requesting.
- lan-mouse-gtk/resources/window.ui: pill class on the Reenable
  buttons so the hover padding matches the rest of libadwaita.
- input-capture/src/macos.rs, input-emulation/src/macos.rs: make
  request_*_permission() a silent preflight (AXIsProcessTrusted /
  CGPreflightListenEventAccess / CGPreflightPostEventAccess), so the
  daemon no longer fires TCC prompts on retry — all prompting is
  owned by the GUI.
- input-capture/src/error.rs, input-emulation/src/error.rs: new
  error variants so the GUI can distinguish missing-AX from
  missing-IM / missing-PostEvent for pane routing.

Verified on macOS 15.5: first launch fires a single AX prompt;
second launch (AX granted) registers under Input Monitoring via the
session-tap attempt and requests Post Event. Sequoia auto-grants the
listen-only path via AX so the IM list may stay empty, which is the
intended OS behavior and no longer blocks capture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00