tenseleyflow/hyprkvm / ecec6ac

Browse files

feat: integrate clipboard sync into control transfer

- Add SyncClipboardOutgoing transfer event
- Trigger clipboard sync on enter (after StartCapture)
- Trigger clipboard sync on leave (before Leave message)
- Add ClipboardManager initialization in daemon
- Handle ClipboardOffer/Request/Data messages
- Sync runs in spawned task to avoid blocking main loop
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ecec6acdf17dda90d5a9ab174f0532d9f7d5fa6e
Parents
7023e90
Tree
eb91f87

2 changed files

StatusFile+-
M hyprkvm-daemon/src/main.rs 84 0
M hyprkvm-daemon/src/transfer/manager.rs 14 0
hyprkvm-daemon/src/main.rsmodified
@@ -10,6 +10,7 @@ use clap::{Parser, Subcommand};
1010
 use tracing::{info, Level};
1111
 use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
1212
 
13
+mod clipboard;
1314
 mod config;
1415
 mod hyprland;
1516
 mod input;
@@ -247,6 +248,11 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
247248
     );
248249
     let transfer_manager = Arc::new(transfer_manager);
249250
 
251
+    // Create clipboard manager
252
+    let clipboard_manager = std::sync::Arc::new(clipboard::ClipboardManager::new(
253
+        config.clipboard.clone(),
254
+    ));
255
+
250256
     // Track which direction we're capturing for
251257
     let mut capture_direction: Option<Direction> = None;
252258
     let mut input_sequence: u64 = 0;
@@ -1102,6 +1108,8 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
11021108
                 }
11031109
 
11041110
                 for direction in directions {
1111
+                    // Clone Arc before shadowing for use in spawned tasks
1112
+                    let peers_arc = peers.clone();
11051113
                     let mut peers = peers.write().await;
11061114
                     if let Some(peer) = peers.get_mut(&direction) {
11071115
                         // Try non-blocking receive using tokio timeout
@@ -1198,6 +1206,55 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
11981206
                                                 .as_millis() as u64 - timestamp
11991207
                                         );
12001208
                                     }
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
+                                    }
12011258
                                     _ => {
12021259
                                         tracing::debug!("Unhandled message: {:?}", msg);
12031260
                                     }
@@ -1337,6 +1394,33 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
13371394
                             emu.keyboard.reset_all_keys();
13381395
                         }
13391396
                     }
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
+                    }
13401424
                 }
13411425
             }
13421426
 
hyprkvm-daemon/src/transfer/manager.rsmodified
@@ -73,6 +73,8 @@ pub enum TransferEvent {
7373
     StopInjection,
7474
     /// Send a message to a peer
7575
     SendMessage { direction: Direction, message: Message },
76
+    /// Sync clipboard to remote machine
77
+    SyncClipboardOutgoing { direction: Direction },
7678
 }
7779
 
7880
 /// Manages control transfer between machines
@@ -229,6 +231,12 @@ impl TransferManager {
229231
                     .await
230232
                     .map_err(|_| TransferError::ChannelClosed)?;
231233
 
234
+                // Trigger clipboard sync (if enabled, handled by main loop)
235
+                self.event_tx
236
+                    .send(TransferEvent::SyncClipboardOutgoing { direction })
237
+                    .await
238
+                    .map_err(|_| TransferError::ChannelClosed)?;
239
+
232240
                 Ok(())
233241
             }
234242
             _ => Err(TransferError::InvalidState(
@@ -350,6 +358,12 @@ impl TransferManager {
350358
 
351359
                 tracing::info!("Returning control to {:?}", direction);
352360
 
361
+                // Trigger clipboard sync before leaving (if enabled, handled by main loop)
362
+                self.event_tx
363
+                    .send(TransferEvent::SyncClipboardOutgoing { direction })
364
+                    .await
365
+                    .map_err(|_| TransferError::ChannelClosed)?;
366
+
353367
                 // Send Leave message
354368
                 let leave = Message::Leave(LeavePayload {
355369
                     to_direction: direction,