mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-26 22:51:03 +03:00
feat: Add relative mouse mode (#13928)
* feat: Add relative mouse mode - Add "Relative Mouse Mode" toggle in desktop toolbar and bind to InputModel - Implement relative mouse movement path: Flutter pointer deltas -> `type: move_relative` -> new `MOUSE_TYPE_MOVE_RELATIVE` in Rust - In server input service, simulate relative movement via Enigo and keep latest cursor position in sync - Track pointer-lock center in Flutter (local widget + screen coordinates) and re-center OS cursor after each relative move - Update pointer-lock center on window move/resize/restore/maximize and when remote display geometry changes - Hide local cursor when relative mouse mode is active (both Flutter cursor and OS cursor), restore on leave/disable - On Windows, clip OS cursor to the window rect while in relative mode and release clip when leaving/turning off - Implement platform helpers: `get_cursor_pos`, `set_cursor_pos`, `show_cursor`, `clip_cursor` (no-op clip/hide on Linux for now) - Add keyboard shortcut Ctrl+Alt+Shift+M to toggle relative mode (enabled by default, works on all platforms) - Remove `enable-relative-mouse-shortcut` config option - shortcut is now always available when keyboard permission is granted - Handle window blur/focus/minimize events to properly release/restore cursor constraints - Add MOUSE_TYPE_MASK constant and unit tests for mouse event constants Note: Relative mouse mode state is NOT persisted to config (session-only). Note: On Linux, show_cursor and clip_cursor are no-ops; cursor hiding is handled by Flutter side. Signed-off-by: fufesou <linlong1266@gmail.com> * feat(mouse): relative mouse mode, exit hint Signed-off-by: fufesou <linlong1266@gmail.com> * refact(relative mouse): shortcut Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
@@ -71,6 +71,19 @@ pub mod input {
|
||||
pub const MOUSE_TYPE_UP: i32 = 2;
|
||||
pub const MOUSE_TYPE_WHEEL: i32 = 3;
|
||||
pub const MOUSE_TYPE_TRACKPAD: i32 = 4;
|
||||
/// Relative mouse movement type for gaming/3D applications.
|
||||
/// This type sends delta (dx, dy) values instead of absolute coordinates.
|
||||
/// NOTE: This is only supported by the Flutter client. The Sciter client (deprecated)
|
||||
/// does not support relative mouse mode due to:
|
||||
/// 1. Fixed send_mouse() function signature that doesn't allow type differentiation
|
||||
/// 2. Lack of pointer lock API in Sciter/TIS
|
||||
/// 3. No OS cursor control (hide/show/clip) FFI bindings in Sciter UI
|
||||
pub const MOUSE_TYPE_MOVE_RELATIVE: i32 = 5;
|
||||
|
||||
/// Mask to extract the mouse event type from the mask field.
|
||||
/// The lower 3 bits contain the event type (MOUSE_TYPE_*), giving a valid range of 0-7.
|
||||
/// Currently defined types use values 0-5; values 6 and 7 are reserved for future use.
|
||||
pub const MOUSE_TYPE_MASK: i32 = 0x7;
|
||||
|
||||
pub const MOUSE_BUTTON_LEFT: i32 = 0x01;
|
||||
pub const MOUSE_BUTTON_RIGHT: i32 = 0x02;
|
||||
@@ -175,6 +188,20 @@ pub fn is_support_file_transfer_resume_num(ver: i64) -> bool {
|
||||
ver >= hbb_common::get_version_number("1.4.2")
|
||||
}
|
||||
|
||||
/// Minimum server version required for relative mouse mode support.
|
||||
/// This constant must mirror Flutter's `kMinVersionForRelativeMouseMode` in `consts.dart`.
|
||||
const MIN_VERSION_RELATIVE_MOUSE_MODE: &str = "1.4.5";
|
||||
|
||||
#[inline]
|
||||
pub fn is_support_relative_mouse_mode(ver: &str) -> bool {
|
||||
is_support_relative_mouse_mode_num(hbb_common::get_version_number(ver))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_support_relative_mouse_mode_num(ver: i64) -> bool {
|
||||
ver >= hbb_common::get_version_number(MIN_VERSION_RELATIVE_MOUSE_MODE)
|
||||
}
|
||||
|
||||
// is server process, with "--server" args
|
||||
#[inline]
|
||||
pub fn is_server() -> bool {
|
||||
@@ -2462,4 +2489,36 @@ mod tests {
|
||||
assert!(!is_public("https://rustdesk.computer.com"));
|
||||
assert!(!is_public("rustdesk.comhello.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mouse_event_constants_and_mask_layout() {
|
||||
use super::input::*;
|
||||
|
||||
// Verify MOUSE_TYPE constants are unique and within the mask range.
|
||||
let types = [
|
||||
MOUSE_TYPE_MOVE,
|
||||
MOUSE_TYPE_DOWN,
|
||||
MOUSE_TYPE_UP,
|
||||
MOUSE_TYPE_WHEEL,
|
||||
MOUSE_TYPE_TRACKPAD,
|
||||
MOUSE_TYPE_MOVE_RELATIVE,
|
||||
];
|
||||
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for t in types.iter() {
|
||||
assert!(seen.insert(*t), "Duplicate mouse type: {}", t);
|
||||
assert_eq!(
|
||||
*t & MOUSE_TYPE_MASK,
|
||||
*t,
|
||||
"Mouse type {} exceeds mask {}",
|
||||
t,
|
||||
MOUSE_TYPE_MASK
|
||||
);
|
||||
}
|
||||
|
||||
// The mask layout is: lower 3 bits for type, upper bits for buttons (shifted by 3).
|
||||
let combined_mask = MOUSE_TYPE_DOWN | ((MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT) << 3);
|
||||
assert_eq!(combined_mask & MOUSE_TYPE_MASK, MOUSE_TYPE_DOWN);
|
||||
assert_eq!(combined_mask >> 3, MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1215,6 +1215,66 @@ pub fn main_set_input_source(session_id: SessionID, value: String) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set cursor position (for pointer lock re-centering).
|
||||
///
|
||||
/// # Returns
|
||||
/// - `true`: cursor position was successfully set
|
||||
/// - `false`: operation failed or not supported
|
||||
///
|
||||
/// # Platform behavior
|
||||
/// - Windows/macOS/Linux: attempts to move the cursor to (x, y)
|
||||
/// - Android/iOS: no-op, always returns `false`
|
||||
pub fn main_set_cursor_position(x: i32, y: i32) -> SyncReturn<bool> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
SyncReturn(crate::set_cursor_pos(x, y))
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let _ = (x, y);
|
||||
SyncReturn(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Clip cursor to a rectangle (for pointer lock).
|
||||
///
|
||||
/// When `enable` is true, the cursor is clipped to the rectangle defined by
|
||||
/// `left`, `top`, `right`, `bottom`. When `enable` is false, the rectangle
|
||||
/// values are ignored and the cursor is unclipped.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `true`: operation succeeded or no-op completed
|
||||
/// - `false`: operation failed
|
||||
///
|
||||
/// # Platform behavior
|
||||
/// - Windows: uses ClipCursor API to confine cursor to the specified rectangle
|
||||
/// - macOS: uses CGAssociateMouseAndMouseCursorPosition for pointer lock effect;
|
||||
/// the rect coordinates are ignored (only Some/None matters)
|
||||
/// - Linux: no-op, always returns `true`; use pointer warping for similar effect
|
||||
/// - Android/iOS: no-op, always returns `false`
|
||||
pub fn main_clip_cursor(
|
||||
left: i32,
|
||||
top: i32,
|
||||
right: i32,
|
||||
bottom: i32,
|
||||
enable: bool,
|
||||
) -> SyncReturn<bool> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
let rect = if enable {
|
||||
Some((left, top, right, bottom))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
SyncReturn(crate::clip_cursor(rect))
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
{
|
||||
let _ = (left, top, right, bottom, enable);
|
||||
SyncReturn(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_get_my_id() -> String {
|
||||
get_id()
|
||||
}
|
||||
@@ -1748,8 +1808,99 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||
super::flutter::session_send_pointer(session_id, msg);
|
||||
}
|
||||
|
||||
/// Send mouse event from Flutter to the remote peer.
|
||||
///
|
||||
/// # Relative Mouse Mode Message Contract
|
||||
///
|
||||
/// When the message contains a `relative_mouse_mode` field, this function validates
|
||||
/// and filters activation/deactivation markers.
|
||||
///
|
||||
/// **Mode Authority:**
|
||||
/// The Flutter InputModel is authoritative for relative mouse mode activation/deactivation.
|
||||
/// The server (via `input_service.rs`) only consumes forwarded delta movements and tracks
|
||||
/// relative movement processing state, but does NOT control mode activation/deactivation.
|
||||
///
|
||||
/// **Deactivation Markers are Local-Only:**
|
||||
/// Deactivation markers (`relative_mouse_mode: "0"`) are NEVER forwarded to the server.
|
||||
/// They are handled entirely on the client side to reset local UI state (cursor visibility,
|
||||
/// pointer lock, etc.). The server does not rely on deactivation markers and should not
|
||||
/// expect to receive them.
|
||||
///
|
||||
/// **Contract (Flutter side MUST adhere to):**
|
||||
/// 1. `relative_mouse_mode` field is ONLY present on activation/deactivation marker messages,
|
||||
/// NEVER on normal pointer events (move, button, scroll).
|
||||
/// 2. Deactivation marker: `{"relative_mouse_mode": "0"}` - local-only, never forwarded.
|
||||
/// 3. Activation marker: `{"relative_mouse_mode": "1", "type": "move_relative", "x": "0", "y": "0"}`
|
||||
/// - MUST use `type="move_relative"` with `x="0"` and `y="0"` (safe no-op).
|
||||
/// - Any other combination is dropped to prevent accidental cursor movement.
|
||||
///
|
||||
/// If these assumptions are violated (e.g., `relative_mouse_mode` is added to normal events),
|
||||
/// legitimate mouse events may be silently dropped by the early-return logic below.
|
||||
pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
||||
if let Ok(m) = serde_json::from_str::<HashMap<String, String>>(&msg) {
|
||||
// Relative mouse mode marker validation (Flutter-only).
|
||||
// This only validates and filters markers; the server tracks per-connection
|
||||
// relative-movement processing state but not mode activation/deactivation.
|
||||
// See doc comment above for the message contract.
|
||||
if let Some(v) = m.get("relative_mouse_mode") {
|
||||
let active = matches!(v.as_str(), "1" | "Y" | "on");
|
||||
|
||||
// Disable marker: local-only, never forwarded to the server.
|
||||
// The server does not track mode deactivation; it simply stops receiving
|
||||
// relative move events when the client exits relative mouse mode.
|
||||
if !active {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::keyboard::set_relative_mouse_mode_state(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable marker: validate BEFORE setting state to avoid desync.
|
||||
// This ensures we only mark as active if the marker will actually be forwarded.
|
||||
|
||||
// Enable marker is allowed to go through only if it's a safe no-op relative move.
|
||||
// This avoids accidentally moving the remote cursor (e.g. if type/x/y are missing).
|
||||
let msg_type = m.get("type").map(|t| t.as_str());
|
||||
if msg_type != Some("move_relative") {
|
||||
log::warn!(
|
||||
"relative_mouse_mode activation marker has invalid type: {:?}, expected 'move_relative'. Dropping.",
|
||||
msg_type
|
||||
);
|
||||
return;
|
||||
}
|
||||
let x_marker = m
|
||||
.get("x")
|
||||
.map(|x| x.parse::<i32>().unwrap_or(0))
|
||||
.unwrap_or(0);
|
||||
let y_marker = m
|
||||
.get("y")
|
||||
.map(|y| y.parse::<i32>().unwrap_or(0))
|
||||
.unwrap_or(0);
|
||||
if x_marker != 0 || y_marker != 0 {
|
||||
log::warn!(
|
||||
"relative_mouse_mode activation marker has non-zero coordinates: x={}, y={}. Dropping.",
|
||||
x_marker, y_marker
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard against unexpected fields that could turn this no-op into a real event.
|
||||
if m.contains_key("buttons")
|
||||
|| m.contains_key("alt")
|
||||
|| m.contains_key("ctrl")
|
||||
|| m.contains_key("shift")
|
||||
|| m.contains_key("command")
|
||||
{
|
||||
log::warn!(
|
||||
"relative_mouse_mode activation marker contains unexpected fields (buttons/alt/ctrl/shift/command). Dropping."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// All validation passed - marker will be forwarded as a no-op relative move.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
crate::keyboard::set_relative_mouse_mode_state(true);
|
||||
}
|
||||
|
||||
let alt = m.get("alt").is_some();
|
||||
let ctrl = m.get("ctrl").is_some();
|
||||
let shift = m.get("shift").is_some();
|
||||
@@ -1769,6 +1920,7 @@ pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
||||
"up" => MOUSE_TYPE_UP,
|
||||
"wheel" => MOUSE_TYPE_WHEEL,
|
||||
"trackpad" => MOUSE_TYPE_TRACKPAD,
|
||||
"move_relative" => MOUSE_TYPE_MOVE_RELATIVE,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
171
src/keyboard.rs
171
src/keyboard.rs
@@ -32,9 +32,33 @@ const OS_LOWER_MACOS: &str = "macos";
|
||||
#[allow(dead_code)]
|
||||
const OS_LOWER_ANDROID: &str = "android";
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Track key down state for relative mouse mode exit shortcut.
|
||||
// macOS: Cmd+G (track G key)
|
||||
// Windows/Linux: Ctrl+Alt (track whichever modifier was pressed last)
|
||||
// This prevents the exit from retriggering on OS key-repeat.
|
||||
#[cfg(all(feature = "flutter", any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
static EXIT_SHORTCUT_KEY_DOWN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Track whether relative mouse mode is currently active.
|
||||
// This is set by Flutter via set_relative_mouse_mode_state() and checked
|
||||
// by the rdev grab loop to determine if exit shortcuts should be processed.
|
||||
#[cfg(all(feature = "flutter", any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
static RELATIVE_MOUSE_MODE_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Set the relative mouse mode state from Flutter.
|
||||
/// This is called when entering or exiting relative mouse mode.
|
||||
#[cfg(all(feature = "flutter", any(target_os = "windows", target_os = "macos", target_os = "linux")))]
|
||||
pub fn set_relative_mouse_mode_state(active: bool) {
|
||||
RELATIVE_MOUSE_MODE_ACTIVE.store(active, Ordering::SeqCst);
|
||||
// Reset exit shortcut state when mode changes to avoid stale state
|
||||
if !active {
|
||||
EXIT_SHORTCUT_KEY_DOWN.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
static IS_RDEV_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
@@ -82,7 +106,7 @@ pub mod client {
|
||||
GrabState::Run => {
|
||||
#[cfg(windows)]
|
||||
update_grab_get_key_name(keyboard_mode);
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
KEYBOARD_HOOKED.swap(true, Ordering::SeqCst);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -94,7 +118,7 @@ pub mod client {
|
||||
|
||||
release_remote_keys(keyboard_mode);
|
||||
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
KEYBOARD_HOOKED.swap(false, Ordering::SeqCst);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -266,6 +290,136 @@ fn get_keyboard_mode() -> String {
|
||||
"legacy".to_string()
|
||||
}
|
||||
|
||||
/// Check if exit shortcut for relative mouse mode is active.
|
||||
/// Exit shortcuts (only exits, not toggles):
|
||||
/// - macOS: Cmd+G
|
||||
/// - Windows/Linux: Ctrl+Alt (triggered when both are pressed)
|
||||
/// Note: This shortcut is only available in Flutter client. Sciter client does not support relative mouse mode.
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
fn is_exit_relative_mouse_shortcut(key: Key) -> bool {
|
||||
let modifiers = MODIFIERS_STATE.lock().unwrap();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// macOS: Cmd+G to exit
|
||||
if key != Key::KeyG {
|
||||
return false;
|
||||
}
|
||||
let meta = *modifiers.get(&Key::MetaLeft).unwrap_or(&false)
|
||||
|| *modifiers.get(&Key::MetaRight).unwrap_or(&false);
|
||||
return meta;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// Windows/Linux: Ctrl+Alt to exit
|
||||
// Triggered when Ctrl is pressed while Alt is down, or Alt is pressed while Ctrl is down
|
||||
let is_ctrl_key = key == Key::ControlLeft || key == Key::ControlRight;
|
||||
let is_alt_key = key == Key::Alt || key == Key::AltGr;
|
||||
|
||||
if !is_ctrl_key && !is_alt_key {
|
||||
return false;
|
||||
}
|
||||
|
||||
let ctrl = *modifiers.get(&Key::ControlLeft).unwrap_or(&false)
|
||||
|| *modifiers.get(&Key::ControlRight).unwrap_or(&false);
|
||||
let alt = *modifiers.get(&Key::Alt).unwrap_or(&false)
|
||||
|| *modifiers.get(&Key::AltGr).unwrap_or(&false);
|
||||
|
||||
// When Ctrl is pressed and Alt is already down, or vice versa
|
||||
(is_ctrl_key && alt) || (is_alt_key && ctrl)
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify Flutter to exit relative mouse mode.
|
||||
/// Note: This is Flutter-only. Sciter client does not support relative mouse mode.
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
fn notify_exit_relative_mouse_mode() {
|
||||
let session_id = flutter::get_cur_session_id();
|
||||
flutter::push_session_event(&session_id, "exit_relative_mouse_mode", vec![]);
|
||||
}
|
||||
|
||||
|
||||
/// Handle relative mouse mode shortcuts in the rdev grab loop.
|
||||
/// Returns true if the event should be blocked from being sent to the peer.
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
#[inline]
|
||||
fn can_exit_relative_mouse_mode_from_grab_loop() -> bool {
|
||||
// Only process exit shortcuts when relative mouse mode is actually active.
|
||||
// This prevents blocking Ctrl+Alt (or Cmd+G) when not in relative mouse mode.
|
||||
if !RELATIVE_MOUSE_MODE_ACTIVE.load(Ordering::SeqCst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(session) = flutter::get_cur_session() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Only for remote desktop sessions.
|
||||
if !session.is_default() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must have keyboard permission and not be in view-only mode.
|
||||
if !*session.server_keyboard_enabled.read().unwrap() {
|
||||
return false;
|
||||
}
|
||||
let lc = session.lc.read().unwrap();
|
||||
if lc.view_only.v {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Peer must support relative mouse mode.
|
||||
crate::common::is_support_relative_mouse_mode_num(lc.version)
|
||||
}
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
|
||||
#[inline]
|
||||
fn should_block_relative_mouse_shortcut(key: Key, is_press: bool) -> bool {
|
||||
if !KEYBOARD_HOOKED.load(Ordering::SeqCst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine which key to track for key-up blocking based on platform
|
||||
#[cfg(target_os = "macos")]
|
||||
let is_tracked_key = key == Key::KeyG;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let is_tracked_key = key == Key::ControlLeft
|
||||
|| key == Key::ControlRight
|
||||
|| key == Key::Alt
|
||||
|| key == Key::AltGr;
|
||||
|
||||
// Block key up if key down was blocked (to avoid orphan key up event on remote).
|
||||
// This must be checked before clearing the flag below.
|
||||
if is_tracked_key && !is_press && EXIT_SHORTCUT_KEY_DOWN.swap(false, Ordering::SeqCst) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exit relative mouse mode shortcuts:
|
||||
// - macOS: Cmd+G
|
||||
// - Windows/Linux: Ctrl+Alt
|
||||
// Guard it to supported/eligible sessions to avoid blocking the chord unexpectedly.
|
||||
if is_exit_relative_mouse_shortcut(key) {
|
||||
if !can_exit_relative_mouse_mode_from_grab_loop() {
|
||||
return false;
|
||||
}
|
||||
if is_press {
|
||||
// Only trigger exit on transition from "not pressed" to "pressed".
|
||||
// This prevents retriggering on OS key-repeat.
|
||||
if !EXIT_SHORTCUT_KEY_DOWN.swap(true, Ordering::SeqCst) {
|
||||
notify_exit_relative_mouse_mode();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn start_grab_loop() {
|
||||
std::env::set_var("KEYBOARD_ONLY", "y");
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
@@ -278,6 +432,12 @@ fn start_grab_loop() {
|
||||
|
||||
let _scan_code = event.position_code;
|
||||
let _code = event.platform_code as KeyCode;
|
||||
|
||||
#[cfg(feature = "flutter")]
|
||||
if should_block_relative_mouse_shortcut(key, is_press) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = if KEYBOARD_HOOKED.load(Ordering::SeqCst) {
|
||||
client::process_event(&get_keyboard_mode(), &event, None);
|
||||
if is_press {
|
||||
@@ -337,9 +497,14 @@ fn start_grab_loop() {
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Err(err) = rdev::start_grab_listen(move |event: Event| match event.event_type {
|
||||
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
|
||||
let is_press = matches!(event.event_type, EventType::KeyPress(_));
|
||||
if let Key::Unknown(keycode) = key {
|
||||
log::error!("rdev get unknown key, keycode is {:?}", keycode);
|
||||
} else {
|
||||
#[cfg(feature = "flutter")]
|
||||
if should_block_relative_mouse_shortcut(key, is_press) {
|
||||
return None;
|
||||
}
|
||||
client::process_event(&get_keyboard_mode(), &event, None);
|
||||
}
|
||||
None
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "أدخل الملاحظة هنا"),
|
||||
("note-at-conn-end-tip", "سيتم عرض هذه الملاحظة عند نهاية الاتصال"),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "输入备注"),
|
||||
("note-at-conn-end-tip", "在连接结束时请求备注"),
|
||||
("Show terminal extra keys", "显示终端扩展键"),
|
||||
("Relative mouse mode", "相对鼠标模式"),
|
||||
("rel-mouse-not-supported-peer-tip", "被控端不支持相对鼠标模式"),
|
||||
("rel-mouse-not-ready-tip", "相对鼠标模式尚未准备好,请稍后再试"),
|
||||
("rel-mouse-lock-failed-tip", "无法锁定鼠标,相对鼠标模式已禁用"),
|
||||
("rel-mouse-exit-{}-tip", "按下 {} 退出"),
|
||||
("rel-mouse-permission-lost-tip", "键盘权限被撤销。相对鼠标模式已被禁用。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Hier eine Notiz eingeben"),
|
||||
("note-at-conn-end-tip", "Am Ende der Verbindung um eine Notiz bitten."),
|
||||
("Show terminal extra keys", "Zusätzliche Tasten des Terminals anzeigen"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -262,5 +262,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("disable-udp-tip", "Controls whether to use TCP only.\nWhen this option enabled, RustDesk will not use UDP 21116 any more, TCP 21116 will be used instead."),
|
||||
("server-oss-not-support-tip", "NOTE: RustDesk server OSS doesn't include this feature."),
|
||||
("note-at-conn-end-tip", "Ask for note at end of connection"),
|
||||
("rel-mouse-not-supported-peer-tip", "Relative Mouse Mode is not supported by the connected peer."),
|
||||
("rel-mouse-not-ready-tip", "Relative Mouse Mode is not ready yet. Please try again."),
|
||||
("rel-mouse-lock-failed-tip", "Failed to lock cursor. Relative Mouse Mode has been disabled."),
|
||||
("rel-mouse-exit-{}-tip", "Press {} to exit."),
|
||||
("rel-mouse-permission-lost-tip", "Keyboard permission was revoked. Relative Mouse Mode has been disabled."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "یادداشت را اینجا وارد کنید"),
|
||||
("note-at-conn-end-tip", "در پایان اتصال، یادداشت بخواهید"),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "saisir la note ici"),
|
||||
("note-at-conn-end-tip", "Proposer de rédiger une note une fois la connexion terminée"),
|
||||
("Show terminal extra keys", "Afficher les touches supplémentaires du terminal"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Megjegyzés bevitele"),
|
||||
("note-at-conn-end-tip", "Megjegyzés a kapcsolat végén"),
|
||||
("Show terminal extra keys", "További terminálgombok megjelenítése"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Inserisci nota qui"),
|
||||
("note-at-conn-end-tip", "Visualizza nota alla fine della connessione"),
|
||||
("Show terminal extra keys", "Visualizza tasti aggiuntivi terminale"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "ここにメモを入力"),
|
||||
("note-at-conn-end-tip", "接続終了時にメモを要求する"),
|
||||
("Show terminal extra keys", "ターミナルの追加キーを表示する"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "여기에 노트 입력"),
|
||||
("note-at-conn-end-tip", "연결이 끝날 때 메모 요청"),
|
||||
("Show terminal extra keys", "터미널 추가 키 표시"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "voeg hier een opmerking toe"),
|
||||
("note-at-conn-end-tip", "Vraag om een opmerking aan het einde van de verbinding"),
|
||||
("Show terminal extra keys", "Toon extra toetsen voor terminal"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Wstaw tutaj notatkę"),
|
||||
("note-at-conn-end-tip", "Poproś o notatkę po zakończeniu połączenia."),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "введите заметку"),
|
||||
("note-at-conn-end-tip", "Запрашивать заметку в конце соединения"),
|
||||
("Show terminal extra keys", "Показывать дополнительные кнопки терминала"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "Notu buraya girin"),
|
||||
("note-at-conn-end-tip", "Bağlantı bittiğinde not sorulsun"),
|
||||
("Show terminal extra keys", "Terminal ek tuşlarını göster"),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", "輸入備註"),
|
||||
("note-at-conn-end-tip", "在連接結束時請求備註"),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -730,5 +730,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input note here", ""),
|
||||
("note-at-conn-end-tip", ""),
|
||||
("Show terminal extra keys", ""),
|
||||
("Relative mouse mode", ""),
|
||||
("rel-mouse-not-supported-peer-tip", ""),
|
||||
("rel-mouse-not-ready-tip", ""),
|
||||
("rel-mouse-lock-failed-tip", ""),
|
||||
("rel-mouse-exit-{}-tip", ""),
|
||||
("rel-mouse-permission-lost-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ mod keyboard;
|
||||
pub mod platform;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use platform::{
|
||||
get_cursor, get_cursor_data, get_cursor_pos, get_focused_display, start_os_service,
|
||||
clip_cursor, get_cursor, get_cursor_data, get_cursor_pos, get_focused_display,
|
||||
set_cursor_pos, start_os_service,
|
||||
};
|
||||
#[cfg(not(any(target_os = "ios")))]
|
||||
/// cbindgen:ignore
|
||||
|
||||
@@ -97,6 +97,7 @@ extern "C" {
|
||||
y: *mut c_int,
|
||||
screen_num: *mut c_int,
|
||||
) -> c_int;
|
||||
fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int;
|
||||
fn xdo_new(display: *const c_char) -> Xdo;
|
||||
fn xdo_get_active_window(xdo: Xdo, window: *mut *mut c_void) -> c_int;
|
||||
fn xdo_get_window_location(
|
||||
@@ -174,6 +175,56 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn set_cursor_pos(x: i32, y: i32) -> bool {
|
||||
let mut res = false;
|
||||
XDO.with(|xdo| {
|
||||
match xdo.try_borrow_mut() {
|
||||
Ok(xdo) => {
|
||||
if xdo.is_null() {
|
||||
log::debug!("set_cursor_pos: xdo is null");
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let ret = xdo_move_mouse(*xdo, x, y, 0);
|
||||
if ret != 0 {
|
||||
log::debug!(
|
||||
"set_cursor_pos: xdo_move_mouse failed with code {} for coordinates ({}, {})",
|
||||
ret, x, y
|
||||
);
|
||||
}
|
||||
res = ret == 0;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("set_cursor_pos: failed to borrow xdo");
|
||||
}
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
/// Clip cursor - Linux implementation is a no-op.
|
||||
///
|
||||
/// On X11, there's no direct equivalent to Windows ClipCursor. XGrabPointer
|
||||
/// can confine the pointer but requires a window handle and has side effects.
|
||||
///
|
||||
/// On Wayland, pointer constraints require the zwp_pointer_constraints_v1
|
||||
/// protocol which is compositor-dependent.
|
||||
///
|
||||
/// For relative mouse mode on Linux, the Flutter side uses pointer warping
|
||||
/// (set_cursor_pos) to re-center the cursor after each movement, which achieves
|
||||
/// a similar effect without requiring cursor clipping.
|
||||
///
|
||||
/// Returns true (always succeeds as no-op).
|
||||
pub fn clip_cursor(_rect: Option<(i32, i32, i32, i32)>) -> bool {
|
||||
// Log only once per process to avoid flooding logs when called frequently.
|
||||
static LOGGED: AtomicBool = AtomicBool::new(false);
|
||||
if !LOGGED.swap(true, Ordering::Relaxed) {
|
||||
log::debug!("clip_cursor called (no-op on Linux, this message is logged only once)");
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn reset_input_cache() {}
|
||||
|
||||
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
|
||||
@@ -32,8 +32,12 @@ use std::{
|
||||
os::unix::process::CommandExt,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
// macOS boolean_t is defined as `int` in <mach/boolean.h>
|
||||
type BooleanT = hbb_common::libc::c_int;
|
||||
|
||||
static PRIVILEGES_SCRIPTS_DIR: Dir =
|
||||
include_dir!("$CARGO_MANIFEST_DIR/src/platform/privileges_scripts");
|
||||
static mut LATEST_SEED: i32 = 0;
|
||||
@@ -42,6 +46,11 @@ static mut LATEST_SEED: i32 = 0;
|
||||
// using one that includes the custom client name.
|
||||
const UPDATE_TEMP_DIR: &str = "/tmp/.rustdeskupdate";
|
||||
|
||||
/// Global mutex to serialize CoreGraphics cursor operations.
|
||||
/// This prevents race conditions between cursor visibility (hide depth tracking)
|
||||
/// and cursor positioning/clipping operations.
|
||||
static CG_CURSOR_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
extern "C" {
|
||||
fn CGSCurrentCursorSeed() -> i32;
|
||||
fn CGEventCreate(r: *const c_void) -> *const c_void;
|
||||
@@ -64,6 +73,8 @@ extern "C" {
|
||||
fn majorVersion() -> u32;
|
||||
fn MacGetMode(display: u32, width: *mut u32, height: *mut u32) -> BOOL;
|
||||
fn MacSetMode(display: u32, width: u32, height: u32, tryHiDPI: bool) -> BOOL;
|
||||
fn CGWarpMouseCursorPosition(newCursorPosition: CGPoint) -> CGError;
|
||||
fn CGAssociateMouseAndMouseCursorPosition(connected: BooleanT) -> CGError;
|
||||
}
|
||||
|
||||
pub fn major_version() -> u32 {
|
||||
@@ -387,6 +398,99 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
*/
|
||||
}
|
||||
|
||||
/// Warp the mouse cursor to the specified screen position.
|
||||
///
|
||||
/// # Thread Safety
|
||||
/// This function affects global cursor state and acquires `CG_CURSOR_MUTEX`.
|
||||
/// Callers must ensure no nested calls occur while the mutex is held.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `x` - X coordinate in screen points (macOS uses points, not pixels)
|
||||
/// * `y` - Y coordinate in screen points
|
||||
pub fn set_cursor_pos(x: i32, y: i32) -> bool {
|
||||
// Acquire lock with deadlock detection in debug builds.
|
||||
// In debug builds, try_lock detects re-entrant calls early; on failure we return immediately.
|
||||
// In release builds, we use blocking lock() which will wait if contended.
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = match CG_CURSOR_MUTEX.try_lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(std::sync::TryLockError::WouldBlock) => {
|
||||
log::error!("[BUG] set_cursor_pos: CG_CURSOR_MUTEX is already held - potential deadlock!");
|
||||
debug_assert!(false, "Re-entrant call to set_cursor_pos detected");
|
||||
return false;
|
||||
}
|
||||
Err(std::sync::TryLockError::Poisoned(e)) => e.into_inner(),
|
||||
};
|
||||
#[cfg(not(debug_assertions))]
|
||||
let _guard = CG_CURSOR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
|
||||
unsafe {
|
||||
let result = CGWarpMouseCursorPosition(CGPoint {
|
||||
x: x as f64,
|
||||
y: y as f64,
|
||||
});
|
||||
if result != CGError::Success {
|
||||
log::error!(
|
||||
"CGWarpMouseCursorPosition({}, {}) returned error: {:?}",
|
||||
x,
|
||||
y,
|
||||
result
|
||||
);
|
||||
}
|
||||
result == CGError::Success
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle pointer lock (dissociate/associate mouse from cursor position).
|
||||
///
|
||||
/// On macOS, cursor clipping is not supported directly like Windows ClipCursor.
|
||||
/// Instead, we use CGAssociateMouseAndMouseCursorPosition to dissociate mouse
|
||||
/// movement from cursor position, achieving a "pointer lock" effect.
|
||||
///
|
||||
/// # Thread Safety
|
||||
/// This function affects global cursor state and acquires `CG_CURSOR_MUTEX`.
|
||||
/// Callers must ensure only one owner toggles pointer lock at a time;
|
||||
/// nested Some/None transitions from different call sites may cause unexpected behavior.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `rect` - When `Some(_)`, dissociates mouse from cursor (enables pointer lock).
|
||||
/// When `None`, re-associates mouse with cursor (disables pointer lock).
|
||||
/// The rect coordinate values are ignored on macOS; only `Some`/`None` matters.
|
||||
/// The parameter signature matches Windows for API consistency.
|
||||
pub fn clip_cursor(rect: Option<(i32, i32, i32, i32)>) -> bool {
|
||||
// Acquire lock with deadlock detection in debug builds.
|
||||
// In debug builds, try_lock detects re-entrant calls early; on failure we return immediately.
|
||||
// In release builds, we use blocking lock() which will wait if contended.
|
||||
#[cfg(debug_assertions)]
|
||||
let _guard = match CG_CURSOR_MUTEX.try_lock() {
|
||||
Ok(guard) => guard,
|
||||
Err(std::sync::TryLockError::WouldBlock) => {
|
||||
log::error!("[BUG] clip_cursor: CG_CURSOR_MUTEX is already held - potential deadlock!");
|
||||
debug_assert!(false, "Re-entrant call to clip_cursor detected");
|
||||
return false;
|
||||
}
|
||||
Err(std::sync::TryLockError::Poisoned(e)) => e.into_inner(),
|
||||
};
|
||||
#[cfg(not(debug_assertions))]
|
||||
let _guard = CG_CURSOR_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
|
||||
// CGAssociateMouseAndMouseCursorPosition takes a boolean_t:
|
||||
// 1 (true) = associate mouse with cursor position (normal mode)
|
||||
// 0 (false) = dissociate mouse from cursor position (pointer lock mode)
|
||||
// When rect is Some, we want pointer lock (dissociate), so associate = false (0).
|
||||
// When rect is None, we want normal mode (associate), so associate = true (1).
|
||||
let associate: BooleanT = if rect.is_some() { 0 } else { 1 };
|
||||
unsafe {
|
||||
let result = CGAssociateMouseAndMouseCursorPosition(associate);
|
||||
if result != CGError::Success {
|
||||
log::warn!(
|
||||
"CGAssociateMouseAndMouseCursorPosition({}) returned error: {:?}",
|
||||
associate,
|
||||
result
|
||||
);
|
||||
}
|
||||
result == CGError::Success
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
autoreleasepool(|| unsafe_get_focused_display(displays))
|
||||
}
|
||||
|
||||
@@ -26,18 +26,13 @@ pub mod linux_desktop_manager;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod gtk_sudo;
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{
|
||||
message_proto::CursorData,
|
||||
sysinfo::Pid,
|
||||
ResultType,
|
||||
};
|
||||
#[cfg(all(
|
||||
not(all(target_os = "windows", not(target_pointer_width = "64"))),
|
||||
not(any(target_os = "android", target_os = "ios"))))]
|
||||
use hbb_common::{
|
||||
sysinfo::System,
|
||||
};
|
||||
not(any(target_os = "android", target_os = "ios"))
|
||||
))]
|
||||
use hbb_common::sysinfo::System;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use hbb_common::{message_proto::CursorData, sysinfo::Pid, ResultType};
|
||||
use std::sync::{Arc, Mutex};
|
||||
#[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))]
|
||||
pub const SERVICE_INTERVAL: u64 = 300;
|
||||
|
||||
@@ -116,12 +116,51 @@ pub fn get_focused_display(displays: Vec<DisplayInfo>) -> Option<usize> {
|
||||
|
||||
pub fn get_cursor_pos() -> Option<(i32, i32)> {
|
||||
unsafe {
|
||||
#[allow(invalid_value)]
|
||||
let mut out = mem::MaybeUninit::uninit().assume_init();
|
||||
if GetCursorPos(&mut out) == FALSE {
|
||||
let mut out = mem::MaybeUninit::<POINT>::uninit();
|
||||
if GetCursorPos(out.as_mut_ptr()) == FALSE {
|
||||
return None;
|
||||
}
|
||||
return Some((out.x, out.y));
|
||||
let out = out.assume_init();
|
||||
Some((out.x, out.y))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_pos(x: i32, y: i32) -> bool {
|
||||
unsafe {
|
||||
if SetCursorPos(x, y) == FALSE {
|
||||
let err = GetLastError();
|
||||
log::warn!("SetCursorPos failed: x={}, y={}, error_code={}", x, y, err);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Clip cursor to a rectangle. Pass None to unclip.
|
||||
pub fn clip_cursor(rect: Option<(i32, i32, i32, i32)>) -> bool {
|
||||
unsafe {
|
||||
let result = match rect {
|
||||
Some((left, top, right, bottom)) => {
|
||||
let r = RECT {
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
};
|
||||
ClipCursor(&r)
|
||||
}
|
||||
None => ClipCursor(std::ptr::null()),
|
||||
};
|
||||
if result == FALSE {
|
||||
let err = GetLastError();
|
||||
log::warn!(
|
||||
"ClipCursor failed: rect={:?}, error_code={}",
|
||||
rect,
|
||||
err
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5173,9 +5173,13 @@ impl Retina {
|
||||
|
||||
#[inline]
|
||||
fn on_mouse_event(&mut self, e: &mut MouseEvent, current: usize) {
|
||||
let evt_type = e.mask & 0x7;
|
||||
if evt_type == crate::input::MOUSE_TYPE_WHEEL {
|
||||
// x and y are always 0, +1 or -1
|
||||
let evt_type = e.mask & crate::input::MOUSE_TYPE_MASK;
|
||||
// Delta-based events do not contain absolute coordinates.
|
||||
// Avoid applying Retina coordinate scaling to them.
|
||||
if evt_type == crate::input::MOUSE_TYPE_WHEEL
|
||||
|| evt_type == crate::input::MOUSE_TYPE_TRACKPAD
|
||||
|| evt_type == crate::input::MOUSE_TYPE_MOVE_RELATIVE
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(d) = self.displays.get(current) else {
|
||||
@@ -5421,6 +5425,9 @@ mod raii {
|
||||
.unwrap()
|
||||
.on_connection_close(self.0);
|
||||
}
|
||||
// Clear per-connection state to avoid stale behavior if conn ids are reused.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
clear_relative_mouse_active(self.0);
|
||||
AUTHED_CONNS.lock().unwrap().retain(|c| c.conn_id != self.0);
|
||||
let remote_count = AUTHED_CONNS
|
||||
.lock()
|
||||
|
||||
@@ -26,6 +26,7 @@ use std::{
|
||||
thread,
|
||||
time::{self, Duration, Instant},
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
use winapi::um::winuser::WHEEL_DELTA;
|
||||
|
||||
@@ -447,7 +448,36 @@ lazy_static::lazy_static! {
|
||||
static ref KEYS_DOWN: Arc<Mutex<HashMap<KeysDown, Instant>>> = Default::default();
|
||||
static ref LATEST_PEER_INPUT_CURSOR: Arc<Mutex<Input>> = Default::default();
|
||||
static ref LATEST_SYS_CURSOR_POS: Arc<Mutex<(Option<Instant>, (i32, i32))>> = Arc::new(Mutex::new((None, (INVALID_CURSOR_POS, INVALID_CURSOR_POS))));
|
||||
// Track connections that are currently using relative mouse movement.
|
||||
// Used to disable whiteboard/cursor display for all events while in relative mode.
|
||||
static ref RELATIVE_MOUSE_CONNS: Arc<Mutex<std::collections::HashSet<i32>>> = Default::default();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_relative_mouse_active(conn: i32, active: bool) {
|
||||
let mut lock = RELATIVE_MOUSE_CONNS.lock().unwrap();
|
||||
if active {
|
||||
lock.insert(conn);
|
||||
} else {
|
||||
lock.remove(&conn);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_relative_mouse_active(conn: i32) -> bool {
|
||||
RELATIVE_MOUSE_CONNS.lock().unwrap().contains(&conn)
|
||||
}
|
||||
|
||||
/// Clears the relative mouse mode state for a connection.
|
||||
///
|
||||
/// This must be called when an authenticated connection is dropped (during connection teardown)
|
||||
/// to avoid leaking the connection id in `RELATIVE_MOUSE_CONNS` (a `Mutex<HashSet<i32>>`).
|
||||
/// Callers are responsible for invoking this on disconnect.
|
||||
#[inline]
|
||||
pub(crate) fn clear_relative_mouse_active(conn: i32) {
|
||||
set_relative_mouse_active(conn, false);
|
||||
}
|
||||
|
||||
static EXITING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
const MOUSE_MOVE_PROTECTION_TIMEOUT: Duration = Duration::from_millis(1_000);
|
||||
@@ -644,8 +674,8 @@ async fn set_uinput_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> Re
|
||||
|
||||
pub fn is_left_up(evt: &MouseEvent) -> bool {
|
||||
let buttons = evt.mask >> 3;
|
||||
let evt_type = evt.mask & 0x7;
|
||||
return buttons == 1 && evt_type == 2;
|
||||
let evt_type = evt.mask & MOUSE_TYPE_MASK;
|
||||
buttons == MOUSE_BUTTON_LEFT && evt_type == MOUSE_TYPE_UP
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -1003,8 +1033,16 @@ pub fn handle_mouse_(
|
||||
handle_mouse_simulation_(evt, conn);
|
||||
}
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
if _show_cursor {
|
||||
handle_mouse_show_cursor_(evt, conn, _username, _argb);
|
||||
{
|
||||
let evt_type = evt.mask & MOUSE_TYPE_MASK;
|
||||
// Relative (delta) mouse events do not include absolute coordinates, so
|
||||
// whiteboard/cursor rendering must be disabled during relative mode to prevent
|
||||
// incorrect cursor/whiteboard updates. We check both is_relative_mouse_active(conn)
|
||||
// (connection already in relative mode from prior events) and evt_type (current
|
||||
// event is relative) to guard against the first relative event before the flag is set.
|
||||
if _show_cursor && !is_relative_mouse_active(conn) && evt_type != MOUSE_TYPE_MOVE_RELATIVE {
|
||||
handle_mouse_show_cursor_(evt, conn, _username, _argb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1020,7 +1058,7 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
||||
#[cfg(windows)]
|
||||
crate::platform::windows::try_change_desktop();
|
||||
let buttons = evt.mask >> 3;
|
||||
let evt_type = evt.mask & 0x7;
|
||||
let evt_type = evt.mask & MOUSE_TYPE_MASK;
|
||||
let mut en = ENIGO.lock().unwrap();
|
||||
#[cfg(target_os = "macos")]
|
||||
en.set_ignore_flags(enigo_ignore_flags());
|
||||
@@ -1048,6 +1086,8 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
||||
}
|
||||
match evt_type {
|
||||
MOUSE_TYPE_MOVE => {
|
||||
// Switching back to absolute movement implicitly disables relative mouse mode.
|
||||
set_relative_mouse_active(conn, false);
|
||||
en.mouse_move_to(evt.x, evt.y);
|
||||
*LATEST_PEER_INPUT_CURSOR.lock().unwrap() = Input {
|
||||
conn,
|
||||
@@ -1056,6 +1096,28 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
||||
y: evt.y,
|
||||
};
|
||||
}
|
||||
// MOUSE_TYPE_MOVE_RELATIVE: Relative mouse movement for gaming/3D applications.
|
||||
// Each client independently decides whether to use relative mode.
|
||||
// Multiple clients can mix absolute and relative movements without conflict,
|
||||
// as the server simply applies the delta to the current cursor position.
|
||||
MOUSE_TYPE_MOVE_RELATIVE => {
|
||||
set_relative_mouse_active(conn, true);
|
||||
// Clamp delta to prevent extreme/malicious values from reaching OS APIs.
|
||||
// This matches the Flutter client's kMaxRelativeMouseDelta constant.
|
||||
const MAX_RELATIVE_MOUSE_DELTA: i32 = 10000;
|
||||
let dx = evt.x.clamp(-MAX_RELATIVE_MOUSE_DELTA, MAX_RELATIVE_MOUSE_DELTA);
|
||||
let dy = evt.y.clamp(-MAX_RELATIVE_MOUSE_DELTA, MAX_RELATIVE_MOUSE_DELTA);
|
||||
en.mouse_move_relative(dx, dy);
|
||||
// Get actual cursor position after relative movement for tracking
|
||||
if let Some((x, y)) = crate::get_cursor_pos() {
|
||||
*LATEST_PEER_INPUT_CURSOR.lock().unwrap() = Input {
|
||||
conn,
|
||||
time: get_time(),
|
||||
x,
|
||||
y,
|
||||
};
|
||||
}
|
||||
}
|
||||
MOUSE_TYPE_DOWN => match buttons {
|
||||
MOUSE_BUTTON_LEFT => {
|
||||
allow_err!(en.mouse_down(MouseButton::Left));
|
||||
@@ -1154,7 +1216,7 @@ pub fn handle_mouse_simulation_(evt: &MouseEvent, conn: i32) {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn handle_mouse_show_cursor_(evt: &MouseEvent, conn: i32, username: String, argb: u32) {
|
||||
let buttons = evt.mask >> 3;
|
||||
let evt_type = evt.mask & 0x7;
|
||||
let evt_type = evt.mask & MOUSE_TYPE_MASK;
|
||||
match evt_type {
|
||||
MOUSE_TYPE_MOVE => {
|
||||
whiteboard::update_whiteboard(
|
||||
@@ -1170,11 +1232,22 @@ pub fn handle_mouse_show_cursor_(evt: &MouseEvent, conn: i32, username: String,
|
||||
}
|
||||
MOUSE_TYPE_UP => {
|
||||
if buttons == MOUSE_BUTTON_LEFT {
|
||||
// Some clients intentionally send button events without coordinates.
|
||||
// Fall back to the last known cursor position to avoid jumping to (0, 0).
|
||||
// TODO(protocol): (0, 0) is a valid screen coordinate. Consider using a dedicated
|
||||
// sentinel value (e.g. INVALID_CURSOR_POS) or a protocol-level flag to distinguish
|
||||
// "coordinates not provided" from "coordinates are (0, 0)". Impact is minor since
|
||||
// this only affects whiteboard rendering and clicking exactly at (0, 0) is rare.
|
||||
let (x, y) = if evt.x == 0 && evt.y == 0 {
|
||||
get_last_input_cursor_pos()
|
||||
} else {
|
||||
(evt.x, evt.y)
|
||||
};
|
||||
whiteboard::update_whiteboard(
|
||||
whiteboard::get_key_cursor(conn),
|
||||
whiteboard::CustomEvent::Cursor(whiteboard::Cursor {
|
||||
x: evt.x as _,
|
||||
y: evt.y as _,
|
||||
x: x as _,
|
||||
y: y as _,
|
||||
argb,
|
||||
btns: buttons,
|
||||
text: username,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
common::{get_supported_keyboard_modes, is_keyboard_mode_supported},
|
||||
input::{MOUSE_BUTTON_LEFT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP, MOUSE_TYPE_WHEEL},
|
||||
input::{
|
||||
MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_MASK,
|
||||
MOUSE_TYPE_TRACKPAD, MOUSE_TYPE_UP, MOUSE_TYPE_WHEEL,
|
||||
},
|
||||
ui_interface::use_texture_render,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
@@ -1222,7 +1225,9 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let (x, y) = if mask == MOUSE_TYPE_WHEEL || mask == MOUSE_TYPE_TRACKPAD {
|
||||
// Compute event type once using MOUSE_TYPE_MASK for reuse
|
||||
let event_type = mask & MOUSE_TYPE_MASK;
|
||||
let (x, y) = if event_type == MOUSE_TYPE_WHEEL || event_type == MOUSE_TYPE_TRACKPAD {
|
||||
self.get_scroll_xy((x, y))
|
||||
} else {
|
||||
(x, y)
|
||||
@@ -1231,8 +1236,6 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
// #[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let (alt, ctrl, shift, command) =
|
||||
keyboard::client::get_modifiers_state(alt, ctrl, shift, command);
|
||||
|
||||
use crate::input::*;
|
||||
let is_left = (mask & (MOUSE_BUTTON_LEFT << 3)) > 0;
|
||||
let is_right = (mask & (MOUSE_BUTTON_RIGHT << 3)) > 0;
|
||||
if is_left ^ is_right {
|
||||
@@ -1252,9 +1255,8 @@ impl<T: InvokeUiSession> Session<T> {
|
||||
// to-do: how about ctrl + left from win to macos
|
||||
if cfg!(target_os = "macos") {
|
||||
let buttons = mask >> 3;
|
||||
let evt_type = mask & 0x7;
|
||||
if buttons == MOUSE_BUTTON_LEFT
|
||||
&& evt_type == MOUSE_TYPE_DOWN
|
||||
&& event_type == MOUSE_TYPE_DOWN
|
||||
&& ctrl
|
||||
&& self.peer_platform() != "Mac OS"
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user