fix(install): linux xdo (#14096)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2026-01-21 20:43:15 +08:00
committed by GitHub
parent 21a7cef98a
commit be4bbd018d
12 changed files with 666 additions and 141 deletions

10
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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"

View File

@@ -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;
}
if !self.xdo.is_null() {
unsafe {
xdo_free(self.xdo);
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,
);
}
}

View File

@@ -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" }

View File

@@ -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<FnXdoNewWithOpenedDisplay>,
xdo_free: FnXdoFree,
xdo_send_keysequence_window: FnXdoSendKeysequenceWindow,
xdo_send_keysequence_window_down: Option<FnXdoSendKeysequenceWindowDown>,
xdo_send_keysequence_window_up: Option<FnXdoSendKeysequenceWindowUp>,
xdo_enter_text_window: Option<FnXdoEnterTextWindow>,
xdo_click_window: Option<FnXdoClickWindow>,
xdo_mouse_down: Option<FnXdoMouseDown>,
xdo_mouse_up: Option<FnXdoMouseUp>,
xdo_move_mouse: Option<FnXdoMoveMouse>,
xdo_move_mouse_relative: Option<FnXdoMoveMouseRelative>,
xdo_move_mouse_relative_to_window: Option<FnXdoMoveMouseRelativeToWindow>,
xdo_get_mouse_location: Option<FnXdoGetMouseLocation>,
xdo_get_mouse_location2: Option<FnXdoGetMouseLocation2>,
xdo_get_active_window: Option<FnXdoGetActiveWindow>,
xdo_get_focused_window: Option<FnXdoGetFocusedWindow>,
xdo_get_focused_window_sane: Option<FnXdoGetFocusedWindowSane>,
xdo_get_window_location: Option<FnXdoGetWindowLocation>,
xdo_get_window_size: Option<FnXdoGetWindowSize>,
xdo_get_input_state: Option<FnXdoGetInputState>,
xdo_activate_window: Option<FnXdoActivateWindow>,
xdo_wait_for_mouse_move_from: Option<FnXdoWaitForMouseMoveFrom>,
xdo_wait_for_mouse_move_to: Option<FnXdoWaitForMouseMoveTo>,
xdo_set_window_class: Option<FnXdoSetWindowClass>,
xdo_search_windows: Option<FnXdoSearchWindows>,
}
impl XdoLib {
fn load() -> Option<Self> {
// 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<FnXdoNewWithOpenedDisplay>| *s);
let xdo_send_keysequence_window_down = lib
.get(b"xdo_send_keysequence_window_down")
.ok()
.map(|s: Symbol<FnXdoSendKeysequenceWindowDown>| *s);
let xdo_send_keysequence_window_up = lib
.get(b"xdo_send_keysequence_window_up")
.ok()
.map(|s: Symbol<FnXdoSendKeysequenceWindowUp>| *s);
let xdo_enter_text_window = lib
.get(b"xdo_enter_text_window")
.ok()
.map(|s: Symbol<FnXdoEnterTextWindow>| *s);
let xdo_click_window = lib
.get(b"xdo_click_window")
.ok()
.map(|s: Symbol<FnXdoClickWindow>| *s);
let xdo_mouse_down = lib
.get(b"xdo_mouse_down")
.ok()
.map(|s: Symbol<FnXdoMouseDown>| *s);
let xdo_mouse_up = lib
.get(b"xdo_mouse_up")
.ok()
.map(|s: Symbol<FnXdoMouseUp>| *s);
let xdo_move_mouse = lib
.get(b"xdo_move_mouse")
.ok()
.map(|s: Symbol<FnXdoMoveMouse>| *s);
let xdo_move_mouse_relative = lib
.get(b"xdo_move_mouse_relative")
.ok()
.map(|s: Symbol<FnXdoMoveMouseRelative>| *s);
let xdo_move_mouse_relative_to_window = lib
.get(b"xdo_move_mouse_relative_to_window")
.ok()
.map(|s: Symbol<FnXdoMoveMouseRelativeToWindow>| *s);
let xdo_get_mouse_location = lib
.get(b"xdo_get_mouse_location")
.ok()
.map(|s: Symbol<FnXdoGetMouseLocation>| *s);
let xdo_get_mouse_location2 = lib
.get(b"xdo_get_mouse_location2")
.ok()
.map(|s: Symbol<FnXdoGetMouseLocation2>| *s);
let xdo_get_active_window = lib
.get(b"xdo_get_active_window")
.ok()
.map(|s: Symbol<FnXdoGetActiveWindow>| *s);
let xdo_get_focused_window = lib
.get(b"xdo_get_focused_window")
.ok()
.map(|s: Symbol<FnXdoGetFocusedWindow>| *s);
let xdo_get_focused_window_sane = lib
.get(b"xdo_get_focused_window_sane")
.ok()
.map(|s: Symbol<FnXdoGetFocusedWindowSane>| *s);
let xdo_get_window_location = lib
.get(b"xdo_get_window_location")
.ok()
.map(|s: Symbol<FnXdoGetWindowLocation>| *s);
let xdo_get_window_size = lib
.get(b"xdo_get_window_size")
.ok()
.map(|s: Symbol<FnXdoGetWindowSize>| *s);
let xdo_get_input_state = lib
.get(b"xdo_get_input_state")
.ok()
.map(|s: Symbol<FnXdoGetInputState>| *s);
let xdo_activate_window = lib
.get(b"xdo_activate_window")
.ok()
.map(|s: Symbol<FnXdoActivateWindow>| *s);
let xdo_wait_for_mouse_move_from = lib
.get(b"xdo_wait_for_mouse_move_from")
.ok()
.map(|s: Symbol<FnXdoWaitForMouseMoveFrom>| *s);
let xdo_wait_for_mouse_move_to = lib
.get(b"xdo_wait_for_mouse_move_to")
.ok()
.map(|s: Symbol<FnXdoWaitForMouseMoveTo>| *s);
let xdo_set_window_class = lib
.get(b"xdo_set_window_class")
.ok()
.map(|s: Symbol<FnXdoSetWindowClass>| *s);
let xdo_search_windows = lib
.get(b"xdo_search_windows")
.ok()
.map(|s: Symbol<FnXdoSearchWindows>| *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<Option<XdoLib>> = 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))
}

View File

@@ -5,8 +5,8 @@ Summary: RPM package
License: GPL-3.0
URL: https://rustdesk.com
Vendor: rustdesk <info@rustdesk.com>
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/

View File

@@ -5,8 +5,8 @@ Summary: RPM package
License: GPL-3.0
URL: https://rustdesk.com
Vendor: rustdesk <info@rustdesk.com>
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/

View File

@@ -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/

View File

@@ -5,8 +5,8 @@ Summary: RPM package
License: GPL-3.0
URL: https://rustdesk.com
Vendor: rustdesk <info@rustdesk.com>
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/

View File

@@ -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<Xdo> = 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<DisplayInfo>) -> Option<usize> {
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<DisplayInfo>) -> Option<usize> {
{
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<String> {
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;