From b3a7c6b4540c859cbd32f8819c0ec1d2ca54dbbe Mon Sep 17 00:00:00 2001 From: Minh Date: Fri, 9 Oct 2026 07:18:54 -0300 Subject: [PATCH] fix(windows): stop retrying SendInput forever when it is refused `send_input_safe` called `SendInput` in a loop until it reported success. SendInput returns zero when the event is not inserted, because UIPI blocks it (for example while a window with a higher integrity level has focus) or because another thread blocked input. For as long as that lasts, the loop never ended. The daemon runs on a single-threaded runtime, so it stopped handling input and IPC, and the cleanup that releases held keys could not run either. The key repeat task spun the same way. Make a single attempt and return an error when it fails. The error ends the emulation session like any other emulation error. Releasing held keys is attempted on the way out, but goes through the same call and is refused too while the cause persists, so held keys can stay pressed. Stop the key repeat task when its injection is refused, and note the limitation in the README. --- README.md | 4 ++ input-emulation/src/windows.rs | 80 ++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e9ca39acd..3fb5cdfa1 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ Most current desktop environments and operating systems are fully supported, thi > > - **Windows**: The mouse cursor will be invisible when sending input to a Windows system if > there is no real mouse connected to the machine. +> +> - **Windows**: Input cannot be sent to windows that run with higher privileges than lan-mouse, +> e.g. applications started as administrator. While such a window has focus, input emulation +> stops and has to be enabled again. Keys that were held at that moment can stay pressed. For more detailed information about os support see [Detailed OS Support](#detailed-os-support) diff --git a/input-emulation/src/windows.rs b/input-emulation/src/windows.rs index 6610ec257..44101ce83 100644 --- a/input-emulation/src/windows.rs +++ b/input-emulation/src/windows.rs @@ -5,6 +5,7 @@ use input_event::{ }; use async_trait::async_trait; +use std::io; use std::ops::BitOrAssign; use std::time::Duration; use tokio::task::AbortHandle; @@ -40,19 +41,19 @@ impl Emulation for WindowsEmulation { match event { Event::Pointer(pointer_event) => match pointer_event { PointerEvent::Motion { time: _, dx, dy } => { - rel_mouse(dx as i32, dy as i32); + rel_mouse(dx as i32, dy as i32)?; } PointerEvent::Button { time: _, button, state, - } => mouse_button(button, state), + } => mouse_button(button, state)?, PointerEvent::Axis { time: _, axis, value, - } => scroll(axis, value as i32), - PointerEvent::AxisDiscrete120 { axis, value } => scroll(axis, value), + } => scroll(axis, value as i32)?, + PointerEvent::AxisDiscrete120 { axis, value } => scroll(axis, value)?, }, Event::Keyboard(keyboard_event) => match keyboard_event { KeyboardEvent::Key { @@ -66,7 +67,7 @@ impl Emulation for WindowsEmulation { 1 => self.spawn_repeat_task(key).await, _ => {} } - key_event(key, state) + key_event(key, state)?; } KeyboardEvent::Modifiers { .. } => {} }, @@ -90,7 +91,12 @@ impl WindowsEmulation { let repeat_task = tokio::task::spawn_local(async move { tokio::time::sleep(DEFAULT_REPEAT_DELAY).await; loop { - key_event(key, 1); + if let Err(e) = key_event(key, 1) { + // This task cannot hand the error to the emulation task, so log it + // and stop repeating. The key-up for this key is still sent as usual. + log::warn!("stopping key repeat for key {key}: {e}"); + break; + } tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await; } }); @@ -103,31 +109,39 @@ impl WindowsEmulation { } } -fn send_input_safe(input: INPUT) { - unsafe { - loop { - /* retval = number of successfully submitted events */ - if SendInput(&[input], std::mem::size_of::() as i32) > 0 { - break; - } - } +/// Submits one input event, returning an error if the operating system refuses it. +/// +/// `SendInput` returns zero when the event was not inserted into the input stream, +/// either because UIPI blocked it (for example while a window with a higher integrity +/// level has focus) or because another thread blocked input. Windows does not say +/// which. Looping until the call succeeds would block the single-threaded runtime for +/// as long as the refusal lasts, so the failure is reported to the caller instead. +fn send_input(input: INPUT) -> Result<(), EmulationError> { + // SAFETY: `input` is a fully initialized `INPUT` and `cbSize` is the size of one + // `INPUT`, as required by `SendInput`. + if unsafe { SendInput(&[input], std::mem::size_of::() as i32) } == 0 { + return Err(io::Error::other( + "SendInput refused the event (blocked by UIPI or another thread)", + ) + .into()); } + Ok(()) } -fn send_mouse_input(mi: MOUSEINPUT) { - send_input_safe(INPUT { +fn send_mouse_input(mi: MOUSEINPUT) -> Result<(), EmulationError> { + send_input(INPUT { r#type: INPUT_MOUSE, Anonymous: INPUT_0 { mi }, - }); + }) } -fn send_keyboard_input(ki: KEYBDINPUT) { - send_input_safe(INPUT { +fn send_keyboard_input(ki: KEYBDINPUT) -> Result<(), EmulationError> { + send_input(INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki }, - }); + }) } -fn rel_mouse(dx: i32, dy: i32) { +fn rel_mouse(dx: i32, dy: i32) -> Result<(), EmulationError> { let mi = MOUSEINPUT { dx, dy, @@ -136,10 +150,10 @@ fn rel_mouse(dx: i32, dy: i32) { time: 0, dwExtraInfo: 0, }; - send_mouse_input(mi); + send_mouse_input(mi) } -fn mouse_button(button: u32, state: u32) { +fn mouse_button(button: u32, state: u32) -> Result<(), EmulationError> { let dw_flags = match state { 0 => match button { BTN_LEFT => MOUSEEVENTF_LEFTUP, @@ -147,7 +161,7 @@ fn mouse_button(button: u32, state: u32) { BTN_MIDDLE => MOUSEEVENTF_MIDDLEUP, BTN_BACK => MOUSEEVENTF_XUP, BTN_FORWARD => MOUSEEVENTF_XUP, - _ => return, + _ => return Ok(()), }, 1 => match button { BTN_LEFT => MOUSEEVENTF_LEFTDOWN, @@ -155,9 +169,9 @@ fn mouse_button(button: u32, state: u32) { BTN_MIDDLE => MOUSEEVENTF_MIDDLEDOWN, BTN_BACK => MOUSEEVENTF_XDOWN, BTN_FORWARD => MOUSEEVENTF_XDOWN, - _ => return, + _ => return Ok(()), }, - _ => return, + _ => return Ok(()), }; let mouse_data = match button { BTN_BACK => XBUTTON1 as u32, @@ -172,14 +186,14 @@ fn mouse_button(button: u32, state: u32) { time: 0, dwExtraInfo: 0, }; - send_mouse_input(mi); + send_mouse_input(mi) } -fn scroll(axis: u8, value: i32) { +fn scroll(axis: u8, value: i32) -> Result<(), EmulationError> { let event_type = match axis { 0 => MOUSEEVENTF_WHEEL, 1 => MOUSEEVENTF_HWHEEL, - _ => return, + _ => return Ok(()), }; let mi = MOUSEINPUT { dx: 0, @@ -189,13 +203,13 @@ fn scroll(axis: u8, value: i32) { time: 0, dwExtraInfo: 0, }; - send_mouse_input(mi); + send_mouse_input(mi) } -fn key_event(key: u32, state: u8) { +fn key_event(key: u32, state: u8) -> Result<(), EmulationError> { let scancode = match linux_keycode_to_windows_scancode(key) { Some(code) => code, - None => return, + None => return Ok(()), }; let extended = scancode > 0xff; let scancode = scancode & 0xff; @@ -213,7 +227,7 @@ fn key_event(key: u32, state: u8) { time: 0, dwExtraInfo: 0, }; - send_keyboard_input(ki); + send_keyboard_input(ki) } fn linux_keycode_to_windows_scancode(linux_keycode: u32) -> Option {