Commit Graph

495 Commits

Author SHA1 Message Date
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
07cc40f6ba fix(input-capture): don't drop events after TCC Accessibility revocation
When the user revokes Accessibility in System Settings while Lan
Mouse is in a captured session (cursor on a remote client), the
session-level event tap receives `TapDisabledByUserInput`. The
previous flow was:

  1. Callback sends `ProducerEvent::EventTapDisabled` to notify_tx.
  2. Callback falls through the `current_pos.is_some()` branch and
     returns `CallbackResult::Drop` — *this very event*, plus any
     racing callback still in flight, get `set_type(Null)`'d and
     consumed.
  3. Outer task calls `handle_producer_event(..).unwrap_or_else(|e|
     log::error!(..))` — the `EventTapDisabled` error is just logged,
     the loop keeps running, `current_pos` stays `Some`, cursor
     stays hidden.

Net effect for the user: mouse motion keeps working (pass-through
when the tap is fully dead), but clicks and keypresses in the brief
disable window silently disappear, and the cursor is still hidden
where the captured session left it. Input looks broken until the
app is force-quit.

Fix:
- In the callback, when `TapDisabled*` fires, clear `current_pos`
  and `CGDisplay::show_cursor` synchronously, then return
  `CallbackResult::Keep` so this event (and any subsequent racing
  one) can't hit the drop branch.
- Mirror the cleanup in `handle_producer_event`'s
  `EventTapDisabled` arm so even if the outer task only logs the
  error, state is still released.

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
2dc9ebb6cd macos: hide Reenable warning row once Accessibility is granted
The yellow "input capture is disabled" / "input emulation is disabled"
rows were showing simultaneously with the Relaunch toast after a
live AX grant, double-prompting the user for the same action.

Gate the warning row visibility on Accessibility actually being
missing: when AX is granted but capture/emulation remain inactive,
we're in the pending-relaunch state and the Relaunch toast is the
authoritative prompt. Trigger a status refresh from the AX watcher
so the rows hide the instant AX flips to granted, not when the
daemon next reports status.

On non-macOS platforms the visibility logic is unchanged.

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
cbdb86ce05 fix: write a default config.toml on every Config::new()
The previous fix created the config directory but left config.toml
absent on a first launch, which surfaced as two "No such file or
directory (os error 2)" warnings (one from the main process, one
from the spawned daemon child) and left the app starting up with
config_toml=None until the GUI persisted something.

Write ConfigToml::default() to the path if it doesn't exist, so
every entry point — GUI main, spawned daemon, CLI, test commands
— gets a concrete file to read, and first-launch logs stay clean.

Also reorders Config::new() so both the directory creation and the
file bootstrap run before the first read attempt, eliminating the
warning at the source rather than hiding it.

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
Jon Kinney
903b0504e0 macos: run as LSUIElement menubar app with NSStatusItem
Ship Lan Mouse on macOS as an accessory app (no Dock icon, no main
window on launch) with a status-bar item for show/quit. Closing the
window hides it instead of quitting so the menu bar stays the primary
surface.

