diff --git a/src/hid/ch9329.rs b/src/hid/ch9329.rs index 5423627c..0893a49e 100644 --- a/src/hid/ch9329.rs +++ b/src/hid/ch9329.rs @@ -395,6 +395,8 @@ pub struct Ch9329Backend { last_abs_x: AtomicU16, /// Last absolute mouse Y position (CH9329 coordinate: 0-4095) last_abs_y: AtomicU16, + /// Whether relative mouse mode is active (set by incoming events) + relative_mouse_active: AtomicBool, /// Consecutive error count error_count: AtomicU32, /// Whether a reset is in progress @@ -426,6 +428,7 @@ impl Ch9329Backend { address: DEFAULT_ADDR, last_abs_x: AtomicU16::new(0), last_abs_y: AtomicU16::new(0), + relative_mouse_active: AtomicBool::new(false), error_count: AtomicU32::new(0), reset_in_progress: AtomicBool::new(false), last_success: Mutex::new(None), @@ -1014,12 +1017,14 @@ impl HidBackend for Ch9329Backend { match event.event_type { MouseEventType::Move => { // Relative movement - send delta directly without inversion + self.relative_mouse_active.store(true, Ordering::Relaxed); let dx = event.x.clamp(-127, 127) as i8; let dy = event.y.clamp(-127, 127) as i8; self.send_mouse_relative(buttons, dx, dy, 0)?; } MouseEventType::MoveAbs => { // Absolute movement + self.relative_mouse_active.store(false, Ordering::Relaxed); // Frontend sends 0-32767 (HID standard), CH9329 expects 0-4095 let x = ((event.x.clamp(0, 32767) as u32) * CH9329_MOUSE_RESOLUTION / 32768) as u16; let y = ((event.y.clamp(0, 32767) as u32) * CH9329_MOUSE_RESOLUTION / 32768) as u16; @@ -1031,28 +1036,40 @@ impl HidBackend for Ch9329Backend { MouseEventType::Down => { if let Some(button) = event.button { let bit = button.to_hid_bit(); - let x = self.last_abs_x.load(Ordering::Relaxed); - let y = self.last_abs_y.load(Ordering::Relaxed); let new_buttons = self.mouse_buttons.fetch_or(bit, Ordering::Relaxed) | bit; trace!("Mouse down: {:?} buttons=0x{:02X}", button, new_buttons); - self.send_mouse_absolute(new_buttons, x, y, 0)?; + if self.relative_mouse_active.load(Ordering::Relaxed) { + self.send_mouse_relative(new_buttons, 0, 0, 0)?; + } else { + let x = self.last_abs_x.load(Ordering::Relaxed); + let y = self.last_abs_y.load(Ordering::Relaxed); + self.send_mouse_absolute(new_buttons, x, y, 0)?; + } } } MouseEventType::Up => { if let Some(button) = event.button { let bit = button.to_hid_bit(); - let x = self.last_abs_x.load(Ordering::Relaxed); - let y = self.last_abs_y.load(Ordering::Relaxed); let new_buttons = self.mouse_buttons.fetch_and(!bit, Ordering::Relaxed) & !bit; trace!("Mouse up: {:?} buttons=0x{:02X}", button, new_buttons); - self.send_mouse_absolute(new_buttons, x, y, 0)?; + if self.relative_mouse_active.load(Ordering::Relaxed) { + self.send_mouse_relative(new_buttons, 0, 0, 0)?; + } else { + let x = self.last_abs_x.load(Ordering::Relaxed); + let y = self.last_abs_y.load(Ordering::Relaxed); + self.send_mouse_absolute(new_buttons, x, y, 0)?; + } } } MouseEventType::Scroll => { - // Use absolute mouse for scroll with last position - let x = self.last_abs_x.load(Ordering::Relaxed); - let y = self.last_abs_y.load(Ordering::Relaxed); - self.send_mouse_absolute(buttons, x, y, event.scroll)?; + if self.relative_mouse_active.load(Ordering::Relaxed) { + self.send_mouse_relative(buttons, 0, 0, event.scroll)?; + } else { + // Use absolute mouse for scroll with last position + let x = self.last_abs_x.load(Ordering::Relaxed); + let y = self.last_abs_y.load(Ordering::Relaxed); + self.send_mouse_absolute(buttons, x, y, event.scroll)?; + } } } @@ -1073,6 +1090,7 @@ impl HidBackend for Ch9329Backend { self.mouse_buttons.store(0, Ordering::Relaxed); self.last_abs_x.store(0, Ordering::Relaxed); self.last_abs_y.store(0, Ordering::Relaxed); + self.relative_mouse_active.store(false, Ordering::Relaxed); self.send_mouse_absolute(0, 0, 0, 0)?; // Reset media keys diff --git a/src/rustdesk/connection.rs b/src/rustdesk/connection.rs index 39a90a67..b408ed12 100644 --- a/src/rustdesk/connection.rs +++ b/src/rustdesk/connection.rs @@ -47,6 +47,11 @@ const DEFAULT_SCREEN_HEIGHT: u32 = 1080; /// Default mouse event throttle interval (16ms ≈ 60Hz) const DEFAULT_MOUSE_THROTTLE_MS: u64 = 16; +/// Advertised RustDesk version for client compatibility. +const RUSTDESK_COMPAT_VERSION: &str = "1.4.5"; +// Advertised platform for RustDesk clients. This affects which UI options are shown. +const RUSTDESK_COMPAT_PLATFORM: &str = "Windows"; + /// Input event throttler /// /// Limits the rate of input events sent to HID devices to prevent EAGAIN errors. @@ -164,6 +169,8 @@ pub struct Connection { last_test_delay_sent: Option, /// Last known CapsLock state from RustDesk modifiers (for detecting toggle) last_caps_lock: bool, + /// Whether relative mouse mode is currently active for this connection + relative_mouse_active: bool, } /// Messages sent to connection handler @@ -241,6 +248,7 @@ impl Connection { last_delay: 0, last_test_delay_sent: None, last_caps_lock: false, + relative_mouse_active: false, }; (conn, rx) @@ -1113,11 +1121,11 @@ impl Connection { let mut peer_info = PeerInfo::new(); peer_info.username = "one-kvm".to_string(); peer_info.hostname = get_hostname(); - peer_info.platform = "Linux".to_string(); + peer_info.platform = RUSTDESK_COMPAT_PLATFORM.to_string(); peer_info.displays.push(display_info); peer_info.current_display = 0; peer_info.sas_enabled = false; - peer_info.version = env!("CARGO_PKG_VERSION").to_string(); + peer_info.version = RUSTDESK_COMPAT_VERSION.to_string(); peer_info.encoding = protobuf::MessageField::some(encoding); let mut login_response = LoginResponse::new(); @@ -1324,9 +1332,16 @@ impl Connection { async fn handle_mouse_event(&mut self, me: &MouseEvent) -> anyhow::Result<()> { // Parse RustDesk mask format: (button << 3) | event_type let event_type = me.mask & 0x07; + let is_relative_move = event_type == mouse_type::MOVE_RELATIVE; + + if is_relative_move { + self.relative_mouse_active = true; + } else if event_type == mouse_type::MOVE { + self.relative_mouse_active = false; + } // Check if this is a pure move event (no button/scroll) - let is_pure_move = event_type == mouse_type::MOVE; + let is_pure_move = event_type == mouse_type::MOVE || is_relative_move; // For pure move events, apply throttling if is_pure_move && !self.input_throttler.should_send_mouse_move() { @@ -1337,7 +1352,8 @@ impl Connection { debug!("Mouse event: x={}, y={}, mask={}", me.x, me.y, me.mask); // Convert RustDesk mouse event to One-KVM mouse events - let mouse_events = convert_mouse_event(me, self.screen_width, self.screen_height); + let mouse_events = + convert_mouse_event(me, self.screen_width, self.screen_height, self.relative_mouse_active); // Send to HID controller if available if let Some(ref hid) = self.hid { diff --git a/src/rustdesk/hid_adapter.rs b/src/rustdesk/hid_adapter.rs index 4261f8d9..7a89cdbb 100644 --- a/src/rustdesk/hid_adapter.rs +++ b/src/rustdesk/hid_adapter.rs @@ -18,6 +18,7 @@ pub mod mouse_type { pub const UP: i32 = 2; pub const WHEEL: i32 = 3; pub const TRACKPAD: i32 = 4; + pub const MOVE_RELATIVE: i32 = 5; } /// Mouse button IDs from RustDesk protocol (before left shift by 3) @@ -36,23 +37,25 @@ pub fn convert_mouse_event( event: &MouseEvent, screen_width: u32, screen_height: u32, + relative_mode: bool, ) -> Vec { let mut events = Vec::new(); - // RustDesk uses absolute coordinates - let x = event.x.max(0) as u32; - let y = event.y.max(0) as u32; - - // Normalize to 0-32767 range for absolute mouse (USB HID standard) - let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32; - let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32; - // Parse RustDesk mask format: (button << 3) | event_type let event_type = event.mask & 0x07; let button_id = event.mask >> 3; + let include_abs_move = !relative_mode; match event_type { mouse_type::MOVE => { + // RustDesk uses absolute coordinates + let x = event.x.max(0) as u32; + let y = event.y.max(0) as u32; + + // Normalize to 0-32767 range for absolute mouse (USB HID standard) + let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32; + let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32; + // Move event - may have button held down (button_id > 0 means dragging) // Just send move, button state is tracked separately by HID backend events.push(OneKvmMouseEvent { @@ -63,55 +66,83 @@ pub fn convert_mouse_event( scroll: 0, }); } - mouse_type::DOWN => { - // Button down - first move, then press + mouse_type::MOVE_RELATIVE => { + // Relative movement uses delta values directly (dx, dy). events.push(OneKvmMouseEvent { - event_type: MouseEventType::MoveAbs, - x: abs_x, - y: abs_y, + event_type: MouseEventType::Move, + x: event.x, + y: event.y, button: None, scroll: 0, }); + } + mouse_type::DOWN => { + if include_abs_move { + // Button down - first move, then press + let x = event.x.max(0) as u32; + let y = event.y.max(0) as u32; + let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32; + let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32; + events.push(OneKvmMouseEvent { + event_type: MouseEventType::MoveAbs, + x: abs_x, + y: abs_y, + button: None, + scroll: 0, + }); + } if let Some(button) = button_id_to_button(button_id) { events.push(OneKvmMouseEvent { event_type: MouseEventType::Down, - x: abs_x, - y: abs_y, + x: 0, + y: 0, button: Some(button), scroll: 0, }); } } mouse_type::UP => { - // Button up - first move, then release - events.push(OneKvmMouseEvent { - event_type: MouseEventType::MoveAbs, - x: abs_x, - y: abs_y, - button: None, - scroll: 0, - }); + if include_abs_move { + // Button up - first move, then release + let x = event.x.max(0) as u32; + let y = event.y.max(0) as u32; + let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32; + let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32; + events.push(OneKvmMouseEvent { + event_type: MouseEventType::MoveAbs, + x: abs_x, + y: abs_y, + button: None, + scroll: 0, + }); + } if let Some(button) = button_id_to_button(button_id) { events.push(OneKvmMouseEvent { event_type: MouseEventType::Up, - x: abs_x, - y: abs_y, + x: 0, + y: 0, button: Some(button), scroll: 0, }); } } mouse_type::WHEEL => { - // Scroll event - move first, then scroll - events.push(OneKvmMouseEvent { - event_type: MouseEventType::MoveAbs, - x: abs_x, - y: abs_y, - button: None, - scroll: 0, - }); + if include_abs_move { + // Scroll event - move first, then scroll + let x = event.x.max(0) as u32; + let y = event.y.max(0) as u32; + let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32; + let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32; + events.push(OneKvmMouseEvent { + event_type: MouseEventType::MoveAbs, + x: abs_x, + y: abs_y, + button: None, + scroll: 0, + }); + } // RustDesk encodes scroll direction in the y coordinate // Positive y = scroll up, Negative y = scroll down @@ -119,21 +150,27 @@ pub fn convert_mouse_event( let scroll = if event.y > 0 { 1i8 } else { -1i8 }; events.push(OneKvmMouseEvent { event_type: MouseEventType::Scroll, - x: abs_x, - y: abs_y, + x: 0, + y: 0, button: None, scroll, }); } _ => { - // Unknown event type, just move - events.push(OneKvmMouseEvent { - event_type: MouseEventType::MoveAbs, - x: abs_x, - y: abs_y, - button: None, - scroll: 0, - }); + if include_abs_move { + // Unknown event type, just move + let x = event.x.max(0) as u32; + let y = event.y.max(0) as u32; + let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32; + let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32; + events.push(OneKvmMouseEvent { + event_type: MouseEventType::MoveAbs, + x: abs_x, + y: abs_y, + button: None, + scroll: 0, + }); + } } } @@ -522,7 +559,7 @@ mod tests { event.y = 300; event.mask = mouse_type::MOVE; // Pure move event - let events = convert_mouse_event(&event, 1920, 1080); + let events = convert_mouse_event(&event, 1920, 1080, false); assert!(!events.is_empty()); assert_eq!(events[0].event_type, MouseEventType::MoveAbs); } @@ -534,7 +571,7 @@ mod tests { event.y = 300; event.mask = (mouse_button::LEFT << 3) | mouse_type::DOWN; - let events = convert_mouse_event(&event, 1920, 1080); + let events = convert_mouse_event(&event, 1920, 1080, false); assert!(events.len() >= 2); // Should have a button down event assert!(events @@ -542,6 +579,20 @@ mod tests { .any(|e| e.event_type == MouseEventType::Down && e.button == Some(MouseButton::Left))); } + #[test] + fn test_convert_mouse_move_relative() { + let mut event = MouseEvent::new(); + event.x = -12; + event.y = 8; + event.mask = mouse_type::MOVE_RELATIVE; + + let events = convert_mouse_event(&event, 1920, 1080, true); + assert_eq!(events.len(), 1); + assert_eq!(events[0].event_type, MouseEventType::Move); + assert_eq!(events[0].x, -12); + assert_eq!(events[0].y, 8); + } + #[test] fn test_convert_key_event() { use protobuf::EnumOrUnknown;