Retry SendInput after switching desktops upon initial failure

When Windows UAC is active, `SendInput` fails because "the UAC desktop
is in focus" (blocking events on the original desktop).

As a initial work around for this use case, the desktop is switched and
the `SendInput` is retried.

To be able to do this, the process needs to run with sufficient
privileges to access the UAC desktop.

There are several ways to do this.
One example is the `psexec` tool (you may need to use absolute paths -
even in the config):

```
psexec -sid client ...
```

I am not sure whether this simple "retry once" strategy is sufficient
or a more sophisticated approach is required.
This commit is contained in:
Florian Hassanen 2021-02-25 00:16:09 +01:00
parent bf133665eb
commit a20198875a
2 changed files with 36 additions and 3 deletions

View file

@ -43,6 +43,7 @@ Regardless, if you want a working and stable solution for crossplatform keyboard
## Limitations
- Only keyboard and relative mouse events work (that is, can be forwarded to clients)
- Clients only are supported on Windows, however, server support will be added in the future
- When Windows UAC is active the client needs elevated privileges to function properly. You may need to run the client in the System account (e.g. `psexec -sid client ...`)
## Project structure
- `server` - server application code

View file

@ -4,7 +4,7 @@ use std::time::{Duration, Instant};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot::{self, Receiver};
use tokio::time;
use winapi::um::winuser::{self, INPUT};
use winapi::um::winuser::{self, DESKTOP_JOURNALPLAYBACK, INPUT};
pub struct EventWriter {
event_sender: UnboundedSender<Event>,
@ -98,8 +98,40 @@ fn write_raw(events: &mut [INPUT]) -> Result<(), Error> {
)
};
if written != 1 {
return Err(Error::last_os_error());
if written == 0 {
let original = unsafe {
winuser::GetThreadDesktop(winapi::um::processthreadsapi::GetCurrentThreadId())
};
if original.is_null() {
return Err(Error::last_os_error());
}
let other =
unsafe { winuser::OpenInputDesktop(0 as _, 0 as _, DESKTOP_JOURNALPLAYBACK as _) };
if other.is_null() {
return Err(Error::last_os_error());
}
if unsafe { winuser::SetThreadDesktop(other) } == 0 {
return Err(Error::last_os_error());
}
let written = unsafe {
winuser::SendInput(
events.len() as _,
events.as_mut_ptr(),
std::mem::size_of_val(&events[0]) as _,
)
};
if written == 0 {
return Err(Error::last_os_error());
}
if unsafe { winuser::SetThreadDesktop(original) } == 0 {
return Err(Error::last_os_error());
}
if unsafe { winuser::CloseDesktop(other) } == 0 {
return Err(Error::last_os_error());
}
}
Ok(())