cursor tracker start loop bug fix
This commit is contained in:
@@ -15,7 +15,7 @@ pub async fn init(handle: &AppHandle) -> Result<(), String> {
|
||||
.map_err(crate::db::command_error)?;
|
||||
|
||||
if settings.onboarding_done {
|
||||
start(handle);
|
||||
start(handle).await;
|
||||
if cfg!(target_os = "macos") && !accessibility_permission_granted {
|
||||
crate::ui::onboarding::show_accessibility_page(handle)?;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ pub async fn init(handle: &AppHandle) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start(handle: &AppHandle) {
|
||||
pub async fn start(handle: &AppHandle) {
|
||||
let state = handle.state::<ApplicationState>();
|
||||
if state
|
||||
.started
|
||||
@@ -36,7 +36,7 @@ pub fn start(handle: &AppHandle) {
|
||||
}
|
||||
|
||||
crate::ui::init(handle);
|
||||
reconcile_cursor(handle);
|
||||
reconcile_cursor(handle).await;
|
||||
}
|
||||
|
||||
pub fn is_started(handle: &AppHandle) -> bool {
|
||||
@@ -46,12 +46,12 @@ pub fn is_started(handle: &AppHandle) -> bool {
|
||||
.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn reconcile_cursor(handle: &AppHandle) {
|
||||
pub async fn reconcile_cursor(handle: &AppHandle) {
|
||||
if !is_started(handle) || !crate::macos::accessibility_permission_granted(handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(error) = crate::cursor::start_tracking(handle) {
|
||||
eprintln!("failed to initialize cursor tracking; will retry: {error}");
|
||||
if let Err(error) = crate::cursor::start_tracking(handle).await {
|
||||
eprintln!("failed to initialize cursor tracking: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
+81
-38
@@ -8,8 +8,7 @@ use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_specta::Event;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::watch;
|
||||
use tokio::sync::{mpsc, oneshot, watch};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -104,13 +103,18 @@ fn current_position(handle: &AppHandle) -> Result<CursorPositions, String> {
|
||||
// complains even when there's no external references.
|
||||
// Possibly because of `lazy_static!`.
|
||||
// Just leave it public I guess.
|
||||
pub struct CursorTask {
|
||||
struct CursorTask {
|
||||
stop_tx: watch::Sender<bool>,
|
||||
task: tauri::async_runtime::JoinHandle<()>,
|
||||
}
|
||||
|
||||
enum CursorTracker {
|
||||
Starting,
|
||||
Running(CursorTask),
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CURSOR_TASK: Mutex<Option<CursorTask>> = Mutex::new(None);
|
||||
static ref CURSOR_TRACKER: Mutex<Option<CursorTracker>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
pub fn init(app: &AppHandle) {
|
||||
@@ -118,47 +122,71 @@ pub fn init(app: &AppHandle) {
|
||||
}
|
||||
|
||||
/// Starts cursor tracking after Accessibility permission is available.
|
||||
pub fn start_tracking(app: &AppHandle) -> Result<(), String> {
|
||||
println!("init_cursor_tracking called");
|
||||
|
||||
pub async fn start_tracking(app: &AppHandle) -> Result<(), String> {
|
||||
if !crate::macos::accessibility_permission_granted(app) {
|
||||
return Err("macOS Accessibility permission has not been granted".to_owned());
|
||||
}
|
||||
|
||||
let mut tracker = match CURSOR_TASK.lock() {
|
||||
Ok(tracker) => tracker,
|
||||
Err(e) => {
|
||||
return Err(format!("Failed to lock cursor tracker state: {e}"));
|
||||
}
|
||||
};
|
||||
|
||||
if tracker.is_some() {
|
||||
println!("Cursor tracking already initialized");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match current_position(app) {
|
||||
Ok(positions) => update_cursor_position(app, positions),
|
||||
Err(error) => eprintln!("Failed to resolve current cursor position: {error}"),
|
||||
}
|
||||
|
||||
let (stop_tx, stop_rx) = watch::channel(false);
|
||||
|
||||
println!("Spawning cursor tracking task");
|
||||
|
||||
let primary_monitor = app
|
||||
.primary_monitor()
|
||||
.map_err(|error| error.to_string())?
|
||||
.ok_or_else(|| "Failed to resolve primary monitor".to_owned())?;
|
||||
|
||||
{
|
||||
let mut tracker = CURSOR_TRACKER
|
||||
.lock()
|
||||
.map_err(|error| format!("Failed to lock cursor tracker state: {error}"))?;
|
||||
if tracker.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
*tracker = Some(CursorTracker::Starting);
|
||||
}
|
||||
|
||||
println!("Initializing cursor tracking...");
|
||||
match current_position(app) {
|
||||
Ok(positions) => update_cursor_position(app, positions),
|
||||
Err(error) => eprintln!("Failed to resolve current cursor position: {error}"),
|
||||
}
|
||||
|
||||
let (stop_tx, stop_rx) = watch::channel(false);
|
||||
let (ready_tx, ready_rx) = oneshot::channel();
|
||||
|
||||
let handle = app.clone();
|
||||
let task = tauri::async_runtime::spawn(async move {
|
||||
if let Err(e) = init_cursor_tracking_i(stop_rx, primary_monitor, handle).await {
|
||||
if let Err(e) = init_cursor_tracking_i(stop_rx, primary_monitor, handle, ready_tx).await {
|
||||
println!("Failed to initialize cursor tracking: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
*tracker = Some(CursorTask { stop_tx, task });
|
||||
let startup = ready_rx
|
||||
.await
|
||||
.unwrap_or_else(|_| Err("Cursor tracking task stopped during initialization".to_owned()));
|
||||
if let Err(error) = startup {
|
||||
if let Ok(mut tracker) = CURSOR_TRACKER.lock()
|
||||
&& matches!(*tracker, Some(CursorTracker::Starting))
|
||||
{
|
||||
*tracker = None;
|
||||
}
|
||||
let _ = task.await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let installed = {
|
||||
let mut tracker = CURSOR_TRACKER
|
||||
.lock()
|
||||
.map_err(|error| format!("Failed to lock cursor tracker state: {error}"))?;
|
||||
if matches!(*tracker, Some(CursorTracker::Starting)) {
|
||||
*tracker = Some(CursorTracker::Running(CursorTask { stop_tx, task }));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !installed {
|
||||
return Err("Cursor tracking startup was cancelled".to_owned());
|
||||
}
|
||||
|
||||
println!("EVENT: Cursor Tracker Enabled");
|
||||
Ok(())
|
||||
}
|
||||
@@ -297,7 +325,7 @@ mod tests {
|
||||
pub async fn stop_cursor_tracking() {
|
||||
println!("stop_cursor_tracking called");
|
||||
|
||||
let tracker = match CURSOR_TASK.lock() {
|
||||
let tracker = match CURSOR_TRACKER.lock() {
|
||||
Ok(mut tracker) => tracker.take(),
|
||||
Err(e) => {
|
||||
println!("Failed to lock cursor tracker state: {}", e);
|
||||
@@ -305,9 +333,16 @@ pub async fn stop_cursor_tracking() {
|
||||
}
|
||||
};
|
||||
|
||||
let Some(tracker) = tracker else {
|
||||
println!("Cursor tracking is not running");
|
||||
return;
|
||||
let tracker = match tracker {
|
||||
Some(CursorTracker::Running(tracker)) => tracker,
|
||||
Some(CursorTracker::Starting) => {
|
||||
println!("Cursor tracking startup cancelled");
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
println!("Cursor tracking is not running");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = tracker.stop_tx.send(true) {
|
||||
@@ -325,9 +360,8 @@ async fn init_cursor_tracking_i(
|
||||
mut stop_rx: watch::Receiver<bool>,
|
||||
monitor: tauri::Monitor,
|
||||
handle: AppHandle,
|
||||
ready_tx: oneshot::Sender<Result<(), String>>,
|
||||
) -> Result<(), String> {
|
||||
println!("Initializing cursor tracking...");
|
||||
|
||||
// Create a channel to decouple event generation (producer) from processing (consumer).
|
||||
// Capacity 100 is plenty for 500ms polling (2Hz).
|
||||
let (tx, mut rx) = mpsc::channel::<CursorPositions>(100);
|
||||
@@ -345,8 +379,14 @@ async fn init_cursor_tracking_i(
|
||||
println!("Cursor event consumer stopped (channel closed)");
|
||||
});
|
||||
|
||||
let device_state = DeviceEventsHandler::new(Duration::from_millis(500))
|
||||
.ok_or("Failed to create device event handler (already running?)")?;
|
||||
let device_state = match DeviceEventsHandler::new(Duration::from_millis(500)) {
|
||||
Some(device_state) => device_state,
|
||||
None => {
|
||||
let error = "Failed to create device event handler (already running?)".to_owned();
|
||||
let _ = ready_tx.send(Err(error.clone()));
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
println!("Device event handler created successfully");
|
||||
println!("Setting up mouse move handler for event broadcasting...");
|
||||
@@ -383,6 +423,9 @@ async fn init_cursor_tracking_i(
|
||||
}
|
||||
});
|
||||
|
||||
ready_tx
|
||||
.send(Ok(()))
|
||||
.map_err(|_| "Cursor tracking startup was cancelled".to_owned())?;
|
||||
println!("Mouse move handler registered - now broadcasting cursor events to all windows");
|
||||
|
||||
// Keep the handler alive while tracking is enabled.
|
||||
|
||||
@@ -49,11 +49,6 @@ pub fn accessibility_permission_granted(handle: &AppHandle) -> bool {
|
||||
}
|
||||
|
||||
async fn apply_permission_state(handle: &AppHandle, granted: bool) -> Result<bool, String> {
|
||||
if handle.state::<AccessibilityPermissionState>().granted() == granted {
|
||||
crate::application::reconcile_cursor(handle);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if !handle
|
||||
.state::<AccessibilityPermissionState>()
|
||||
.replace(granted)
|
||||
@@ -64,7 +59,7 @@ async fn apply_permission_state(handle: &AppHandle, granted: bool) -> Result<boo
|
||||
let status = crate::onboarding::emit_status(handle, &database).await?;
|
||||
if status.onboarding_done {
|
||||
if granted {
|
||||
crate::application::reconcile_cursor(handle);
|
||||
crate::application::reconcile_cursor(handle).await;
|
||||
} else {
|
||||
crate::ui::onboarding::show_accessibility_page(handle)?;
|
||||
}
|
||||
@@ -106,3 +101,18 @@ pub async fn request_accessibility_permission(handle: AppHandle) -> Result<bool,
|
||||
apply_permission_state(&handle, granted).await?;
|
||||
Ok(granted)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn permission_state_reports_only_actual_transitions() {
|
||||
let state = AccessibilityPermissionState::new(false);
|
||||
|
||||
assert!(!state.replace(false));
|
||||
assert!(state.replace(true));
|
||||
assert!(!state.replace(true));
|
||||
assert!(state.replace(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ pub async fn complete_onboarding(
|
||||
.await
|
||||
.map_err(db::command_error)?;
|
||||
emit_status(&handle, &database).await?;
|
||||
crate::application::start(&handle);
|
||||
crate::application::start(&handle).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user