Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/rsnap/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ use winit::event_loop::ActiveEventLoop;
#[cfg(target_os = "macos")]
use winit::event_loop::EventLoopProxy;

#[cfg(target_os = "macos")]
use self::capture_host_macos::OverlayNativeCaptureInputBuffer;
#[cfg(target_os = "macos")]
use self::scroll_input_macos::ScrollInputObserverLifecycle;
#[cfg(target_os = "macos")]
Expand All @@ -45,7 +47,7 @@ use crate::settings::AppSettings;
use crate::settings_window::{SettingsWindow, SettingsWindowEntry};
use rsnap_overlay::OverlaySession;
#[cfg(target_os = "macos")]
use rsnap_overlay::{FrozenGlobalHotkey, MacOSCaptureHost, MacOSNativeCaptureInputEvent};
use rsnap_overlay::{FrozenGlobalHotkey, MacOSCaptureHost};

pub(crate) enum UserEvent {
TrayIcon,
Expand All @@ -60,7 +62,7 @@ pub(crate) enum UserEvent {
#[cfg(target_os = "macos")]
OverlayWorkerResponse,
#[cfg(target_os = "macos")]
OverlayNativeCaptureInput(u64, MacOSNativeCaptureInputEvent),
OverlayNativeCaptureInput,
}

#[cfg(target_os = "macos")]
Expand Down Expand Up @@ -160,6 +162,8 @@ struct App {
#[cfg(target_os = "macos")]
overlay_stream_event_pending: Arc<AtomicBool>,
#[cfg(target_os = "macos")]
overlay_native_capture_input_buffer: OverlayNativeCaptureInputBuffer,
#[cfg(target_os = "macos")]
latest_deferred_ocr_generation: Arc<AtomicU64>,
#[cfg(target_os = "macos")]
pending_deferred_ocr_generation: Arc<AtomicU64>,
Expand Down Expand Up @@ -280,6 +284,8 @@ impl App {
#[cfg(target_os = "macos")]
overlay_stream_event_pending: Arc::new(AtomicBool::new(false)),
#[cfg(target_os = "macos")]
overlay_native_capture_input_buffer: OverlayNativeCaptureInputBuffer::new(),
#[cfg(target_os = "macos")]
latest_deferred_ocr_generation: Arc::new(AtomicU64::new(0)),
#[cfg(target_os = "macos")]
pending_deferred_ocr_generation: Arc::new(AtomicU64::new(0)),
Expand Down
3 changes: 3 additions & 0 deletions apps/rsnap/src/app/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ impl App {

#[cfg(target_os = "macos")]
fn reset_capture_start_after_failure(&mut self) {
self.reset_overlay_native_capture_input_dispatch();
self.pending_deferred_ocr_generation.store(0, Ordering::Release);
self.scroll_input_shared_state.set_enabled(false);
self.scroll_input_shared_state.set_event_waker(None);
Expand Down Expand Up @@ -635,6 +636,7 @@ impl App {
let reset_started_at = Instant::now();

self.finish_coalesced_overlay_stream_frame_send();
self.reset_overlay_native_capture_input_dispatch();
self.scroll_input_shared_state.clear();

reset_started_at.elapsed().as_millis()
Expand Down Expand Up @@ -736,6 +738,7 @@ impl App {
#[cfg(target_os = "macos")]
{
self.teardown_overlay_capture_host();
self.reset_overlay_native_capture_input_dispatch();
self.unregister_overlay_cancel_hotkey();
self.unregister_overlay_loupe_hotkey();
self.unregister_overlay_frozen_hotkeys();
Expand Down
155 changes: 151 additions & 4 deletions apps/rsnap/src/app/capture_host_macos.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,130 @@
use std::sync::Arc;
use std::collections::VecDeque;
use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};

use crate::app::{App, UserEvent};
use rsnap_overlay::{MacOSCaptureHost, OverlayExit};
use rsnap_overlay::{MacOSCaptureHost, MacOSNativeCaptureInputEvent, OverlayControl, OverlayExit};

#[derive(Clone)]
pub(super) struct OverlayNativeCaptureInputBuffer {
queue: Arc<Mutex<VecDeque<(u64, MacOSNativeCaptureInputEvent)>>>,
event_pending: Arc<AtomicBool>,
}
impl OverlayNativeCaptureInputBuffer {
pub(super) fn new() -> Self {
Self {
queue: Arc::new(Mutex::new(VecDeque::new())),
event_pending: Arc::new(AtomicBool::new(false)),
}
}

fn enqueue(&self, generation: u64, event: MacOSNativeCaptureInputEvent) -> bool {
match self.queue.lock() {
Ok(mut queue) => queue.push_back((generation, event)),
Err(poisoned) => {
tracing::warn!(
op = "capture.native_input_queue_poisoned",
"Dropping native capture input event because the queue lock was poisoned."
);

poisoned.into_inner().clear();

return false;
},
}

self.event_pending
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}

fn finish_send(&self) {
self.event_pending.store(false, Ordering::Release);
}

fn drain(&self) -> Vec<(u64, MacOSNativeCaptureInputEvent)> {
match self.queue.lock() {
Ok(mut queue) => queue.drain(..).collect(),
Err(poisoned) => {
tracing::warn!(
op = "capture.native_input_queue_poisoned",
"Draining native capture input from a poisoned queue."
);

poisoned.into_inner().drain(..).collect()
},
}
}

fn reset(&self) {
self.finish_send();

match self.queue.lock() {
Ok(mut queue) => queue.clear(),
Err(poisoned) => {
tracing::warn!(
op = "capture.native_input_queue_poisoned",
"Resetting native capture input from a poisoned queue."
);

poisoned.into_inner().clear();
},
}
}
}

impl App {
pub(super) fn build_overlay_capture_host(&self) -> MacOSCaptureHost {
let overlay_proxy = self.overlay_proxy.clone();
let native_input_buffer = self.overlay_native_capture_input_buffer.clone();
let generation = self.overlay_session_generation;

MacOSCaptureHost::new(Arc::new(move |event| {
let _ =
overlay_proxy.send_event(UserEvent::OverlayNativeCaptureInput(generation, event));
if native_input_buffer.enqueue(generation, event)
&& overlay_proxy.send_event(UserEvent::OverlayNativeCaptureInput).is_err()
{
native_input_buffer.finish_send();
}
}))
}

pub(super) fn finish_coalesced_overlay_native_capture_input_send(&self) {
self.overlay_native_capture_input_buffer.finish_send();
}

pub(super) fn drain_overlay_native_capture_input_events(
&self,
) -> Vec<(u64, MacOSNativeCaptureInputEvent)> {
self.overlay_native_capture_input_buffer.drain()
}

pub(super) fn handle_overlay_native_capture_input_ready(&mut self) -> OverlayControl {
self.finish_coalesced_overlay_native_capture_input_send();

for (generation, event) in self.drain_overlay_native_capture_input_events() {
if generation != self.overlay_session_generation {
continue;
}

let Some(session) = self.overlay_session.as_mut() else {
break;
};
let control = session.handle_native_capture_input_event(event);

if !matches!(control, OverlayControl::Continue) {
return control;
}
}

OverlayControl::Continue
}

pub(super) fn reset_overlay_native_capture_input_dispatch(&self) {
self.overlay_native_capture_input_buffer.reset();
}

pub(super) fn sync_overlay_capture_host(&mut self) {
let sync_result = match (self.overlay_session.as_mut(), self.overlay_capture_host.as_mut())
{
Expand All @@ -36,3 +147,39 @@ impl App {
self.overlay_capture_host = None;
}
}

#[cfg(test)]
mod tests {
use crate::app::capture_host_macos::OverlayNativeCaptureInputBuffer;
use rsnap_overlay::MacOSNativeCaptureInputEvent;

#[test]
fn native_capture_input_buffer_coalesces_multiple_events_behind_one_wakeup() {
let buffer = OverlayNativeCaptureInputBuffer::new();

assert!(buffer.enqueue(7, MacOSNativeCaptureInputEvent::ToolbarPointerLeft));
assert!(!buffer.enqueue(7, MacOSNativeCaptureInputEvent::ToolbarPointerLeft));

buffer.finish_send();

assert_eq!(
buffer.drain(),
vec![
(7, MacOSNativeCaptureInputEvent::ToolbarPointerLeft),
(7, MacOSNativeCaptureInputEvent::ToolbarPointerLeft),
]
);
}

#[test]
fn native_capture_input_buffer_reset_clears_pending_and_buffered_events() {
let buffer = OverlayNativeCaptureInputBuffer::new();

assert!(buffer.enqueue(5, MacOSNativeCaptureInputEvent::ToolbarPointerLeft));

buffer.reset();

assert!(buffer.drain().is_empty());
assert!(buffer.enqueue(6, MacOSNativeCaptureInputEvent::ToolbarPointerLeft));
}
}
12 changes: 3 additions & 9 deletions apps/rsnap/src/app/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,10 @@ impl ApplicationHandler<UserEvent> for App {
}
},
#[cfg(target_os = "macos")]
UserEvent::OverlayNativeCaptureInput(generation, event) => {
if generation != self.overlay_session_generation {
return;
}
UserEvent::OverlayNativeCaptureInput => {
let control = self.handle_overlay_native_capture_input_ready();

if let Some(session) = self.overlay_session.as_mut() {
let control = session.handle_native_capture_input_event(event);

self.handle_overlay_control(control);
}
self.handle_overlay_control(control);
},
}
}
Expand Down
Loading