@@ -110,6 +110,13 @@ async fn main() -> anyhow::Result<()> { |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | 112 | async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 113 | + use std::collections::HashMap; |
| 114 | + use std::net::SocketAddr; |
| 115 | + use std::sync::Arc; |
| 116 | + use tokio::sync::RwLock; |
| 117 | + use hyprkvm_common::Direction; |
| 118 | + use hyprkvm_common::protocol::{Message, HelloPayload, PROTOCOL_VERSION}; |
| 119 | + |
| 113 | 120 | // Load or create default config |
| 114 | 121 | let config = match Config::load(config_path) { |
| 115 | 122 | Ok(cfg) => cfg, |
@@ -133,48 +140,314 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 133 | 140 | info!(" {} at ({}, {}) {}x{}", mon.name, mon.x, mon.y, mon.width, mon.height); |
| 134 | 141 | } |
| 135 | 142 | |
| 143 | + // Calculate screen bounds |
| 144 | + let screen_width: u32 = monitors.iter().map(|m| m.x as u32 + m.width).max().unwrap_or(1920); |
| 145 | + let screen_height: u32 = monitors.iter().map(|m| m.y as u32 + m.height).max().unwrap_or(1080); |
| 146 | + |
| 136 | 147 | // Determine which edges have network neighbors |
| 137 | 148 | let mut enabled_edges = Vec::new(); |
| 149 | + let mut neighbor_map: HashMap<Direction, SocketAddr> = HashMap::new(); |
| 138 | 150 | for neighbor in &config.machines.neighbors { |
| 139 | 151 | enabled_edges.push(neighbor.direction); |
| 140 | | - info!(" Network neighbor: {} ({})", neighbor.name, neighbor.direction); |
| 152 | + neighbor_map.insert(neighbor.direction, neighbor.address); |
| 153 | + info!(" Network neighbor: {} ({}) at {}", neighbor.name, neighbor.direction, neighbor.address); |
| 141 | 154 | } |
| 142 | 155 | |
| 143 | | - // If no neighbors configured, enable all edges for testing |
| 156 | + // If no neighbors configured, just run in demo mode |
| 144 | 157 | if enabled_edges.is_empty() { |
| 145 | | - info!("No neighbors configured, enabling all edges for testing"); |
| 146 | | - enabled_edges = vec![ |
| 147 | | - hyprkvm_common::Direction::Left, |
| 148 | | - hyprkvm_common::Direction::Right, |
| 149 | | - ]; |
| 158 | + info!("No neighbors configured. Add neighbors in config to enable control transfer."); |
| 159 | + enabled_edges = vec![Direction::Left, Direction::Right]; |
| 150 | 160 | } |
| 151 | 161 | |
| 152 | 162 | // Start edge capture |
| 153 | 163 | info!("Starting edge capture for: {:?}", enabled_edges); |
| 154 | 164 | let edge_capture = input::EdgeCapture::new(input::EdgeCaptureConfig { |
| 155 | 165 | barrier_size: 1, |
| 156 | | - enabled_edges, |
| 166 | + enabled_edges: enabled_edges.clone(), |
| 157 | 167 | })?; |
| 158 | 168 | |
| 169 | + // Create transfer manager |
| 170 | + let (transfer_manager, mut transfer_events) = transfer::TransferManager::new( |
| 171 | + config.machines.self_name.clone(), |
| 172 | + ); |
| 173 | + let transfer_manager = Arc::new(transfer_manager); |
| 174 | + |
| 175 | + // Connection storage: direction -> peer connection |
| 176 | + let peers: Arc<RwLock<HashMap<Direction, network::FramedConnection>>> = |
| 177 | + Arc::new(RwLock::new(HashMap::new())); |
| 178 | + |
| 179 | + // Start network server |
| 180 | + let listen_addr: SocketAddr = format!("0.0.0.0:{}", config.network.listen_port).parse()?; |
| 181 | + let server = network::Server::bind(listen_addr).await?; |
| 182 | + info!("Listening for connections on {}", server.local_addr()); |
| 183 | + |
| 184 | + // Spawn task to accept incoming connections |
| 185 | + let peers_clone = peers.clone(); |
| 186 | + let machine_name = config.machines.self_name.clone(); |
| 187 | + let accept_handle = tokio::spawn(async move { |
| 188 | + loop { |
| 189 | + match server.accept().await { |
| 190 | + Ok(mut conn) => { |
| 191 | + let addr = conn.remote_addr(); |
| 192 | + info!("Incoming connection from {}", addr); |
| 193 | + |
| 194 | + // Receive Hello |
| 195 | + match conn.recv().await { |
| 196 | + Ok(Some(Message::Hello(hello))) => { |
| 197 | + info!("Peer {} connected (protocol v{})", hello.machine_name, hello.protocol_version); |
| 198 | + |
| 199 | + // Send HelloAck |
| 200 | + let ack = Message::HelloAck(hyprkvm_common::protocol::HelloAckPayload { |
| 201 | + accepted: true, |
| 202 | + protocol_version: PROTOCOL_VERSION, |
| 203 | + machine_name: machine_name.clone(), |
| 204 | + error: None, |
| 205 | + }); |
| 206 | + if let Err(e) = conn.send(&ack).await { |
| 207 | + tracing::error!("Failed to send HelloAck: {}", e); |
| 208 | + continue; |
| 209 | + } |
| 210 | + |
| 211 | + // TODO: Determine direction from peer info |
| 212 | + // For now, assume first connection is from configured neighbor |
| 213 | + // In production, match by machine name |
| 214 | + } |
| 215 | + Ok(Some(other)) => { |
| 216 | + tracing::warn!("Expected Hello, got {:?}", other); |
| 217 | + } |
| 218 | + Ok(None) => { |
| 219 | + tracing::debug!("Connection closed during handshake"); |
| 220 | + } |
| 221 | + Err(e) => { |
| 222 | + tracing::error!("Handshake error: {}", e); |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + Err(e) => { |
| 227 | + tracing::error!("Accept error: {}", e); |
| 228 | + } |
| 229 | + } |
| 230 | + } |
| 231 | + }); |
| 232 | + |
| 233 | + // Channel for incoming messages from peers |
| 234 | + let (peer_msg_tx, mut peer_msg_rx) = tokio::sync::mpsc::channel::<(Direction, Message)>(64); |
| 235 | + |
| 236 | + // Connect to configured peers |
| 237 | + for neighbor in &config.machines.neighbors { |
| 238 | + let addr = neighbor.address; |
| 239 | + let direction = neighbor.direction; |
| 240 | + let peers_clone = peers.clone(); |
| 241 | + let machine_name = config.machines.self_name.clone(); |
| 242 | + let msg_tx = peer_msg_tx.clone(); |
| 243 | + |
| 244 | + tokio::spawn(async move { |
| 245 | + info!("Connecting to {} at {}...", direction, addr); |
| 246 | + match network::connect(addr).await { |
| 247 | + Ok(mut conn) => { |
| 248 | + // Send Hello |
| 249 | + let hello = Message::Hello(HelloPayload { |
| 250 | + protocol_version: PROTOCOL_VERSION, |
| 251 | + machine_name: machine_name.clone(), |
| 252 | + capabilities: vec![], |
| 253 | + }); |
| 254 | + |
| 255 | + if let Err(e) = conn.send(&hello).await { |
| 256 | + tracing::error!("Failed to send Hello to {}: {}", direction, e); |
| 257 | + return; |
| 258 | + } |
| 259 | + |
| 260 | + // Wait for HelloAck |
| 261 | + match conn.recv().await { |
| 262 | + Ok(Some(Message::HelloAck(ack))) => { |
| 263 | + if ack.accepted { |
| 264 | + info!("Connected to {} ({})", ack.machine_name, direction); |
| 265 | + |
| 266 | + // Split connection: store for sending, spawn receiver |
| 267 | + // For now, just store and we'll poll in the main loop |
| 268 | + let mut peers = peers_clone.write().await; |
| 269 | + peers.insert(direction, conn); |
| 270 | + } else { |
| 271 | + tracing::error!("Connection rejected: {:?}", ack.error); |
| 272 | + } |
| 273 | + } |
| 274 | + Ok(Some(other)) => { |
| 275 | + tracing::warn!("Expected HelloAck, got {:?}", other); |
| 276 | + } |
| 277 | + Ok(None) => { |
| 278 | + tracing::warn!("Connection closed during handshake"); |
| 279 | + } |
| 280 | + Err(e) => { |
| 281 | + tracing::error!("Handshake error: {}", e); |
| 282 | + } |
| 283 | + } |
| 284 | + } |
| 285 | + Err(e) => { |
| 286 | + tracing::warn!("Failed to connect to {} ({}): {}", direction, addr, e); |
| 287 | + } |
| 288 | + } |
| 289 | + }); |
| 290 | + } |
| 291 | + |
| 159 | 292 | // Listen for Hyprland events |
| 160 | 293 | let mut event_stream = hyprland::events::HyprlandEventStream::connect().await?; |
| 161 | 294 | |
| 162 | | - info!("Daemon running. Move mouse to screen edges to test. Press Ctrl+C to stop."); |
| 295 | + info!("Daemon running. Move mouse to screen edges to trigger transfer. Press Ctrl+C to stop."); |
| 163 | 296 | |
| 164 | 297 | loop { |
| 165 | 298 | tokio::select! { |
| 166 | | - // Check for edge events (non-blocking via channel) |
| 299 | + // Check for edge events and poll peer messages |
| 167 | 300 | _ = tokio::time::sleep(std::time::Duration::from_millis(10)) => { |
| 301 | + // Handle edge events |
| 168 | 302 | while let Some(edge_event) = edge_capture.try_recv() { |
| 169 | | - info!( |
| 170 | | - "EDGE EVENT: {:?} at ({}, {})", |
| 171 | | - edge_event.direction, |
| 172 | | - edge_event.position.0, |
| 173 | | - edge_event.position.1 |
| 174 | | - ); |
| 175 | | - |
| 176 | | - // TODO: Sprint 4 - Trigger network switch |
| 177 | | - // For now, just log it |
| 303 | + let direction = edge_event.direction; |
| 304 | + |
| 305 | + // Check if we have a peer in this direction |
| 306 | + let has_peer = { |
| 307 | + let peers = peers.read().await; |
| 308 | + peers.contains_key(&direction) |
| 309 | + }; |
| 310 | + |
| 311 | + if has_peer { |
| 312 | + info!( |
| 313 | + "EDGE: {:?} at ({}, {}) - initiating transfer", |
| 314 | + direction, |
| 315 | + edge_event.position.0, |
| 316 | + edge_event.position.1 |
| 317 | + ); |
| 318 | + |
| 319 | + if let Err(e) = transfer_manager.initiate_transfer( |
| 320 | + direction, |
| 321 | + edge_event.position, |
| 322 | + screen_height, |
| 323 | + screen_width, |
| 324 | + ).await { |
| 325 | + tracing::warn!("Failed to initiate transfer: {}", e); |
| 326 | + } |
| 327 | + } else { |
| 328 | + tracing::debug!( |
| 329 | + "EDGE: {:?} but no peer connected", |
| 330 | + direction |
| 331 | + ); |
| 332 | + } |
| 333 | + } |
| 334 | + |
| 335 | + // Poll for incoming messages from peers (non-blocking) |
| 336 | + let directions: Vec<Direction> = { |
| 337 | + let peers = peers.read().await; |
| 338 | + peers.keys().cloned().collect() |
| 339 | + }; |
| 340 | + |
| 341 | + for direction in directions { |
| 342 | + let mut peers = peers.write().await; |
| 343 | + if let Some(peer) = peers.get_mut(&direction) { |
| 344 | + // Try non-blocking receive using tokio timeout |
| 345 | + match tokio::time::timeout( |
| 346 | + std::time::Duration::from_millis(1), |
| 347 | + peer.recv() |
| 348 | + ).await { |
| 349 | + Ok(Ok(Some(msg))) => { |
| 350 | + tracing::debug!("Received from {:?}: {:?}", direction, msg); |
| 351 | + // Handle incoming message |
| 352 | + match msg { |
| 353 | + Message::Enter(payload) => { |
| 354 | + info!("Received Enter from {:?}", direction); |
| 355 | + match transfer_manager.handle_enter( |
| 356 | + direction, |
| 357 | + payload, |
| 358 | + screen_width, |
| 359 | + screen_height, |
| 360 | + ).await { |
| 361 | + Ok(pos) => { |
| 362 | + info!("Positioned cursor at {:?}", pos); |
| 363 | + } |
| 364 | + Err(e) => { |
| 365 | + tracing::error!("Failed to handle Enter: {}", e); |
| 366 | + } |
| 367 | + } |
| 368 | + } |
| 369 | + Message::EnterAck(ack) => { |
| 370 | + info!("Received EnterAck: success={}", ack.success); |
| 371 | + if let Err(e) = transfer_manager.handle_enter_ack(ack).await { |
| 372 | + tracing::error!("Failed to handle EnterAck: {}", e); |
| 373 | + } |
| 374 | + } |
| 375 | + Message::Leave(payload) => { |
| 376 | + info!("Received Leave from {:?}", direction); |
| 377 | + if let Err(e) = transfer_manager.handle_leave(payload).await { |
| 378 | + tracing::error!("Failed to handle Leave: {}", e); |
| 379 | + } |
| 380 | + } |
| 381 | + Message::LeaveAck => { |
| 382 | + info!("Received LeaveAck"); |
| 383 | + // Transfer complete |
| 384 | + } |
| 385 | + Message::InputEvent(input) => { |
| 386 | + tracing::trace!("Received input event: {:?}", input); |
| 387 | + // TODO: Inject input via emulation module |
| 388 | + } |
| 389 | + Message::Ping { timestamp } => { |
| 390 | + let _ = peer.send(&Message::Pong { timestamp }).await; |
| 391 | + } |
| 392 | + Message::Pong { timestamp } => { |
| 393 | + tracing::trace!("Pong received, rtt={}ms", |
| 394 | + std::time::SystemTime::now() |
| 395 | + .duration_since(std::time::UNIX_EPOCH) |
| 396 | + .unwrap() |
| 397 | + .as_millis() as u64 - timestamp |
| 398 | + ); |
| 399 | + } |
| 400 | + _ => { |
| 401 | + tracing::debug!("Unhandled message: {:?}", msg); |
| 402 | + } |
| 403 | + } |
| 404 | + } |
| 405 | + Ok(Ok(None)) => { |
| 406 | + // Connection closed |
| 407 | + info!("Peer {:?} disconnected", direction); |
| 408 | + peers.remove(&direction); |
| 409 | + } |
| 410 | + Ok(Err(e)) => { |
| 411 | + tracing::error!("Error receiving from {:?}: {}", direction, e); |
| 412 | + peers.remove(&direction); |
| 413 | + } |
| 414 | + Err(_) => { |
| 415 | + // Timeout - no message available, that's fine |
| 416 | + } |
| 417 | + } |
| 418 | + } |
| 419 | + } |
| 420 | + } |
| 421 | + |
| 422 | + // Handle transfer events |
| 423 | + Some(event) = transfer_events.recv() => { |
| 424 | + match event { |
| 425 | + transfer::TransferEvent::SendMessage { direction, message } => { |
| 426 | + let mut peers = peers.write().await; |
| 427 | + if let Some(peer) = peers.get_mut(&direction) { |
| 428 | + tracing::debug!("Sending {:?} to {:?}", message, direction); |
| 429 | + if let Err(e) = peer.send(&message).await { |
| 430 | + tracing::error!("Failed to send message: {}", e); |
| 431 | + } |
| 432 | + } else { |
| 433 | + tracing::warn!("No peer for direction {:?}", direction); |
| 434 | + } |
| 435 | + } |
| 436 | + transfer::TransferEvent::StartCapture { direction } => { |
| 437 | + info!("Starting input capture for {:?}", direction); |
| 438 | + // TODO: Implement actual input capture |
| 439 | + // For now, just log |
| 440 | + } |
| 441 | + transfer::TransferEvent::StopCapture => { |
| 442 | + info!("Stopping input capture"); |
| 443 | + } |
| 444 | + transfer::TransferEvent::StartInjection { from } => { |
| 445 | + info!("Starting input injection from {:?}", from); |
| 446 | + // TODO: Implement actual input injection |
| 447 | + } |
| 448 | + transfer::TransferEvent::StopInjection => { |
| 449 | + info!("Stopping input injection"); |
| 450 | + } |
| 178 | 451 | } |
| 179 | 452 | } |
| 180 | 453 | |
@@ -182,7 +455,7 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 182 | 455 | event = event_stream.next_event() => { |
| 183 | 456 | match event { |
| 184 | 457 | Ok(evt) => { |
| 185 | | - tracing::debug!("Hyprland event: {:?}", evt); |
| 458 | + tracing::trace!("Hyprland event: {:?}", evt); |
| 186 | 459 | } |
| 187 | 460 | Err(e) => { |
| 188 | 461 | tracing::error!("Event error: {e}"); |
@@ -194,6 +467,7 @@ async fn run_daemon(config_path: &std::path::Path) -> anyhow::Result<()> { |
| 194 | 467 | // Shutdown |
| 195 | 468 | _ = tokio::signal::ctrl_c() => { |
| 196 | 469 | info!("Shutting down..."); |
| 470 | + accept_handle.abort(); |
| 197 | 471 | break; |
| 198 | 472 | } |
| 199 | 473 | } |