diff --git a/Cargo.lock b/Cargo.lock index 2c8cf996d..5aec38900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2517,6 +2517,7 @@ version = "0.0.14" dependencies = [ "core-graphics 0.22.3", "hbb_common", + "libxdo-sys", "log", "objc", "pkg-config", @@ -3720,6 +3721,7 @@ dependencies = [ "httparse", "lazy_static", "libc", + "libloading 0.8.4", "log", "mac_address", "machine-uid", @@ -3755,6 +3757,7 @@ dependencies = [ "webrtc", "whoami", "winapi 0.3.9", + "x11 2.21.0", "zstd 0.13.1", ] @@ -4546,11 +4549,8 @@ dependencies = [ [[package]] name = "libxdo-sys" version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212" dependencies = [ - "libc", - "x11 2.21.0", + "hbb_common", ] [[package]] @@ -7181,9 +7181,9 @@ dependencies = [ "kcp-sys", "keepawake", "lazy_static", - "libloading 0.8.4", "libpulse-binding", "libpulse-simple-binding", + "libxdo-sys", "mac_address", "magnum-opus", "nix 0.29.0", diff --git a/Cargo.toml b/Cargo.toml index 890da5647..ac1050bf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,6 @@ crossbeam-queue = "0.3" hex = "0.4" chrono = "0.4" cidr-utils = "0.5" -libloading = "0.8" fon = "0.6" zip = "0.6" shutdown_hooks = "0.1" @@ -177,6 +176,7 @@ bytemuck = "1.23" ttf-parser = "0.25" [target.'cfg(target_os = "linux")'.dependencies] +libxdo-sys = "0.11" psimple = { package = "libpulse-simple-binding", version = "2.27" } pulse = { package = "libpulse-binding", version = "2.27" } rust-pulsectl = { git = "https://github.com/rustdesk-org/pulsectl" } @@ -207,6 +207,11 @@ android-wakelock = { git = "https://github.com/rustdesk-org/android-wakelock" } members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/virtual_display/dylib", "libs/portable", "libs/remote_printer"] exclude = ["vdi/host", "examples/custom_plugin"] +# Patch libxdo-sys to use a stub implementation that doesn't require libxdo +# This allows building and running on systems without libxdo installed (e.g., Wayland-only) +[patch.crates-io] +libxdo-sys = { path = "libs/libxdo-sys-stub" } + [package.metadata.winres] LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved." ProductName = "RustDesk" diff --git a/libs/enigo/Cargo.toml b/libs/enigo/Cargo.toml index a5b6d5622..6468eeedd 100644 --- a/libs/enigo/Cargo.toml +++ b/libs/enigo/Cargo.toml @@ -37,5 +37,8 @@ core-graphics = "0.22" objc = "0.2" unicode-segmentation = "1.10" +[target.'cfg(target_os = "linux")'.dependencies] +libxdo-sys = "0.11" + [build-dependencies] pkg-config = "0.3" diff --git a/libs/enigo/src/linux/xdo.rs b/libs/enigo/src/linux/xdo.rs index f0f7d49af..26d090855 100644 --- a/libs/enigo/src/linux/xdo.rs +++ b/libs/enigo/src/linux/xdo.rs @@ -1,50 +1,22 @@ +//! XDO-based input emulation for Linux. +//! +//! This module uses libxdo-sys (patched to use dynamic loading stub) for input emulation. +//! The stub handles dynamic loading of libxdo, so we just call the functions directly. +//! +//! If libxdo is not available at runtime, all operations become no-ops. + use crate::{Key, KeyboardControllable, MouseButton, MouseControllable}; -use hbb_common::libc::{c_char, c_int, c_void, useconds_t}; -use std::{borrow::Cow, ffi::CString, ptr}; +use hbb_common::libc::c_int; +use libxdo_sys::{self, xdo_t, CURRENTWINDOW}; +use std::{borrow::Cow, ffi::CString}; -const CURRENT_WINDOW: c_int = 0; +/// Default delay per keypress in microseconds. +/// This value is passed to libxdo functions and must fit in `useconds_t` (u32). const DEFAULT_DELAY: u64 = 12000; -type Window = c_int; -type Xdo = *const c_void; -#[link(name = "xdo")] -extern "C" { - fn xdo_free(xdo: Xdo); - fn xdo_new(display: *const c_char) -> Xdo; - - fn xdo_click_window(xdo: Xdo, window: Window, button: c_int) -> c_int; - fn xdo_mouse_down(xdo: Xdo, window: Window, button: c_int) -> c_int; - fn xdo_mouse_up(xdo: Xdo, window: Window, button: c_int) -> c_int; - fn xdo_move_mouse(xdo: Xdo, x: c_int, y: c_int, screen: c_int) -> c_int; - fn xdo_move_mouse_relative(xdo: Xdo, x: c_int, y: c_int) -> c_int; - - fn xdo_enter_text_window( - xdo: Xdo, - window: Window, - string: *const c_char, - delay: useconds_t, - ) -> c_int; - fn xdo_send_keysequence_window( - xdo: Xdo, - window: Window, - string: *const c_char, - delay: useconds_t, - ) -> c_int; - fn xdo_send_keysequence_window_down( - xdo: Xdo, - window: Window, - string: *const c_char, - delay: useconds_t, - ) -> c_int; - fn xdo_send_keysequence_window_up( - xdo: Xdo, - window: Window, - string: *const c_char, - delay: useconds_t, - ) -> c_int; - fn xdo_get_input_state(xdo: Xdo) -> u32; -} +/// Maximum allowed delay value (u32::MAX as u64). +const MAX_DELAY: u64 = u32::MAX as u64; fn mousebutton(button: MouseButton) -> c_int { match button { @@ -62,7 +34,7 @@ fn mousebutton(button: MouseButton) -> c_int { /// The main struct for handling the event emitting pub(super) struct EnigoXdo { - xdo: Xdo, + xdo: *mut xdo_t, delay: u64, } // This is safe, we have a unique pointer. @@ -70,37 +42,61 @@ pub(super) struct EnigoXdo { unsafe impl Send for EnigoXdo {} impl Default for EnigoXdo { - /// Create a new EnigoXdo instance + /// Create a new EnigoXdo instance. + /// + /// If libxdo is not available, the xdo pointer will be null and all + /// input operations will be no-ops. fn default() -> Self { + let xdo = unsafe { libxdo_sys::xdo_new(std::ptr::null()) }; + if xdo.is_null() { + log::warn!("Failed to create xdo context, xdo functions will be disabled"); + } else { + log::info!("xdo context created successfully"); + } Self { - xdo: unsafe { xdo_new(ptr::null()) }, + xdo, delay: DEFAULT_DELAY, } } } + impl EnigoXdo { - /// Get the delay per keypress. - /// Default value is 12000. - /// This is Linux-specific. + /// Get the delay per keypress in microseconds. + /// + /// Default value is 12000 (12ms). This is Linux-specific. pub fn delay(&self) -> u64 { self.delay } - /// Set the delay per keypress. - /// This is Linux-specific. + + /// Set the delay per keypress in microseconds. + /// + /// This is Linux-specific. The value is clamped to `u32::MAX` (approximately + /// 4295 seconds) because libxdo uses `useconds_t` which is typically `u32`. + /// + /// # Arguments + /// * `delay` - Delay in microseconds. Values exceeding `u32::MAX` will be clamped. pub fn set_delay(&mut self, delay: u64) { - self.delay = delay; + self.delay = delay.min(MAX_DELAY); + if delay > MAX_DELAY { + log::warn!( + "delay value {} exceeds maximum {}, clamped", + delay, + MAX_DELAY + ); + } } } + impl Drop for EnigoXdo { fn drop(&mut self) { - if self.xdo.is_null() { - return; - } - unsafe { - xdo_free(self.xdo); + if !self.xdo.is_null() { + unsafe { + libxdo_sys::xdo_free(self.xdo); + } } } } + impl MouseControllable for EnigoXdo { fn as_any(&self) -> &dyn std::any::Any { self @@ -115,42 +111,47 @@ impl MouseControllable for EnigoXdo { return; } unsafe { - xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0); + libxdo_sys::xdo_move_mouse(self.xdo as *const _, x, y, 0); } } + fn mouse_move_relative(&mut self, x: i32, y: i32) { if self.xdo.is_null() { return; } unsafe { - xdo_move_mouse_relative(self.xdo, x as c_int, y as c_int); + libxdo_sys::xdo_move_mouse_relative(self.xdo as *const _, x, y); } } + fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType { if self.xdo.is_null() { return Ok(()); } unsafe { - xdo_mouse_down(self.xdo, CURRENT_WINDOW, mousebutton(button)); + libxdo_sys::xdo_mouse_down(self.xdo as *const _, CURRENTWINDOW, mousebutton(button)); } Ok(()) } + fn mouse_up(&mut self, button: MouseButton) { if self.xdo.is_null() { return; } unsafe { - xdo_mouse_up(self.xdo, CURRENT_WINDOW, mousebutton(button)); + libxdo_sys::xdo_mouse_up(self.xdo as *const _, CURRENTWINDOW, mousebutton(button)); } } + fn mouse_click(&mut self, button: MouseButton) { if self.xdo.is_null() { return; } unsafe { - xdo_click_window(self.xdo, CURRENT_WINDOW, mousebutton(button)); + libxdo_sys::xdo_click_window(self.xdo as *const _, CURRENTWINDOW, mousebutton(button)); } } + fn mouse_scroll_x(&mut self, length: i32) { let button; let mut length = length; @@ -169,6 +170,7 @@ impl MouseControllable for EnigoXdo { self.mouse_click(button); } } + fn mouse_scroll_y(&mut self, length: i32) { let button; let mut length = length; @@ -188,6 +190,7 @@ impl MouseControllable for EnigoXdo { } } } + fn keysequence<'a>(key: Key) -> Cow<'a, str> { if let Key::Layout(c) = key { return Cow::Owned(format!("U{:X}", c as u32)); @@ -284,6 +287,7 @@ fn keysequence<'a>(key: Key) -> Cow<'a, str> { _ => "", }) } + impl KeyboardControllable for EnigoXdo { fn as_any(&self) -> &dyn std::any::Any { self @@ -314,7 +318,7 @@ impl KeyboardControllable for EnigoXdo { let mod_alt = 1 << 3; let mod_numlock = 1 << 4; let mod_meta = 1 << 6; - let mask = unsafe { xdo_get_input_state(self.xdo) }; + let mask = unsafe { libxdo_sys::xdo_get_input_state(self.xdo as *const _) }; match key { Key::Shift => mask & mod_shift != 0, Key::CapsLock => mask & mod_lock != 0, @@ -332,56 +336,59 @@ impl KeyboardControllable for EnigoXdo { } if let Ok(string) = CString::new(sequence) { unsafe { - xdo_enter_text_window( - self.xdo, - CURRENT_WINDOW, + libxdo_sys::xdo_enter_text_window( + self.xdo as *const _, + CURRENTWINDOW, string.as_ptr(), - self.delay as useconds_t, + self.delay as libxdo_sys::useconds_t, ); } } } + fn key_down(&mut self, key: Key) -> crate::ResultType { if self.xdo.is_null() { return Ok(()); } let string = CString::new(&*keysequence(key))?; unsafe { - xdo_send_keysequence_window_down( - self.xdo, - CURRENT_WINDOW, + libxdo_sys::xdo_send_keysequence_window_down( + self.xdo as *const _, + CURRENTWINDOW, string.as_ptr(), - self.delay as useconds_t, + self.delay as libxdo_sys::useconds_t, ); } Ok(()) } + fn key_up(&mut self, key: Key) { if self.xdo.is_null() { return; } if let Ok(string) = CString::new(&*keysequence(key)) { unsafe { - xdo_send_keysequence_window_up( - self.xdo, - CURRENT_WINDOW, + libxdo_sys::xdo_send_keysequence_window_up( + self.xdo as *const _, + CURRENTWINDOW, string.as_ptr(), - self.delay as useconds_t, + self.delay as libxdo_sys::useconds_t, ); } } } + fn key_click(&mut self, key: Key) { if self.xdo.is_null() { return; } if let Ok(string) = CString::new(&*keysequence(key)) { unsafe { - xdo_send_keysequence_window( - self.xdo, - CURRENT_WINDOW, + libxdo_sys::xdo_send_keysequence_window( + self.xdo as *const _, + CURRENTWINDOW, string.as_ptr(), - self.delay as useconds_t, + self.delay as libxdo_sys::useconds_t, ); } } diff --git a/libs/hbb_common b/libs/hbb_common index 7d93d5af4..900077a2c 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 7d93d5af48db34dbd4a9d317e4a69d04b0bcf703 +Subproject commit 900077a2c2651336317f8094ea44074c48acd2a4 diff --git a/libs/libxdo-sys-stub/Cargo.toml b/libs/libxdo-sys-stub/Cargo.toml new file mode 100644 index 000000000..0b52cfb63 --- /dev/null +++ b/libs/libxdo-sys-stub/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "libxdo-sys" +version = "0.11.0" +edition = "2021" +publish = false +description = "Dynamic loading wrapper for libxdo-sys that doesn't require libxdo at compile/link time" + +[dependencies] +hbb_common = { path = "../hbb_common" } diff --git a/libs/libxdo-sys-stub/src/lib.rs b/libs/libxdo-sys-stub/src/lib.rs new file mode 100644 index 000000000..53d0e099c --- /dev/null +++ b/libs/libxdo-sys-stub/src/lib.rs @@ -0,0 +1,505 @@ +//! Dynamic loading wrapper for libxdo. +//! +//! Provides the same API as libxdo-sys but loads libxdo at runtime, +//! allowing the program to run on systems without libxdo installed +//! (e.g., Wayland-only environments). + +use hbb_common::{ + libc::{c_char, c_int, c_uint}, + libloading::{Library, Symbol}, + log, +}; +use std::sync::OnceLock; + +pub use hbb_common::x11::xlib::{Display, Screen, Window}; + +#[repr(C)] +pub struct xdo_t { + _private: [u8; 0], +} + +#[repr(C)] +pub struct charcodemap_t { + _private: [u8; 0], +} + +#[repr(C)] +pub struct xdo_search_t { + _private: [u8; 0], +} + +pub type useconds_t = c_uint; + +pub const CURRENTWINDOW: Window = 0; + +type FnXdoNew = unsafe extern "C" fn(*const c_char) -> *mut xdo_t; +type FnXdoNewWithOpenedDisplay = + unsafe extern "C" fn(*mut Display, *const c_char, c_int) -> *mut xdo_t; +type FnXdoFree = unsafe extern "C" fn(*mut xdo_t); +type FnXdoSendKeysequenceWindow = + unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int; +type FnXdoSendKeysequenceWindowDown = + unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int; +type FnXdoSendKeysequenceWindowUp = + unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int; +type FnXdoEnterTextWindow = + unsafe extern "C" fn(*const xdo_t, Window, *const c_char, useconds_t) -> c_int; +type FnXdoClickWindow = unsafe extern "C" fn(*const xdo_t, Window, c_int) -> c_int; +type FnXdoMouseDown = unsafe extern "C" fn(*const xdo_t, Window, c_int) -> c_int; +type FnXdoMouseUp = unsafe extern "C" fn(*const xdo_t, Window, c_int) -> c_int; +type FnXdoMoveMouse = unsafe extern "C" fn(*const xdo_t, c_int, c_int, c_int) -> c_int; +type FnXdoMoveMouseRelative = unsafe extern "C" fn(*const xdo_t, c_int, c_int) -> c_int; +type FnXdoMoveMouseRelativeToWindow = + unsafe extern "C" fn(*const xdo_t, Window, c_int, c_int) -> c_int; +type FnXdoGetMouseLocation = + unsafe extern "C" fn(*const xdo_t, *mut c_int, *mut c_int, *mut c_int) -> c_int; +type FnXdoGetMouseLocation2 = + unsafe extern "C" fn(*const xdo_t, *mut c_int, *mut c_int, *mut c_int, *mut Window) -> c_int; +type FnXdoGetActiveWindow = unsafe extern "C" fn(*const xdo_t, *mut Window) -> c_int; +type FnXdoGetFocusedWindow = unsafe extern "C" fn(*const xdo_t, *mut Window) -> c_int; +type FnXdoGetFocusedWindowSane = unsafe extern "C" fn(*const xdo_t, *mut Window) -> c_int; +type FnXdoGetWindowLocation = + unsafe extern "C" fn(*const xdo_t, Window, *mut c_int, *mut c_int, *mut *mut Screen) -> c_int; +type FnXdoGetWindowSize = + unsafe extern "C" fn(*const xdo_t, Window, *mut c_uint, *mut c_uint) -> c_int; +type FnXdoGetInputState = unsafe extern "C" fn(*const xdo_t) -> c_uint; +type FnXdoActivateWindow = unsafe extern "C" fn(*const xdo_t, Window) -> c_int; +type FnXdoWaitForMouseMoveFrom = unsafe extern "C" fn(*const xdo_t, c_int, c_int) -> c_int; +type FnXdoWaitForMouseMoveTo = unsafe extern "C" fn(*const xdo_t, c_int, c_int) -> c_int; +type FnXdoSetWindowClass = + unsafe extern "C" fn(*const xdo_t, Window, *const c_char, *const c_char) -> c_int; +type FnXdoSearchWindows = + unsafe extern "C" fn(*const xdo_t, *const xdo_search_t, *mut *mut Window, *mut c_uint) -> c_int; + +struct XdoLib { + _lib: Library, + xdo_new: FnXdoNew, + xdo_new_with_opened_display: Option, + xdo_free: FnXdoFree, + xdo_send_keysequence_window: FnXdoSendKeysequenceWindow, + xdo_send_keysequence_window_down: Option, + xdo_send_keysequence_window_up: Option, + xdo_enter_text_window: Option, + xdo_click_window: Option, + xdo_mouse_down: Option, + xdo_mouse_up: Option, + xdo_move_mouse: Option, + xdo_move_mouse_relative: Option, + xdo_move_mouse_relative_to_window: Option, + xdo_get_mouse_location: Option, + xdo_get_mouse_location2: Option, + xdo_get_active_window: Option, + xdo_get_focused_window: Option, + xdo_get_focused_window_sane: Option, + xdo_get_window_location: Option, + xdo_get_window_size: Option, + xdo_get_input_state: Option, + xdo_activate_window: Option, + xdo_wait_for_mouse_move_from: Option, + xdo_wait_for_mouse_move_to: Option, + xdo_set_window_class: Option, + xdo_search_windows: Option, +} + +impl XdoLib { + fn load() -> Option { + // https://github.com/rustdesk/rustdesk/issues/13711 + const LIB_NAMES: [&str; 3] = ["libxdo.so.4", "libxdo.so.3", "libxdo.so"]; + + unsafe { + let (lib, lib_name) = LIB_NAMES + .iter() + .find_map(|name| Library::new(name).ok().map(|lib| (lib, *name)))?; + + log::info!("libxdo-sys Loaded {}", lib_name); + + let xdo_new: FnXdoNew = *lib.get(b"xdo_new").ok()?; + let xdo_free: FnXdoFree = *lib.get(b"xdo_free").ok()?; + let xdo_send_keysequence_window: FnXdoSendKeysequenceWindow = + *lib.get(b"xdo_send_keysequence_window").ok()?; + + let xdo_new_with_opened_display = lib + .get(b"xdo_new_with_opened_display") + .ok() + .map(|s: Symbol| *s); + let xdo_send_keysequence_window_down = lib + .get(b"xdo_send_keysequence_window_down") + .ok() + .map(|s: Symbol| *s); + let xdo_send_keysequence_window_up = lib + .get(b"xdo_send_keysequence_window_up") + .ok() + .map(|s: Symbol| *s); + let xdo_enter_text_window = lib + .get(b"xdo_enter_text_window") + .ok() + .map(|s: Symbol| *s); + let xdo_click_window = lib + .get(b"xdo_click_window") + .ok() + .map(|s: Symbol| *s); + let xdo_mouse_down = lib + .get(b"xdo_mouse_down") + .ok() + .map(|s: Symbol| *s); + let xdo_mouse_up = lib + .get(b"xdo_mouse_up") + .ok() + .map(|s: Symbol| *s); + let xdo_move_mouse = lib + .get(b"xdo_move_mouse") + .ok() + .map(|s: Symbol| *s); + let xdo_move_mouse_relative = lib + .get(b"xdo_move_mouse_relative") + .ok() + .map(|s: Symbol| *s); + let xdo_move_mouse_relative_to_window = lib + .get(b"xdo_move_mouse_relative_to_window") + .ok() + .map(|s: Symbol| *s); + let xdo_get_mouse_location = lib + .get(b"xdo_get_mouse_location") + .ok() + .map(|s: Symbol| *s); + let xdo_get_mouse_location2 = lib + .get(b"xdo_get_mouse_location2") + .ok() + .map(|s: Symbol| *s); + let xdo_get_active_window = lib + .get(b"xdo_get_active_window") + .ok() + .map(|s: Symbol| *s); + let xdo_get_focused_window = lib + .get(b"xdo_get_focused_window") + .ok() + .map(|s: Symbol| *s); + let xdo_get_focused_window_sane = lib + .get(b"xdo_get_focused_window_sane") + .ok() + .map(|s: Symbol| *s); + let xdo_get_window_location = lib + .get(b"xdo_get_window_location") + .ok() + .map(|s: Symbol| *s); + let xdo_get_window_size = lib + .get(b"xdo_get_window_size") + .ok() + .map(|s: Symbol| *s); + let xdo_get_input_state = lib + .get(b"xdo_get_input_state") + .ok() + .map(|s: Symbol| *s); + let xdo_activate_window = lib + .get(b"xdo_activate_window") + .ok() + .map(|s: Symbol| *s); + let xdo_wait_for_mouse_move_from = lib + .get(b"xdo_wait_for_mouse_move_from") + .ok() + .map(|s: Symbol| *s); + let xdo_wait_for_mouse_move_to = lib + .get(b"xdo_wait_for_mouse_move_to") + .ok() + .map(|s: Symbol| *s); + let xdo_set_window_class = lib + .get(b"xdo_set_window_class") + .ok() + .map(|s: Symbol| *s); + let xdo_search_windows = lib + .get(b"xdo_search_windows") + .ok() + .map(|s: Symbol| *s); + + Some(Self { + _lib: lib, + xdo_new, + xdo_new_with_opened_display, + xdo_free, + xdo_send_keysequence_window, + xdo_send_keysequence_window_down, + xdo_send_keysequence_window_up, + xdo_enter_text_window, + xdo_click_window, + xdo_mouse_down, + xdo_mouse_up, + xdo_move_mouse, + xdo_move_mouse_relative, + xdo_move_mouse_relative_to_window, + xdo_get_mouse_location, + xdo_get_mouse_location2, + xdo_get_active_window, + xdo_get_focused_window, + xdo_get_focused_window_sane, + xdo_get_window_location, + xdo_get_window_size, + xdo_get_input_state, + xdo_activate_window, + xdo_wait_for_mouse_move_from, + xdo_wait_for_mouse_move_to, + xdo_set_window_class, + xdo_search_windows, + }) + } + } +} + +static XDO_LIB: OnceLock> = OnceLock::new(); + +fn get_lib() -> Option<&'static XdoLib> { + XDO_LIB + .get_or_init(|| { + let lib = XdoLib::load(); + if lib.is_none() { + log::info!("libxdo-sys libxdo not found, xdo functions will be disabled"); + } + lib + }) + .as_ref() +} + +pub unsafe extern "C" fn xdo_new(display: *const c_char) -> *mut xdo_t { + get_lib().map_or(std::ptr::null_mut(), |lib| (lib.xdo_new)(display)) +} + +pub unsafe extern "C" fn xdo_new_with_opened_display( + xdpy: *mut Display, + display: *const c_char, + close_display_when_freed: c_int, +) -> *mut xdo_t { + get_lib() + .and_then(|lib| lib.xdo_new_with_opened_display) + .map_or(std::ptr::null_mut(), |f| { + f(xdpy, display, close_display_when_freed) + }) +} + +pub unsafe extern "C" fn xdo_free(xdo: *mut xdo_t) { + if xdo.is_null() { + return; + } + if let Some(lib) = get_lib() { + (lib.xdo_free)(xdo); + } +} + +pub unsafe extern "C" fn xdo_send_keysequence_window( + xdo: *const xdo_t, + window: Window, + keysequence: *const c_char, + delay: useconds_t, +) -> c_int { + get_lib().map_or(1, |lib| { + (lib.xdo_send_keysequence_window)(xdo, window, keysequence, delay) + }) +} + +pub unsafe extern "C" fn xdo_send_keysequence_window_down( + xdo: *const xdo_t, + window: Window, + keysequence: *const c_char, + delay: useconds_t, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_send_keysequence_window_down) + .map_or(1, |f| f(xdo, window, keysequence, delay)) +} + +pub unsafe extern "C" fn xdo_send_keysequence_window_up( + xdo: *const xdo_t, + window: Window, + keysequence: *const c_char, + delay: useconds_t, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_send_keysequence_window_up) + .map_or(1, |f| f(xdo, window, keysequence, delay)) +} + +pub unsafe extern "C" fn xdo_enter_text_window( + xdo: *const xdo_t, + window: Window, + string: *const c_char, + delay: useconds_t, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_enter_text_window) + .map_or(1, |f| f(xdo, window, string, delay)) +} + +pub unsafe extern "C" fn xdo_click_window( + xdo: *const xdo_t, + window: Window, + button: c_int, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_click_window) + .map_or(1, |f| f(xdo, window, button)) +} + +pub unsafe extern "C" fn xdo_mouse_down(xdo: *const xdo_t, window: Window, button: c_int) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_mouse_down) + .map_or(1, |f| f(xdo, window, button)) +} + +pub unsafe extern "C" fn xdo_mouse_up(xdo: *const xdo_t, window: Window, button: c_int) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_mouse_up) + .map_or(1, |f| f(xdo, window, button)) +} + +pub unsafe extern "C" fn xdo_move_mouse( + xdo: *const xdo_t, + x: c_int, + y: c_int, + screen: c_int, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_move_mouse) + .map_or(1, |f| f(xdo, x, y, screen)) +} + +pub unsafe extern "C" fn xdo_move_mouse_relative(xdo: *const xdo_t, x: c_int, y: c_int) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_move_mouse_relative) + .map_or(1, |f| f(xdo, x, y)) +} + +pub unsafe extern "C" fn xdo_move_mouse_relative_to_window( + xdo: *const xdo_t, + window: Window, + x: c_int, + y: c_int, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_move_mouse_relative_to_window) + .map_or(1, |f| f(xdo, window, x, y)) +} + +pub unsafe extern "C" fn xdo_get_mouse_location( + xdo: *const xdo_t, + x: *mut c_int, + y: *mut c_int, + screen_num: *mut c_int, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_mouse_location) + .map_or(1, |f| f(xdo, x, y, screen_num)) +} + +pub unsafe extern "C" fn xdo_get_mouse_location2( + xdo: *const xdo_t, + x: *mut c_int, + y: *mut c_int, + screen_num: *mut c_int, + window: *mut Window, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_mouse_location2) + .map_or(1, |f| f(xdo, x, y, screen_num, window)) +} + +pub unsafe extern "C" fn xdo_get_active_window( + xdo: *const xdo_t, + window_ret: *mut Window, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_active_window) + .map_or(1, |f| f(xdo, window_ret)) +} + +pub unsafe extern "C" fn xdo_get_focused_window( + xdo: *const xdo_t, + window_ret: *mut Window, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_focused_window) + .map_or(1, |f| f(xdo, window_ret)) +} + +pub unsafe extern "C" fn xdo_get_focused_window_sane( + xdo: *const xdo_t, + window_ret: *mut Window, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_focused_window_sane) + .map_or(1, |f| f(xdo, window_ret)) +} + +pub unsafe extern "C" fn xdo_get_window_location( + xdo: *const xdo_t, + window: Window, + x: *mut c_int, + y: *mut c_int, + screen_ret: *mut *mut Screen, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_window_location) + .map_or(1, |f| f(xdo, window, x, y, screen_ret)) +} + +pub unsafe extern "C" fn xdo_get_window_size( + xdo: *const xdo_t, + window: Window, + width: *mut c_uint, + height: *mut c_uint, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_get_window_size) + .map_or(1, |f| f(xdo, window, width, height)) +} + +pub unsafe extern "C" fn xdo_get_input_state(xdo: *const xdo_t) -> c_uint { + get_lib() + .and_then(|lib| lib.xdo_get_input_state) + .map_or(0, |f| f(xdo)) +} + +pub unsafe extern "C" fn xdo_activate_window(xdo: *const xdo_t, wid: Window) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_activate_window) + .map_or(1, |f| f(xdo, wid)) +} + +pub unsafe extern "C" fn xdo_wait_for_mouse_move_from( + xdo: *const xdo_t, + origin_x: c_int, + origin_y: c_int, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_wait_for_mouse_move_from) + .map_or(1, |f| f(xdo, origin_x, origin_y)) +} + +pub unsafe extern "C" fn xdo_wait_for_mouse_move_to( + xdo: *const xdo_t, + dest_x: c_int, + dest_y: c_int, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_wait_for_mouse_move_to) + .map_or(1, |f| f(xdo, dest_x, dest_y)) +} + +pub unsafe extern "C" fn xdo_set_window_class( + xdo: *const xdo_t, + wid: Window, + name: *const c_char, + class: *const c_char, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_set_window_class) + .map_or(1, |f| f(xdo, wid, name, class)) +} + +pub unsafe extern "C" fn xdo_search_windows( + xdo: *const xdo_t, + search: *const xdo_search_t, + windowlist_ret: *mut *mut Window, + nwindows_ret: *mut c_uint, +) -> c_int { + get_lib() + .and_then(|lib| lib.xdo_search_windows) + .map_or(1, |f| f(xdo, search, windowlist_ret, nwindows_ret)) +} diff --git a/res/rpm-flutter-suse.spec b/res/rpm-flutter-suse.spec index d11e0b69a..2049b5f4f 100644 --- a/res/rpm-flutter-suse.spec +++ b/res/rpm-flutter-suse.spec @@ -5,8 +5,8 @@ Summary: RPM package License: GPL-3.0 URL: https://rustdesk.com Vendor: rustdesk -Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire -Recommends: libayatana-appindicator3-1 +Requires: gtk3 libxcb1 libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire +Recommends: libayatana-appindicator3-1 xdotool Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit) # https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/ diff --git a/res/rpm-flutter.spec b/res/rpm-flutter.spec index 3b6ad5f5d..f8bc7a1a1 100644 --- a/res/rpm-flutter.spec +++ b/res/rpm-flutter.spec @@ -5,8 +5,8 @@ Summary: RPM package License: GPL-3.0 URL: https://rustdesk.com Vendor: rustdesk -Requires: gtk3 libxcb libxdo libXfixes alsa-lib libva pam gstreamer1-plugins-base -Recommends: libayatana-appindicator-gtk3 +Requires: gtk3 libxcb libXfixes alsa-lib libva pam gstreamer1-plugins-base +Recommends: libayatana-appindicator-gtk3 libxdo Provides: libdesktop_drop_plugin.so()(64bit), libdesktop_multi_window_plugin.so()(64bit), libfile_selector_linux_plugin.so()(64bit), libflutter_custom_cursor_plugin.so()(64bit), libflutter_linux_gtk.so()(64bit), libscreen_retriever_plugin.so()(64bit), libtray_manager_plugin.so()(64bit), liburl_launcher_linux_plugin.so()(64bit), libwindow_manager_plugin.so()(64bit), libwindow_size_plugin.so()(64bit), libtexture_rgba_renderer_plugin.so()(64bit) # https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/ diff --git a/res/rpm-suse.spec b/res/rpm-suse.spec index 79b26d6f0..14364eb77 100644 --- a/res/rpm-suse.spec +++ b/res/rpm-suse.spec @@ -3,8 +3,8 @@ Version: 1.1.9 Release: 0 Summary: RPM package License: GPL-3.0 -Requires: gtk3 libxcb1 xdotool libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire -Recommends: libayatana-appindicator3-1 +Requires: gtk3 libxcb1 libXfixes3 alsa-utils libXtst6 libva2 pam gstreamer-plugins-base gstreamer-plugin-pipewire +Recommends: libayatana-appindicator3-1 xdotool # https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/ diff --git a/res/rpm.spec b/res/rpm.spec index 67c7abe36..26c497121 100644 --- a/res/rpm.spec +++ b/res/rpm.spec @@ -5,8 +5,8 @@ Summary: RPM package License: GPL-3.0 URL: https://rustdesk.com Vendor: rustdesk -Requires: gtk3 libxcb libxdo libXfixes alsa-lib libva2 pam gstreamer1-plugins-base -Recommends: libayatana-appindicator-gtk3 +Requires: gtk3 libxcb libXfixes alsa-lib libva2 pam gstreamer1-plugins-base +Recommends: libayatana-appindicator-gtk3 libxdo # https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/ diff --git a/src/platform/linux.rs b/src/platform/linux.rs index c546673eb..382af72cf 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -6,29 +6,26 @@ use hbb_common::{ anyhow::anyhow, bail, config::{keys::OPTION_ALLOW_LINUX_HEADLESS, Config}, - libc::{c_char, c_int, c_long, c_void}, + libc::{c_char, c_int, c_long, c_uint, c_void}, log, message_proto::{DisplayInfo, Resolution}, regex::{Captures, Regex}, users::{get_user_by_name, os::unix::UserExt}, }; +use libxdo_sys::{self, xdo_t, Window}; use std::{ cell::RefCell, ffi::{OsStr, OsString}, path::{Path, PathBuf}, process::{Child, Command}, string::String, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::atomic::{AtomicBool, Ordering}, + sync::Arc, time::{Duration, Instant}, }; use terminfo::{capability as cap, Database}; use wallpaper; -type Xdo = *const c_void; - pub const PA_SAMPLE_RATE: u32 = 48000; static mut UNMODIFIED: bool = true; @@ -86,35 +83,20 @@ lazy_static::lazy_static! { } thread_local! { - static XDO: RefCell = RefCell::new(unsafe { xdo_new(std::ptr::null()) }); + // XDO context - created via libxdo-sys (which uses dynamic loading stub). + // If libxdo is not available, xdo will be null and xdo-based functions become no-ops. + static XDO: RefCell<*mut xdo_t> = RefCell::new({ + let xdo = unsafe { libxdo_sys::xdo_new(std::ptr::null()) }; + if xdo.is_null() { + log::warn!("Failed to create xdo context, xdo functions will be disabled"); + } else { + log::info!("xdo context created successfully"); + } + xdo + }); static DISPLAY: RefCell<*mut c_void> = RefCell::new(unsafe { XOpenDisplay(std::ptr::null())}); } -extern "C" { - fn xdo_get_mouse_location( - xdo: Xdo, - x: *mut c_int, - 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( - xdo: Xdo, - window: *mut c_void, - x: *mut c_int, - y: *mut c_int, - screen_num: *mut c_int, - ) -> c_int; - fn xdo_get_window_size( - xdo: Xdo, - window: *mut c_void, - width: *mut c_int, - height: *mut c_int, - ) -> c_int; -} - #[link(name = "X11")] extern "C" { fn XOpenDisplay(display_name: *const c_char) -> *mut c_void; @@ -160,14 +142,19 @@ fn sleep_millis(millis: u64) { pub fn get_cursor_pos() -> Option<(i32, i32)> { let mut res = None; XDO.with(|xdo| { - if let Ok(xdo) = xdo.try_borrow_mut() { + if let Ok(xdo) = xdo.try_borrow() { if xdo.is_null() { return; } let mut x: c_int = 0; let mut y: c_int = 0; unsafe { - xdo_get_mouse_location(*xdo, &mut x as _, &mut y as _, std::ptr::null_mut()); + libxdo_sys::xdo_get_mouse_location( + *xdo as *const _, + &mut x as _, + &mut y as _, + std::ptr::null_mut(), + ); } res = Some((x, y)); } @@ -178,14 +165,14 @@ pub fn get_cursor_pos() -> Option<(i32, i32)> { pub fn set_cursor_pos(x: i32, y: i32) -> bool { let mut res = false; XDO.with(|xdo| { - match xdo.try_borrow_mut() { + match xdo.try_borrow() { 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); + let ret = libxdo_sys::xdo_move_mouse(*xdo as *const _, x, y, 0); if ret != 0 { log::debug!( "set_cursor_pos: xdo_move_mouse failed with code {} for coordinates ({}, {})", @@ -230,22 +217,22 @@ pub fn reset_input_cache() {} pub fn get_focused_display(displays: Vec) -> Option { let mut res = None; XDO.with(|xdo| { - if let Ok(xdo) = xdo.try_borrow_mut() { + if let Ok(xdo) = xdo.try_borrow() { if xdo.is_null() { return; } let mut x: c_int = 0; let mut y: c_int = 0; - let mut width: c_int = 0; - let mut height: c_int = 0; - let mut window: *mut c_void = std::ptr::null_mut(); + let mut width: c_uint = 0; + let mut height: c_uint = 0; + let mut window: Window = 0; unsafe { - if xdo_get_active_window(*xdo, &mut window) != 0 { + if libxdo_sys::xdo_get_active_window(*xdo as *const _, &mut window) != 0 { return; } - if xdo_get_window_location( - *xdo, + if libxdo_sys::xdo_get_window_location( + *xdo as *const _, window, &mut x as _, &mut y as _, @@ -254,11 +241,17 @@ pub fn get_focused_display(displays: Vec) -> Option { { return; } - if xdo_get_window_size(*xdo, window, &mut width as _, &mut height as _) != 0 { + if libxdo_sys::xdo_get_window_size( + *xdo as *const _, + window, + &mut width, + &mut height, + ) != 0 + { return; } - let center_x = x + width / 2; - let center_y = y + height / 2; + let center_x = x + (width / 2) as c_int; + let center_y = y + (height / 2) as c_int; res = displays.iter().position(|d| { center_x >= d.x && center_x < d.x + d.width @@ -497,7 +490,10 @@ fn get_all_term_values(uid: &str) -> Vec { let Ok(cmdline) = std::fs::read(&cmdline_path) else { continue; }; - let exe_end = cmdline.iter().position(|&b| b == 0).unwrap_or(cmdline.len()); + let exe_end = cmdline + .iter() + .position(|&b| b == 0) + .unwrap_or(cmdline.len()); let exe_str = String::from_utf8_lossy(&cmdline[..exe_end]); if !re.is_match(&exe_str) { continue;