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 {