From c3268cf29812da45f2855b11b95ad33aaf3221a1 Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Tue, 16 Dec 2025 15:08:59 +0800 Subject: [PATCH] preperation for usage of `grid_to_absolute_position` --- src-tauri/src/models/app_data.rs | 35 ++++++ src-tauri/src/services/cursor.rs | 103 ++++++------------ src-tauri/src/state.rs | 64 +++++++++++ src/types/bindings/AppData.ts | 10 +- src/types/bindings/DisplayData.ts | 3 + .../bindings/FriendRequestResponseDto.ts | 12 +- src/types/bindings/FriendshipResponseDto.ts | 9 +- src/types/bindings/SceneData.ts | 4 + src/types/bindings/SendFriendRequestDto.ts | 6 +- src/types/bindings/UpdateUserDto.ts | 3 + src/types/bindings/UserBasicDto.ts | 8 +- src/types/bindings/UserProfile.ts | 2 +- 12 files changed, 162 insertions(+), 97 deletions(-) create mode 100644 src/types/bindings/DisplayData.ts create mode 100644 src/types/bindings/SceneData.ts create mode 100644 src/types/bindings/UpdateUserDto.ts diff --git a/src-tauri/src/models/app_data.rs b/src-tauri/src/models/app_data.rs index e37bf96..ff749b5 100644 --- a/src-tauri/src/models/app_data.rs +++ b/src-tauri/src/models/app_data.rs @@ -3,9 +3,44 @@ use ts_rs::TS; use crate::remotes::{friends::FriendshipResponseDto, user::UserProfile}; +#[derive(Serialize, Deserialize, Clone, Debug, TS)] +#[ts(export)] +pub struct DisplayData { + pub screen_width: i32, + pub screen_height: i32, + pub monitor_scale_factor: f64, +} + +impl Default for DisplayData { + fn default() -> Self { + Self { + screen_width: 0, + screen_height: 0, + monitor_scale_factor: 1.0, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, TS)] +#[ts(export)] +pub struct SceneData { + pub display: DisplayData, + pub grid_size: i32, +} + +impl Default for SceneData { + fn default() -> Self { + Self { + display: DisplayData::default(), + grid_size: 600, + } + } +} + #[derive(Default, Serialize, Deserialize, Clone, Debug, TS)] #[ts(export)] pub struct AppData { pub user: Option, pub friends: Option>, + pub scene: SceneData, } diff --git a/src-tauri/src/services/cursor.rs b/src-tauri/src/services/cursor.rs index fb4f45b..21351e9 100644 --- a/src-tauri/src/services/cursor.rs +++ b/src-tauri/src/services/cursor.rs @@ -5,10 +5,10 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::time::Duration; use tauri::Emitter; -use tracing::{error, info, warn}; +use tracing::{error, info}; use ts_rs::TS; -use crate::get_app_handle; +use crate::{get_app_handle, lock_r, state::FDOLL}; #[derive(Clone, Serialize, TS)] #[serde(rename_all = "camelCase")] @@ -28,18 +28,32 @@ pub struct CursorPositions { static CURSOR_TRACKER: OnceCell<()> = OnceCell::new(); -fn map_to_grid( - pos: &CursorPosition, - grid_size: i32, - screen_w: i32, - screen_h: i32, -) -> CursorPosition { +/// Convert absolute screen coordinates to grid coordinates +pub fn absolute_position_to_grid(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; + CursorPosition { x: pos.x * grid_size / screen_w, y: pos.y * grid_size / screen_h, } } +/// Convert grid coordinates to absolute screen coordinates +pub fn grid_to_absolute_position(grid: &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; + + CursorPosition { + x: (grid.x * screen_w + grid_size / 2) / grid_size, + y: (grid.y * screen_h + grid_size / 2) / grid_size, + } +} + /// Initialize cursor tracking - can be called multiple times safely from any window /// Only the first call will actually start tracking, subsequent calls are no-ops #[tauri::command] @@ -64,55 +78,6 @@ async fn init_cursor_tracking() -> Result<(), String> { info!("Initializing cursor tracking..."); let app_handle = get_app_handle(); - // Get primary monitor with retries - let primary_monitor = { - let mut retry_count = 0; - let max_retries = 3; - loop { - match app_handle.primary_monitor() { - Ok(Some(monitor)) => { - info!("Primary monitor acquired"); - break monitor; - } - Ok(None) => { - retry_count += 1; - if retry_count >= max_retries { - return Err(format!( - "No primary monitor found after {} retries", - max_retries - )); - } - warn!( - "Primary monitor not available, retrying... ({}/{})", - retry_count, max_retries - ); - tokio::time::sleep(Duration::from_millis(100)).await; - } - Err(e) => { - retry_count += 1; - if retry_count >= max_retries { - return Err(format!("Failed to get primary monitor: {}", e)); - } - warn!( - "Error getting primary monitor, retrying... ({}/{}): {}", - retry_count, max_retries, e - ); - tokio::time::sleep(Duration::from_millis(100)).await; - } - } - } - }; - - let monitor_dimensions = primary_monitor.size(); - let monitor_scale_factor = primary_monitor.scale_factor(); - let logical_monitor_dimensions: tauri::LogicalSize = - monitor_dimensions.to_logical(monitor_scale_factor); - - info!( - "Monitor dimensions: {}x{}", - logical_monitor_dimensions.width, logical_monitor_dimensions.height - ); - // Try to initialize the device event handler let device_state = DeviceEventsHandler::new(Duration::from_millis(500)) .ok_or("Failed to create device event handler (already running?)")?; @@ -124,17 +89,26 @@ async fn init_cursor_tracking() -> Result<(), String> { let send_count_clone = Arc::clone(&send_count); let app_handle_clone = app_handle.clone(); + #[cfg(target_os = "windows")] + { + // Get scale factor from global state + let scale_factor = { + let guard = lock_r!(FDOLL); + guard.app_data.scene.display.monitor_scale_factor + }; + } + let _guard = device_state.on_mouse_move(move |position: &(i32, i32)| { // `device_query` crate appears to behave - // differently on Windows vs other paltforms. + // differently on Windows vs other platforms. // // It doesn't take into account the monitor scale // factor on Windows, so we handle it manually. #[cfg(target_os = "windows")] let raw = CursorPosition { - x: (position.0 as f64 / monitor_scale_factor) as i32, - y: (position.1 as f64 / monitor_scale_factor) as i32, + x: (position.0 as f64 / scale_factor) as i32, + y: (position.1 as f64 / scale_factor) as i32, }; #[cfg(not(target_os = "windows"))] @@ -143,12 +117,7 @@ async fn init_cursor_tracking() -> Result<(), String> { y: position.1, }; - let mapped = map_to_grid( - &raw, - 600, - logical_monitor_dimensions.width, - logical_monitor_dimensions.height, - ); + let mapped = absolute_position_to_grid(&raw); let positions = CursorPositions { raw, mapped: mapped.clone(), @@ -166,7 +135,7 @@ async fn init_cursor_tracking() -> Result<(), String> { 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); + count, positions.raw.x, positions.raw.y, positions.mapped.x, positions.mapped.y); } } Err(e) => { diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 2e71802..d52dc6f 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -83,6 +83,70 @@ pub fn init_fdoll_state() { let has_auth = guard.auth_pass.is_some(); + // Initialize screen dimensions + let app_handle = get_app_handle(); + + // Get primary monitor with retries + // Note: This duplicates logic from init_cursor_tracking, but we need it here for global state + let primary_monitor = { + let mut retry_count = 0; + let max_retries = 3; + loop { + match app_handle.primary_monitor() { + Ok(Some(monitor)) => { + info!("Primary monitor acquired for state initialization"); + break Some(monitor); + } + Ok(None) => { + retry_count += 1; + if retry_count >= max_retries { + warn!("No primary monitor found after {} retries during state init", max_retries); + break None; + } + warn!( + "Primary monitor not available during state init, retrying... ({}/{})", + retry_count, max_retries + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + } + Err(e) => { + retry_count += 1; + if retry_count >= max_retries { + warn!("Failed to get primary monitor during state init: {}", e); + break None; + } + warn!( + "Error getting primary monitor during state init, retrying... ({}/{}): {}", + retry_count, max_retries, e + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + } + }; + + if let Some(monitor) = primary_monitor { + let monitor_dimensions = monitor.size(); + let monitor_scale_factor = monitor.scale_factor(); + let logical_monitor_dimensions: tauri::LogicalSize = + monitor_dimensions.to_logical(monitor_scale_factor); + + guard.app_data.scene.display.screen_width = logical_monitor_dimensions.width; + guard.app_data.scene.display.screen_height = logical_monitor_dimensions.height; + guard.app_data.scene.display.monitor_scale_factor = monitor_scale_factor; + guard.app_data.scene.grid_size = 600; // Hardcoded grid size + + info!( + "Initialized global AppData with screen dimensions: {}x{}, scale: {}, grid: {}", + logical_monitor_dimensions.width, + logical_monitor_dimensions.height, + monitor_scale_factor, + guard.app_data.scene.grid_size + ); + } else { + warn!("Could not initialize screen dimensions in global state - no monitor found"); + } + drop(guard); if has_auth { diff --git a/src/types/bindings/AppData.ts b/src/types/bindings/AppData.ts index cba307b..dda0b92 100644 --- a/src/types/bindings/AppData.ts +++ b/src/types/bindings/AppData.ts @@ -1,8 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { UserProfile } from "./UserProfile.js"; -import type { FriendshipResponseDto } from "./FriendshipResponseDto.js"; +import type { FriendshipResponseDto } from "./FriendshipResponseDto"; +import type { SceneData } from "./SceneData"; +import type { UserProfile } from "./UserProfile"; -export type AppData = { - user: UserProfile | null; - friends: Array | null; -}; +export type AppData = { user: UserProfile | null, friends: Array | null, scene: SceneData, }; diff --git a/src/types/bindings/DisplayData.ts b/src/types/bindings/DisplayData.ts new file mode 100644 index 0000000..67bd38d --- /dev/null +++ b/src/types/bindings/DisplayData.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type DisplayData = { screen_width: number, screen_height: number, monitor_scale_factor: number, }; diff --git a/src/types/bindings/FriendRequestResponseDto.ts b/src/types/bindings/FriendRequestResponseDto.ts index c94af2a..d21a5c1 100644 --- a/src/types/bindings/FriendRequestResponseDto.ts +++ b/src/types/bindings/FriendRequestResponseDto.ts @@ -1,10 +1,4 @@ -import type { UserBasicDto } from "./UserBasicDto.js"; +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { UserBasicDto } from "./UserBasicDto"; -export type FriendRequestResponseDto = { - id: string; - sender: UserBasicDto; - receiver: UserBasicDto; - status: string; - createdAt: string; - updatedAt: string; -}; +export type FriendRequestResponseDto = { id: string, sender: UserBasicDto, receiver: UserBasicDto, status: string, createdAt: string, updatedAt: string, }; diff --git a/src/types/bindings/FriendshipResponseDto.ts b/src/types/bindings/FriendshipResponseDto.ts index 366ae09..0dfca75 100644 --- a/src/types/bindings/FriendshipResponseDto.ts +++ b/src/types/bindings/FriendshipResponseDto.ts @@ -1,7 +1,4 @@ -import type { UserBasicDto } from "./UserBasicDto.js"; +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { UserBasicDto } from "./UserBasicDto"; -export type FriendshipResponseDto = { - id: string; - friend: UserBasicDto; - createdAt: string; -}; +export type FriendshipResponseDto = { id: string, friend: UserBasicDto, createdAt: string, }; diff --git a/src/types/bindings/SceneData.ts b/src/types/bindings/SceneData.ts new file mode 100644 index 0000000..0fe91db --- /dev/null +++ b/src/types/bindings/SceneData.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DisplayData } from "./DisplayData"; + +export type SceneData = { display: DisplayData, grid_size: number, }; diff --git a/src/types/bindings/SendFriendRequestDto.ts b/src/types/bindings/SendFriendRequestDto.ts index c2be6ae..0156ca0 100644 --- a/src/types/bindings/SendFriendRequestDto.ts +++ b/src/types/bindings/SendFriendRequestDto.ts @@ -1,3 +1,3 @@ -export type SendFriendRequestDto = { - receiverId: string; -}; +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type SendFriendRequestDto = { receiverId: string, }; diff --git a/src/types/bindings/UpdateUserDto.ts b/src/types/bindings/UpdateUserDto.ts new file mode 100644 index 0000000..6eea6a4 --- /dev/null +++ b/src/types/bindings/UpdateUserDto.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UpdateUserDto = Record; diff --git a/src/types/bindings/UserBasicDto.ts b/src/types/bindings/UserBasicDto.ts index 3b796c6..87566c2 100644 --- a/src/types/bindings/UserBasicDto.ts +++ b/src/types/bindings/UserBasicDto.ts @@ -1,5 +1,3 @@ -export type UserBasicDto = { - id: string; - name: string; - username: string | null; -}; +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type UserBasicDto = { id: string, name: string, username: string | null, }; diff --git a/src/types/bindings/UserProfile.ts b/src/types/bindings/UserProfile.ts index ee6cca6..362562f 100644 --- a/src/types/bindings/UserProfile.ts +++ b/src/types/bindings/UserProfile.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type UserProfile = { id: string, name: string, email: string, username: string, createdAt: string, lastLoginAt: string, }; +export type UserProfile = { id: string, keycloakSub: string, name: string, email: string, username: string | null, roles: Array, createdAt: string, updatedAt: string, lastLoginAt: string | null, };