tenseleyflow/hyprkvm / a719cf8

Browse files

feat: add bilateral direction sync via DirectionChange protocol

When the user changes a neighbor's direction in the GUI:
1. Local config is saved with the new direction
2. DirectionChange message is sent to the affected peer
3. Peer updates its config to store us in the opposite direction
4. Both machines restart with synchronized edge barriers

This replaces the previous auto-correction behavior that would
overwrite config changes on reconnect. Now direction changes are
explicit and initiated by the user.

Changes:
- Add DirectionChange/DirectionChangeAck protocol messages
- Handle incoming DirectionChange by updating config and restarting
- Send DirectionChange to peers when IPC Reload detects direction changes
- Remove auto-correction on Hello that was overwriting GUI changes
Authored by espadonne
SHA
a719cf85568c8816525718b3e2945fa7856ebaa7
Parents
d3af6ee
Tree
e7f382e

2 changed files

StatusFile+-
M hyprkvm-common/src/protocol/mod.rs 18 0
M hyprkvm-daemon/src/main.rs 100 38
hyprkvm-common/src/protocol/mod.rsmodified
@@ -37,6 +37,10 @@ pub enum Message {
3737
     ClipboardRequest(ClipboardRequestPayload),
3838
     ClipboardData(ClipboardDataPayload),
3939
 
40
+    // Configuration sync
41
+    DirectionChange(DirectionChangePayload),
42
+    DirectionChangeAck { success: bool },
43
+
4044
     // Health
4145
     Ping { timestamp: u64 },
4246
     Pong { timestamp: u64 },
@@ -89,6 +93,20 @@ pub struct NeighborInfo {
8993
     pub direction: Direction,
9094
 }
9195
 
96
+// ============================================================================
97
+// Configuration Sync Messages
98
+// ============================================================================
99
+
100
+/// Notification that a peer has changed our relative direction in their config.
101
+/// When received, the recipient should update their config to match.
102
+#[derive(Debug, Clone, Serialize, Deserialize)]
103
+pub struct DirectionChangePayload {
104
+    /// The direction the sender has configured for the recipient.
105
+    /// The recipient should store the OPPOSITE direction for the sender.
106
+    /// e.g., if sender says "I have you on my Right", recipient stores sender as "Left"
107
+    pub your_direction_from_me: Direction,
108
+}
109
+
92110
 // ============================================================================
93111
 // Control Transfer Messages
94112
 // ============================================================================
hyprkvm-daemon/src/main.rsmodified
@@ -457,52 +457,25 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
457457
                                     .map(|n| n.direction)
458458
                             };
459459
 
460
-                            // Check if we need to update our config due to direction change
460
+                            // NOTE: We no longer auto-correct direction based on peer claims.
461
+                            // Direction changes are now handled explicitly via DirectionChange messages
462
+                            // sent when the user changes direction in the GUI.
463
+                            // This prevents the config from being overwritten on reconnect.
461464
                             if let Some(peer_dir) = hello.my_direction_for_you {
462465
                                 let new_dir = peer_dir.opposite();
463466
                                 let existing = neighbors_for_accept
464467
                                     .iter()
465468
                                     .find(|n| n.name == hello.machine_name);
466469
 
467
-                                let needs_update = match existing {
468
-                                    Some(n) => n.direction != new_dir,
469
-                                    None => true, // Peer not in our config - could add them
470
-                                };
471
-
472
-                                if needs_update {
473
-                                    if let Some(existing_neighbor) = existing {
474
-                                        info!(
475
-                                            "Direction mismatch for {}: our config says {:?}, peer says we should be {:?}",
470
+                                if let Some(existing_neighbor) = existing {
471
+                                    if existing_neighbor.direction != new_dir {
472
+                                        // Just log the mismatch, don't auto-correct
473
+                                        // The user should update both configs via the GUI
474
+                                        tracing::warn!(
475
+                                            "Direction mismatch for {}: our config says {:?}, peer claims {:?}. \
476
+                                             Use the GUI to update directions on both machines.",
476477
                                             hello.machine_name, existing_neighbor.direction, new_dir
477478
                                         );
478
-
479
-                                        // Load, update, and save config
480
-                                        match Config::load(&config_path_for_accept) {
481
-                                            Ok(mut cfg) => {
482
-                                                // Find and update the neighbor's direction
483
-                                                for neighbor in &mut cfg.machines.neighbors {
484
-                                                    if neighbor.name == hello.machine_name {
485
-                                                        info!("Updating {} direction: {:?} -> {:?}",
486
-                                                              neighbor.name, neighbor.direction, new_dir);
487
-                                                        neighbor.direction = new_dir;
488
-                                                        break;
489
-                                                    }
490
-                                                }
491
-
492
-                                                // Save updated config
493
-                                                if let Err(e) = cfg.save(&config_path_for_accept) {
494
-                                                    tracing::error!("Failed to save updated config: {}", e);
495
-                                                } else {
496
-                                                    info!("Config updated with new direction, signaling restart...");
497
-                                                    let _ = restart_tx_for_accept.try_send(
498
-                                                        format!("Direction changed for {}", hello.machine_name)
499
-                                                    );
500
-                                                }
501
-                                            }
502
-                                            Err(e) => {
503
-                                                tracing::error!("Failed to load config for update: {}", e);
504
-                                            }
505
-                                        }
506479
                                     }
507480
                                 }
508481
                             }
@@ -1399,6 +1372,63 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
13991372
                                             }
14001373
                                         });
14011374
                                     }
