diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 4cdbf49..250a38d 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -1,8 +1,8 @@ { "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], + "description": "Capability for Wyd UI windows", + "windows": ["debug", "scene"], "permissions": [ "core:default", "opener:default" diff --git a/src-tauri/src/cursor/mod.rs b/src-tauri/src/cursor/mod.rs index 7e2598c..e1e87e4 100644 --- a/src-tauri/src/cursor/mod.rs +++ b/src-tauri/src/cursor/mod.rs @@ -2,6 +2,7 @@ use device_query::{DeviceEvents, DeviceEventsHandler}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use specta::Type; +use std::collections::HashMap; use std::sync::Mutex; use std::sync::RwLock; use std::time::Duration; @@ -27,18 +28,23 @@ pub struct CursorPositions { #[derive(Debug, Clone, Serialize, Deserialize, Type, Event)] #[serde(rename_all = "camelCase")] pub struct CursorPositionChanged { - pub positions: CursorPositions, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)] -#[serde(rename_all = "camelCase")] -pub struct FriendCursorPositionChanged { - pub friend_id: String, - pub positions: CursorPositions, + pub positions: HashMap, } #[derive(Default)] -pub struct CursorState(RwLock); +pub struct CursorState(RwLock>); + +impl CursorState { + fn update( + &self, + user_id: String, + positions: CursorPositions, + ) -> Result, String> { + let mut positions_by_user = self.0.write().map_err(|error| error.to_string())?; + positions_by_user.insert(user_id, positions); + Ok(positions_by_user.clone()) + } +} // Was private, but for some reason LSP // complains even when there's no external references. @@ -92,37 +98,28 @@ pub fn init(app: &AppHandle) { #[inline] fn update_cursor_position(handle: &AppHandle, positions: CursorPositions) { - let state = handle.state::(); - let mut guard = state.0.write().expect("Cursor Position lock failed"); - - *guard = positions.clone(); - drop(guard); - - if let Err(error) = (CursorPositionChanged { - positions: positions.clone(), - }) - .emit(handle) - { - eprintln!("Failed to emit cursor position change: {error}"); - } + let user_id = handle + .state::() + .public_key() + .to_owned(); + emit_position(handle, user_id, positions.clone()); handle .state::() .send_live_data(crate::live_data::LiveData::Cursor { positions }); } -pub(crate) fn emit_friend_position( - handle: &AppHandle, - friend_id: String, - positions: CursorPositions, -) { - if let Err(error) = (FriendCursorPositionChanged { - friend_id, - positions, - }) - .emit(handle) - { - eprintln!("Failed to emit friend cursor position change: {error}"); +pub(crate) fn emit_position(handle: &AppHandle, user_id: String, positions: CursorPositions) { + let positions = match handle.state::().update(user_id, positions) { + Ok(positions) => positions, + Err(error) => { + eprintln!("Failed to update cursor positions: {error}"); + return; + } + }; + + if let Err(error) = (CursorPositionChanged { positions }).emit(handle) { + eprintln!("Failed to emit cursor position change: {error}"); } } @@ -156,6 +153,35 @@ fn transform_coords(pos: &CursorPosition, to_normalized: bool, w: f64, h: f64) - } } +#[cfg(test)] +mod tests { + use super::*; + + fn positions(x: f64) -> CursorPositions { + CursorPositions { + raw: CursorPosition { x, y: x }, + mapped: CursorPosition { x, y: x }, + } + } + + #[test] + fn cursor_state_tracks_and_replaces_positions_by_user_id() { + let state = CursorState::default(); + + let snapshot = state.update("local".to_owned(), positions(1.0)).unwrap(); + assert_eq!(snapshot.len(), 1); + assert_eq!(snapshot["local"].raw.x, 1.0); + + let snapshot = state.update("friend".to_owned(), positions(2.0)).unwrap(); + assert_eq!(snapshot.len(), 2); + assert_eq!(snapshot["friend"].raw.x, 2.0); + + let snapshot = state.update("local".to_owned(), positions(3.0)).unwrap(); + assert_eq!(snapshot.len(), 2); + assert_eq!(snapshot["local"].raw.x, 3.0); + } +} + /// Stop cursor tracking and unregister all listeners. #[allow(dead_code)] // TODO: get rid of this when app teardown sequence is introduced. pub async fn stop_cursor_tracking() { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a283a9e..754579c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -9,7 +9,6 @@ mod remotes; mod ufa; mod ui; mod user; -mod windowing; use tauri::Manager; @@ -22,7 +21,6 @@ async fn launch_app(app: &tauri::App) -> Result<(), Box> app.manage(cursor::CursorState::default()); ufa::init(handle); cursor::init(handle); - // windowing::init(handle); ui::init(handle); Ok(()) } @@ -76,7 +74,6 @@ fn specta_builder() -> tauri_specta::Builder { profile::ProfileChanged, network::NetworkStatusChanged, cursor::CursorPositionChanged, - cursor::FriendCursorPositionChanged, ufa::ForegroundAppChanged, ufa::FriendForegroundAppChanged, ]) diff --git a/src-tauri/src/network/mod.rs b/src-tauri/src/network/mod.rs index 90a67a6..971a97a 100644 --- a/src-tauri/src/network/mod.rs +++ b/src-tauri/src/network/mod.rs @@ -327,7 +327,7 @@ async fn connect( ServerMessage::FriendLiveData { friend_id, payload } => { match serde_json::from_str(&payload) { Ok(LiveData::Cursor { positions }) => { - crate::cursor::emit_friend_position(handle, friend_id, positions); + crate::cursor::emit_position(handle, friend_id, positions); } Ok(LiveData::ForegroundApp { meta }) => { crate::ufa::emit_friend_app(handle, friend_id, meta); diff --git a/src-tauri/src/windowing/mod.rs b/src-tauri/src/ui/debug/mod.rs similarity index 92% rename from src-tauri/src/windowing/mod.rs rename to src-tauri/src/ui/debug/mod.rs index 54e270e..2951594 100644 --- a/src-tauri/src/windowing/mod.rs +++ b/src-tauri/src/ui/debug/mod.rs @@ -1,6 +1,6 @@ use tauri::{AppHandle, Manager, WebviewUrl}; -const WINDOW_LABEL: &str = "main"; +const WINDOW_LABEL: &str = "debug"; pub fn init(handle: &AppHandle) { if let Some(window) = handle.get_webview_window(WINDOW_LABEL) { @@ -14,7 +14,7 @@ pub fn init(handle: &AppHandle) { let builder = tauri::WebviewWindowBuilder::new(handle, WINDOW_LABEL, WebviewUrl::App("/".into())) - .title("Wyd") + .title("Wyd Debug") .inner_size(800.0, 600.0) .min_inner_size(500.0, 400.0) .visible(true); diff --git a/src-tauri/src/ui/mod.rs b/src-tauri/src/ui/mod.rs index 89ca094..85d252b 100644 --- a/src-tauri/src/ui/mod.rs +++ b/src-tauri/src/ui/mod.rs @@ -1,7 +1,11 @@ use tauri::AppHandle; +#[cfg(debug_assertions)] +mod debug; mod scene; pub fn init(app_handle: &AppHandle) { + #[cfg(debug_assertions)] + debug::init(app_handle); scene::init(app_handle); } diff --git a/src/lib/bindings.ts b/src/lib/bindings.ts index 91c997d..f5cd3ea 100644 --- a/src/lib/bindings.ts +++ b/src/lib/bindings.ts @@ -52,7 +52,6 @@ async listStatuses() : Promise { export const events = __makeEvents__<{ cursorPositionChanged: CursorPositionChanged, foregroundAppChanged: ForegroundAppChanged, -friendCursorPositionChanged: FriendCursorPositionChanged, friendForegroundAppChanged: FriendForegroundAppChanged, friendsChanged: FriendsChanged, networkStatusChanged: NetworkStatusChanged, @@ -61,7 +60,6 @@ remotesChanged: RemotesChanged }>({ cursorPositionChanged: "cursor-position-changed", foregroundAppChanged: "foreground-app-changed", -friendCursorPositionChanged: "friend-cursor-position-changed", friendForegroundAppChanged: "friend-foreground-app-changed", friendsChanged: "friends-changed", networkStatusChanged: "network-status-changed", @@ -82,10 +80,9 @@ export type AppMeta = { local: string | null; unlocal: string | null; ico: strin export type ConnectionState = "connecting" | "connected" | "disconnected" export type ConnectionStatus = { remoteId: string; address: string; name: string | null; state: ConnectionState } export type CursorPosition = { x: number; y: number } -export type CursorPositionChanged = { positions: CursorPositions } +export type CursorPositionChanged = { positions: Partial<{ [key in string]: CursorPositions }> } export type CursorPositions = { raw: CursorPosition; mapped: CursorPosition } export type ForegroundAppChanged = { meta: AppMeta } -export type FriendCursorPositionChanged = { friendId: string; positions: CursorPositions } export type FriendForegroundAppChanged = { friendId: string; meta: AppMeta } export type FriendsChanged = { friends: User[] } export type NetworkStatusChanged = { statuses: ConnectionStatus[] } diff --git a/src/lib/components/friends-live-data.svelte b/src/lib/components/friends-live-data.svelte deleted file mode 100644 index 324e0b8..0000000 --- a/src/lib/components/friends-live-data.svelte +++ /dev/null @@ -1,469 +0,0 @@ - - -
-
-
-

Mutual connections

-

Friend signals

-
- Live -
- - {#if error} - - {:else if friends.length === 0} -
- -

Add a friend to watch their live activity arrive here.

-
- {:else} -
    - {#each friends as friend (friend.id)} - {@const data = liveData.get(friend.id) ?? emptyLiveData()} -
  • -
    - -
    -

    {friend.displayName}

    - {compactId(friend.id)} -
    -
    - -
    -
    - {#if data.foregroundApp} - {#if data.foregroundApp.ico} - - {:else} - - {/if} -
    - Foreground - - {data.foregroundApp.local ?? - data.foregroundApp.unlocal ?? - "Unknown app"} - -
    - {:else} - -
    - Foreground - Waiting for activity -
    - {/if} -
    - -
    -
    - Cursor - {#if data.cursor} - - {Math.round(data.cursor.mapped.x * 100)} · - {Math.round(data.cursor.mapped.y * 100)} - - {:else} - — · — - {/if} -
    -
    - -
    -
    -
    -
  • - {/each} -
- {/if} -
- - diff --git a/src/lib/components/live-metadata.svelte b/src/lib/components/live-metadata.svelte index 11b6096..73d209e 100644 --- a/src/lib/components/live-metadata.svelte +++ b/src/lib/components/live-metadata.svelte @@ -1,45 +1,81 @@ @@ -47,27 +83,42 @@

Live metadata

-

Cursor

- {#if cursor} -

Raw: {cursor.raw.x}, {cursor.raw.y}

-

Mapped: {cursor.mapped.x}, {cursor.mapped.y}

+ {#if error} +

{error}

+ {:else if liveUserIds().length === 0} +

Waiting for live metadata...

{:else} -

Waiting for cursor data...

- {/if} + {#each liveUserIds() as userId (userId)} + {@const cursor = cursorPositions[userId]} + {@const foregroundApp = foregroundApps.get(userId)} +
+

{userId === localId ? "Local user" : "Remote user"}

+

ID: {compactId(userId)}

-

Foreground app

- {#if foregroundApp} - {#if foregroundApp.ico} - - {/if} -

Localized name: {foregroundApp.local ?? "Unavailable"}

-

Executable name: {foregroundApp.unlocal ?? "Unavailable"}

- {:else} -

Waiting for foreground app data...

+

Cursor

+ {#if cursor} +

Raw: {cursor.raw.x}, {cursor.raw.y}

+

Mapped: {cursor.mapped.x}, {cursor.mapped.y}

+ {:else} +

Waiting for cursor data...

+ {/if} + +

Foreground app

+ {#if foregroundApp} + {#if foregroundApp.ico} + + {/if} +

Localized name: {foregroundApp.local ?? "Unavailable"}

+

Executable name: {foregroundApp.unlocal ?? "Unavailable"}

+ {:else} +

Waiting for foreground app data...

+ {/if} +
+ {/each} {/if}
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3d04240..e4bbe0b 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,6 +1,5 @@