From 9363a2e96b50bd808b7ea9e58317bfe170c3d31b Mon Sep 17 00:00:00 2001 From: Wind-Explorer Date: Tue, 16 Dec 2025 00:40:27 +0800 Subject: [PATCH] friends cursor broadcast system (?) --- src-tauri/src/services/ws.rs | 15 +++++++- src/events/cursor.ts | 66 ++++++++++++++++++++++++++++++----- src/routes/scene/+page.svelte | 25 ++++++++++++- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/services/ws.rs b/src-tauri/src/services/ws.rs index 35ae443..1fb051c 100644 --- a/src-tauri/src/services/ws.rs +++ b/src-tauri/src/services/ws.rs @@ -17,6 +17,7 @@ impl WS_EVENT { pub const FRIEND_REQUEST_ACCEPTED: &str = "friend-request-accepted"; pub const FRIEND_REQUEST_DENIED: &str = "friend-request-denied"; pub const UNFRIENDED: &str = "unfriended"; + pub const FRIEND_CURSOR_POSITION: &str = "friend-cursor-position"; } fn on_friend_request_received(payload: Payload, _socket: RawClient) { @@ -65,6 +66,17 @@ fn on_unfriended(payload: Payload, _socket: RawClient) { } } +fn on_friend_cursor_position(payload: Payload, _socket: RawClient) { + match payload { + Payload::Text(str) => { + get_app_handle() + .emit(WS_EVENT::FRIEND_CURSOR_POSITION, str) + .unwrap(); + } + _ => todo!(), + } +} + pub async fn report_cursor_data(cursor_position: CursorPosition) { let client = { let guard = lock_r!(FDOLL); @@ -81,7 +93,7 @@ pub async fn report_cursor_data(cursor_position: CursorPosition) { match async_runtime::spawn_blocking(move || { client.emit( WS_EVENT::CURSOR_REPORT_POSITION, - Payload::Text(vec![json!({ "position": cursor_position })]), + Payload::Text(vec![json!(cursor_position)]), ) }) .await @@ -132,6 +144,7 @@ pub async fn build_ws_client(app_config: &AppConfig) -> rust_socketio::client::C ) .on(WS_EVENT::FRIEND_REQUEST_DENIED, on_friend_request_denied) .on(WS_EVENT::UNFRIENDED, on_unfriended) + .on(WS_EVENT::FRIEND_CURSOR_POSITION, on_friend_cursor_position) .auth(json!({ "token": token })) .connect() }) diff --git a/src/events/cursor.ts b/src/events/cursor.ts index 5ce267f..c5ca7fd 100644 --- a/src/events/cursor.ts +++ b/src/events/cursor.ts @@ -2,13 +2,24 @@ import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { writable } from "svelte/store"; import type { CursorPositions } from "../types/bindings/CursorPositions"; +import type { CursorPosition } from "../types/bindings/CursorPosition"; export let cursorPositionOnScreen = writable({ raw: { x: 0, y: 0 }, mapped: { x: 0, y: 0 }, }); -let unlisten: UnlistenFn | null = null; +export type FriendCursorPosition = { + userId: string; + position: CursorPosition; +}; + +export let friendsCursorPositions = writable>( + {}, +); + +let unlistenCursor: UnlistenFn | null = null; +let unlistenFriendCursor: UnlistenFn | null = null; let isListening = false; /** @@ -26,9 +37,44 @@ export async function initCursorTracking() { await invoke("start_cursor_tracking"); // Listen to cursor position events (each window subscribes independently) - unlisten = await listen("cursor-position", (event) => { - cursorPositionOnScreen.set(event.payload); - }); + unlistenCursor = await listen( + "cursor-position", + (event) => { + cursorPositionOnScreen.set(event.payload); + }, + ); + + // Listen to friend cursor position events + unlistenFriendCursor = await listen<[FriendCursorPosition]>( + "friend-cursor-position", + (event) => { + // payload might be a string JSON if it comes directly from rust_socketio Payload::Text + let payload = event.payload; + + if (typeof payload === "string") { + try { + payload = JSON.parse(payload); + } catch (e) { + console.error( + "[Cursor] Failed to parse friend cursor position payload:", + e, + ); + return; + } + } + + // Rust socket.io client returns payload as an array of arguments + // Since we only send one argument { userId, position }, it's the first element + const data = Array.isArray(payload) ? payload[0] : payload; + + friendsCursorPositions.update((current) => { + return { + ...current, + [data.userId]: data.position, + }; + }); + }, + ); isListening = true; } catch (err) { @@ -42,11 +88,15 @@ export async function initCursorTracking() { * Note: This doesn't stop the Rust-side tracking, just stops this window from receiving events. */ export function stopCursorTracking() { - if (unlisten) { - unlisten(); - unlisten = null; - isListening = false; + if (unlistenCursor) { + unlistenCursor(); + unlistenCursor = null; } + if (unlistenFriendCursor) { + unlistenFriendCursor(); + unlistenFriendCursor = null; + } + isListening = false; } // Handle HMR (Hot Module Replacement) cleanup diff --git a/src/routes/scene/+page.svelte b/src/routes/scene/+page.svelte index 2f7070b..5a42c58 100644 --- a/src/routes/scene/+page.svelte +++ b/src/routes/scene/+page.svelte @@ -1,5 +1,14 @@
@@ -19,6 +28,20 @@ .mapped.y})
+ + {#if Object.keys($friendsCursorPositions).length > 0} +
+

Friends Online

+
+ {#each Object.entries($friendsCursorPositions) as [userId, position]} +
+ {getFriendName(userId)} + ({position.x}, {position.y}) +
+ {/each} +
+
+ {/if}