[fix] prevent remote input from blocking transport callbacks

This commit is contained in:
dijunkun
2026-10-22 16:10:58 +08:00
parent c25e7266e5
commit f8f4aa3cea
15 changed files with 829 additions and 160 deletions
@@ -64,6 +64,7 @@ int GuiApplication::RunUnattended(void* stop_event) {
}
// Quiesce network callbacks before destroying the controllers they use.
remote_input_.Stop();
CloseAllRemoteSessions();
WaitForSessionCleanup();
keyboard_.ReleaseAllRemotePressedKeys("unattended_host_exit");
@@ -16,6 +16,7 @@ namespace {
struct InputResult {
UINT sent;
DWORD error;
bool requires_worker = false;
};
InputResult InjectOnUserDesktop(INPUT* inputs, UINT count) {
@@ -25,7 +26,10 @@ InputResult InjectOnUserDesktop(INPUT* inputs, UINT count) {
ScopedInteractiveDesktop desktop;
if (!desktop.Bind(L"")) {
const DWORD error = GetLastError();
return {0, error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE};
// A library may have created a window/hook on an opted-in thread. This
// failure occurs before SendInput, so retrying on the worker is safe.
return {0, error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE,
error == ERROR_BUSY};
}
if (!desktop.IsReceivingInput()) {
return {0, ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION};
@@ -99,8 +103,13 @@ UINT SendInputOnUserDesktop(INPUT* inputs, UINT count) {
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
static UserDesktopInputWorker worker;
const InputResult result = worker.Send(inputs, count);
InputResult result{0, ERROR_SUCCESS, true};
if (ScopedUserDesktopInputThread::IsCurrent())
result = InjectOnUserDesktop(inputs, count);
if (result.requires_worker) {
static UserDesktopInputWorker worker;
result = worker.Send(inputs, count);
}
const DWORD error = result.error;
if (error != ERROR_SUCCESS) {
// Include session identity even when Windows reports no useful SendInput
@@ -11,9 +11,23 @@
namespace crossdesk {
// Executes on a worker without windows or hooks, attached to this process's
// current user input desktop. Returns a SendInput count and preserves the
// worker's failure code in the calling thread's last-error value.
// Opt in only on the remote input worker. UI/hook callers keep using the
// fallback worker. Desktop attachment is still checked for each injection.
class ScopedUserDesktopInputThread {
public:
ScopedUserDesktopInputThread() { ++depth_; }
~ScopedUserDesktopInputThread() { --depth_; }
ScopedUserDesktopInputThread(const ScopedUserDesktopInputThread&) = delete;
ScopedUserDesktopInputThread& operator=(const ScopedUserDesktopInputThread&) =
delete;
static bool IsCurrent() { return depth_ != 0; }
private:
inline static thread_local unsigned depth_ = 0;
};
// Executes directly on an opted-in worker, or on the fallback input worker,
// attached to this process's current user input desktop. Returns a SendInput
// count and preserves the failure code in the calling thread's last-error value.
UINT SendInputOnUserDesktop(const INPUT& input);
// Synchronously injects one ordered batch. The caller owns the buffer until