@@ -10,6 +10,7 @@ use clap::{Parser, Subcommand}; |
| 10 | use tracing::{info, Level}; | 10 | use tracing::{info, Level}; |
| 11 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; | 11 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; |
| 12 | | 12 | |
| | 13 | +mod clipboard; |
| 13 | mod config; | 14 | mod config; |
| 14 | mod hyprland; | 15 | mod hyprland; |
| 15 | mod input; | 16 | mod input; |
@@ -247,6 +248,11 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 247 | ); | 248 | ); |
| 248 | let transfer_manager = Arc::new(transfer_manager); | 249 | let transfer_manager = Arc::new(transfer_manager); |
| 249 | | 250 | |
| | 251 | + // Create clipboard manager |
| | 252 | + let clipboard_manager = std::sync::Arc::new(clipboard::ClipboardManager::new( |
| | 253 | + config.clipboard.clone(), |
| | 254 | + )); |
| | 255 | + |
| 250 | // Track which direction we're capturing for | 256 | // Track which direction we're capturing for |
| 251 | let mut capture_direction: Option<Direction> = None; | 257 | let mut capture_direction: Option<Direction> = None; |
| 252 | let mut input_sequence: u64 = 0; | 258 | let mut input_sequence: u64 = 0; |
@@ -1102,6 +1108,8 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 1102 | } | 1108 | } |
| 1103 | | 1109 | |
| 1104 | for direction in directions { | 1110 | for direction in directions { |
| | 1111 | + // Clone Arc before shadowing for use in spawned tasks |
| | 1112 | + let peers_arc = peers.clone(); |
| 1105 | let mut peers = peers.write().await; | 1113 | let mut peers = peers.write().await; |
| 1106 | if let Some(peer) = peers.get_mut(&direction) { | 1114 | if let Some(peer) = peers.get_mut(&direction) { |
| 1107 | // Try non-blocking receive using tokio timeout | 1115 | // Try non-blocking receive using tokio timeout |
@@ -1198,6 +1206,55 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 1198 | .as_millis() as u64 - timestamp | 1206 | .as_millis() as u64 - timestamp |
| 1199 | ); | 1207 | ); |
| 1200 | } | 1208 | } |
| | 1209 | + Message::ClipboardOffer(offer) => { |
| | 1210 | + // Handle clipboard offer from peer |
| | 1211 | + let cm = clipboard_manager.clone(); |
| | 1212 | + let peers_clone = peers_arc.clone(); |
| | 1213 | + let dir = direction; |
| | 1214 | + tokio::spawn(async move { |
| | 1215 | + if let Some(request) = cm.handle_offer(offer).await { |
| | 1216 | + let mut peers_guard = peers_clone.write().await; |
| | 1217 | + if let Some(peer) = peers_guard.get_mut(&dir) { |
| | 1218 | + if let Err(e) = peer.send(&Message::ClipboardRequest(request)).await { |
| | 1219 | + tracing::warn!("Failed to send clipboard request: {}", e); |
| | 1220 | + } |
| | 1221 | + } |
| | 1222 | + } |
| | 1223 | + }); |
| | 1224 | + } |
| | 1225 | + Message::ClipboardRequest(request) => { |
| | 1226 | + // Handle clipboard request from peer |
| | 1227 | + let cm = clipboard_manager.clone(); |
| | 1228 | + let peers_clone = peers_arc.clone(); |
| | 1229 | + let dir = direction; |
| | 1230 | + tokio::spawn(async move { |
| | 1231 | + match cm.handle_request(request).await { |
| | 1232 | + Ok(data_chunks) => { |
| | 1233 | + let mut peers_guard = peers_clone.write().await; |
| | 1234 | + if let Some(peer) = peers_guard.get_mut(&dir) { |
| | 1235 | + for chunk in data_chunks { |
| | 1236 | + if let Err(e) = peer.send(&Message::ClipboardData(chunk)).await { |
| | 1237 | + tracing::warn!("Failed to send clipboard data: {}", e); |
| | 1238 | + break; |
| | 1239 | + } |
| | 1240 | + } |
| | 1241 | + } |
| | 1242 | + } |
| | 1243 | + Err(e) => { |
| | 1244 | + tracing::warn!("Clipboard request failed: {}", e); |
| | 1245 | + } |
| | 1246 | + } |
| | 1247 | + }); |
| | 1248 | + } |
| | 1249 | + Message::ClipboardData(data) => { |
| | 1250 | + // Handle clipboard data from peer |
| | 1251 | + let cm = clipboard_manager.clone(); |
| | 1252 | + tokio::spawn(async move { |
| | 1253 | + if let Err(e) = cm.handle_data(data).await { |
| | 1254 | + tracing::warn!("Clipboard data handling failed: {}", e); |
| | 1255 | + } |
| | 1256 | + }); |
| | 1257 | + } |
| 1201 | _ => { | 1258 | _ => { |
| 1202 | tracing::debug!("Unhandled message: {:?}", msg); | 1259 | tracing::debug!("Unhandled message: {:?}", msg); |
| 1203 | } | 1260 | } |
@@ -1337,6 +1394,33 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 1337 | emu.keyboard.reset_all_keys(); | 1394 | emu.keyboard.reset_all_keys(); |
| 1338 | } | 1395 | } |
| 1339 | } | 1396 | } |
| | 1397 | + transfer::TransferEvent::SyncClipboardOutgoing { direction } => { |
| | 1398 | + // Sync clipboard to the peer in the given direction |
| | 1399 | + // Check if clipboard sync is enabled and appropriate for this event |
| | 1400 | + if config.clipboard.enabled { |
| | 1401 | + let cm = clipboard_manager.clone(); |
| | 1402 | + let peers_clone = peers.clone(); |
| | 1403 | + tokio::spawn(async move { |
| | 1404 | + match cm.create_offer().await { |
| | 1405 | + Ok(Some(offer)) => { |
| | 1406 | + let mut peers_guard = peers_clone.write().await; |
| | 1407 | + if let Some(peer) = peers_guard.get_mut(&direction) { |
| | 1408 | + info!("Syncing clipboard to {:?}", direction); |
| | 1409 | + if let Err(e) = peer.send(&Message::ClipboardOffer(offer)).await { |
| | 1410 | + tracing::warn!("Failed to send clipboard offer: {}", e); |
| | 1411 | + } |
| | 1412 | + } |
| | 1413 | + } |
| | 1414 | + Ok(None) => { |
| | 1415 | + tracing::debug!("No clipboard content to sync"); |
| | 1416 | + } |
| | 1417 | + Err(e) => { |
| | 1418 | + tracing::warn!("Failed to read clipboard for sync: {}", e); |
| | 1419 | + } |
| | 1420 | + } |
| | 1421 | + }); |
| | 1422 | + } |
| | 1423 | + } |
| 1340 | } | 1424 | } |
| 1341 | } | 1425 | } |
| 1342 | | 1426 | |