Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion DOC.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ the standardized event format.

### Dispatcher
The dispatcher component takes events from the event receiver and passes them
to the correct backend corresponding to the type of client.
to the correct backend corresponding to the type of client. The Windows backend
reports an event the operating system refuses to inject as an error after a few
attempts, instead of retrying it forever.


## Requests
Expand Down
81 changes: 49 additions & 32 deletions input-emulation/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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 { .. } => {}
},
Expand All @@ -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) {
// Retrying a refused injection in a tight loop would freeze this task
// forever, so stop repeating and let the error reach the caller.
log::warn!("stopping key repeat for key {key}: {e}");
break;
}
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
}
});
Expand All @@ -103,31 +109,42 @@ impl WindowsEmulation {
}
}

fn send_input_safe(input: INPUT) {
unsafe {
loop {
/* retval = number of successfully submitted events */
if SendInput(&[input], std::mem::size_of::<INPUT>() as i32) > 0 {
break;
}
/// Number of attempts made for a single `SendInput` call before reporting a failure.
///
/// `SendInput` returns zero when the injection is refused, for example while a
/// higher-integrity window has focus. Retrying forever would block this task
/// completely, so give up after a few attempts and return an error that the caller
/// can act on.
const MAX_SEND_INPUT_ATTEMPTS: usize = 3;

/// Submits one input event, returning an error if the operating system refuses it.
fn send_input(input: INPUT) -> Result<(), EmulationError> {
for _ in 0..MAX_SEND_INPUT_ATTEMPTS {
// SAFETY: `input` is a fully initialized `INPUT` and the slice length matches
// the `cbSize` argument, as required by `SendInput`.
if unsafe { SendInput(&[input], std::mem::size_of::<INPUT>() as i32) } > 0 {
return Ok(());
}
}
Err(EmulationError::Io(io::Error::other(
"SendInput refused the event",
)))
}

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,
Expand All @@ -136,28 +153,28 @@ 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,
BTN_RIGHT => MOUSEEVENTF_RIGHTUP,
BTN_MIDDLE => MOUSEEVENTF_MIDDLEUP,
BTN_BACK => MOUSEEVENTF_XUP,
BTN_FORWARD => MOUSEEVENTF_XUP,
_ => return,
_ => return Ok(()),
},
1 => match button {
BTN_LEFT => MOUSEEVENTF_LEFTDOWN,
BTN_RIGHT => MOUSEEVENTF_RIGHTDOWN,
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,
Expand All @@ -172,14 +189,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,
Expand All @@ -189,13 +206,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;
Expand All @@ -213,7 +230,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<u16> {
Expand Down
Loading