//! Window rendering and frame display use super::{ViewerEvent, InputEvent}; use crate::proto; #[cfg(windows)] use super::input; use anyhow::Result; use std::num::NonZeroU32; use std::sync::Arc; use tokio::sync::mpsc; use tracing::{debug, error, info, warn}; use winit::{ application::ApplicationHandler, dpi::LogicalSize, event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, window::{Window, WindowId}, }; /// Frame data received from server #[derive(Debug, Clone)] pub struct FrameData { pub width: u32, pub height: u32, pub data: Vec, pub compressed: bool, pub is_keyframe: bool, } struct ViewerApp { window: Option>, surface: Option, Arc>>, frame_buffer: Vec, frame_width: u32, frame_height: u32, viewer_rx: mpsc::Receiver, input_tx: mpsc::Sender, mouse_x: i32, mouse_y: i32, #[cfg(windows)] keyboard_hook: Option, } impl ViewerApp { fn new( viewer_rx: mpsc::Receiver, input_tx: mpsc::Sender, ) -> Self { Self { window: None, surface: None, frame_buffer: Vec::new(), frame_width: 0, frame_height: 0, viewer_rx, input_tx, mouse_x: 0, mouse_y: 0, #[cfg(windows)] keyboard_hook: None, } } fn process_frame(&mut self, frame: FrameData) { let data = if frame.compressed { // Decompress zstd match zstd::decode_all(frame.data.as_slice()) { Ok(decompressed) => decompressed, Err(e) => { error!("Failed to decompress frame: {}", e); return; } } } else { frame.data }; // Convert BGRA to ARGB (softbuffer expects 0RGB format on little-endian) let pixel_count = (frame.width * frame.height) as usize; if data.len() < pixel_count * 4 { error!("Frame data too small: {} < {}", data.len(), pixel_count * 4); return; } // Resize frame buffer if needed if self.frame_width != frame.width || self.frame_height != frame.height { self.frame_width = frame.width; self.frame_height = frame.height; self.frame_buffer.resize(pixel_count, 0); // Resize window to match frame if let Some(window) = &self.window { let _ = window.request_inner_size(LogicalSize::new(frame.width, frame.height)); } } // Convert BGRA to 0RGB (ignore alpha, swap B and R) for i in 0..pixel_count { let offset = i * 4; let b = data[offset] as u32; let g = data[offset + 1] as u32; let r = data[offset + 2] as u32; // 0RGB format: 0x00RRGGBB self.frame_buffer[i] = (r << 16) | (g << 8) | b; } // Request redraw if let Some(window) = &self.window { window.request_redraw(); } } fn render(&mut self) { let Some(surface) = &mut self.surface else { return }; let Some(window) = &self.window else { return }; if self.frame_buffer.is_empty() || self.frame_width == 0 || self.frame_height == 0 { return; } let size = window.inner_size(); if size.width == 0 || size.height == 0 { return; } // Resize surface if needed let width = NonZeroU32::new(size.width).unwrap(); let height = NonZeroU32::new(size.height).unwrap(); if let Err(e) = surface.resize(width, height) { error!("Failed to resize surface: {}", e); return; } let mut buffer = match surface.buffer_mut() { Ok(b) => b, Err(e) => { error!("Failed to get surface buffer: {}", e); return; } }; // Simple nearest-neighbor scaling let scale_x = self.frame_width as f32 / size.width as f32; let scale_y = self.frame_height as f32 / size.height as f32; for y in 0..size.height { for x in 0..size.width { let src_x = ((x as f32 * scale_x) as u32).min(self.frame_width - 1); let src_y = ((y as f32 * scale_y) as u32).min(self.frame_height - 1); let src_idx = (src_y * self.frame_width + src_x) as usize; let dst_idx = (y * size.width + x) as usize; if src_idx < self.frame_buffer.len() && dst_idx < buffer.len() { buffer[dst_idx] = self.frame_buffer[src_idx]; } } } if let Err(e) = buffer.present() { error!("Failed to present buffer: {}", e); } } fn send_mouse_event(&self, event_type: proto::MouseEventType, x: i32, y: i32) { let event = proto::MouseEvent { x, y, buttons: Some(proto::MouseButtons::default()), wheel_delta_x: 0, wheel_delta_y: 0, event_type: event_type as i32, }; let _ = self.input_tx.try_send(InputEvent::Mouse(event)); } fn send_mouse_button(&self, button: MouseButton, state: ElementState) { let event_type = match state { ElementState::Pressed => proto::MouseEventType::MouseDown, ElementState::Released => proto::MouseEventType::MouseUp, }; let mut buttons = proto::MouseButtons::default(); match button { MouseButton::Left => buttons.left = true, MouseButton::Right => buttons.right = true, MouseButton::Middle => buttons.middle = true, _ => {} } let event = proto::MouseEvent { x: self.mouse_x, y: self.mouse_y, buttons: Some(buttons), wheel_delta_x: 0, wheel_delta_y: 0, event_type: event_type as i32, }; let _ = self.input_tx.try_send(InputEvent::Mouse(event)); } fn send_mouse_wheel(&self, delta_x: i32, delta_y: i32) { let event = proto::MouseEvent { x: self.mouse_x, y: self.mouse_y, buttons: Some(proto::MouseButtons::default()), wheel_delta_x: delta_x, wheel_delta_y: delta_y, event_type: proto::MouseEventType::MouseWheel as i32, }; let _ = self.input_tx.try_send(InputEvent::Mouse(event)); } fn send_key_event(&self, key: PhysicalKey, state: ElementState) { let vk_code = match key { PhysicalKey::Code(code) => keycode_to_vk(code), _ => return, }; let event = proto::KeyEvent { down: state == ElementState::Pressed, key_type: proto::KeyEventType::KeyVk as i32, vk_code, scan_code: 0, unicode: String::new(), modifiers: Some(proto::Modifiers::default()), }; let _ = self.input_tx.try_send(InputEvent::Key(event)); } fn screen_to_frame_coords(&self, x: f64, y: f64) -> (i32, i32) { let Some(window) = &self.window else { return (x as i32, y as i32); }; let size = window.inner_size(); if size.width == 0 || size.height == 0 || self.frame_width == 0 || self.frame_height == 0 { return (x as i32, y as i32); } // Scale from window coordinates to frame coordinates let scale_x = self.frame_width as f64 / size.width as f64; let scale_y = self.frame_height as f64 / size.height as f64; let frame_x = (x * scale_x) as i32; let frame_y = (y * scale_y) as i32; (frame_x, frame_y) } } impl ApplicationHandler for ViewerApp { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.window.is_some() { return; } let window_attrs = Window::default_attributes() .with_title("GuruConnect Viewer") .with_inner_size(LogicalSize::new(1280, 720)); let window = Arc::new(event_loop.create_window(window_attrs).unwrap()); // Create software rendering surface let context = softbuffer::Context::new(window.clone()).unwrap(); let surface = softbuffer::Surface::new(&context, window.clone()).unwrap(); self.window = Some(window.clone()); self.surface = Some(surface); // Install keyboard hook #[cfg(windows)] { let input_tx = self.input_tx.clone(); match input::KeyboardHook::new(input_tx) { Ok(hook) => { info!("Keyboard hook installed"); self.keyboard_hook = Some(hook); } Err(e) => { error!("Failed to install keyboard hook: {}", e); } } } info!("Window created"); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) { // Check for incoming viewer events (non-blocking) while let Ok(viewer_event) = self.viewer_rx.try_recv() { match viewer_event { ViewerEvent::Frame(frame) => { self.process_frame(frame); } ViewerEvent::Connected => { info!("Connected to remote session"); } ViewerEvent::Disconnected(reason) => { warn!("Disconnected: {}", reason); event_loop.exit(); } ViewerEvent::CursorPosition(_x, _y, _visible) => { // Could update cursor display here } ViewerEvent::CursorShape(_shape) => { // Could update cursor shape here } } } match event { WindowEvent::CloseRequested => { info!("Window close requested"); event_loop.exit(); } WindowEvent::RedrawRequested => { self.render(); } WindowEvent::Resized(size) => { debug!("Window resized to {}x{}", size.width, size.height); if let Some(window) = &self.window { window.request_redraw(); } } WindowEvent::CursorMoved { position, .. } => { let (x, y) = self.screen_to_frame_coords(position.x, position.y); self.mouse_x = x; self.mouse_y = y; self.send_mouse_event(proto::MouseEventType::MouseMove, x, y); } WindowEvent::MouseInput { state, button, .. } => { self.send_mouse_button(button, state); } WindowEvent::MouseWheel { delta, .. } => { let (dx, dy) = match delta { MouseScrollDelta::LineDelta(x, y) => (x as i32 * 120, y as i32 * 120), MouseScrollDelta::PixelDelta(pos) => (pos.x as i32, pos.y as i32), }; self.send_mouse_wheel(dx, dy); } WindowEvent::KeyboardInput { event, .. } => { // Note: This handles keys that aren't captured by the low-level hook // The hook handles Win key and other special keys if !event.repeat { self.send_key_event(event.physical_key, event.state); } } _ => {} } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { // Keep checking for events event_loop.set_control_flow(ControlFlow::Poll); // Process Windows messages for keyboard hook #[cfg(windows)] input::pump_messages(); // Request redraw periodically to check for new frames if let Some(window) = &self.window { window.request_redraw(); } } } /// Run the viewer window pub async fn run_window( viewer_rx: mpsc::Receiver, input_tx: mpsc::Sender, ) -> Result<()> { let event_loop = EventLoop::new()?; let mut app = ViewerApp::new(viewer_rx, input_tx); event_loop.run_app(&mut app)?; Ok(()) } /// Convert winit KeyCode to Windows virtual key code fn keycode_to_vk(code: KeyCode) -> u32 { match code { // Letters KeyCode::KeyA => 0x41, KeyCode::KeyB => 0x42, KeyCode::KeyC => 0x43, KeyCode::KeyD => 0x44, KeyCode::KeyE => 0x45, KeyCode::KeyF => 0x46, KeyCode::KeyG => 0x47, KeyCode::KeyH => 0x48, KeyCode::KeyI => 0x49, KeyCode::KeyJ => 0x4A, KeyCode::KeyK => 0x4B, KeyCode::KeyL => 0x4C, KeyCode::KeyM => 0x4D, KeyCode::KeyN => 0x4E, KeyCode::KeyO => 0x4F, KeyCode::KeyP => 0x50, KeyCode::KeyQ => 0x51, KeyCode::KeyR => 0x52, KeyCode::KeyS => 0x53, KeyCode::KeyT => 0x54, KeyCode::KeyU => 0x55, KeyCode::KeyV => 0x56, KeyCode::KeyW => 0x57, KeyCode::KeyX => 0x58, KeyCode::KeyY => 0x59, KeyCode::KeyZ => 0x5A, // Numbers KeyCode::Digit0 => 0x30, KeyCode::Digit1 => 0x31, KeyCode::Digit2 => 0x32, KeyCode::Digit3 => 0x33, KeyCode::Digit4 => 0x34, KeyCode::Digit5 => 0x35, KeyCode::Digit6 => 0x36, KeyCode::Digit7 => 0x37, KeyCode::Digit8 => 0x38, KeyCode::Digit9 => 0x39, // Function keys KeyCode::F1 => 0x70, KeyCode::F2 => 0x71, KeyCode::F3 => 0x72, KeyCode::F4 => 0x73, KeyCode::F5 => 0x74, KeyCode::F6 => 0x75, KeyCode::F7 => 0x76, KeyCode::F8 => 0x77, KeyCode::F9 => 0x78, KeyCode::F10 => 0x79, KeyCode::F11 => 0x7A, KeyCode::F12 => 0x7B, // Special keys KeyCode::Escape => 0x1B, KeyCode::Tab => 0x09, KeyCode::CapsLock => 0x14, KeyCode::ShiftLeft => 0x10, KeyCode::ShiftRight => 0x10, KeyCode::ControlLeft => 0x11, KeyCode::ControlRight => 0x11, KeyCode::AltLeft => 0x12, KeyCode::AltRight => 0x12, KeyCode::Space => 0x20, KeyCode::Enter => 0x0D, KeyCode::Backspace => 0x08, KeyCode::Delete => 0x2E, KeyCode::Insert => 0x2D, KeyCode::Home => 0x24, KeyCode::End => 0x23, KeyCode::PageUp => 0x21, KeyCode::PageDown => 0x22, // Arrow keys KeyCode::ArrowUp => 0x26, KeyCode::ArrowDown => 0x28, KeyCode::ArrowLeft => 0x25, KeyCode::ArrowRight => 0x27, // Numpad KeyCode::NumLock => 0x90, KeyCode::Numpad0 => 0x60, KeyCode::Numpad1 => 0x61, KeyCode::Numpad2 => 0x62, KeyCode::Numpad3 => 0x63, KeyCode::Numpad4 => 0x64, KeyCode::Numpad5 => 0x65, KeyCode::Numpad6 => 0x66, KeyCode::Numpad7 => 0x67, KeyCode::Numpad8 => 0x68, KeyCode::Numpad9 => 0x69, KeyCode::NumpadAdd => 0x6B, KeyCode::NumpadSubtract => 0x6D, KeyCode::NumpadMultiply => 0x6A, KeyCode::NumpadDivide => 0x6F, KeyCode::NumpadDecimal => 0x6E, KeyCode::NumpadEnter => 0x0D, // Punctuation KeyCode::Semicolon => 0xBA, KeyCode::Equal => 0xBB, KeyCode::Comma => 0xBC, KeyCode::Minus => 0xBD, KeyCode::Period => 0xBE, KeyCode::Slash => 0xBF, KeyCode::Backquote => 0xC0, KeyCode::BracketLeft => 0xDB, KeyCode::Backslash => 0xDC, KeyCode::BracketRight => 0xDD, KeyCode::Quote => 0xDE, // Other KeyCode::PrintScreen => 0x2C, KeyCode::ScrollLock => 0x91, KeyCode::Pause => 0x13, _ => 0, } }