- build-aux/macos-lsui-element.plist: LSUIElement=true plus
  NSInputMonitoringUsageDescription and NSAppleEventsUsageDescription
  (merged into Info.plist via cargo-bundle's osx_info_plist_exts).
- lan-mouse-gtk/src/macos_status_item.rs: NSStatusItem setup via raw
  objc_msgSend FFI. Loads a bundled 22pt PNG as a template image so
  it auto-tints for light/dark menu bars.
- scripts/makeicns.sh: emit Contents/Resources/menubar-template.png
  from the existing SVG.
- scripts/copy-macos-dylib.sh: flatten cargo-bundle's preserved
  target/ subdir under Resources so NSBundle pathForResource: finds
  the template image.
- lan-mouse-gtk/src/lib.rs: register the new modules, set up a
  Cmd+Q-wired quit action, configure bundle env vars (schemas,
  XDG_DATA_DIRS, GTK_DATA_PREFIX) when running from inside the .app,
  and filter the known upstream Gtk theme-parser warning spam.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
c40e10505b fix: create ~/.config/lan-mouse/ on first launch
notify::Watcher fails on macOS if the config directory doesn't
exist. Create it with create_dir_all before calling config.watch().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
f858a7de00 makeicns.sh: produce Big Sur+ style macOS app icon
The previous script generated a 1024x1024 icon from the SVG with no
squircle background and no transparent padding, which caused macOS
to render the Dock/Finder icon noticeably larger than first-party
apps and without the rounded-square shape users expect.

Rewrite the script to follow Apple's Big Sur+ icon template:

  - 1024x1024 canvas
  - 824x824 white squircle, centered (100px transparent padding outside)
  - Artwork rendered at 560x560, centered inside the squircle
  - Squircle corner radius ~22.5% of the squircle size

Use rsvg-convert to rasterize the SVG (ImageMagick crops this
particular SVG when rendering directly), then composite onto the
squircle background in two steps for reliability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Jon Kinney
e6cd1630b2 bundle Adwaita symbolic icons in gresource
On macOS the Adwaita icon theme is not installed by default, so
symbolic icons (edit-copy, auth-fingerprint, network-wired,
dialog-warning, etc.) render as the "image-missing" placeholder.

Bundle the symbolic SVGs used by the GTK frontend into the embedded
gresource so the app is self-contained and doesn't depend on any
system-installed icon theme. The existing
`IconTheme::add_resource_path("/de/feschber/LanMouse/icons")` call
already tells GTK to search this prefix, so no code changes are
needed.

Icons are sourced from Adwaita and placed under the standard
`scalable/{actions,devices,places,status}/` hicolor layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 22:59:43 +02:00
Ferdinand Schober
a878c985f0 automatically update config when changed (#402) 2026-04-09 12:04:21 +02:00
Ferdinand Schober
aef05f386f fix: config initialization in authorize 2026-04-08 17:43:25 +02:00
Ferdinand Schober
38920917cd update ashpd main 2026-04-08 13:11:18 +02:00
Ferdinand Schober
1075a90c5b update dependencies 2026-04-08 13:00:36 +02:00
Ferdinand Schober
2e1b5278ce fix: README badge 2026-03-25 13:36:57 +01:00
Jon Stelly
4d8f7d7813 chore: developer experience - pre-commit hook, ai instructions, yaml formatting (#374)
* chore: developer experience - pre-commit hook, ai instructions, yaml formatting for prettier

* no prettierrc, editorconfig instead

* fixes from copilot suggestions

---------

Co-authored-by: Ferdinand Schober <ferdinandschober20@gmail.com>
2026-03-25 13:34:17 +01:00
Ferdinand Schober
0ef8edb7b2 update checkout and upload-artifact actions 2026-03-25 13:31:20 +01:00
Jon Stelly
3eba50a0d3 feat: workflow and build updates (#372)
* feat: workflow and build updates

- macos 15 runners
- add linux arm build
- combine pre-release and tagged-release workflows

* remove old release job

* rename x86-64 -> x86_64

* rename arm -> arm64

* downgrade arm runner to ubuntu-22.04

---------

Co-authored-by: Ferdinand Schober <ferdinandschober20@gmail.com>
Co-authored-by: Ferdinand Schober <ferdinand.schober@fau.de>
2026-03-25 13:09:28 +01:00
Ferdinand Schober
9540739d89 add cancel in progress for CI 2026-03-25 12:44:59 +01:00
Ferdinand Schober
810e25a7fc Fix CI (#400)
apparently inkscape is now required on macos
2026-03-25 11:46:35 +01:00
Ferdinand Schober
9af5f9452e fix icon build (#399) 2026-03-24 15:06:03 +01:00
Ferdinand Schober
7fa3d2fafd update makeicns.sh 2026-03-24 14:02:16 +01:00
onelock
cd9fc43af4 fix: nix evaluation warnings + flake improvements (#395)
* fix: nix evaluation warning

* nix: minor flake improvements/maintenance

* nix: fix macos build errors

* nix: minor cleanup/fixes

* nix: remove redundant deps

* nix: remove reliance on systems input
2026-03-24 12:55:31 +01:00
Ty Smith
27225ed564 fix(macos): forward back/forward mouse buttons in capture and emulation (#392)
* fix(macos): forward back/forward mouse buttons in capture and emulation

OtherMouseDown/Up events on macOS carry a button number field that
distinguishes middle (2), back (3), and forward (4) buttons. The
capture backend was unconditionally mapping all OtherMouse events to
BTN_MIDDLE, silently dropping back/forward. The emulation backend had
no match arms for BTN_BACK/BTN_FORWARD, causing them to be dropped
with a warning.

Fix capture by reading MOUSE_EVENT_BUTTON_NUMBER and mapping 3->BTN_BACK,
4->BTN_FORWARD. Fix emulation by adding match arms for BTN_BACK/BTN_FORWARD
and setting MOUSE_EVENT_BUTTON_NUMBER on the emitted CGEvent so macOS
apps receive the correct button identity.

* fix(macos): track button state and double-clicks by evdev code instead of CGMouseButton

Back, forward, and middle buttons all map to CGMouseButton::Center on
macOS, which caused them to share a single pressed-state boolean and
alias in double-click detection. Replace the ButtonState struct with a
HashSet<u32> keyed by evdev button code so each button is tracked
independently.

---------

Co-authored-by: Ferdinand Schober <ferdinandschober20@gmail.com>
2026-02-22 17:45:53 +01:00
Kenichi Nakamura
bcf9c35301 Fix stuck modifiers (#385)
fixes #357
2026-02-22 17:45:14 +01:00
Ferdinand Schober
e8ff3957df CI: fix cargo build command 2026-02-20 16:45:42 +01:00
Ferdinand Schober
466fe4b3bd update cachix and disable magic nix-cache (#393)
magic nix cache seems to hang forever.
2026-02-20 16:43:57 +01:00
Peter Hutterer
ad63b6ba20 Handle the RemoteDesktop portal restore token correctly (#383)
For a session to actually persist, we need to request a persistence mode
which we already do. The portal then returns a restore-token (in the
form of an uuid) to us as part of the response to Start.

This token must then be passed into the *next* session during
SelectDevices to restore the previous session.

The token is officially a single-use token, so we need to overwrite it
every time. In practise the current XDP implementation may re-use the
token but we cannot rely on that.

Reading and writing the token is not async since we expect them to be
uuid-length.

Closes #74.
2026-02-11 13:27:32 +01:00
Ferdinand Schober
e80625648e build releases on ubuntu 22.04 (#382) 2026-02-10 07:29:45 +01:00
Ferdinand Schober
96c63374d0 rust.yml: run fmt/build/check/test separately (#375) 2026-02-08 16:54:42 +01:00
Ferdinand Schober
b8fdbb35ac fix: build failure in lan-mouse-ipc standalone 2026-02-08 14:22:46 +01:00
Ferdinand Schober
5d5f4bbe6f fix: build failure in input-capture standalone 2026-02-08 14:19:47 +01:00
Ferdinand Schober
8e96025f12 clear config, when unable to parse 2026-02-08 13:27:38 +01:00
Ferdinand Schober
f01459b2a8 fix crash when config file does not exist 2026-02-08 13:23:29 +01:00
Ferdinand Schober
394c018e11 ad fixme for memory leak 2026-02-08 13:14:11 +01:00
Ferdinand Schober
648b2b58a4 Save config (#345)
* add setters for clients and authorized keys

* impl change config request

* basic saving functionality

* save config automatically

* add TODO comment
2026-02-07 18:36:07 +01:00
Ferdinand Schober
a987f93133 Update systemd service instructions
closes #298
2026-02-07 13:23:09 +01:00
Jon Stelly
0d96948c26 fix: remote key-up on triggered release (#371)
Fixes #369
2026-02-06 16:06:19 +01:00
Ferdinand Schober
7863e8b110 CI: update macos runners 2026-02-06 15:32:56 +01:00
Ferdinand Schober
708a40d0da macos: fix memory leak
probably should use an AutoreleasePool as well
2026-02-06 15:29:24 +01:00
skifli
3922b45bd9 feat: add binary cache to instructions (#353) 2025-12-04 12:09:10 +01:00
Ferdinand Schober
640fa995a4 improve reliability of connections (#349) 2025-11-03 18:04:18 +01:00
Ferdinand Schober
bdafaa07e5 macos: fix scroll capture (#350) 2025-11-03 18:04:09 +01:00
NeoTheFox
3f13714d8a Add rustfmt.toml for explicit styling (#348)
* Propose an explicit .rustfnt.toml
Use 2024 style, 4 spaces for tabs and epand the default width a tad

* Auto-format the existing code with new rules
2025-11-02 11:52:01 +01:00
Ferdinand Schober
3483d242e2 fix inconsistent mouse capture on macos (#346) 2025-10-31 14:43:28 +01:00
Ferdinand Schober
35773dfd07 macos: fix modifier capture (#342) 2025-10-30 20:16:27 +01:00
Ferdinand Schober
f91b6bd3c1 macos: reset double click when mouse is moved (#341) 2025-10-30 00:48:24 +01:00
Ferdinand Schober
2d1a037eba macos: fix duplicated key release event (#340) 2025-10-29 18:37:24 +01:00