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

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;
}
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,
);
}
}