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 {