minor refinements
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
use device_query::{DeviceEvents, DeviceEventsHandler};
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::Emitter;
|
||||
use tracing::{error, info};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::{get_app_handle, lock_r, state::FDOLL};
|
||||
@@ -14,8 +13,8 @@ use crate::{get_app_handle, lock_r, state::FDOLL};
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct CursorPosition {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, TS)]
|
||||
@@ -28,29 +27,27 @@ pub struct CursorPositions {
|
||||
|
||||
static CURSOR_TRACKER: OnceCell<()> = OnceCell::new();
|
||||
|
||||
/// Convert absolute screen coordinates to grid coordinates
|
||||
pub fn absolute_position_to_grid(pos: &CursorPosition) -> CursorPosition {
|
||||
/// Convert absolute screen coordinates to normalized coordinates (0.0 - 1.0)
|
||||
pub fn absolute_to_normalized(pos: &CursorPosition) -> CursorPosition {
|
||||
let guard = lock_r!(FDOLL);
|
||||
let grid_size = guard.app_data.scene.grid_size;
|
||||
let screen_w = guard.app_data.scene.display.screen_width;
|
||||
let screen_h = guard.app_data.scene.display.screen_height;
|
||||
let screen_w = guard.app_data.scene.display.screen_width as f64;
|
||||
let screen_h = guard.app_data.scene.display.screen_height as f64;
|
||||
|
||||
CursorPosition {
|
||||
x: pos.x * grid_size / screen_w,
|
||||
y: pos.y * grid_size / screen_h,
|
||||
x: (pos.x / screen_w).clamp(0.0, 1.0),
|
||||
y: (pos.y / screen_h).clamp(0.0, 1.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert grid coordinates to absolute screen coordinates
|
||||
pub fn grid_to_absolute_position(grid: &CursorPosition) -> CursorPosition {
|
||||
/// Convert normalized coordinates to absolute screen coordinates
|
||||
pub fn normalized_to_absolute(normalized: &CursorPosition) -> CursorPosition {
|
||||
let guard = lock_r!(FDOLL);
|
||||
let grid_size = guard.app_data.scene.grid_size;
|
||||
let screen_w = guard.app_data.scene.display.screen_width;
|
||||
let screen_h = guard.app_data.scene.display.screen_height;
|
||||
let screen_w = guard.app_data.scene.display.screen_width as f64;
|
||||
let screen_h = guard.app_data.scene.display.screen_height as f64;
|
||||
|
||||
CursorPosition {
|
||||
x: (grid.x * screen_w + grid_size / 2) / grid_size,
|
||||
y: (grid.y * screen_h + grid_size / 2) / grid_size,
|
||||
x: (normalized.x * screen_w).round(),
|
||||
y: (normalized.y * screen_h).round(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,19 +73,40 @@ pub async fn start_cursor_tracking() -> Result<(), String> {
|
||||
|
||||
async fn init_cursor_tracking() -> Result<(), String> {
|
||||
info!("Initializing cursor tracking...");
|
||||
let app_handle = get_app_handle();
|
||||
|
||||
// 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);
|
||||
|
||||
// Spawn the consumer task
|
||||
// This task handles WebSocket reporting and local broadcasting.
|
||||
// It runs independently of the device event loop.
|
||||
tauri::async_runtime::spawn(async move {
|
||||
info!("Cursor event consumer started");
|
||||
let app_handle = get_app_handle();
|
||||
|
||||
while let Some(positions) = rx.recv().await {
|
||||
let mapped_for_ws = positions.mapped.clone();
|
||||
|
||||
// 1. WebSocket reporting
|
||||
crate::services::ws::report_cursor_data(mapped_for_ws).await;
|
||||
|
||||
// 2. Broadcast to local windows
|
||||
if let Err(e) = app_handle.emit("cursor-position", &positions) {
|
||||
error!("Failed to emit cursor position event: {:?}", e);
|
||||
}
|
||||
}
|
||||
warn!("Cursor event consumer stopped (channel closed)");
|
||||
});
|
||||
|
||||
// Try to initialize the device event handler
|
||||
// Using 500ms sleep as requested by user to reduce CPU usage
|
||||
let device_state = DeviceEventsHandler::new(Duration::from_millis(500))
|
||||
.ok_or("Failed to create device event handler (already running?)")?;
|
||||
|
||||
info!("Device event handler created successfully");
|
||||
info!("Setting up mouse move handler for event broadcasting...");
|
||||
|
||||
let send_count = Arc::new(AtomicU64::new(0));
|
||||
let send_count_clone = Arc::clone(&send_count);
|
||||
let app_handle_clone = app_handle.clone();
|
||||
|
||||
// Get scale factor from global state
|
||||
#[cfg(target_os = "windows")]
|
||||
let scale_factor = {
|
||||
@@ -96,8 +114,11 @@ async fn init_cursor_tracking() -> Result<(), String> {
|
||||
guard.app_data.scene.display.monitor_scale_factor
|
||||
};
|
||||
|
||||
// The producer closure moves `tx` into it.
|
||||
// device_query runs this closure on its own thread.
|
||||
// Explicitly clone tx to ensure clear capture semantics
|
||||
let tx_clone = tx.clone();
|
||||
let _guard = device_state.on_mouse_move(move |position: &(i32, i32)| {
|
||||
|
||||
// `device_query` crate appears to behave
|
||||
// differently on Windows vs other platforms.
|
||||
//
|
||||
@@ -105,46 +126,33 @@ async fn init_cursor_tracking() -> Result<(), String> {
|
||||
// factor on Windows, so we handle it manually.
|
||||
#[cfg(target_os = "windows")]
|
||||
let raw = CursorPosition {
|
||||
x: (position.0 as f64 / scale_factor) as i32,
|
||||
y: (position.1 as f64 / scale_factor) as i32,
|
||||
x: position.0 as f64 / scale_factor,
|
||||
y: position.1 as f64 / scale_factor,
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let raw = CursorPosition {
|
||||
x: position.0,
|
||||
y: position.1,
|
||||
x: position.0 as f64,
|
||||
y: position.1 as f64,
|
||||
};
|
||||
|
||||
let mapped = absolute_position_to_grid(&raw);
|
||||
let mapped = absolute_to_normalized(&raw);
|
||||
|
||||
let positions = CursorPositions {
|
||||
raw,
|
||||
mapped: mapped.clone(),
|
||||
mapped,
|
||||
};
|
||||
|
||||
// Report to server (existing functionality)
|
||||
let mapped_for_ws = mapped.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
crate::services::ws::report_cursor_data(mapped_for_ws).await;
|
||||
});
|
||||
|
||||
// Broadcast to ALL windows using events
|
||||
match app_handle_clone.emit("cursor-position", &positions) {
|
||||
Ok(_) => {
|
||||
let count = send_count_clone.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if count % 100 == 0 {
|
||||
info!("Broadcast {} cursor position updates to all windows. Latest: raw({}, {}), mapped({}, {})",
|
||||
count, positions.raw.x, positions.raw.y, positions.mapped.x, positions.mapped.y);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to emit cursor position event: {:?}", e);
|
||||
}
|
||||
// Send to consumer channel (non-blocking)
|
||||
if let Err(e) = tx_clone.try_send(positions) {
|
||||
debug!("Failed to send cursor position to channel: {:?}", e);
|
||||
}
|
||||
});
|
||||
|
||||
info!("Mouse move handler registered - now broadcasting cursor events to all windows");
|
||||
|
||||
// Keep the handler alive forever
|
||||
// This loop is necessary to keep `_guard` and `device_state` in scope.
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(3600)).await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user