feat: RustDesk 扩展支持相对鼠标模式,上报设备标识修改为 Windows

This commit is contained in:
mofeng
2026-01-30 13:10:55 +08:00
parent f3b42e2aaf
commit 58f9020192
3 changed files with 144 additions and 59 deletions

View File

@@ -395,6 +395,8 @@ pub struct Ch9329Backend {
last_abs_x: AtomicU16, last_abs_x: AtomicU16,
/// Last absolute mouse Y position (CH9329 coordinate: 0-4095) /// Last absolute mouse Y position (CH9329 coordinate: 0-4095)
last_abs_y: AtomicU16, last_abs_y: AtomicU16,
/// Whether relative mouse mode is active (set by incoming events)
relative_mouse_active: AtomicBool,
/// Consecutive error count /// Consecutive error count
error_count: AtomicU32, error_count: AtomicU32,
/// Whether a reset is in progress /// Whether a reset is in progress
@@ -426,6 +428,7 @@ impl Ch9329Backend {
address: DEFAULT_ADDR, address: DEFAULT_ADDR,
last_abs_x: AtomicU16::new(0), last_abs_x: AtomicU16::new(0),
last_abs_y: AtomicU16::new(0), last_abs_y: AtomicU16::new(0),
relative_mouse_active: AtomicBool::new(false),
error_count: AtomicU32::new(0), error_count: AtomicU32::new(0),
reset_in_progress: AtomicBool::new(false), reset_in_progress: AtomicBool::new(false),
last_success: Mutex::new(None), last_success: Mutex::new(None),
@@ -1014,12 +1017,14 @@ impl HidBackend for Ch9329Backend {
match event.event_type { match event.event_type {
MouseEventType::Move => { MouseEventType::Move => {
// Relative movement - send delta directly without inversion // 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 dx = event.x.clamp(-127, 127) as i8;
let dy = event.y.clamp(-127, 127) as i8; let dy = event.y.clamp(-127, 127) as i8;
self.send_mouse_relative(buttons, dx, dy, 0)?; self.send_mouse_relative(buttons, dx, dy, 0)?;
} }
MouseEventType::MoveAbs => { MouseEventType::MoveAbs => {
// Absolute movement // Absolute movement
self.relative_mouse_active.store(false, Ordering::Relaxed);
// Frontend sends 0-32767 (HID standard), CH9329 expects 0-4095 // 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 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; 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 => { MouseEventType::Down => {
if let Some(button) = event.button { if let Some(button) = event.button {
let bit = button.to_hid_bit(); 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; let new_buttons = self.mouse_buttons.fetch_or(bit, Ordering::Relaxed) | bit;
trace!("Mouse down: {:?} buttons=0x{:02X}", button, new_buttons); 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 => { MouseEventType::Up => {
if let Some(button) = event.button { if let Some(button) = event.button {
let bit = button.to_hid_bit(); 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; let new_buttons = self.mouse_buttons.fetch_and(!bit, Ordering::Relaxed) & !bit;
trace!("Mouse up: {:?} buttons=0x{:02X}", button, new_buttons); 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 => { MouseEventType::Scroll => {
// Use absolute mouse for scroll with last position if self.relative_mouse_active.load(Ordering::Relaxed) {
let x = self.last_abs_x.load(Ordering::Relaxed); self.send_mouse_relative(buttons, 0, 0, event.scroll)?;
let y = self.last_abs_y.load(Ordering::Relaxed); } else {
self.send_mouse_absolute(buttons, x, y, event.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)?;
}
} }
} }
@@ -1073,6 +1090,7 @@ impl HidBackend for Ch9329Backend {
self.mouse_buttons.store(0, Ordering::Relaxed); self.mouse_buttons.store(0, Ordering::Relaxed);
self.last_abs_x.store(0, Ordering::Relaxed); self.last_abs_x.store(0, Ordering::Relaxed);
self.last_abs_y.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)?; self.send_mouse_absolute(0, 0, 0, 0)?;
// Reset media keys // Reset media keys

View File

@@ -47,6 +47,11 @@ const DEFAULT_SCREEN_HEIGHT: u32 = 1080;
/// Default mouse event throttle interval (16ms ≈ 60Hz) /// Default mouse event throttle interval (16ms ≈ 60Hz)
const DEFAULT_MOUSE_THROTTLE_MS: u64 = 16; 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 /// Input event throttler
/// ///
/// Limits the rate of input events sent to HID devices to prevent EAGAIN errors. /// 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<Instant>, last_test_delay_sent: Option<Instant>,
/// Last known CapsLock state from RustDesk modifiers (for detecting toggle) /// Last known CapsLock state from RustDesk modifiers (for detecting toggle)
last_caps_lock: bool, last_caps_lock: bool,
/// Whether relative mouse mode is currently active for this connection
relative_mouse_active: bool,
} }
/// Messages sent to connection handler /// Messages sent to connection handler
@@ -241,6 +248,7 @@ impl Connection {
last_delay: 0, last_delay: 0,
last_test_delay_sent: None, last_test_delay_sent: None,
last_caps_lock: false, last_caps_lock: false,
relative_mouse_active: false,
}; };
(conn, rx) (conn, rx)
@@ -1113,11 +1121,11 @@ impl Connection {
let mut peer_info = PeerInfo::new(); let mut peer_info = PeerInfo::new();
peer_info.username = "one-kvm".to_string(); peer_info.username = "one-kvm".to_string();
peer_info.hostname = get_hostname(); 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.displays.push(display_info);
peer_info.current_display = 0; peer_info.current_display = 0;
peer_info.sas_enabled = false; 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); peer_info.encoding = protobuf::MessageField::some(encoding);
let mut login_response = LoginResponse::new(); let mut login_response = LoginResponse::new();
@@ -1324,9 +1332,16 @@ impl Connection {
async fn handle_mouse_event(&mut self, me: &MouseEvent) -> anyhow::Result<()> { async fn handle_mouse_event(&mut self, me: &MouseEvent) -> anyhow::Result<()> {
// Parse RustDesk mask format: (button << 3) | event_type // Parse RustDesk mask format: (button << 3) | event_type
let event_type = me.mask & 0x07; 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) // 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 // For pure move events, apply throttling
if is_pure_move && !self.input_throttler.should_send_mouse_move() { 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); debug!("Mouse event: x={}, y={}, mask={}", me.x, me.y, me.mask);
// Convert RustDesk mouse event to One-KVM mouse events // 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 // Send to HID controller if available
if let Some(ref hid) = self.hid { if let Some(ref hid) = self.hid {

View File

@@ -18,6 +18,7 @@ pub mod mouse_type {
pub const UP: i32 = 2; pub const UP: i32 = 2;
pub const WHEEL: i32 = 3; pub const WHEEL: i32 = 3;
pub const TRACKPAD: i32 = 4; pub const TRACKPAD: i32 = 4;
pub const MOVE_RELATIVE: i32 = 5;
} }
/// Mouse button IDs from RustDesk protocol (before left shift by 3) /// Mouse button IDs from RustDesk protocol (before left shift by 3)
@@ -36,23 +37,25 @@ pub fn convert_mouse_event(
event: &MouseEvent, event: &MouseEvent,
screen_width: u32, screen_width: u32,
screen_height: u32, screen_height: u32,
relative_mode: bool,
) -> Vec<OneKvmMouseEvent> { ) -> Vec<OneKvmMouseEvent> {
let mut events = Vec::new(); 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 // Parse RustDesk mask format: (button << 3) | event_type
let event_type = event.mask & 0x07; let event_type = event.mask & 0x07;
let button_id = event.mask >> 3; let button_id = event.mask >> 3;
let include_abs_move = !relative_mode;
match event_type { match event_type {
mouse_type::MOVE => { 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) // Move event - may have button held down (button_id > 0 means dragging)
// Just send move, button state is tracked separately by HID backend // Just send move, button state is tracked separately by HID backend
events.push(OneKvmMouseEvent { events.push(OneKvmMouseEvent {
@@ -63,55 +66,83 @@ pub fn convert_mouse_event(
scroll: 0, scroll: 0,
}); });
} }
mouse_type::DOWN => { mouse_type::MOVE_RELATIVE => {
// Button down - first move, then press // Relative movement uses delta values directly (dx, dy).
events.push(OneKvmMouseEvent { events.push(OneKvmMouseEvent {
event_type: MouseEventType::MoveAbs, event_type: MouseEventType::Move,
x: abs_x, x: event.x,
y: abs_y, y: event.y,
button: None, button: None,
scroll: 0, 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) { if let Some(button) = button_id_to_button(button_id) {
events.push(OneKvmMouseEvent { events.push(OneKvmMouseEvent {
event_type: MouseEventType::Down, event_type: MouseEventType::Down,
x: abs_x, x: 0,
y: abs_y, y: 0,
button: Some(button), button: Some(button),
scroll: 0, scroll: 0,
}); });
} }
} }
mouse_type::UP => { mouse_type::UP => {
// Button up - first move, then release if include_abs_move {
events.push(OneKvmMouseEvent { // Button up - first move, then release
event_type: MouseEventType::MoveAbs, let x = event.x.max(0) as u32;
x: abs_x, let y = event.y.max(0) as u32;
y: abs_y, let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
button: None, let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32;
scroll: 0, 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) { if let Some(button) = button_id_to_button(button_id) {
events.push(OneKvmMouseEvent { events.push(OneKvmMouseEvent {
event_type: MouseEventType::Up, event_type: MouseEventType::Up,
x: abs_x, x: 0,
y: abs_y, y: 0,
button: Some(button), button: Some(button),
scroll: 0, scroll: 0,
}); });
} }
} }
mouse_type::WHEEL => { mouse_type::WHEEL => {
// Scroll event - move first, then scroll if include_abs_move {
events.push(OneKvmMouseEvent { // Scroll event - move first, then scroll
event_type: MouseEventType::MoveAbs, let x = event.x.max(0) as u32;
x: abs_x, let y = event.y.max(0) as u32;
y: abs_y, let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
button: None, let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32;
scroll: 0, 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 // RustDesk encodes scroll direction in the y coordinate
// Positive y = scroll up, Negative y = scroll down // 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 }; let scroll = if event.y > 0 { 1i8 } else { -1i8 };
events.push(OneKvmMouseEvent { events.push(OneKvmMouseEvent {
event_type: MouseEventType::Scroll, event_type: MouseEventType::Scroll,
x: abs_x, x: 0,
y: abs_y, y: 0,
button: None, button: None,
scroll, scroll,
}); });
} }
_ => { _ => {
// Unknown event type, just move if include_abs_move {
events.push(OneKvmMouseEvent { // Unknown event type, just move
event_type: MouseEventType::MoveAbs, let x = event.x.max(0) as u32;
x: abs_x, let y = event.y.max(0) as u32;
y: abs_y, let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
button: None, let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32;
scroll: 0, 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.y = 300;
event.mask = mouse_type::MOVE; // Pure move event 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!(!events.is_empty());
assert_eq!(events[0].event_type, MouseEventType::MoveAbs); assert_eq!(events[0].event_type, MouseEventType::MoveAbs);
} }
@@ -534,7 +571,7 @@ mod tests {
event.y = 300; event.y = 300;
event.mask = (mouse_button::LEFT << 3) | mouse_type::DOWN; 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); assert!(events.len() >= 2);
// Should have a button down event // Should have a button down event
assert!(events assert!(events
@@ -542,6 +579,20 @@ mod tests {
.any(|e| e.event_type == MouseEventType::Down && e.button == Some(MouseButton::Left))); .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] #[test]
fn test_convert_key_event() { fn test_convert_key_event() {
use protobuf::EnumOrUnknown; use protobuf::EnumOrUnknown;