mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-18 09:53:05 +08:00
[fix] correct Windows remote control session routing and key mapping
This commit is contained in:
@@ -52,6 +52,9 @@ void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t* scan_code_out,
|
||||
if (!scan_code_out || !extended_out) {
|
||||
return;
|
||||
}
|
||||
if (LookupWindowsKeyMetadataFromVk(key_code, scan_code_out, extended_out)) {
|
||||
return;
|
||||
}
|
||||
#if _WIN32
|
||||
const UINT scan_code =
|
||||
MapVirtualKeyW(static_cast<UINT>(key_code), MAPVK_VK_TO_VSC_EX);
|
||||
@@ -61,7 +64,6 @@ void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t* scan_code_out,
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
LookupWindowsKeyMetadataFromVk(key_code, scan_code_out, extended_out);
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "rd_log.h"
|
||||
|
||||
#if _WIN32
|
||||
#include "interactive_desktop.h"
|
||||
#include "interactive_state.h"
|
||||
#include "service_host.h"
|
||||
#endif
|
||||
@@ -19,6 +20,7 @@ namespace {
|
||||
#if _WIN32
|
||||
struct WindowsServiceInteractiveStatus {
|
||||
bool available = false;
|
||||
bool session_mismatch = false;
|
||||
bool sas_secure_desktop_grace_active = false;
|
||||
unsigned int error_code = 0;
|
||||
std::string interactive_stage;
|
||||
@@ -65,6 +67,14 @@ bool QueryWindowsServiceInteractiveStatus(
|
||||
if (!status->available) {
|
||||
status->error = json.value("error", std::string("service_unavailable"));
|
||||
status->error_code = json.value("code", 0u);
|
||||
status->session_mismatch = status->error == "service_session_mismatch";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsCurrentProcessSession(json.value("active_session_id", 0xFFFFFFFFu))) {
|
||||
status->available = false;
|
||||
status->session_mismatch = true;
|
||||
status->error = "service_session_mismatch";
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -145,6 +155,9 @@ void GuiRuntime::HandleWindowsServiceIntegration() {
|
||||
|
||||
WindowsServiceInteractiveStatus status;
|
||||
const bool status_ok = QueryWindowsServiceInteractiveStatus(&status);
|
||||
const bool user_desktop_recovered =
|
||||
!status.available && IsCurrentSessionUserDesktopActive();
|
||||
if (user_desktop_recovered) status.interactive_stage = "user-desktop";
|
||||
WindowsServiceInteractiveStatus broadcast_status = status;
|
||||
const bool previous_secure_desktop_interaction =
|
||||
IsSecureDesktopInteractionRequired(local_interactive_stage_);
|
||||
@@ -167,8 +180,11 @@ void GuiRuntime::HandleWindowsServiceIntegration() {
|
||||
local_interactive_stage_ = status.interactive_stage;
|
||||
optimistic_windows_secure_desktop_until_tick_ = 0;
|
||||
}
|
||||
} else if (!previous_secure_desktop_interaction) {
|
||||
local_interactive_stage_.clear();
|
||||
} else if (status.session_mismatch || user_desktop_recovered ||
|
||||
!previous_secure_desktop_interaction) {
|
||||
// A verified local desktop or a different service session invalidates the
|
||||
// cached secure stage. An unexplained IPC failure alone still does not.
|
||||
local_interactive_stage_ = status.interactive_stage;
|
||||
optimistic_windows_secure_desktop_until_tick_ = 0;
|
||||
}
|
||||
|
||||
@@ -184,9 +200,9 @@ void GuiRuntime::HandleWindowsServiceIntegration() {
|
||||
LOG_INFO(
|
||||
"Local Windows service available for secure desktop integration");
|
||||
} else if (IsTransientWindowsServiceStatusError(status.error)) {
|
||||
LOG_INFO("Local Windows service temporarily unavailable, keeping last "
|
||||
"secure desktop state: error={}, code={}",
|
||||
status.error, status.error_code);
|
||||
LOG_INFO("Local Windows service temporarily unavailable: error={}, "
|
||||
"code={}, local_stage='{}'",
|
||||
status.error, status.error_code, local_interactive_stage_);
|
||||
} else {
|
||||
LOG_WARN(
|
||||
"Local Windows service unavailable, secure desktop integration "
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include <hidusage.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
#include "windows_input_injector.h"
|
||||
#include "windows_input_marker.h"
|
||||
#include "windows_key_metadata.h"
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
@@ -264,6 +266,11 @@ void PlatformKeyboardCapturer::HandleRawInput(HRAWINPUT raw_input_handle) {
|
||||
// Apply remote keyboard commands to the local machine.
|
||||
int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
||||
uint32_t scan_code, bool extended) {
|
||||
if (scan_code == 0) {
|
||||
// Some layouts omit the E0 prefix for navigation keys in MapVirtualKey.
|
||||
// Preserve explicit metadata, including non-extended numpad navigation.
|
||||
LookupWindowsKeyMetadataFromVk(key_code, &scan_code, &extended);
|
||||
}
|
||||
INPUT input = {0};
|
||||
input.type = INPUT_KEYBOARD;
|
||||
input.ki.dwExtraInfo =
|
||||
@@ -304,7 +311,7 @@ int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
||||
input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
||||
}
|
||||
|
||||
const UINT sent = SendInput(1, &input, sizeof(INPUT));
|
||||
const UINT sent = SendInputOnUserDesktop(input);
|
||||
if (sent != 1) {
|
||||
LOG_WARN("SendInput failed for key_code={}, is_down={}, err={}", key_code,
|
||||
is_down, GetLastError());
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "../../windows_thread_dpi.h"
|
||||
#include "rd_log.h"
|
||||
#include "windows_input_injector.h"
|
||||
#include "windows_input_marker.h"
|
||||
#include "windows_thread_dpi.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -33,7 +34,7 @@ int PlatformMouseController::ReleasePressedButtons() {
|
||||
if (pressed & 1) input.mi.dwFlags |= MOUSEEVENTF_LEFTUP;
|
||||
if (pressed & 2) input.mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
|
||||
if (pressed & 4) input.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
|
||||
if (SendInput(1, &input, sizeof(INPUT)) != 1) {
|
||||
if (SendInputOnUserDesktop(input) != 1) {
|
||||
LOG_WARN("Release remote mouse buttons failed, error={}", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
@@ -114,7 +115,7 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action,
|
||||
ip.mi.dwFlags |=
|
||||
MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
||||
ip.mi.dwExtraInfo = kInjectedMouseInputMarker;
|
||||
const UINT sent = SendInput(1, &ip, sizeof(INPUT));
|
||||
const UINT sent = SendInputOnUserDesktop(ip);
|
||||
if (sent != 1) {
|
||||
LOG_WARN("SendInput failed for mouse x={}, y={}, wheel={}, flag={}, err={}",
|
||||
ip.mi.dx, ip.mi.dy, remote_action.m.s,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
#include "windows_input_injector.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "interactive_desktop.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
namespace crossdesk {
|
||||
namespace {
|
||||
|
||||
DWORD InjectOnUserDesktop(INPUT input) {
|
||||
if (!IsCurrentSessionUnlocked()) {
|
||||
return ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION;
|
||||
}
|
||||
ScopedInteractiveDesktop desktop;
|
||||
if (!desktop.Bind(L"")) {
|
||||
const DWORD error = GetLastError();
|
||||
return error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE;
|
||||
}
|
||||
if (!desktop.IsReceivingInput()) {
|
||||
return ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION;
|
||||
}
|
||||
// Lock screens and UAC still require the session-checked service path.
|
||||
if (_wcsicmp(desktop.name().c_str(), L"Default") != 0) {
|
||||
return ERROR_ACCESS_DENIED;
|
||||
}
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (SendInput(1, &input, sizeof(INPUT)) == 1) return ERROR_SUCCESS;
|
||||
const DWORD error = GetLastError();
|
||||
return error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE;
|
||||
}
|
||||
|
||||
class UserDesktopInputWorker {
|
||||
public:
|
||||
UserDesktopInputWorker() : thread_([this] { Run(); }) {}
|
||||
|
||||
~UserDesktopInputWorker() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
stopping_ = true;
|
||||
}
|
||||
wake_.notify_one();
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
DWORD Send(INPUT input) {
|
||||
std::packaged_task<DWORD()> task(
|
||||
[input] { return InjectOnUserDesktop(input); });
|
||||
auto result = task.get_future();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
pending_.push(std::move(task));
|
||||
}
|
||||
wake_.notify_one();
|
||||
return result.get();
|
||||
}
|
||||
|
||||
private:
|
||||
void Run() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
for (;;) {
|
||||
wake_.wait(lock, [this] { return stopping_ || !pending_.empty(); });
|
||||
if (pending_.empty()) return;
|
||||
auto task = std::move(pending_.front());
|
||||
pending_.pop();
|
||||
lock.unlock();
|
||||
task();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
std::condition_variable wake_;
|
||||
std::queue<std::packaged_task<DWORD()>> pending_;
|
||||
bool stopping_ = false;
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
UINT SendInputOnUserDesktop(const INPUT& input) {
|
||||
static UserDesktopInputWorker worker;
|
||||
const DWORD error = worker.Send(input);
|
||||
if (error != ERROR_SUCCESS) {
|
||||
// Include session identity even when Windows reports no useful SendInput
|
||||
// error (for example UIPI). Keep repeated mouse-move failures bounded.
|
||||
static std::mutex log_mutex;
|
||||
static ULONGLONG last_log_tick = 0;
|
||||
std::lock_guard<std::mutex> lock(log_mutex);
|
||||
const ULONGLONG now = GetTickCount64();
|
||||
if (last_log_tick == 0 || now - last_log_tick >= 2000) {
|
||||
DWORD session_id = 0xFFFFFFFF;
|
||||
ProcessIdToSessionId(GetCurrentProcessId(), &session_id);
|
||||
LOG_WARN("User desktop input unavailable: session_id={}, error={}",
|
||||
session_id, error);
|
||||
last_log_tick = now;
|
||||
}
|
||||
}
|
||||
SetLastError(error);
|
||||
return error == ERROR_SUCCESS ? 1 : 0;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2026-09-19
|
||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _WINDOWS_INPUT_INJECTOR_H_
|
||||
#define _WINDOWS_INPUT_INJECTOR_H_
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
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.
|
||||
UINT SendInputOnUserDesktop(const INPUT& input);
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
#endif
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "captured_nv12_frame.h"
|
||||
#include "captured_cursor_state.h"
|
||||
#include "dxgi_cursor_state.h"
|
||||
#include "interactive_desktop.h"
|
||||
#include "interactive_state.h"
|
||||
#include "named_pipe_deadline.h"
|
||||
#include "rd_log.h"
|
||||
@@ -269,7 +270,7 @@ bool ParseSecureDesktopFrameResponse(const std::vector<uint8_t>& response,
|
||||
return true;
|
||||
}
|
||||
|
||||
SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() {
|
||||
SecureDesktopServiceStatus ReadSecureDesktopServiceStatus() {
|
||||
SecureDesktopServiceStatus status;
|
||||
std::vector<uint8_t> response;
|
||||
DWORD error_code = 0;
|
||||
@@ -289,6 +290,14 @@ SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() {
|
||||
if (!status.service_available) {
|
||||
status.error = json.value("error", std::string("service_unavailable"));
|
||||
status.error_code = json.value("code", 0u);
|
||||
status.desktop_state_known = status.error == "service_session_mismatch";
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!IsCurrentProcessSession(json.value("active_session_id", 0xFFFFFFFFu))) {
|
||||
status.service_available = false;
|
||||
status.desktop_state_known = true;
|
||||
status.error = "service_session_mismatch";
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -326,6 +335,16 @@ SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() {
|
||||
auto status = ReadSecureDesktopServiceStatus();
|
||||
if (!status.service_available && IsCurrentSessionUserDesktopActive()) {
|
||||
status.desktop_state_known = true;
|
||||
status.capture_active = false;
|
||||
status.interactive_stage = "user-desktop";
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool QuerySecureDesktopHelperCommand(DWORD session_id,
|
||||
const std::string& command,
|
||||
std::vector<uint8_t>* response_out,
|
||||
@@ -1262,9 +1281,8 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
|
||||
} else if (IsTransientWindowsServiceStatusError(status.error)) {
|
||||
LOG_INFO(
|
||||
"Windows capturer secure desktop service temporarily "
|
||||
"unavailable; "
|
||||
"keeping last capture state: error={}, code={}",
|
||||
status.error, status.error_code);
|
||||
"unavailable: error={}, code={}, capture_active={}",
|
||||
status.error, status.error_code, status.capture_active);
|
||||
} else {
|
||||
LOG_WARN(
|
||||
"Windows capturer secure desktop service unavailable: "
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace crossdesk {
|
||||
|
||||
struct SecureDesktopServiceStatus {
|
||||
bool service_available = false;
|
||||
bool desktop_state_known = false;
|
||||
bool capture_active = false;
|
||||
bool helper_running = false;
|
||||
uint32_t helper_process_id = 0;
|
||||
@@ -31,7 +32,7 @@ struct SecureDesktopServiceStatus {
|
||||
std::string error;
|
||||
|
||||
void UpdateFrom(SecureDesktopServiceStatus latest) {
|
||||
if (latest.service_available) {
|
||||
if (latest.service_available || latest.desktop_state_known) {
|
||||
*this = std::move(latest);
|
||||
} else {
|
||||
// Failed IPC says nothing about the desktop. Retain the last confirmed
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define _INTERACTIVE_DESKTOP_H_
|
||||
|
||||
#include <Windows.h>
|
||||
#include <WtsApi32.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
@@ -33,6 +34,52 @@ inline std::wstring GetDesktopNameW(HDESK desktop) {
|
||||
return name;
|
||||
}
|
||||
|
||||
inline bool IsDesktopReceivingInput(HDESK desktop) {
|
||||
BOOL receiving_input = FALSE;
|
||||
return desktop != nullptr &&
|
||||
GetUserObjectInformationW(desktop, UOI_IO, &receiving_input,
|
||||
sizeof(receiving_input), nullptr) &&
|
||||
receiving_input != FALSE;
|
||||
}
|
||||
|
||||
inline bool IsCurrentProcessSession(DWORD session_id) {
|
||||
DWORD process_session_id = 0xFFFFFFFF;
|
||||
return session_id != 0xFFFFFFFF &&
|
||||
ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id) &&
|
||||
session_id == process_session_id;
|
||||
}
|
||||
|
||||
inline bool IsCurrentSessionUnlocked() {
|
||||
DWORD session_id = 0xFFFFFFFF;
|
||||
if (!ProcessIdToSessionId(GetCurrentProcessId(), &session_id)) return false;
|
||||
PWTSINFOEXW info = nullptr;
|
||||
DWORD bytes = 0;
|
||||
if (!WTSQuerySessionInformationW(
|
||||
WTS_CURRENT_SERVER_HANDLE, session_id, WTSSessionInfoEx,
|
||||
reinterpret_cast<LPWSTR*>(&info), &bytes)) {
|
||||
return false;
|
||||
}
|
||||
const bool unlocked =
|
||||
info != nullptr && bytes >= sizeof(WTSINFOEXW) && info->Level == 1 &&
|
||||
info->Data.WTSInfoExLevel1.SessionState == WTSActive &&
|
||||
info->Data.WTSInfoExLevel1.SessionFlags == WTS_SESSIONSTATE_UNLOCK;
|
||||
if (info != nullptr) WTSFreeMemory(info);
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
inline bool IsCurrentSessionUserDesktopActive() {
|
||||
// LockApp can be visible on Default while the session is still locked.
|
||||
if (!IsCurrentSessionUnlocked()) return false;
|
||||
HDESK desktop = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
|
||||
if (desktop == nullptr) return false;
|
||||
// OpenInputDesktop also succeeds in disconnected RDP sessions. The name
|
||||
// alone does not tell us whether this desktop can currently receive input.
|
||||
const bool active = IsDesktopReceivingInput(desktop) &&
|
||||
_wcsicmp(GetDesktopNameW(desktop).c_str(), L"Default") == 0;
|
||||
CloseDesktop(desktop);
|
||||
return active;
|
||||
}
|
||||
|
||||
// Use only on worker threads without windows or hooks. Restore the original
|
||||
// desktop before closing the handle: Windows cannot close a desktop in use.
|
||||
class ScopedInteractiveDesktop {
|
||||
@@ -88,6 +135,10 @@ class ScopedInteractiveDesktop {
|
||||
return !name_.empty() && _wcsicmp(name_.c_str(), input_name.c_str()) == 0;
|
||||
}
|
||||
|
||||
bool IsReceivingInput() const {
|
||||
return IsDesktopReceivingInput(desktop_);
|
||||
}
|
||||
|
||||
const std::wstring& name() const { return name_; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -1313,8 +1313,14 @@ void CrossDeskServiceHost::IpcServerLoop() {
|
||||
DWORD bytes_read = 0;
|
||||
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) &&
|
||||
bytes_read > 0) {
|
||||
std::string response =
|
||||
HandleIpcCommand(std::string(buffer, buffer + bytes_read));
|
||||
ULONG client_session_id = 0xFFFFFFFF;
|
||||
// Obtain identity from Windows, never from caller-supplied JSON. A GUI
|
||||
// left in an RDP session must not control a different console session.
|
||||
const std::string response =
|
||||
GetNamedPipeClientSessionId(pipe, &client_session_id)
|
||||
? HandleIpcCommand(std::string(buffer, buffer + bytes_read),
|
||||
client_session_id)
|
||||
: BuildErrorJson("client_session_query_failed", GetLastError());
|
||||
DWORD bytes_written = 0;
|
||||
WriteFile(pipe, response.data(), static_cast<DWORD>(response.size()),
|
||||
&bytes_written, nullptr);
|
||||
@@ -1329,7 +1335,13 @@ void CrossDeskServiceHost::IpcServerLoop() {
|
||||
|
||||
void CrossDeskServiceHost::RefreshSessionState() {
|
||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||
const DWORD previous_session_id = active_session_id_;
|
||||
active_session_id_ = WTSGetActiveConsoleSessionId();
|
||||
if (active_session_id_ != previous_session_id) {
|
||||
ResetSessionHelperReportedStateLocked("session_changed", 0);
|
||||
sas_secure_desktop_until_tick_ = 0;
|
||||
sas_secure_desktop_seen_ = false;
|
||||
}
|
||||
DWORD process_session_id = 0xFFFFFFFF;
|
||||
if (ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id)) {
|
||||
process_session_id_ = process_session_id;
|
||||
@@ -1931,6 +1943,14 @@ void CrossDeskServiceHost::RefreshSessionHelperReportedState() {
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||
if (target_session_id != active_session_id_ ||
|
||||
target_session_id != session_helper_session_id_ ||
|
||||
json.value("session_id", static_cast<DWORD>(0xFFFFFFFF)) !=
|
||||
target_session_id ||
|
||||
json.value("process_id", 0u) != session_helper_process_id_) {
|
||||
ResetSessionHelperReportedStateLocked("helper_session_mismatch", 0);
|
||||
return;
|
||||
}
|
||||
session_helper_status_ok_ = true;
|
||||
session_helper_status_error_.clear();
|
||||
session_helper_status_error_code_ = 0;
|
||||
@@ -1969,7 +1989,13 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type,
|
||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||
last_session_event_type_ = event_type;
|
||||
last_session_event_session_id_ = session_id;
|
||||
const DWORD previous_session_id = active_session_id_;
|
||||
active_session_id_ = WTSGetActiveConsoleSessionId();
|
||||
if (active_session_id_ != previous_session_id) {
|
||||
ResetSessionHelperReportedStateLocked("session_changed", 0);
|
||||
sas_secure_desktop_until_tick_ = 0;
|
||||
sas_secure_desktop_seen_ = false;
|
||||
}
|
||||
DWORD process_session_id = 0xFFFFFFFF;
|
||||
if (ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id)) {
|
||||
process_session_id_ = process_session_id;
|
||||
@@ -1988,10 +2014,11 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type,
|
||||
prelogin_ = !username_available || username.empty();
|
||||
|
||||
if (!QuerySessionLockState(active_session_id_, &session_locked_)) {
|
||||
if (event_type == WTS_SESSION_LOCK) {
|
||||
if (session_id == active_session_id_ && event_type == WTS_SESSION_LOCK) {
|
||||
session_locked_ = true;
|
||||
} else if (event_type == WTS_SESSION_UNLOCK ||
|
||||
event_type == WTS_SESSION_LOGON) {
|
||||
} else if (session_id == active_session_id_ &&
|
||||
(event_type == WTS_SESSION_UNLOCK ||
|
||||
event_type == WTS_SESSION_LOGON)) {
|
||||
session_locked_ = false;
|
||||
} else if (logon_ui_visible_ || secure_desktop_active_) {
|
||||
session_locked_ = !prelogin_;
|
||||
@@ -2007,16 +2034,17 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type,
|
||||
}
|
||||
}
|
||||
|
||||
std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command) {
|
||||
std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command,
|
||||
DWORD client_session_id) {
|
||||
std::string normalized = ToLower(Trim(command));
|
||||
if (normalized == "ping") {
|
||||
return "{\"ok\":true,\"reply\":\"pong\"}";
|
||||
}
|
||||
if (normalized == "status") {
|
||||
return BuildStatusResponse();
|
||||
return BuildStatusResponse(client_session_id);
|
||||
}
|
||||
if (normalized == "sas") {
|
||||
return SendSecureAttentionSequence();
|
||||
return SendSecureAttentionSequence(client_session_id);
|
||||
}
|
||||
int key_code = 0;
|
||||
bool is_down = false;
|
||||
@@ -2024,22 +2052,29 @@ std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command) {
|
||||
bool extended = false;
|
||||
if (ParseSecureDesktopKeyboardIpcCommand(normalized, &key_code, &is_down,
|
||||
&scan_code, &extended)) {
|
||||
return SendSecureDesktopKeyboardInput(key_code, is_down, scan_code,
|
||||
extended);
|
||||
return SendSecureDesktopKeyboardInput(client_session_id, key_code, is_down,
|
||||
scan_code, extended);
|
||||
}
|
||||
SecureDesktopMouseRequest mouse_request;
|
||||
if (ParseSecureDesktopMouseIpcCommand(normalized, &mouse_request)) {
|
||||
return SendSecureDesktopMouseInput(mouse_request.x, mouse_request.y,
|
||||
mouse_request.wheel,
|
||||
mouse_request.flag);
|
||||
return SendSecureDesktopMouseInput(client_session_id, mouse_request.x,
|
||||
mouse_request.y, mouse_request.wheel,
|
||||
mouse_request.flag);
|
||||
}
|
||||
return BuildErrorJson("unknown_command");
|
||||
}
|
||||
|
||||
std::string CrossDeskServiceHost::BuildStatusResponse() {
|
||||
std::string CrossDeskServiceHost::BuildStatusResponse(DWORD client_session_id) {
|
||||
ReapSecureInputHelper();
|
||||
ReapSessionHelper();
|
||||
RefreshSessionState();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||
if (client_session_id == 0xFFFFFFFF ||
|
||||
active_session_id_ != client_session_id) {
|
||||
return BuildErrorJson("service_session_mismatch");
|
||||
}
|
||||
}
|
||||
EnsureSessionHelper();
|
||||
RefreshSessionHelperReportedState();
|
||||
bool keep_secure_input_helper = false;
|
||||
@@ -2077,6 +2112,9 @@ std::string CrossDeskServiceHost::BuildStatusResponse() {
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(state_mutex_);
|
||||
if (active_session_id_ != client_session_id) {
|
||||
return BuildErrorJson("service_session_mismatch");
|
||||
}
|
||||
std::wstring username;
|
||||
GetSessionUserName(active_session_id_, &username);
|
||||
std::string username_utf8 = EscapeJsonString(WideToUtf8(username));
|
||||
@@ -2263,8 +2301,13 @@ std::string CrossDeskServiceHost::BuildStatusResponse() {
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string CrossDeskServiceHost::SendSecureAttentionSequence() {
|
||||
std::string CrossDeskServiceHost::SendSecureAttentionSequence(
|
||||
DWORD client_session_id) {
|
||||
RefreshSessionState();
|
||||
if (client_session_id == 0xFFFFFFFF ||
|
||||
client_session_id != WTSGetActiveConsoleSessionId()) {
|
||||
return BuildErrorJson("service_session_mismatch");
|
||||
}
|
||||
LOG_INFO("Received SAS request for session_id={}", active_session_id_);
|
||||
SasResult result = SendSasNow();
|
||||
{
|
||||
@@ -2286,7 +2329,8 @@ std::string CrossDeskServiceHost::SendSecureAttentionSequence() {
|
||||
}
|
||||
|
||||
std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput(
|
||||
int key_code, bool is_down, uint32_t scan_code, bool extended) {
|
||||
DWORD client_session_id, int key_code, bool is_down,
|
||||
uint32_t scan_code, bool extended) {
|
||||
RefreshSessionState();
|
||||
ReapSecureInputHelper();
|
||||
EnsureSessionHelper();
|
||||
@@ -2314,6 +2358,9 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput(
|
||||
if (target_session_id == 0xFFFFFFFF) {
|
||||
return BuildErrorJson("no_active_console_session");
|
||||
}
|
||||
if (target_session_id != client_session_id) {
|
||||
return BuildErrorJson("service_session_mismatch");
|
||||
}
|
||||
if (!can_inject) {
|
||||
return BuildErrorJson("secure_input_not_active");
|
||||
}
|
||||
@@ -2336,9 +2383,8 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput(
|
||||
1000);
|
||||
}
|
||||
|
||||
std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y,
|
||||
int wheel,
|
||||
int flag) {
|
||||
std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(
|
||||
DWORD client_session_id, int x, int y, int wheel, int flag) {
|
||||
RefreshSessionState();
|
||||
ReapSecureInputHelper();
|
||||
EnsureSessionHelper();
|
||||
@@ -2366,6 +2412,9 @@ std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y,
|
||||
if (target_session_id == 0xFFFFFFFF) {
|
||||
return BuildErrorJson("no_active_console_session");
|
||||
}
|
||||
if (target_session_id != client_session_id) {
|
||||
return BuildErrorJson("service_session_mismatch");
|
||||
}
|
||||
if (!can_inject) {
|
||||
return BuildErrorJson("secure_input_not_active");
|
||||
}
|
||||
|
||||
@@ -65,13 +65,16 @@ class CrossDeskServiceHost {
|
||||
const std::string& interactive_stage) const;
|
||||
void RefreshSessionHelperReportedState();
|
||||
void RecordSessionEvent(DWORD event_type, DWORD session_id);
|
||||
std::string HandleIpcCommand(const std::string& command);
|
||||
std::string BuildStatusResponse();
|
||||
std::string SendSecureAttentionSequence();
|
||||
std::string SendSecureDesktopKeyboardInput(int key_code, bool is_down,
|
||||
std::string HandleIpcCommand(const std::string& command,
|
||||
DWORD client_session_id);
|
||||
std::string BuildStatusResponse(DWORD client_session_id);
|
||||
std::string SendSecureAttentionSequence(DWORD client_session_id);
|
||||
std::string SendSecureDesktopKeyboardInput(DWORD client_session_id,
|
||||
int key_code, bool is_down,
|
||||
uint32_t scan_code = 0,
|
||||
bool extended = false);
|
||||
std::string SendSecureDesktopMouseInput(int x, int y, int wheel, int flag);
|
||||
std::string SendSecureDesktopMouseInput(DWORD client_session_id,
|
||||
int x, int y, int wheel, int flag);
|
||||
|
||||
static void WINAPI ServiceMain(DWORD argc, LPWSTR* argv);
|
||||
static BOOL WINAPI ConsoleControlHandler(DWORD control_type);
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "windows_cursor_state.h"
|
||||
#include "cursor_draw.h"
|
||||
#include "windows_input_marker.h"
|
||||
#include "windows_key_metadata.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -637,6 +638,13 @@ bool EnsureThreadInteractiveDesktopForStage(
|
||||
desktop_name = L"Winlogon";
|
||||
}
|
||||
if (desktop->Bind(desktop_name)) {
|
||||
if (!desktop->IsReceivingInput()) {
|
||||
if (switch_details != nullptr) {
|
||||
switch_details->error_code = ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION;
|
||||
}
|
||||
SetLastError(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
|
||||
return false;
|
||||
}
|
||||
if (switch_details != nullptr) {
|
||||
switch_details->target_desktop = WideToUtf8(desktop->name());
|
||||
switch_details->current_desktop = switch_details->target_desktop;
|
||||
@@ -695,6 +703,9 @@ InputInjectionResult InjectKeyboardInput(
|
||||
input.ki.dwExtraInfo = static_cast<ULONG_PTR>(
|
||||
crossdesk::kInjectedKeyboardInputMarker);
|
||||
|
||||
if (scan_code == 0) {
|
||||
crossdesk::LookupWindowsKeyMetadataFromVk(key_code, &scan_code, &extended);
|
||||
}
|
||||
const bool prefer_vk = PreferSideSpecificVkInjection(key_code);
|
||||
const UINT resolved_scan_code =
|
||||
scan_code != 0
|
||||
|
||||
@@ -282,10 +282,14 @@ function setup_targets()
|
||||
if is_os("windows") then
|
||||
add_files("apps/desktop/src/platform/windows/input/mouse/*.cpp",
|
||||
"apps/desktop/src/platform/windows/input/keyboard/*.cpp",
|
||||
"apps/desktop/src/platform/windows/input/windows_input_injector.cpp",
|
||||
"apps/desktop/src/platform/windows/input/device_controller_factory.cpp")
|
||||
add_includedirs("apps/desktop/src/platform/windows/input/mouse",
|
||||
add_includedirs("apps/desktop/src/platform/windows",
|
||||
"apps/desktop/src/platform/windows/service",
|
||||
"apps/desktop/src/platform/windows/input/mouse",
|
||||
"apps/desktop/src/platform/windows/input/keyboard",
|
||||
"apps/desktop/src/platform/windows/input")
|
||||
add_syslinks("Wtsapi32", {public = true})
|
||||
elseif is_os("macosx") then
|
||||
add_files("apps/desktop/src/platform/macos/input/mouse/*.cpp",
|
||||
"apps/desktop/src/platform/macos/input/keyboard/*.cpp",
|
||||
@@ -473,7 +477,8 @@ function setup_targets()
|
||||
add_includedirs("apps/desktop/src/common",
|
||||
"apps/desktop/src/platform/windows/service",
|
||||
"apps/desktop/src/platform/windows/screen_capturer")
|
||||
add_includedirs("apps/desktop/src/platform/windows/input")
|
||||
add_includedirs("apps/desktop/src/platform/windows/input",
|
||||
"apps/desktop/src/platform/common/input")
|
||||
add_includedirs("libs/wire/include")
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user