//! Session management for the agent //! //! Handles the lifecycle of a remote session including: //! - Connection to server //! - Authentication //! - Frame capture and encoding loop //! - Input event handling use crate::capture::{self, Capturer, Display}; use crate::config::Config; use crate::encoder::{self, Encoder}; use crate::input::InputController; use crate::proto::{Message, message}; use crate::transport::WebSocketTransport; use anyhow::Result; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::mpsc; /// Session manager handles the remote control session pub struct SessionManager { config: Config, transport: Option, state: SessionState, } #[derive(Debug, Clone, PartialEq)] enum SessionState { Disconnected, Connecting, Connected, Active, } impl SessionManager { /// Create a new session manager pub fn new(config: Config) -> Self { Self { config, transport: None, state: SessionState::Disconnected, } } /// Connect to the server pub async fn connect(&mut self) -> Result<()> { self.state = SessionState::Connecting; let transport = WebSocketTransport::connect( &self.config.server_url, &self.config.api_key, ).await?; self.transport = Some(transport); self.state = SessionState::Connected; Ok(()) } /// Run the session main loop pub async fn run(&mut self) -> Result<()> { if self.transport.is_none() { anyhow::bail!("Not connected"); } self.state = SessionState::Active; // Get primary display let primary_display = capture::primary_display()?; tracing::info!("Using display: {} ({}x{})", primary_display.name, primary_display.width, primary_display.height); // Create capturer let mut capturer = capture::create_capturer( primary_display.clone(), self.config.capture.use_dxgi, self.config.capture.gdi_fallback, )?; // Create encoder let mut encoder = encoder::create_encoder( &self.config.encoding.codec, self.config.encoding.quality, )?; // Create input controller let mut input = InputController::new()?; // Calculate frame interval let frame_interval = Duration::from_millis(1000 / self.config.capture.fps as u64); let mut last_frame_time = Instant::now(); // Main loop loop { // Check for incoming messages (non-blocking) // Collect messages first, then release borrow before handling let messages: Vec = { let transport = self.transport.as_mut().unwrap(); let mut msgs = Vec::new(); while let Some(msg) = transport.try_recv()? { msgs.push(msg); } msgs }; for msg in messages { self.handle_message(&mut input, msg)?; } // Capture and send frame if interval elapsed if last_frame_time.elapsed() >= frame_interval { last_frame_time = Instant::now(); if let Some(frame) = capturer.capture()? { let encoded = encoder.encode(&frame)?; // Skip empty frames (no changes) if encoded.size > 0 { let msg = Message { payload: Some(message::Payload::VideoFrame(encoded.frame)), }; let transport = self.transport.as_mut().unwrap(); transport.send(msg).await?; } } } // Small sleep to prevent busy loop tokio::time::sleep(Duration::from_millis(1)).await; // Check if still connected if !transport.is_connected() { tracing::warn!("Connection lost"); break; } } self.state = SessionState::Disconnected; Ok(()) } /// Handle incoming message from server fn handle_message(&mut self, input: &mut InputController, msg: Message) -> Result<()> { match msg.payload { Some(message::Payload::MouseEvent(mouse)) => { // Handle mouse event use crate::proto::MouseEventType; use crate::input::MouseButton; match MouseEventType::try_from(mouse.event_type).unwrap_or(MouseEventType::MouseMove) { MouseEventType::MouseMove => { input.mouse_move(mouse.x, mouse.y)?; } MouseEventType::MouseDown => { input.mouse_move(mouse.x, mouse.y)?; if let Some(ref buttons) = mouse.buttons { if buttons.left { input.mouse_click(MouseButton::Left, true)?; } if buttons.right { input.mouse_click(MouseButton::Right, true)?; } if buttons.middle { input.mouse_click(MouseButton::Middle, true)?; } } } MouseEventType::MouseUp => { if let Some(ref buttons) = mouse.buttons { if buttons.left { input.mouse_click(MouseButton::Left, false)?; } if buttons.right { input.mouse_click(MouseButton::Right, false)?; } if buttons.middle { input.mouse_click(MouseButton::Middle, false)?; } } } MouseEventType::MouseWheel => { input.mouse_scroll(mouse.wheel_delta_x, mouse.wheel_delta_y)?; } } } Some(message::Payload::KeyEvent(key)) => { // Handle keyboard event input.key_event(key.vk_code as u16, key.down)?; } Some(message::Payload::SpecialKey(special)) => { use crate::proto::SpecialKey; match SpecialKey::try_from(special.key).ok() { Some(SpecialKey::CtrlAltDel) => { input.send_ctrl_alt_del()?; } _ => {} } } Some(message::Payload::Heartbeat(_)) => { // Respond to heartbeat // TODO: Send heartbeat ack } Some(message::Payload::Disconnect(disc)) => { tracing::info!("Disconnect requested: {}", disc.reason); return Err(anyhow::anyhow!("Disconnect: {}", disc.reason)); } _ => { // Ignore unknown messages } } Ok(()) } }