Address @feschber review feedback on PR #441. The repeat-task cleanup
already releases the key with the correct CGKeyCode via the existing
key_event call at the end of the closure — this commit just expands
the surrounding comment to make that explicit and to document why
update_modifiers is intentionally NOT called from this path (Mac
CGKeyCode vs Linux evdev scancode collision).
No behavioral change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
spawn_repeat_task() takes a Mac CGKeyCode, but the cleanup block was
passing that value to update_modifiers(), which expects a Linux evdev
scancode (it calls scancode::Linux::try_from(key)). The two codespaces
collide on several values, so cancelling the repeat task could
silently clear a still-held modifier:
Mac LeftShift = 56 == Linux KeyLeftAlt = 56 -> clears Mod1Mask
Mac Down arrow = 125 == Linux KeyLeftMeta = 125 -> clears Mod4Mask
Mac Up arrow = 126 == Linux KeyRightMeta = 126 -> clears Mod4Mask
Mac Backslash = 42 == Linux KeyLeftShift = 42 -> clears ShiftMask
Mac "9" = 29 == Linux KeyLeftCtrl = 29 -> clears ControlMask
In practice this broke chords such as Shift+Option+X and Cmd+Down:
pressing Shift while holding Option cancels Option's repeat task and
runs the buggy cleanup, which then interprets Mac LeftShift's code
(56) as Linux KeyLeftAlt and removes Option from the modifier state.
The next key arrives with Shift only, so window-manager bindings on
the original Option chord never fire.
Remove the buggy update_modifiers() call. Modifier state is owned by
the main consume() loop, which already calls update_modifiers() with
the correct Linux scancode on the real release event.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hardware-generated arrow key events on macOS carry the NumericPad and
SecondaryFn flags in addition to any user-pressed modifiers. CGEventTap-
based hotkey matchers (tiling window managers, accessibility tools, etc.)
commonly check those flags to disambiguate navigation arrows from generic
chords, and reject events that lack them.
Before this change, synthesized Option+Arrow chords were silently
swallowed by the focused application instead of being captured by the
window manager, because the events arrived with only the Alternate flag
set. Hardware Option+Arrow chords on the local keyboard worked because
the OS itself set the missing flags.
Add NumericPad + SecondaryFn to the flags posted with arrow key events
(Mac key codes 0x7B-0x7E) so synthesized arrow chords match hardware
chords on the wire.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
* 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>
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.
Using niri as compositor on both sides resulting in: emulation=wlroots capture=layer-shell
Mouse scrolling works fine in terminals, but only scrolls very small amount in google-chrome (in wayland mode)
Using 'wev' to show events, using the real mouse shows
[ 15: wl_pointer] axis_source: 0 (wheel)
[ 15: wl_pointer] axis_value120: axis: 0 (vertical), value120: 120
[ 15: wl_pointer] axis_relative_direction: axis: 0 (vertical), direction: 0
[ 15: wl_pointer] axis: time: 50410752; axis: 0 (vertical), value: 15.000000
Using the lan-mouse shows:
[ 15: wl_pointer] axis_source: 2 (continuous)
[ 15: wl_pointer] axis_value120: axis: 0 (vertical), value120: 1
[ 15: wl_pointer] axis_relative_direction: axis: 0 (vertical), direction: 0
[ 15: wl_pointer] axis: time: -1913142096; axis: 0 (vertical), value: 20.000000
Without axis_source, scrolling over (pinned) tabs also skips one tab.
* Input capture and emulation can now be disabled and will prompt the user to enable again.
* Improved error handling to deliver more useful error messages