From 69d06d2c7d37452e5d9083cedd15ca43861150bd Mon Sep 17 00:00:00 2001 From: Olivia Lee Date: Thu, 29 Jan 2026 16:13:18 -0800 Subject: [PATCH 1/5] change Mouse::new to return a Result Some of the platform backends can fail to initialize, which currently results in either a panic (windows) or undefined behavior (linux). This commit adds the interface to propagate initialization failures to the caller, but does not hook it up for either backend yet. --- src/mouse.rs | 4 ++-- src/sys/linux.rs | 6 +++--- src/sys/macos.rs | 2 +- src/sys/windows.rs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/mouse.rs b/src/mouse.rs index 7c0c95f..4ce28fe 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -9,8 +9,8 @@ pub struct Mouse(sys::Mouse); #[allow(unreachable_code, unused_variables)] impl Mouse { /// This method creates a new mouse instance, must always be run before anything else - pub fn new() -> Mouse { - Mouse(sys::Mouse::new()) + pub fn new() -> Result> { + Ok(Mouse(sys::Mouse::new()?)) } /// This method moves the mouse around diff --git a/src/sys/linux.rs b/src/sys/linux.rs index f2e4f4d..e2dd558 100644 --- a/src/sys/linux.rs +++ b/src/sys/linux.rs @@ -47,11 +47,11 @@ extern "C" { } impl Mouse { - pub fn new() -> Self { - Mouse { + pub fn new() -> Result> { + Ok(Mouse { xdo: unsafe { xdo_new(ptr::null()) }, current_window: 0 - } + }) } pub fn move_to(&self, x: i32, y: i32) -> Result<(), Box> { diff --git a/src/sys/macos.rs b/src/sys/macos.rs index 2b78461..df41c36 100644 --- a/src/sys/macos.rs +++ b/src/sys/macos.rs @@ -54,7 +54,7 @@ impl Mouse { ) } - pub fn new() -> Mouse { + pub fn new() -> Result> { Mouse } diff --git a/src/sys/windows.rs b/src/sys/windows.rs index 7bfe761..a47f077 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -84,10 +84,10 @@ impl Mouse { } } - pub fn new() -> Mouse { - Mouse { + pub fn new() -> Result> { + Ok(Mouse { user32: libloading::Library::new("user32").unwrap(), - } + }) } pub fn move_to(&self, x: i32, y: i32) -> Result<(), Box> { From f63b105089fd88cb9fedfe28e76cb23b4c2f30f4 Mon Sep 17 00:00:00 2001 From: Olivia Lee Date: Thu, 29 Jan 2026 16:25:58 -0800 Subject: [PATCH 2/5] run rustfmt The only unformatted file was src/sys/linux.rs. I'm assuming that the codebase was previously autoformatted and that this file slipped through? --- src/sys/linux.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/sys/linux.rs b/src/sys/linux.rs index e2dd558..6c65bbb 100644 --- a/src/sys/linux.rs +++ b/src/sys/linux.rs @@ -4,8 +4,8 @@ use std::error::Error; use libc; -use libc::{c_int, c_void, c_char}; -use std::{ptr}; +use libc::{c_char, c_int, c_void}; +use std::ptr; type XDO = *const c_void; type WINDOW = c_int; @@ -16,7 +16,7 @@ fn xdo_translate_key(key: &Keys) -> c_int { Keys::LEFT => 1, Keys::WHEEL | Keys::MIDDLE => 2, Keys::RIGHT => 3, - _ => panic!("Invalid key passed: {:?}", key) + _ => panic!("Invalid key passed: {:?}", key), } } @@ -31,7 +31,7 @@ impl From<(c_int, c_int)> for Point { pub struct Mouse { xdo: XDO, - current_window: c_int + current_window: c_int, } #[link(name = "xdo")] @@ -50,7 +50,7 @@ impl Mouse { pub fn new() -> Result> { Ok(Mouse { xdo: unsafe { xdo_new(ptr::null()) }, - current_window: 0 + current_window: 0, }) } @@ -62,16 +62,12 @@ impl Mouse { } pub fn press<'a>(&self, key: &'a Keys) -> Result<(), Box> { - unsafe { - xdo_mouse_down(self.xdo, self.current_window, xdo_translate_key(key)) - } + unsafe { xdo_mouse_down(self.xdo, self.current_window, xdo_translate_key(key)) } Ok(()) } pub fn release<'a>(&self, key: &'a Keys) -> Result<(), Box> { - unsafe { - xdo_mouse_up(self.xdo, self.current_window, xdo_translate_key(key)) - } + unsafe { xdo_mouse_up(self.xdo, self.current_window, xdo_translate_key(key)) } Ok(()) } @@ -81,7 +77,12 @@ impl Mouse { let mut x: c_int = 0; let mut y: c_int = 0; let mut _screen_num: c_int = 0; - xdo_get_mouse_location(self.xdo, &mut x as INTPTR, &mut y as INTPTR, &mut _screen_num as INTPTR); + xdo_get_mouse_location( + self.xdo, + &mut x as INTPTR, + &mut y as INTPTR, + &mut _screen_num as INTPTR, + ); pos = (x, y).into(); } @@ -106,8 +107,8 @@ impl Mouse { impl Drop for Mouse { fn drop(&mut self) { - unsafe { - xdo_free(self.xdo); + unsafe { + xdo_free(self.xdo); } } } From 59bc38a9b62969984fa44e05061954e509b3c0d3 Mon Sep 17 00:00:00 2001 From: Olivia Lee Date: Thu, 29 Jan 2026 16:16:08 -0800 Subject: [PATCH 3/5] propagate initialization errors on windows instead of panicking --- src/sys/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/windows.rs b/src/sys/windows.rs index a47f077..7e17894 100644 --- a/src/sys/windows.rs +++ b/src/sys/windows.rs @@ -86,7 +86,7 @@ impl Mouse { pub fn new() -> Result> { Ok(Mouse { - user32: libloading::Library::new("user32").unwrap(), + user32: libloading::Library::new("user32")?, }) } From c75959d87c3696bcc1b716271013640de22b79f6 Mon Sep 17 00:00:00 2001 From: Olivia Lee Date: Thu, 29 Jan 2026 16:24:21 -0800 Subject: [PATCH 4/5] include return types in xdo FFI declarations All of these functions return an error status, but the return type was previously only present on xdo_move_mouse. --- src/sys/linux.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sys/linux.rs b/src/sys/linux.rs index 6c65bbb..6936a17 100644 --- a/src/sys/linux.rs +++ b/src/sys/linux.rs @@ -40,10 +40,10 @@ extern "C" { fn xdo_free(xdo: XDO); fn xdo_move_mouse(xdo: XDO, x: c_int, y: c_int, screen: c_int) -> c_int; - fn xdo_mouse_down(xdo: XDO, window: WINDOW, button: c_int); - fn xdo_mouse_up(xdo: XDO, window: WINDOW, button: c_int); - fn xdo_click_window(xdo: XDO, window: WINDOW, button: c_int); - fn xdo_get_mouse_location(xdo: XDO, x: INTPTR, y: INTPTR, screen_num: INTPTR); + 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_click_window(xdo: XDO, window: WINDOW, button: c_int) -> c_int; + fn xdo_get_mouse_location(xdo: XDO, x: INTPTR, y: INTPTR, screen_num: INTPTR) -> c_int; } impl Mouse { From fc4cd7e9dc17436cfc2096f83e361ee7ba4d367d Mon Sep 17 00:00:00 2001 From: Olivia Lee Date: Thu, 29 Jan 2026 16:24:21 -0800 Subject: [PATCH 5/5] implement error handling for the linux backend Previously all errors were being ignored, causing undefined behavior on failed initialization. --- src/sys/linux.rs | 69 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/sys/linux.rs b/src/sys/linux.rs index 6936a17..9d661da 100644 --- a/src/sys/linux.rs +++ b/src/sys/linux.rs @@ -1,6 +1,7 @@ use crate::types::keys::Keys; use crate::types::Point; -use std::error::Error; +use std::error; +use std::fmt; use libc; @@ -11,6 +12,8 @@ type XDO = *const c_void; type WINDOW = c_int; type INTPTR = *mut c_int; +const XDO_SUCCESS: c_int = 0; + fn xdo_translate_key(key: &Keys) -> c_int { match key { Keys::LEFT => 1, @@ -29,6 +32,28 @@ impl From<(c_int, c_int)> for Point { } } +/// xdo logs errors to stderr (sometimes!) and does not communicate them back to +/// the caller in a programmatic way, so the best we can do is a generic +/// " failed" :/ +#[derive(Debug)] +pub struct Error; + +impl error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "xdo operation failed") + } +} + +fn check_xdo(result: c_int) -> Result<(), Box> { + if result == XDO_SUCCESS { + Ok(()) + } else { + Err(Box::new(Error)) + } +} + pub struct Mouse { xdo: XDO, current_window: c_int, @@ -47,49 +72,54 @@ extern "C" { } impl Mouse { - pub fn new() -> Result> { + pub fn new() -> Result> { + let xdo = unsafe { xdo_new(ptr::null()) }; + if xdo.is_null() { + return Err(Box::new(Error)); + } + Ok(Mouse { - xdo: unsafe { xdo_new(ptr::null()) }, + xdo, current_window: 0, }) } - pub fn move_to(&self, x: i32, y: i32) -> Result<(), Box> { - unsafe { - xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0); - } - Ok(()) + pub fn move_to(&self, x: i32, y: i32) -> Result<(), Box> { + let result = unsafe { xdo_move_mouse(self.xdo, x as c_int, y as c_int, 0) }; + check_xdo(result) } - pub fn press<'a>(&self, key: &'a Keys) -> Result<(), Box> { - unsafe { xdo_mouse_down(self.xdo, self.current_window, xdo_translate_key(key)) } - Ok(()) + pub fn press<'a>(&self, key: &'a Keys) -> Result<(), Box> { + let result = + unsafe { xdo_mouse_down(self.xdo, self.current_window, xdo_translate_key(key)) }; + check_xdo(result) } - pub fn release<'a>(&self, key: &'a Keys) -> Result<(), Box> { - unsafe { xdo_mouse_up(self.xdo, self.current_window, xdo_translate_key(key)) } - Ok(()) + pub fn release<'a>(&self, key: &'a Keys) -> Result<(), Box> { + let result = unsafe { xdo_mouse_up(self.xdo, self.current_window, xdo_translate_key(key)) }; + check_xdo(result) } - pub fn get_position(&self) -> Result> { + pub fn get_position(&self) -> Result> { let pos: Point; unsafe { let mut x: c_int = 0; let mut y: c_int = 0; let mut _screen_num: c_int = 0; - xdo_get_mouse_location( + let result = xdo_get_mouse_location( self.xdo, &mut x as INTPTR, &mut y as INTPTR, &mut _screen_num as INTPTR, ); + check_xdo(result)?; pos = (x, y).into(); } Ok(pos) } - pub fn wheel(&self, mut delta: i32) -> Result<(), Box> { + pub fn wheel(&self, mut delta: i32) -> Result<(), Box> { let key = if delta < 0 { 4 } else { 5 }; if delta < 0 { @@ -97,9 +127,8 @@ impl Mouse { } for _ in 0..delta { - unsafe { - xdo_click_window(self.xdo, self.current_window, key); - } + let result = unsafe { xdo_click_window(self.xdo, self.current_window, key) }; + check_xdo(result)?; } Ok(()) }