diff --git a/src-tauri/src/runtime/clipboard.rs b/src-tauri/src/runtime/clipboard.rs index 918147a..3de0655 100644 --- a/src-tauri/src/runtime/clipboard.rs +++ b/src-tauri/src/runtime/clipboard.rs @@ -87,6 +87,7 @@ pub async fn start_clipboard_monitor( content_type: CONTENT_TYPE_TEXT.to_string(), data: text, sender_device_id: device_id.clone(), + timestamp: current_timestamp(), }, }; let _ = tx.send(update); @@ -142,6 +143,7 @@ pub async fn start_clipboard_monitor( content_type: CONTENT_TYPE_IMAGE_PNG.to_string(), data: b64, sender_device_id: device_id.clone(), + timestamp: current_timestamp(), }, }; let _ = tx.send(update); @@ -185,6 +187,10 @@ pub async fn start_clipboard_setter( events: mpsc::Sender, cancel: CancellationToken, ) { + let mut last_text: Option = None; + let mut last_text_ts = 0; + let mut last_image_data: Option = None; + let mut last_image_ts = 0; loop { tokio::select! { _ = cancel.cancelled() => break, @@ -192,6 +198,11 @@ pub async fn start_clipboard_setter( if let Some(payload) = maybe_payload { match payload.content_type.as_str() { CONTENT_TYPE_TEXT => { + if payload.timestamp <= last_text_ts || Some(&payload.data) == last_text.as_ref() { + continue; + } + last_text_ts = payload.timestamp; + last_text = Some(payload.data.clone()); if let Err(err) = set_text(&payload.data, disable_flag.clone()).await { let _ = events.send(RuntimeEvent::Log(RuntimeLogEvent::new(Level::Error, format!("设置文本剪贴板失败: {}", err)))).await; } else { @@ -200,6 +211,11 @@ pub async fn start_clipboard_setter( } } CONTENT_TYPE_IMAGE_PNG => { + if payload.timestamp <= last_image_ts || Some(&payload.data) == last_image_data.as_ref() { + continue; + } + last_image_ts = payload.timestamp; + last_image_data = Some(payload.data.clone()); if let Err(err) = set_image_from_base64(&payload.data, disable_flag.clone()).await { let _ = events.send(RuntimeEvent::Log(RuntimeLogEvent::new(Level::Error, format!("设置图片剪贴板失败: {}", err)))).await; } else { @@ -291,6 +307,13 @@ fn read_clipboard_content() -> Result { } } +fn current_timestamp() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64 +} + fn encode_png(bytes: Vec, width: u32, height: u32) -> Result, String> { if let Some(rgba) = RgbaImage::from_raw(width, height, bytes) { let mut cursor = Cursor::new(Vec::new()); diff --git a/src-tauri/src/runtime/lan/discovery.rs b/src-tauri/src/runtime/lan/discovery.rs index 946eee8..dd8cd7c 100644 --- a/src-tauri/src/runtime/lan/discovery.rs +++ b/src-tauri/src/runtime/lan/discovery.rs @@ -85,10 +85,7 @@ pub async fn run_beacon_broadcaster( discovery_port }; - // Bind to INADDR_ANY so the OS picks the right interface. - // We use port 0 for the *sending* socket so we don't conflict with the - // listener that is bound to the same discovery port. - let socket = match UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await { + let mut socket = match UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await { Ok(s) => s, Err(e) => { let _ = events @@ -141,9 +138,39 @@ pub async fn run_beacon_broadcaster( let _ = events .send(RuntimeEvent::Log(RuntimeLogEvent::new( Level::Warn, - format!("LAN beacon send failed: {}", e), + format!("LAN beacon send failed (attempting rebind): {}", e), ))) .await; + + // Attempt to rebind on network error + match UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)).await { + Ok(new_socket) => { + if let Err(err) = new_socket.set_broadcast(true) { + let _ = events + .send(RuntimeEvent::Log(RuntimeLogEvent::new( + Level::Error, + format!("LAN beacon rebind set_broadcast failed: {}", err), + ))) + .await; + } else { + socket = new_socket; + let _ = events + .send(RuntimeEvent::Log(RuntimeLogEvent::new( + Level::Info, + "LAN beacon rebind successful".to_string(), + ))) + .await; + } + } + Err(err) => { + let _ = events + .send(RuntimeEvent::Log(RuntimeLogEvent::new( + Level::Error, + format!("LAN beacon rebind failed: {}", err), + ))) + .await; + } + } } seq = seq.wrapping_add(1); } diff --git a/src-tauri/src/runtime/lan/peer.rs b/src-tauri/src/runtime/lan/peer.rs index ef08ce6..a1b4579 100644 --- a/src-tauri/src/runtime/lan/peer.rs +++ b/src-tauri/src/runtime/lan/peer.rs @@ -441,6 +441,7 @@ async fn run_peer_session( let msg = PeerMessage::Clipboard { content_type: update.payload.content_type.clone(), data: update.payload.data.clone(), + timestamp: update.payload.timestamp, }; let frame = encode_peer_message(&msg); let mut w = writer.lock().await; @@ -490,10 +491,11 @@ async fn run_peer_session( PeerMessage::Pong { .. } => { last_pong = Instant::now(); } - PeerMessage::Clipboard { content_type, data } => { + PeerMessage::Clipboard { content_type, data, timestamp } => { let payload = ClipboardBroadcastPayload { content_type: content_type.clone(), data, + timestamp, }; let _ = tx_in.send(payload).await; diff --git a/src-tauri/src/runtime/lan/protocol.rs b/src-tauri/src/runtime/lan/protocol.rs index 1ed8c40..520ff47 100644 --- a/src-tauri/src/runtime/lan/protocol.rs +++ b/src-tauri/src/runtime/lan/protocol.rs @@ -100,6 +100,7 @@ pub enum PeerMessage { Clipboard { content_type: String, data: String, + timestamp: u64, }, } diff --git a/src-tauri/src/runtime/messages.rs b/src-tauri/src/runtime/messages.rs index 8d63930..853ac31 100644 --- a/src-tauri/src/runtime/messages.rs +++ b/src-tauri/src/runtime/messages.rs @@ -39,6 +39,7 @@ pub struct ClipboardUpdatePayload { pub content_type: String, pub data: String, pub sender_device_id: String, + pub timestamp: u64, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -67,6 +68,7 @@ pub struct ClipboardBroadcast { pub struct ClipboardBroadcastPayload { pub content_type: String, pub data: String, + pub timestamp: u64, } // 客户端发送的消息结构