mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-05-08 07:08:05 +03:00
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>
This commit is contained in:
committed by
Ferdinand Schober
parent
5d7d14fbf7
commit
07cc40f6ba
@@ -176,7 +176,17 @@ impl InputCaptureState {
|
||||
}
|
||||
self.active_clients.remove(&p);
|
||||
}
|
||||
ProducerEvent::EventTapDisabled => return Err(CaptureError::EventTapDisabled),
|
||||
ProducerEvent::EventTapDisabled => {
|
||||
// Tap death can happen mid-capture (TCC Accessibility
|
||||
// revoked, tap-timeout, etc). Release state so we
|
||||
// don't leave the cursor hidden even if the outer
|
||||
// task only logs this error rather than propagating.
|
||||
if self.current_pos.is_some() {
|
||||
self.show_cursor()?;
|
||||
self.current_pos = None;
|
||||
}
|
||||
return Err(CaptureError::EventTapDisabled);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@@ -413,12 +423,27 @@ fn create_event_tap<'a>(
|
||||
event_type,
|
||||
CGEventType::TapDisabledByTimeout | CGEventType::TapDisabledByUserInput
|
||||
) {
|
||||
log::error!("CGEventTap disabled");
|
||||
// When the tap is disabled (including the case where TCC
|
||||
// Accessibility is revoked mid-session), we MUST drop
|
||||
// captured state synchronously and return Keep on this
|
||||
// event. Otherwise the `current_pos.is_some()` branch
|
||||
// below would drop this event (and any racing callback
|
||||
// still in flight) back into `CallbackResult::Drop`,
|
||||
// silently eating the user's clicks and keypresses while
|
||||
// the tap winds down. Clear state + show the cursor
|
||||
// here, then notify the producer loop so the service
|
||||
// can tear down cleanly.
|
||||
log::error!("CGEventTap disabled, releasing capture state");
|
||||
if state.current_pos.is_some() {
|
||||
let _ = CGDisplay::show_cursor(&CGDisplay::main());
|
||||
state.current_pos = None;
|
||||
}
|
||||
notify_tx
|
||||
.blocking_send(ProducerEvent::EventTapDisabled)
|
||||
.unwrap_or_else(|e| {
|
||||
log::error!("Failed to send notification: {e}");
|
||||
});
|
||||
return CallbackResult::Keep;
|
||||
}
|
||||
|
||||
// Are we in a client?
|
||||
|
||||
Reference in New Issue
Block a user