data between friends (with some crazy fancy UI)

This commit is contained in:
2026-08-10 02:07:51 +08:00
parent 8bc1d074d4
commit 4c0cbc9c85
10 changed files with 757 additions and 20 deletions
+31 -1
View File
@@ -30,6 +30,13 @@ 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,
}
#[derive(Default)]
pub struct CursorState(RwLock<CursorPositions>);
@@ -91,9 +98,32 @@ fn update_cursor_position(handle: &AppHandle, positions: CursorPositions) {
*guard = positions.clone();
drop(guard);
if let Err(error) = (CursorPositionChanged { positions }).emit(handle) {
if let Err(error) = (CursorPositionChanged {
positions: positions.clone(),
})
.emit(handle)
{
eprintln!("Failed to emit cursor position change: {error}");
}
handle
.state::<crate::network::Network>()
.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}");
}
}
/// Convert absolute to normalized coordinates (0.12, 0.78), or normalized to absolute (1234, 567)
+3
View File
@@ -2,6 +2,7 @@ mod cursor;
mod db;
mod friends;
mod keypair;
mod live_data;
mod network;
mod profile;
mod remotes;
@@ -73,7 +74,9 @@ fn specta_builder() -> tauri_specta::Builder<tauri::Wry> {
profile::ProfileChanged,
network::NetworkStatusChanged,
cursor::CursorPositionChanged,
cursor::FriendCursorPositionChanged,
ufa::ForegroundAppChanged,
ufa::FriendForegroundAppChanged,
])
}
+37
View File
@@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
use crate::cursor::CursorPositions;
use crate::ufa::AppMeta;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum LiveData {
Cursor { positions: CursorPositions },
ForegroundApp { meta: AppMeta },
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cursor::{CursorPosition, CursorPositions};
#[test]
fn live_data_is_internally_tagged_for_client_side_dispatch() {
let payload = serde_json::to_string(&LiveData::Cursor {
positions: CursorPositions {
raw: CursorPosition { x: 120.0, y: 80.0 },
mapped: CursorPosition { x: 0.25, y: 0.5 },
},
})
.unwrap();
assert_eq!(
payload,
r#"{"type":"cursor","positions":{"raw":{"x":120.0,"y":80.0},"mapped":{"x":0.25,"y":0.5}}}"#
);
assert!(matches!(
serde_json::from_str(&payload).unwrap(),
LiveData::Cursor { .. }
));
}
}
+32 -10
View File
@@ -18,6 +18,7 @@ use wyd_common::{
use crate::db::AppDatabase;
use crate::friends::{self, FriendsChanged};
use crate::keypair::AppKeypair;
use crate::live_data::LiveData;
use crate::remotes::{self, Remote, RemotesChanged};
type Statuses = Arc<Mutex<HashMap<String, (u64, ConnectionStatus)>>>;
@@ -62,16 +63,23 @@ pub struct Network {
}
impl Network {
#[allow(dead_code)] // Ready for the first domain message sender.
pub fn send(&self, remote_id: &str, payload: String) -> Result<(), String> {
let connections = self.connections.lock().map_err(|error| error.to_string())?;
let sender = connections
.get(remote_id)
.map(|connection| &connection.sender)
.ok_or_else(|| "remote is not configured".to_string())?;
sender
.try_send(payload)
.map_err(|error| format!("remote is not ready: {error}"))
pub fn send_live_data(&self, data: LiveData) {
let Ok(payload) = serde_json::to_string(&data) else {
eprintln!("failed to serialize live data");
return;
};
let Ok(connections) = self.connections.lock() else {
eprintln!("failed to lock remote connections for live data");
return;
};
for connection in connections.values() {
match connection.sender.try_send(payload.clone()) {
Ok(()) | Err(mpsc::error::TrySendError::Full(_)) => {}
Err(mpsc::error::TrySendError::Closed(_)) => {
eprintln!("remote {} cannot accept live data", connection.remote.id);
}
}
}
}
pub fn update_profile(&self, profile: crate::user::User) {
@@ -263,6 +271,7 @@ async fn connect(
if !matches!(recv(&mut reader).await?, ServerMessage::Registered) {
return Err("server rejected registration".into());
}
while outgoing.try_recv().is_ok() {}
send(&mut writer, &ClientMessage::SyncFriendProfiles).await?;
changed(
handle,
@@ -315,6 +324,19 @@ async fn connect(
eprintln!("failed to synchronize friend profiles: {error}");
}
}
ServerMessage::FriendLiveData { friend_id, payload } => {
match serde_json::from_str(&payload) {
Ok(LiveData::Cursor { positions }) => {
crate::cursor::emit_friend_position(handle, friend_id, positions);
}
Ok(LiveData::ForegroundApp { meta }) => {
crate::ufa::emit_friend_app(handle, friend_id, meta);
}
Err(error) => {
eprintln!("failed to decode friend live data: {error}");
}
}
}
_ => {}
},
Message::Ping(data) => writer.send(Message::Pong(data)).await?,
+18 -1
View File
@@ -19,6 +19,13 @@ pub struct ForegroundAppChanged {
pub meta: AppMeta,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type, Event)]
#[serde(rename_all = "camelCase")]
pub struct FriendForegroundAppChanged {
pub friend_id: String,
pub meta: AppMeta,
}
#[derive(Default)]
pub struct ForegroundAppState(RwLock<AppMeta>);
@@ -67,8 +74,18 @@ pub fn init(handle: &AppHandle) {
*current = meta.clone();
drop(current);
if let Err(error) = (ForegroundAppChanged { meta }).emit(&handle) {
if let Err(error) = (ForegroundAppChanged { meta: meta.clone() }).emit(&handle) {
eprintln!("Failed to emit foreground app change: {error}");
}
handle
.state::<crate::network::Network>()
.send_live_data(crate::live_data::LiveData::ForegroundApp { meta });
});
}
pub(crate) fn emit_friend_app(handle: &AppHandle, friend_id: String, meta: AppMeta) {
if let Err(error) = (FriendForegroundAppChanged { friend_id, meta }).emit(handle) {
eprintln!("Failed to emit friend foreground app change: {error}");
}
}