1375
+                                    Message::DirectionChange(payload) => {
1376
+                                        // Peer is notifying us that they changed our relative direction
1377
+                                        // We need to update our config to store them in the opposite direction
1378
+                                        let new_dir_for_peer = payload.your_direction_from_me.opposite();
1379
+                                        info!("Received DirectionChange: peer says we are {:?} from them, so we store them as {:?}",
1380
+                                            payload.your_direction_from_me, new_dir_for_peer);
1381
+
1382
+                                        // Find the peer's name from config based on direction
1383
+                                        let peer_name = config.machines.neighbors
1384
+                                            .iter()
1385
+                                            .find(|n| n.direction == direction)
1386
+                                            .map(|n| n.name.clone())
1387
+                                            .unwrap_or_else(|| "unknown".to_string());
1388
+
1389
+                                        // Load, update, and save config
1390
+                                        let config_path_clone = config_path.clone();
1391
+                                        match Config::load(&config_path_clone) {
1392
+                                            Ok(mut cfg) => {
1393
+                                                let mut found = false;
1394
+                                                for neighbor in &mut cfg.machines.neighbors {
1395
+                                                    if neighbor.name == peer_name {
1396
+                                                        info!("DirectionChange: updating {} direction {:?} -> {:?}",
1397
+                                                            neighbor.name, neighbor.direction, new_dir_for_peer);
1398
+                                                        neighbor.direction = new_dir_for_peer;
1399
+                                                        found = true;
1400
+                                                        break;
1401
+                                                    }
1402
+                                                }
1403
+
1404
+                                                if found {
1405
+                                                    if let Err(e) = cfg.save(&config_path_clone) {
1406
+                                                        tracing::error!("DirectionChange: failed to save config: {}", e);
1407
+                                                        let _ = peer.send(&Message::DirectionChangeAck { success: false }).await;
1408
+                                                    } else {
1409
+                                                        info!("DirectionChange: config updated, signaling restart");
1410
+                                                        let _ = peer.send(&Message::DirectionChangeAck { success: true }).await;
1411
+                                                        // Signal restart to apply new edge barriers
1412
+                                                        let _ = restart_tx.try_send(format!("Direction sync from {}", peer_name));
1413
+                                                    }
1414
+                                                } else {
1415
+                                                    tracing::warn!("DirectionChange: peer {} not found in config", peer_name);
1416
+                                                    let _ = peer.send(&Message::DirectionChangeAck { success: false }).await;
1417
+                                                }
1418
+                                            }
1419
+                                            Err(e) => {
1420
+                                                tracing::error!("DirectionChange: failed to load config: {}", e);
1421
+                                                let _ = peer.send(&Message::DirectionChangeAck { success: false }).await;
1422
+                                            }
1423
+                                        }
1424
+                                    }
1425
+                                    Message::DirectionChangeAck { success } => {
1426
+                                        if success {
1427
+                                            info!("DirectionChangeAck: peer acknowledged direction update");
1428
+                                        } else {
1429
+                                            tracing::warn!("DirectionChangeAck: peer failed to update direction");
1430
+                                        }
1431
+                                    }
14021432
                                     _ => {
14031433
                                         tracing::debug!("Unhandled message: {:?}", msg);
14041434
                                     }
@@ -2153,6 +2183,8 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
21532183
                                 }
21542184
 
21552185
                                 // Check for direction changes (requires restart for edge barriers)
2186
+                                // Also send DirectionChange messages to notify peers
2187
+                                let mut direction_changes: Vec<(String, Direction)> = Vec::new();
21562188
                                 for new_neighbor in &new_config.machines.neighbors {
21572189
                                     if let Some(old_neighbor) = config.machines.neighbors
21582190
                                         .iter()
@@ -2164,6 +2196,36 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> {
21642196
                                                 new_neighbor.name, old_neighbor.direction, new_neighbor.direction
21652197
                                             ));
21662198
                                             needs_restart = true;
2199
+                                            // Track this change to notify the peer
2200
+                                            direction_changes.push((new_neighbor.name.clone(), new_neighbor.direction));
2201
+                                        }
2202
+                                    }
2203
+                                }
2204
+
2205
+                                // Send DirectionChange messages to affected peers
2206
+                                // We need to send on the OLD direction since that's where the peer is connected
2207
+                                for (peer_name, new_direction) in &direction_changes {
2208
+                                    // Find the OLD direction for this peer from current config
2209
+                                    let old_direction = config.machines.neighbors
2210
+                                        .iter()
2211
+                                        .find(|n| &n.name == peer_name)
2212
+                                        .map(|n| n.direction);
2213
+
2214
+                                    if let Some(old_dir) = old_direction {
2215
+                                        let mut peers_guard = peers.write().await;
2216
+                                        if let Some(peer) = peers_guard.get_mut(&old_dir) {
2217
+                                            info!("Sending DirectionChange to {}: you are now {:?} from me",
2218
+                                                peer_name, new_direction);
2219
+                                            let msg = Message::DirectionChange(
2220
+                                                hyprkvm_common::protocol::DirectionChangePayload {
2221
+                                                    your_direction_from_me: *new_direction,
2222
+                                                }
2223
+                                            );
2224
+                                            if let Err(e) = peer.send(&msg).await {
2225
+                                                tracing::warn!("Failed to send DirectionChange to {}: {}", peer_name, e);
2226
+                                            }
2227
+                                        } else {
2228
+                                            tracing::warn!("Peer {} not connected on {:?}", peer_name, old_dir);
21672229
                                         }
21682230
                                     }
21692231
                                 }