mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-06-10 17:34:57 +08:00
1872 lines
59 KiB
C++
1872 lines
59 KiB
C++
// clang-format off
|
|
#include <Windows.h>
|
|
#include <TlHelp32.h>
|
|
// clang-format on
|
|
#include <WtsApi32.h>
|
|
#include <libyuv.h>
|
|
#include <sddl.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <nlohmann/json.hpp>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "path_manager.h"
|
|
#include "rd_log.h"
|
|
#include "session_helper_shared.h"
|
|
|
|
namespace {
|
|
|
|
using crossdesk::get_logger;
|
|
using crossdesk::InitLogger;
|
|
using Json = nlohmann::json;
|
|
|
|
struct InputDesktopInfo {
|
|
bool available = false;
|
|
DWORD error_code = 0;
|
|
std::string name;
|
|
};
|
|
|
|
struct HelperState {
|
|
std::mutex mutex;
|
|
DWORD session_id = 0xFFFFFFFF;
|
|
DWORD process_id = 0;
|
|
bool session_locked = false;
|
|
bool input_desktop_available = false;
|
|
DWORD input_desktop_error_code = 0;
|
|
std::string input_desktop_name;
|
|
bool lock_app_visible = false;
|
|
bool logon_ui_visible = false;
|
|
bool secure_desktop_active = false;
|
|
ULONGLONG started_at_tick = 0;
|
|
ULONGLONG last_update_tick = 0;
|
|
};
|
|
|
|
struct SecureCaptureRequest {
|
|
int left = 0;
|
|
int top = 0;
|
|
int width = 0;
|
|
int height = 0;
|
|
bool show_cursor = true;
|
|
int fps = 30;
|
|
std::string interactive_stage;
|
|
};
|
|
|
|
struct SecureMouseRequest {
|
|
int x = 0;
|
|
int y = 0;
|
|
int wheel = 0;
|
|
int flag = 0;
|
|
std::string interactive_stage;
|
|
};
|
|
|
|
struct SecureCaptureBuffers {
|
|
std::vector<uint8_t> nv12_frame;
|
|
};
|
|
|
|
struct SecureSharedCaptureState {
|
|
std::mutex mutex;
|
|
std::thread capture_thread;
|
|
std::atomic<bool> stop_requested{false};
|
|
DWORD session_id = 0xFFFFFFFF;
|
|
SecureCaptureRequest request;
|
|
HANDLE frame_mapping = nullptr;
|
|
HANDLE frame_ready_event = nullptr;
|
|
uint8_t* frame_view = nullptr;
|
|
size_t frame_view_size = 0;
|
|
uint32_t sequence = 0;
|
|
};
|
|
|
|
struct PipeSecurityAttributes {
|
|
PipeSecurityAttributes() = default;
|
|
~PipeSecurityAttributes() {
|
|
if (security_descriptor_ != nullptr) {
|
|
LocalFree(security_descriptor_);
|
|
}
|
|
}
|
|
|
|
bool Initialize() {
|
|
constexpr wchar_t kPipeSddl[] = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)";
|
|
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
|
kPipeSddl, SDDL_REVISION_1, &security_descriptor_, nullptr)) {
|
|
return false;
|
|
}
|
|
|
|
attributes_.nLength = sizeof(attributes_);
|
|
attributes_.lpSecurityDescriptor = security_descriptor_;
|
|
attributes_.bInheritHandle = FALSE;
|
|
return true;
|
|
}
|
|
|
|
SECURITY_ATTRIBUTES* get() { return &attributes_; }
|
|
|
|
private:
|
|
SECURITY_ATTRIBUTES attributes_{};
|
|
PSECURITY_DESCRIPTOR security_descriptor_ = nullptr;
|
|
};
|
|
|
|
void InitializeHelperLogger() {
|
|
static std::once_flag once_flag;
|
|
std::call_once(once_flag, []() {
|
|
crossdesk::PathManager path_manager("CrossDesk");
|
|
std::filesystem::path log_path =
|
|
path_manager.GetLogPath() / "session_helper";
|
|
if (!log_path.empty() && path_manager.CreateDirectories(log_path)) {
|
|
InitLogger(log_path.string());
|
|
return;
|
|
}
|
|
InitLogger("logs/session_helper");
|
|
});
|
|
}
|
|
|
|
void EnablePerMonitorDpiAwareness() {
|
|
SetProcessDpiAwarenessContext(
|
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
}
|
|
|
|
std::wstring Utf8ToWide(const std::string& value) {
|
|
if (value.empty()) {
|
|
return {};
|
|
}
|
|
|
|
int size_needed =
|
|
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, nullptr, 0);
|
|
if (size_needed <= 1) {
|
|
return {};
|
|
}
|
|
|
|
std::wstring result(static_cast<size_t>(size_needed), L'\0');
|
|
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), -1, result.data(),
|
|
size_needed);
|
|
result.pop_back();
|
|
return result;
|
|
}
|
|
|
|
std::string WideToUtf8(const std::wstring& value) {
|
|
if (value.empty()) {
|
|
return {};
|
|
}
|
|
|
|
int size_needed = WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, nullptr,
|
|
0, nullptr, nullptr);
|
|
if (size_needed <= 1) {
|
|
return {};
|
|
}
|
|
|
|
std::string result(static_cast<size_t>(size_needed), '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, value.c_str(), -1, result.data(), size_needed,
|
|
nullptr, nullptr);
|
|
result.pop_back();
|
|
return result;
|
|
}
|
|
|
|
InputDesktopInfo GetInputDesktopInfo() {
|
|
InputDesktopInfo info;
|
|
HDESK desktop = OpenInputDesktop(0, FALSE, GENERIC_READ);
|
|
if (desktop == nullptr) {
|
|
info.error_code = GetLastError();
|
|
return info;
|
|
}
|
|
|
|
DWORD bytes_needed = 0;
|
|
GetUserObjectInformationW(desktop, UOI_NAME, nullptr, 0, &bytes_needed);
|
|
if (bytes_needed == 0) {
|
|
info.error_code = GetLastError();
|
|
CloseDesktop(desktop);
|
|
return info;
|
|
}
|
|
|
|
std::wstring desktop_name(bytes_needed / sizeof(wchar_t), L'\0');
|
|
if (!GetUserObjectInformationW(desktop, UOI_NAME, desktop_name.data(),
|
|
bytes_needed, &bytes_needed)) {
|
|
info.error_code = GetLastError();
|
|
CloseDesktop(desktop);
|
|
return info;
|
|
}
|
|
|
|
CloseDesktop(desktop);
|
|
while (!desktop_name.empty() && desktop_name.back() == L'\0') {
|
|
desktop_name.pop_back();
|
|
}
|
|
info.available = true;
|
|
info.name = WideToUtf8(desktop_name);
|
|
return info;
|
|
}
|
|
|
|
bool IsProcessRunningInCurrentSession(const wchar_t* executable_name,
|
|
DWORD session_id) {
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
if (snapshot == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
|
|
PROCESSENTRY32W entry{};
|
|
entry.dwSize = sizeof(entry);
|
|
bool found = false;
|
|
if (Process32FirstW(snapshot, &entry)) {
|
|
do {
|
|
if (_wcsicmp(entry.szExeFile, executable_name) != 0) {
|
|
continue;
|
|
}
|
|
|
|
DWORD process_session_id = 0xFFFFFFFF;
|
|
if (ProcessIdToSessionId(entry.th32ProcessID, &process_session_id) &&
|
|
process_session_id == session_id) {
|
|
found = true;
|
|
break;
|
|
}
|
|
} while (Process32NextW(snapshot, &entry));
|
|
}
|
|
|
|
CloseHandle(snapshot);
|
|
return found;
|
|
}
|
|
|
|
bool IsLogonUiRunningInCurrentSession(DWORD session_id) {
|
|
return IsProcessRunningInCurrentSession(L"LogonUI.exe", session_id);
|
|
}
|
|
|
|
bool IsLockAppRunningInCurrentSession(DWORD session_id) {
|
|
return IsProcessRunningInCurrentSession(L"LockApp.exe", session_id);
|
|
}
|
|
|
|
bool QuerySessionLockState(DWORD session_id, bool* session_locked_out) {
|
|
if (session_locked_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
*session_locked_out = false;
|
|
PWTSINFOEXW session_info = nullptr;
|
|
DWORD bytes = 0;
|
|
if (!WTSQuerySessionInformationW(
|
|
WTS_CURRENT_SERVER_HANDLE, session_id, WTSSessionInfoEx,
|
|
reinterpret_cast<LPWSTR*>(&session_info), &bytes)) {
|
|
return false;
|
|
}
|
|
|
|
bool success = false;
|
|
if (session_info != nullptr && bytes >= sizeof(WTSINFOEXW) &&
|
|
session_info->Level == 1) {
|
|
const LONG session_flags = session_info->Data.WTSInfoExLevel1.SessionFlags;
|
|
if (session_flags == WTS_SESSIONSTATE_LOCK) {
|
|
*session_locked_out = true;
|
|
success = true;
|
|
} else if (session_flags == WTS_SESSIONSTATE_UNLOCK) {
|
|
*session_locked_out = false;
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if (session_info != nullptr) {
|
|
WTSFreeMemory(session_info);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
const char* DetermineInteractiveStage(bool lock_app_visible,
|
|
bool credential_ui_visible,
|
|
bool secure_desktop_active) {
|
|
if (credential_ui_visible) {
|
|
return "credential-ui";
|
|
}
|
|
if (lock_app_visible) {
|
|
return "lock-screen";
|
|
}
|
|
if (secure_desktop_active) {
|
|
return "secure-desktop";
|
|
}
|
|
return "user-desktop";
|
|
}
|
|
|
|
std::string BuildErrorJson(const char* error, DWORD error_code = 0) {
|
|
Json json;
|
|
json["ok"] = false;
|
|
json["error"] = error;
|
|
if (error_code != 0) {
|
|
json["code"] = error_code;
|
|
}
|
|
return json.dump();
|
|
}
|
|
|
|
void UpdateHelperState(HelperState* helper_state) {
|
|
if (helper_state == nullptr) {
|
|
return;
|
|
}
|
|
|
|
InputDesktopInfo desktop_info = GetInputDesktopInfo();
|
|
bool lock_app_process_running =
|
|
IsLockAppRunningInCurrentSession(helper_state->session_id);
|
|
bool logon_ui_visible =
|
|
IsLogonUiRunningInCurrentSession(helper_state->session_id);
|
|
const bool secure_desktop_active =
|
|
_stricmp(desktop_info.name.c_str(), "Winlogon") == 0;
|
|
bool session_locked = false;
|
|
if (!QuerySessionLockState(helper_state->session_id, &session_locked)) {
|
|
session_locked =
|
|
lock_app_process_running || logon_ui_visible || secure_desktop_active;
|
|
}
|
|
const bool lock_app_visible = session_locked && lock_app_process_running;
|
|
|
|
std::lock_guard<std::mutex> lock(helper_state->mutex);
|
|
helper_state->session_locked = session_locked;
|
|
helper_state->input_desktop_available = desktop_info.available;
|
|
helper_state->input_desktop_error_code = desktop_info.error_code;
|
|
helper_state->input_desktop_name = desktop_info.name;
|
|
helper_state->lock_app_visible = lock_app_visible;
|
|
helper_state->logon_ui_visible = logon_ui_visible;
|
|
helper_state->secure_desktop_active = secure_desktop_active;
|
|
helper_state->last_update_tick = GetTickCount64();
|
|
}
|
|
|
|
std::string BuildHelperStatusResponse(HelperState* helper_state) {
|
|
if (helper_state == nullptr) {
|
|
return BuildErrorJson("invalid_state");
|
|
}
|
|
|
|
Json json;
|
|
std::lock_guard<std::mutex> lock(helper_state->mutex);
|
|
const bool credential_ui_visible =
|
|
helper_state->logon_ui_visible ||
|
|
(helper_state->session_locked && !helper_state->input_desktop_available);
|
|
const bool unlock_ui_visible =
|
|
credential_ui_visible || helper_state->secure_desktop_active;
|
|
json["ok"] = true;
|
|
json["session_id"] = helper_state->session_id;
|
|
json["process_id"] = helper_state->process_id;
|
|
json["session_locked"] = helper_state->session_locked;
|
|
json["input_desktop_available"] = helper_state->input_desktop_available;
|
|
json["input_desktop_error_code"] = helper_state->input_desktop_error_code;
|
|
json["input_desktop"] = helper_state->input_desktop_name;
|
|
json["lock_app_visible"] = helper_state->lock_app_visible;
|
|
json["logon_ui_visible"] = helper_state->logon_ui_visible;
|
|
json["secure_desktop_active"] = helper_state->secure_desktop_active;
|
|
json["credential_ui_visible"] = credential_ui_visible;
|
|
json["unlock_ui_visible"] = unlock_ui_visible;
|
|
json["interactive_stage"] = DetermineInteractiveStage(
|
|
helper_state->lock_app_visible, credential_ui_visible,
|
|
helper_state->secure_desktop_active);
|
|
json["uptime_ms"] = GetTickCount64() >= helper_state->started_at_tick
|
|
? (GetTickCount64() - helper_state->started_at_tick)
|
|
: 0;
|
|
json["state_age_ms"] =
|
|
GetTickCount64() >= helper_state->last_update_tick
|
|
? (GetTickCount64() - helper_state->last_update_tick)
|
|
: 0;
|
|
return json.dump();
|
|
}
|
|
|
|
void HelperIpcServerLoop(HANDLE stop_event, DWORD session_id,
|
|
HelperState* helper_state) {
|
|
PipeSecurityAttributes security_attributes;
|
|
SECURITY_ATTRIBUTES* pipe_attributes = nullptr;
|
|
if (security_attributes.Initialize()) {
|
|
pipe_attributes = security_attributes.get();
|
|
}
|
|
|
|
std::wstring pipe_name =
|
|
crossdesk::GetCrossDeskSessionHelperPipeName(session_id);
|
|
while (stop_event == nullptr ||
|
|
WaitForSingleObject(stop_event, 0) != WAIT_OBJECT_0) {
|
|
HANDLE pipe = CreateNamedPipeW(
|
|
pipe_name.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 4096, 4096, 0,
|
|
pipe_attributes);
|
|
if (pipe == INVALID_HANDLE_VALUE) {
|
|
LOG_ERROR("CreateNamedPipeW failed in helper, error={}", GetLastError());
|
|
if (stop_event != nullptr) {
|
|
WaitForSingleObject(stop_event, 1000);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
OVERLAPPED overlapped{};
|
|
overlapped.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (overlapped.hEvent == nullptr) {
|
|
LOG_ERROR("CreateEventW failed in helper IPC loop, error={}",
|
|
GetLastError());
|
|
CloseHandle(pipe);
|
|
break;
|
|
}
|
|
|
|
BOOL connected = ConnectNamedPipe(pipe, &overlapped);
|
|
DWORD connect_error = connected ? ERROR_SUCCESS : GetLastError();
|
|
if (connected) {
|
|
SetEvent(overlapped.hEvent);
|
|
}
|
|
if (!connected && connect_error == ERROR_PIPE_CONNECTED) {
|
|
SetEvent(overlapped.hEvent);
|
|
connected = TRUE;
|
|
}
|
|
if (!connected && connect_error != ERROR_IO_PENDING) {
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
continue;
|
|
}
|
|
|
|
if (!connected) {
|
|
if (stop_event != nullptr) {
|
|
HANDLE wait_handles[] = {stop_event, overlapped.hEvent};
|
|
DWORD wait_result =
|
|
WaitForMultipleObjects(2, wait_handles, FALSE, INFINITE);
|
|
if (wait_result == WAIT_OBJECT_0) {
|
|
CancelIoEx(pipe, &overlapped);
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
break;
|
|
}
|
|
} else {
|
|
WaitForSingleObject(overlapped.hEvent, INFINITE);
|
|
}
|
|
}
|
|
|
|
char buffer[1024] = {0};
|
|
DWORD bytes_read = 0;
|
|
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) &&
|
|
bytes_read > 0) {
|
|
std::string command(buffer, buffer + bytes_read);
|
|
std::string response;
|
|
if (command == crossdesk::kCrossDeskSessionHelperStatusCommand) {
|
|
response = BuildHelperStatusResponse(helper_state);
|
|
} else if (command == "ping") {
|
|
response = "{\"ok\":true,\"reply\":\"pong\"}";
|
|
} else {
|
|
response = BuildErrorJson("unknown_command");
|
|
}
|
|
|
|
DWORD bytes_written = 0;
|
|
WriteFile(pipe, response.data(), static_cast<DWORD>(response.size()),
|
|
&bytes_written, nullptr);
|
|
FlushFileBuffers(pipe);
|
|
}
|
|
|
|
DisconnectNamedPipe(pipe);
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
}
|
|
}
|
|
|
|
std::wstring GetDesktopNameW(HDESK desktop) {
|
|
if (desktop == nullptr) {
|
|
return L"";
|
|
}
|
|
|
|
DWORD bytes_needed = 0;
|
|
GetUserObjectInformationW(desktop, UOI_NAME, nullptr, 0, &bytes_needed);
|
|
if (bytes_needed == 0) {
|
|
return L"";
|
|
}
|
|
|
|
std::wstring desktop_name(bytes_needed / sizeof(wchar_t), L'\0');
|
|
if (!GetUserObjectInformationW(desktop, UOI_NAME, desktop_name.data(),
|
|
bytes_needed, &bytes_needed)) {
|
|
return L"";
|
|
}
|
|
|
|
while (!desktop_name.empty() && desktop_name.back() == L'\0') {
|
|
desktop_name.pop_back();
|
|
}
|
|
return desktop_name;
|
|
}
|
|
|
|
std::wstring GetCurrentThreadDesktopNameW() {
|
|
return GetDesktopNameW(GetThreadDesktop(GetCurrentThreadId()));
|
|
}
|
|
|
|
constexpr ACCESS_MASK kCrossDeskInteractiveDesktopAccess =
|
|
DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
|
|
DESKTOP_SWITCHDESKTOP;
|
|
|
|
bool EnsureThreadDesktop(const wchar_t* desktop_name,
|
|
HDESK* opened_desktop_out = nullptr) {
|
|
if (desktop_name == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
std::wstring current_desktop = GetCurrentThreadDesktopNameW();
|
|
if (!current_desktop.empty() &&
|
|
_wcsicmp(current_desktop.c_str(), desktop_name) == 0) {
|
|
return true;
|
|
}
|
|
|
|
HDESK desktop =
|
|
OpenDesktopW(desktop_name, 0, FALSE, kCrossDeskInteractiveDesktopAccess);
|
|
if (desktop == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (!SetThreadDesktop(desktop)) {
|
|
CloseDesktop(desktop);
|
|
return false;
|
|
}
|
|
|
|
if (opened_desktop_out != nullptr) {
|
|
*opened_desktop_out = desktop;
|
|
} else {
|
|
CloseDesktop(desktop);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EnsureThreadInputDesktop(HDESK* opened_desktop_out = nullptr) {
|
|
HDESK desktop =
|
|
OpenInputDesktop(0, FALSE, kCrossDeskInteractiveDesktopAccess);
|
|
if (desktop == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const std::wstring input_desktop = GetDesktopNameW(desktop);
|
|
const std::wstring current_desktop = GetCurrentThreadDesktopNameW();
|
|
if (!input_desktop.empty() && !current_desktop.empty() &&
|
|
_wcsicmp(input_desktop.c_str(), current_desktop.c_str()) == 0) {
|
|
if (opened_desktop_out != nullptr) {
|
|
*opened_desktop_out = desktop;
|
|
} else {
|
|
CloseDesktop(desktop);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!SetThreadDesktop(desktop)) {
|
|
CloseDesktop(desktop);
|
|
return false;
|
|
}
|
|
|
|
if (opened_desktop_out != nullptr) {
|
|
*opened_desktop_out = desktop;
|
|
} else {
|
|
CloseDesktop(desktop);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EnsureThreadInteractiveDesktop(HDESK* opened_desktop_out = nullptr) {
|
|
if (opened_desktop_out != nullptr) {
|
|
*opened_desktop_out = nullptr;
|
|
}
|
|
|
|
if (EnsureThreadInputDesktop(opened_desktop_out)) {
|
|
return true;
|
|
}
|
|
const DWORD input_desktop_error = GetLastError();
|
|
|
|
if (EnsureThreadDesktop(L"Winlogon", opened_desktop_out)) {
|
|
return true;
|
|
}
|
|
const DWORD winlogon_error = GetLastError();
|
|
|
|
LOG_WARN(
|
|
"Failed to switch secure input helper desktop, input_error={}, "
|
|
"winlogon_error={}, current='{}'",
|
|
input_desktop_error, winlogon_error,
|
|
WideToUtf8(GetCurrentThreadDesktopNameW()));
|
|
SetLastError(winlogon_error != ERROR_SUCCESS ? winlogon_error
|
|
: input_desktop_error);
|
|
return false;
|
|
}
|
|
|
|
const wchar_t* DesktopNameForInteractiveStage(
|
|
const std::string& interactive_stage) {
|
|
if (interactive_stage == "credential-ui" ||
|
|
interactive_stage == "secure-desktop") {
|
|
return L"Winlogon";
|
|
}
|
|
if (interactive_stage == "lock-screen") {
|
|
return L"Default";
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
struct DesktopSwitchDetails {
|
|
std::string stage;
|
|
std::string target_desktop;
|
|
std::string current_desktop;
|
|
DWORD error_code = ERROR_SUCCESS;
|
|
};
|
|
|
|
struct InputInjectionResult {
|
|
bool ok = true;
|
|
std::string error;
|
|
DWORD error_code = ERROR_SUCCESS;
|
|
DesktopSwitchDetails desktop;
|
|
};
|
|
|
|
DesktopSwitchDetails BuildDesktopSwitchDetails(
|
|
const std::string& interactive_stage) {
|
|
DesktopSwitchDetails details;
|
|
details.stage = interactive_stage;
|
|
const wchar_t* desktop_name =
|
|
DesktopNameForInteractiveStage(interactive_stage);
|
|
details.target_desktop =
|
|
desktop_name != nullptr ? WideToUtf8(std::wstring(desktop_name))
|
|
: "input-or-Winlogon";
|
|
details.current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
return details;
|
|
}
|
|
|
|
InputInjectionResult BuildInputSuccess() {
|
|
return {};
|
|
}
|
|
|
|
InputInjectionResult BuildInputFailure(const char* error, DWORD error_code,
|
|
DesktopSwitchDetails desktop) {
|
|
desktop.error_code = error_code;
|
|
desktop.current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
|
|
InputInjectionResult result;
|
|
result.ok = false;
|
|
result.error = error != nullptr ? error : "input_failed";
|
|
result.error_code =
|
|
error_code != ERROR_SUCCESS ? error_code : ERROR_GEN_FAILURE;
|
|
result.desktop = std::move(desktop);
|
|
return result;
|
|
}
|
|
|
|
Json BuildInputFailureJson(const InputInjectionResult& result) {
|
|
Json json;
|
|
json["ok"] = false;
|
|
json["error"] = result.error;
|
|
json["code"] = result.error_code;
|
|
json["stage"] = result.desktop.stage;
|
|
json["target_desktop"] = result.desktop.target_desktop;
|
|
json["current_desktop"] = result.desktop.current_desktop;
|
|
return json;
|
|
}
|
|
|
|
bool EnsureThreadInteractiveDesktopForStage(
|
|
const std::string& interactive_stage,
|
|
HDESK* opened_desktop_out = nullptr,
|
|
DesktopSwitchDetails* switch_details = nullptr) {
|
|
if (opened_desktop_out != nullptr) {
|
|
*opened_desktop_out = nullptr;
|
|
}
|
|
|
|
DesktopSwitchDetails local_details =
|
|
BuildDesktopSwitchDetails(interactive_stage);
|
|
if (switch_details != nullptr) {
|
|
*switch_details = local_details;
|
|
}
|
|
|
|
const wchar_t* desktop_name =
|
|
DesktopNameForInteractiveStage(interactive_stage);
|
|
if (desktop_name != nullptr) {
|
|
if (EnsureThreadDesktop(desktop_name, opened_desktop_out)) {
|
|
if (switch_details != nullptr) {
|
|
switch_details->current_desktop =
|
|
WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const DWORD error = GetLastError();
|
|
if (switch_details != nullptr) {
|
|
switch_details->error_code = error;
|
|
switch_details->current_desktop =
|
|
WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
}
|
|
LOG_WARN(
|
|
"Failed to switch secure input helper to stage desktop, stage='{}', "
|
|
"desktop='{}', error={}, current='{}'",
|
|
interactive_stage, WideToUtf8(std::wstring(desktop_name)),
|
|
error,
|
|
WideToUtf8(GetCurrentThreadDesktopNameW()));
|
|
SetLastError(error);
|
|
return false;
|
|
}
|
|
|
|
if (EnsureThreadInteractiveDesktop(opened_desktop_out)) {
|
|
if (switch_details != nullptr) {
|
|
switch_details->current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const DWORD error = GetLastError();
|
|
if (switch_details != nullptr) {
|
|
switch_details->error_code = error;
|
|
switch_details->current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct ScopedDesktopHandle {
|
|
HDESK handle = nullptr;
|
|
~ScopedDesktopHandle() {
|
|
if (handle != nullptr) {
|
|
CloseDesktop(handle);
|
|
}
|
|
}
|
|
};
|
|
|
|
bool PreferSideSpecificVkInjection(int key_code) {
|
|
switch (key_code) {
|
|
case VK_LSHIFT:
|
|
case VK_RSHIFT:
|
|
case VK_LCONTROL:
|
|
case VK_RCONTROL:
|
|
case VK_LMENU:
|
|
case VK_RMENU:
|
|
case VK_LWIN:
|
|
case VK_RWIN:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
InputInjectionResult InjectKeyboardInput(
|
|
int key_code, bool is_down, uint32_t scan_code, bool extended,
|
|
const std::string& interactive_stage) {
|
|
ScopedDesktopHandle desktop;
|
|
DesktopSwitchDetails desktop_switch;
|
|
if (!EnsureThreadInteractiveDesktopForStage(interactive_stage,
|
|
&desktop.handle,
|
|
&desktop_switch)) {
|
|
const DWORD error = GetLastError();
|
|
return BuildInputFailure("switch_interactive_desktop_failed",
|
|
error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE,
|
|
desktop_switch);
|
|
}
|
|
|
|
INPUT input = {0};
|
|
input.type = INPUT_KEYBOARD;
|
|
|
|
const bool prefer_vk = PreferSideSpecificVkInjection(key_code);
|
|
const UINT resolved_scan_code =
|
|
scan_code != 0
|
|
? static_cast<UINT>(scan_code & 0xFF) | (extended ? 0xE000u : 0u)
|
|
: MapVirtualKeyW(static_cast<UINT>(key_code), MAPVK_VK_TO_VSC_EX);
|
|
|
|
if (scan_code != 0 && !prefer_vk) {
|
|
input.ki.wVk = 0;
|
|
input.ki.wScan = static_cast<WORD>(scan_code & 0xFF);
|
|
input.ki.dwFlags |= KEYEVENTF_SCANCODE;
|
|
if (extended) {
|
|
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
|
}
|
|
} else {
|
|
input.ki.wVk = static_cast<WORD>(key_code);
|
|
|
|
if (prefer_vk && resolved_scan_code != 0) {
|
|
input.ki.wScan = static_cast<WORD>(resolved_scan_code & 0xFF);
|
|
if ((resolved_scan_code & 0xFF00) != 0) {
|
|
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
|
}
|
|
} else if (resolved_scan_code != 0) {
|
|
input.ki.wVk = 0;
|
|
input.ki.wScan = static_cast<WORD>(resolved_scan_code & 0xFF);
|
|
input.ki.dwFlags |= KEYEVENTF_SCANCODE;
|
|
if ((resolved_scan_code & 0xFF00) != 0) {
|
|
input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!is_down) {
|
|
input.ki.dwFlags |= KEYEVENTF_KEYUP;
|
|
}
|
|
|
|
UINT sent = SendInput(1, &input, sizeof(INPUT));
|
|
if (sent != 1) {
|
|
const DWORD error = GetLastError();
|
|
return BuildInputFailure("send_input_failed",
|
|
error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE,
|
|
desktop_switch);
|
|
}
|
|
|
|
return BuildInputSuccess();
|
|
}
|
|
|
|
bool ParseSecureInputKeyboardCommand(const std::string& command,
|
|
int* key_code_out, bool* is_down_out,
|
|
uint32_t* scan_code_out,
|
|
bool* extended_out,
|
|
std::string* interactive_stage_out =
|
|
nullptr) {
|
|
if (key_code_out == nullptr || is_down_out == nullptr ||
|
|
scan_code_out == nullptr || extended_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
*scan_code_out = 0;
|
|
*extended_out = false;
|
|
if (interactive_stage_out != nullptr) {
|
|
interactive_stage_out->clear();
|
|
}
|
|
|
|
if (command.rfind(crossdesk::kCrossDeskSecureInputKeyboardCommandPrefix, 0) !=
|
|
0) {
|
|
return false;
|
|
}
|
|
|
|
const size_t key_begin =
|
|
std::strlen(crossdesk::kCrossDeskSecureInputKeyboardCommandPrefix);
|
|
const size_t separator = command.find(':', key_begin);
|
|
if (separator == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
*key_code_out = std::stoi(command.substr(key_begin, separator - key_begin));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
const size_t scan_separator = command.find(':', separator + 1);
|
|
const std::string state =
|
|
scan_separator == std::string::npos
|
|
? command.substr(separator + 1)
|
|
: command.substr(separator + 1, scan_separator - separator - 1);
|
|
if (state == "1" || state == "down") {
|
|
*is_down_out = true;
|
|
} else if (state == "0" || state == "up") {
|
|
*is_down_out = false;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (scan_separator == std::string::npos) {
|
|
return true;
|
|
}
|
|
|
|
const size_t extended_separator = command.find(':', scan_separator + 1);
|
|
const std::string scan_code_str =
|
|
extended_separator == std::string::npos
|
|
? command.substr(scan_separator + 1)
|
|
: command.substr(scan_separator + 1,
|
|
extended_separator - scan_separator - 1);
|
|
try {
|
|
*scan_code_out = static_cast<uint32_t>(std::stoul(scan_code_str));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
if (extended_separator == std::string::npos) {
|
|
return true;
|
|
}
|
|
|
|
const size_t stage_separator = command.find(':', extended_separator + 1);
|
|
const std::string extended_str =
|
|
stage_separator == std::string::npos
|
|
? command.substr(extended_separator + 1)
|
|
: command.substr(extended_separator + 1,
|
|
stage_separator - extended_separator - 1);
|
|
if (stage_separator != std::string::npos &&
|
|
interactive_stage_out != nullptr) {
|
|
*interactive_stage_out = command.substr(stage_separator + 1);
|
|
}
|
|
if (extended_str == "1" || extended_str == "true") {
|
|
*extended_out = true;
|
|
return true;
|
|
}
|
|
if (extended_str == "0" || extended_str == "false") {
|
|
*extended_out = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ParseSecureInputMouseCommand(const std::string& command,
|
|
SecureMouseRequest* request_out) {
|
|
if (request_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (command.rfind(crossdesk::kCrossDeskSecureInputMouseCommandPrefix, 0) !=
|
|
0) {
|
|
return false;
|
|
}
|
|
request_out->interactive_stage.clear();
|
|
|
|
const size_t x_begin =
|
|
std::strlen(crossdesk::kCrossDeskSecureInputMouseCommandPrefix);
|
|
size_t separator = command.find(':', x_begin);
|
|
if (separator == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
request_out->x = std::stoi(command.substr(x_begin, separator - x_begin));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
const size_t y_begin = separator + 1;
|
|
separator = command.find(':', y_begin);
|
|
if (separator == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
request_out->y = std::stoi(command.substr(y_begin, separator - y_begin));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
const size_t wheel_begin = separator + 1;
|
|
separator = command.find(':', wheel_begin);
|
|
if (separator == std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
request_out->wheel =
|
|
std::stoi(command.substr(wheel_begin, separator - wheel_begin));
|
|
const size_t flag_begin = separator + 1;
|
|
const size_t stage_separator = command.find(':', flag_begin);
|
|
request_out->flag = std::stoi(
|
|
stage_separator == std::string::npos
|
|
? command.substr(flag_begin)
|
|
: command.substr(flag_begin, stage_separator - flag_begin));
|
|
if (stage_separator != std::string::npos) {
|
|
request_out->interactive_stage = command.substr(stage_separator + 1);
|
|
}
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ParseSecureInputCaptureCommand(const std::string& command,
|
|
SecureCaptureRequest* request_out) {
|
|
if (request_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (command.rfind(crossdesk::kCrossDeskSecureInputCaptureCommandPrefix, 0) !=
|
|
0) {
|
|
return false;
|
|
}
|
|
request_out->interactive_stage.clear();
|
|
|
|
const size_t values_begin =
|
|
std::strlen(crossdesk::kCrossDeskSecureInputCaptureCommandPrefix);
|
|
int parsed_values[5] = {0};
|
|
size_t token_begin = values_begin;
|
|
size_t separator = std::string::npos;
|
|
for (int index = 0; index < 5; ++index) {
|
|
separator = command.find(':', token_begin);
|
|
const size_t token_end =
|
|
separator == std::string::npos ? command.size() : separator;
|
|
if (token_end == std::string::npos || token_end <= token_begin) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
parsed_values[index] =
|
|
std::stoi(command.substr(token_begin, token_end - token_begin));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
token_begin = token_end + 1;
|
|
}
|
|
|
|
request_out->left = parsed_values[0];
|
|
request_out->top = parsed_values[1];
|
|
request_out->width = parsed_values[2] & ~1;
|
|
request_out->height = parsed_values[3] & ~1;
|
|
request_out->show_cursor = parsed_values[4] != 0;
|
|
request_out->interactive_stage =
|
|
separator == std::string::npos ? std::string()
|
|
: command.substr(token_begin);
|
|
return request_out->width > 0 && request_out->height > 0;
|
|
}
|
|
|
|
bool ParseSecureInputCaptureStartCommand(const std::string& command,
|
|
SecureCaptureRequest* request_out) {
|
|
if (request_out == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (command.rfind(
|
|
crossdesk::kCrossDeskSecureInputCaptureStartCommandPrefix, 0) != 0) {
|
|
return false;
|
|
}
|
|
request_out->interactive_stage.clear();
|
|
|
|
const size_t values_begin = std::strlen(
|
|
crossdesk::kCrossDeskSecureInputCaptureStartCommandPrefix);
|
|
int parsed_values[6] = {0};
|
|
size_t token_begin = values_begin;
|
|
size_t separator = std::string::npos;
|
|
for (int index = 0; index < 6; ++index) {
|
|
separator = command.find(':', token_begin);
|
|
const size_t token_end =
|
|
separator == std::string::npos ? command.size() : separator;
|
|
if (token_end == std::string::npos || token_end <= token_begin) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
parsed_values[index] =
|
|
std::stoi(command.substr(token_begin, token_end - token_begin));
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
token_begin = token_end + 1;
|
|
}
|
|
|
|
request_out->left = parsed_values[0];
|
|
request_out->top = parsed_values[1];
|
|
request_out->width = parsed_values[2] & ~1;
|
|
request_out->height = parsed_values[3] & ~1;
|
|
request_out->show_cursor = parsed_values[4] != 0;
|
|
request_out->fps = parsed_values[5] > 0 ? parsed_values[5] : 30;
|
|
request_out->interactive_stage =
|
|
separator == std::string::npos ? std::string()
|
|
: command.substr(token_begin);
|
|
return request_out->width > 0 && request_out->height > 0;
|
|
}
|
|
|
|
LONG NormalizeAbsoluteMouseCoordinate(int value, int origin, int size) {
|
|
if (size <= 1) {
|
|
return 0;
|
|
}
|
|
|
|
const int clamped_value =
|
|
(std::max)(origin, (std::min)(value, origin + size - 1));
|
|
const long long relative_value =
|
|
static_cast<long long>(clamped_value - origin) * 65535;
|
|
return static_cast<LONG>(relative_value / (size - 1));
|
|
}
|
|
|
|
INPUT BuildAbsoluteMouseMoveInput(int x, int y) {
|
|
INPUT input = {0};
|
|
input.type = INPUT_MOUSE;
|
|
|
|
const int virtual_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
|
const int virtual_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
|
const int virtual_width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
|
const int virtual_height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
|
|
|
input.mi.dx =
|
|
NormalizeAbsoluteMouseCoordinate(x, virtual_left, virtual_width);
|
|
input.mi.dy =
|
|
NormalizeAbsoluteMouseCoordinate(y, virtual_top, virtual_height);
|
|
input.mi.dwFlags =
|
|
MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
|
return input;
|
|
}
|
|
|
|
InputInjectionResult InjectMouseInput(const SecureMouseRequest& request) {
|
|
ScopedDesktopHandle desktop;
|
|
DesktopSwitchDetails desktop_switch;
|
|
if (!EnsureThreadInteractiveDesktopForStage(request.interactive_stage,
|
|
&desktop.handle,
|
|
&desktop_switch)) {
|
|
const DWORD error = GetLastError();
|
|
return BuildInputFailure("switch_interactive_desktop_failed",
|
|
error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE,
|
|
desktop_switch);
|
|
}
|
|
|
|
std::vector<INPUT> inputs;
|
|
inputs.push_back(BuildAbsoluteMouseMoveInput(request.x, request.y));
|
|
|
|
INPUT action_input = {0};
|
|
action_input.type = INPUT_MOUSE;
|
|
switch (request.flag) {
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
|
|
break;
|
|
case 2:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_LEFTUP;
|
|
break;
|
|
case 3:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;
|
|
break;
|
|
case 4:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_RIGHTUP;
|
|
break;
|
|
case 5:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN;
|
|
break;
|
|
case 6:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP;
|
|
break;
|
|
case 7:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_WHEEL;
|
|
action_input.mi.mouseData = request.wheel * 120;
|
|
break;
|
|
case 8:
|
|
action_input.mi.dwFlags = MOUSEEVENTF_HWHEEL;
|
|
action_input.mi.mouseData = request.wheel * 120;
|
|
break;
|
|
default:
|
|
action_input.mi.dwFlags = 0;
|
|
break;
|
|
}
|
|
|
|
if (action_input.mi.dwFlags != 0) {
|
|
inputs.push_back(action_input);
|
|
}
|
|
|
|
UINT sent =
|
|
SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
|
|
if (sent != inputs.size()) {
|
|
const DWORD error = GetLastError();
|
|
return BuildInputFailure("send_input_failed",
|
|
error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE,
|
|
desktop_switch);
|
|
}
|
|
|
|
return BuildInputSuccess();
|
|
}
|
|
|
|
std::vector<uint8_t> BuildTextResponseBytes(const std::string& response) {
|
|
return std::vector<uint8_t>(response.begin(), response.end());
|
|
}
|
|
|
|
std::vector<uint8_t> CaptureSecureDesktopFrame(
|
|
const SecureCaptureRequest& request,
|
|
SecureCaptureBuffers* capture_buffers) {
|
|
if (capture_buffers == nullptr) {
|
|
return BuildTextResponseBytes(BuildErrorJson("invalid_capture_buffers"));
|
|
}
|
|
|
|
ScopedDesktopHandle desktop;
|
|
if (!EnsureThreadInteractiveDesktopForStage(request.interactive_stage,
|
|
&desktop.handle)) {
|
|
const DWORD error = GetLastError();
|
|
return BuildTextResponseBytes(BuildErrorJson(
|
|
"switch_interactive_desktop_failed",
|
|
error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE));
|
|
}
|
|
|
|
HDC screen_dc = GetDC(nullptr);
|
|
if (screen_dc == nullptr) {
|
|
return BuildTextResponseBytes(
|
|
BuildErrorJson("get_dc_failed", GetLastError()));
|
|
}
|
|
|
|
HDC mem_dc = CreateCompatibleDC(screen_dc);
|
|
if (mem_dc == nullptr) {
|
|
const DWORD error = GetLastError();
|
|
ReleaseDC(nullptr, screen_dc);
|
|
return BuildTextResponseBytes(BuildErrorJson("create_dc_failed", error));
|
|
}
|
|
|
|
BITMAPINFO bmi{};
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = request.width;
|
|
bmi.bmiHeader.biHeight = -request.height;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
void* bits = nullptr;
|
|
HBITMAP dib =
|
|
CreateDIBSection(mem_dc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
|
if (dib == nullptr || bits == nullptr) {
|
|
const DWORD error = GetLastError();
|
|
DeleteDC(mem_dc);
|
|
ReleaseDC(nullptr, screen_dc);
|
|
return BuildTextResponseBytes(BuildErrorJson("create_dib_failed", error));
|
|
}
|
|
|
|
HGDIOBJ old_bitmap = SelectObject(mem_dc, dib);
|
|
if (!BitBlt(mem_dc, 0, 0, request.width, request.height, screen_dc,
|
|
request.left, request.top, SRCCOPY | CAPTUREBLT)) {
|
|
const DWORD error = GetLastError();
|
|
SelectObject(mem_dc, old_bitmap);
|
|
DeleteObject(dib);
|
|
DeleteDC(mem_dc);
|
|
ReleaseDC(nullptr, screen_dc);
|
|
return BuildTextResponseBytes(BuildErrorJson("bitblt_failed", error));
|
|
}
|
|
|
|
if (request.show_cursor) {
|
|
CURSORINFO cursor_info{};
|
|
cursor_info.cbSize = sizeof(CURSORINFO);
|
|
if (GetCursorInfo(&cursor_info) && cursor_info.flags == CURSOR_SHOWING &&
|
|
cursor_info.hCursor != nullptr) {
|
|
const int cursor_x = cursor_info.ptScreenPos.x - request.left;
|
|
const int cursor_y = cursor_info.ptScreenPos.y - request.top;
|
|
if (cursor_x >= -64 && cursor_y >= -64 && cursor_x < request.width + 64 &&
|
|
cursor_y < request.height + 64) {
|
|
DrawIconEx(mem_dc, cursor_x, cursor_y, cursor_info.hCursor, 0, 0, 0,
|
|
nullptr, DI_NORMAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
const size_t nv12_size =
|
|
static_cast<size_t>(request.width) * request.height * 3 / 2;
|
|
capture_buffers->nv12_frame.resize(nv12_size);
|
|
const int convert_result = libyuv::ARGBToNV12(
|
|
static_cast<const uint8_t*>(bits), request.width * 4,
|
|
capture_buffers->nv12_frame.data(), request.width,
|
|
capture_buffers->nv12_frame.data() + request.width * request.height,
|
|
request.width, request.width, request.height);
|
|
|
|
SelectObject(mem_dc, old_bitmap);
|
|
DeleteObject(dib);
|
|
DeleteDC(mem_dc);
|
|
ReleaseDC(nullptr, screen_dc);
|
|
|
|
if (convert_result != 0) {
|
|
return BuildTextResponseBytes(BuildErrorJson("argb_to_nv12_failed"));
|
|
}
|
|
|
|
crossdesk::CrossDeskSecureDesktopFrameHeader header{};
|
|
header.magic = crossdesk::kCrossDeskSecureDesktopFrameMagic;
|
|
header.version = crossdesk::kCrossDeskSecureDesktopFrameVersion;
|
|
header.left = request.left;
|
|
header.top = request.top;
|
|
header.width = static_cast<uint32_t>(request.width);
|
|
header.height = static_cast<uint32_t>(request.height);
|
|
header.payload_size =
|
|
static_cast<uint32_t>(capture_buffers->nv12_frame.size());
|
|
|
|
std::vector<uint8_t> response(sizeof(header) +
|
|
capture_buffers->nv12_frame.size());
|
|
std::memcpy(response.data(), &header, sizeof(header));
|
|
if (!capture_buffers->nv12_frame.empty()) {
|
|
std::memcpy(response.data() + sizeof(header),
|
|
capture_buffers->nv12_frame.data(),
|
|
capture_buffers->nv12_frame.size());
|
|
}
|
|
return response;
|
|
}
|
|
|
|
void CloseSecureDesktopSharedCaptureResourcesLocked(
|
|
SecureSharedCaptureState* capture_state) {
|
|
if (capture_state == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (capture_state->frame_view != nullptr) {
|
|
UnmapViewOfFile(capture_state->frame_view);
|
|
capture_state->frame_view = nullptr;
|
|
}
|
|
if (capture_state->frame_ready_event != nullptr) {
|
|
CloseHandle(capture_state->frame_ready_event);
|
|
capture_state->frame_ready_event = nullptr;
|
|
}
|
|
if (capture_state->frame_mapping != nullptr) {
|
|
CloseHandle(capture_state->frame_mapping);
|
|
capture_state->frame_mapping = nullptr;
|
|
}
|
|
capture_state->frame_view_size = 0;
|
|
}
|
|
|
|
void SecureDesktopSharedCaptureThread(
|
|
SecureSharedCaptureState* capture_state) {
|
|
if (capture_state == nullptr) {
|
|
return;
|
|
}
|
|
|
|
SecureCaptureRequest request;
|
|
uint8_t* frame_view = nullptr;
|
|
{
|
|
std::lock_guard<std::mutex> lock(capture_state->mutex);
|
|
request = capture_state->request;
|
|
frame_view = capture_state->frame_view;
|
|
}
|
|
|
|
if (frame_view == nullptr || request.width <= 0 || request.height <= 0) {
|
|
return;
|
|
}
|
|
|
|
const int interval_ms =
|
|
request.fps > 0 ? (std::max)(1, 1000 / request.fps) : 33;
|
|
const size_t nv12_size =
|
|
static_cast<size_t>(request.width) * request.height * 3 / 2;
|
|
std::vector<uint8_t> nv12_frame(nv12_size);
|
|
|
|
ScopedDesktopHandle desktop;
|
|
if (!EnsureThreadInteractiveDesktopForStage(request.interactive_stage,
|
|
&desktop.handle)) {
|
|
LOG_ERROR("Secure shared capture desktop switch failed, error={}",
|
|
GetLastError());
|
|
return;
|
|
}
|
|
|
|
HDC screen_dc = GetDC(nullptr);
|
|
if (screen_dc == nullptr) {
|
|
LOG_ERROR("Secure shared capture GetDC failed, error={}", GetLastError());
|
|
return;
|
|
}
|
|
|
|
HDC mem_dc = CreateCompatibleDC(screen_dc);
|
|
if (mem_dc == nullptr) {
|
|
LOG_ERROR("Secure shared capture CreateCompatibleDC failed, error={}",
|
|
GetLastError());
|
|
ReleaseDC(nullptr, screen_dc);
|
|
return;
|
|
}
|
|
|
|
BITMAPINFO bmi{};
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = request.width;
|
|
bmi.bmiHeader.biHeight = -request.height;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
void* bits = nullptr;
|
|
HBITMAP dib =
|
|
CreateDIBSection(mem_dc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
|
if (dib == nullptr || bits == nullptr) {
|
|
LOG_ERROR("Secure shared capture CreateDIBSection failed, error={}",
|
|
GetLastError());
|
|
DeleteDC(mem_dc);
|
|
ReleaseDC(nullptr, screen_dc);
|
|
return;
|
|
}
|
|
|
|
HGDIOBJ old_bitmap = SelectObject(mem_dc, dib);
|
|
while (!capture_state->stop_requested.load(std::memory_order_relaxed)) {
|
|
const auto frame_started = std::chrono::steady_clock::now();
|
|
if (BitBlt(mem_dc, 0, 0, request.width, request.height, screen_dc,
|
|
request.left, request.top, SRCCOPY | CAPTUREBLT)) {
|
|
if (request.show_cursor) {
|
|
CURSORINFO cursor_info{};
|
|
cursor_info.cbSize = sizeof(CURSORINFO);
|
|
if (GetCursorInfo(&cursor_info) &&
|
|
cursor_info.flags == CURSOR_SHOWING &&
|
|
cursor_info.hCursor != nullptr) {
|
|
const int cursor_x = cursor_info.ptScreenPos.x - request.left;
|
|
const int cursor_y = cursor_info.ptScreenPos.y - request.top;
|
|
if (cursor_x >= -64 && cursor_y >= -64 &&
|
|
cursor_x < request.width + 64 &&
|
|
cursor_y < request.height + 64) {
|
|
DrawIconEx(mem_dc, cursor_x, cursor_y, cursor_info.hCursor, 0, 0,
|
|
0, nullptr, DI_NORMAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
const int convert_result = libyuv::ARGBToNV12(
|
|
static_cast<const uint8_t*>(bits), request.width * 4,
|
|
nv12_frame.data(), request.width,
|
|
nv12_frame.data() + request.width * request.height, request.width,
|
|
request.width, request.height);
|
|
if (convert_result == 0) {
|
|
auto* header =
|
|
reinterpret_cast<crossdesk::CrossDeskSecureDesktopSharedFrameHeader*>(
|
|
frame_view);
|
|
uint8_t* payload = frame_view + sizeof(*header);
|
|
header->writing = 1;
|
|
MemoryBarrier();
|
|
header->magic = crossdesk::kCrossDeskSecureDesktopFrameMagic;
|
|
header->version = crossdesk::kCrossDeskSecureDesktopFrameVersion;
|
|
header->left = request.left;
|
|
header->top = request.top;
|
|
header->width = static_cast<uint32_t>(request.width);
|
|
header->height = static_cast<uint32_t>(request.height);
|
|
header->payload_size = static_cast<uint32_t>(nv12_frame.size());
|
|
std::memcpy(payload, nv12_frame.data(), nv12_frame.size());
|
|
header->sequence = ++capture_state->sequence;
|
|
MemoryBarrier();
|
|
header->writing = 0;
|
|
SetEvent(capture_state->frame_ready_event);
|
|
}
|
|
} else {
|
|
LOG_WARN("Secure shared capture BitBlt failed, error={}", GetLastError());
|
|
}
|
|
|
|
const auto elapsed_ms =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::steady_clock::now() - frame_started)
|
|
.count();
|
|
if (elapsed_ms < interval_ms) {
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(interval_ms - elapsed_ms));
|
|
}
|
|
}
|
|
|
|
SelectObject(mem_dc, old_bitmap);
|
|
DeleteObject(dib);
|
|
DeleteDC(mem_dc);
|
|
ReleaseDC(nullptr, screen_dc);
|
|
}
|
|
|
|
std::vector<uint8_t> StopSecureDesktopSharedCapture(
|
|
SecureSharedCaptureState* capture_state) {
|
|
if (capture_state == nullptr) {
|
|
return BuildTextResponseBytes(BuildErrorJson("invalid_capture_state"));
|
|
}
|
|
|
|
std::thread thread_to_join;
|
|
{
|
|
std::lock_guard<std::mutex> lock(capture_state->mutex);
|
|
capture_state->stop_requested.store(true, std::memory_order_relaxed);
|
|
if (capture_state->frame_ready_event != nullptr) {
|
|
SetEvent(capture_state->frame_ready_event);
|
|
}
|
|
if (capture_state->capture_thread.joinable()) {
|
|
thread_to_join = std::move(capture_state->capture_thread);
|
|
}
|
|
}
|
|
|
|
if (thread_to_join.joinable()) {
|
|
thread_to_join.join();
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(capture_state->mutex);
|
|
CloseSecureDesktopSharedCaptureResourcesLocked(capture_state);
|
|
capture_state->stop_requested.store(false, std::memory_order_relaxed);
|
|
capture_state->sequence = 0;
|
|
}
|
|
|
|
return BuildTextResponseBytes("{\"ok\":true,\"shared_capture\":\"stopped\"}");
|
|
}
|
|
|
|
std::vector<uint8_t> StartSecureDesktopSharedCapture(
|
|
const SecureCaptureRequest& request,
|
|
SecureSharedCaptureState* capture_state) {
|
|
if (capture_state == nullptr) {
|
|
return BuildTextResponseBytes(BuildErrorJson("invalid_capture_state"));
|
|
}
|
|
|
|
StopSecureDesktopSharedCapture(capture_state);
|
|
|
|
const size_t payload_size =
|
|
static_cast<size_t>(request.width) * request.height * 3 / 2;
|
|
const size_t mapping_size =
|
|
sizeof(crossdesk::CrossDeskSecureDesktopSharedFrameHeader) + payload_size;
|
|
if (payload_size == 0 || mapping_size > MAXDWORD) {
|
|
return BuildTextResponseBytes(BuildErrorJson("invalid_capture_size"));
|
|
}
|
|
|
|
PipeSecurityAttributes security_attributes;
|
|
SECURITY_ATTRIBUTES* attributes = nullptr;
|
|
if (security_attributes.Initialize()) {
|
|
attributes = security_attributes.get();
|
|
}
|
|
|
|
const std::wstring mapping_name =
|
|
crossdesk::GetCrossDeskSecureDesktopFrameMappingName(
|
|
capture_state->session_id);
|
|
const std::wstring event_name =
|
|
crossdesk::GetCrossDeskSecureDesktopFrameReadyEventName(
|
|
capture_state->session_id);
|
|
|
|
HANDLE frame_mapping =
|
|
CreateFileMappingW(INVALID_HANDLE_VALUE, attributes, PAGE_READWRITE, 0,
|
|
static_cast<DWORD>(mapping_size),
|
|
mapping_name.c_str());
|
|
if (frame_mapping == nullptr) {
|
|
return BuildTextResponseBytes(
|
|
BuildErrorJson("create_frame_mapping_failed", GetLastError()));
|
|
}
|
|
|
|
auto* frame_view = static_cast<uint8_t*>(
|
|
MapViewOfFile(frame_mapping, FILE_MAP_ALL_ACCESS, 0, 0, mapping_size));
|
|
if (frame_view == nullptr) {
|
|
const DWORD error = GetLastError();
|
|
CloseHandle(frame_mapping);
|
|
return BuildTextResponseBytes(BuildErrorJson("map_frame_view_failed",
|
|
error));
|
|
}
|
|
|
|
HANDLE frame_ready_event =
|
|
CreateEventW(attributes, FALSE, FALSE, event_name.c_str());
|
|
if (frame_ready_event == nullptr) {
|
|
const DWORD error = GetLastError();
|
|
UnmapViewOfFile(frame_view);
|
|
CloseHandle(frame_mapping);
|
|
return BuildTextResponseBytes(
|
|
BuildErrorJson("create_frame_event_failed", error));
|
|
}
|
|
|
|
std::memset(frame_view, 0, mapping_size);
|
|
auto* header =
|
|
reinterpret_cast<crossdesk::CrossDeskSecureDesktopSharedFrameHeader*>(
|
|
frame_view);
|
|
header->magic = crossdesk::kCrossDeskSecureDesktopFrameMagic;
|
|
header->version = crossdesk::kCrossDeskSecureDesktopFrameVersion;
|
|
header->left = request.left;
|
|
header->top = request.top;
|
|
header->width = static_cast<uint32_t>(request.width);
|
|
header->height = static_cast<uint32_t>(request.height);
|
|
header->buffer_size = static_cast<uint32_t>(payload_size);
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(capture_state->mutex);
|
|
capture_state->request = request;
|
|
capture_state->frame_mapping = frame_mapping;
|
|
capture_state->frame_ready_event = frame_ready_event;
|
|
capture_state->frame_view = frame_view;
|
|
capture_state->frame_view_size = mapping_size;
|
|
capture_state->sequence = 0;
|
|
capture_state->stop_requested.store(false, std::memory_order_relaxed);
|
|
capture_state->capture_thread =
|
|
std::thread(SecureDesktopSharedCaptureThread, capture_state);
|
|
}
|
|
|
|
Json json;
|
|
json["ok"] = true;
|
|
json["shared_capture"] = "started";
|
|
json["width"] = request.width;
|
|
json["height"] = request.height;
|
|
json["fps"] = request.fps;
|
|
return BuildTextResponseBytes(json.dump());
|
|
}
|
|
|
|
std::vector<uint8_t> HandleSecureInputHelperCommand(
|
|
const std::string& command, SecureCaptureBuffers* capture_buffers,
|
|
SecureSharedCaptureState* capture_state) {
|
|
if (command == "ping") {
|
|
return BuildTextResponseBytes("{\"ok\":true,\"reply\":\"pong\"}");
|
|
}
|
|
|
|
if (command == crossdesk::kCrossDeskSecureInputCaptureStopCommand) {
|
|
return StopSecureDesktopSharedCapture(capture_state);
|
|
}
|
|
|
|
SecureCaptureRequest capture_start_request;
|
|
if (ParseSecureInputCaptureStartCommand(command, &capture_start_request)) {
|
|
return StartSecureDesktopSharedCapture(capture_start_request,
|
|
capture_state);
|
|
}
|
|
|
|
int key_code = 0;
|
|
bool is_down = false;
|
|
uint32_t scan_code = 0;
|
|
bool extended = false;
|
|
std::string interactive_stage;
|
|
if (ParseSecureInputKeyboardCommand(command, &key_code, &is_down, &scan_code,
|
|
&extended, &interactive_stage)) {
|
|
const InputInjectionResult inject_result =
|
|
InjectKeyboardInput(key_code, is_down, scan_code, extended,
|
|
interactive_stage);
|
|
if (!inject_result.ok) {
|
|
LOG_WARN(
|
|
"Secure input helper input failed for key_code={}, is_down={}, "
|
|
"scan_code={}, extended={}, error='{}', stage='{}', target='{}', "
|
|
"current='{}', code={}",
|
|
key_code, is_down, scan_code, extended, inject_result.error,
|
|
interactive_stage, inject_result.desktop.target_desktop,
|
|
inject_result.desktop.current_desktop, inject_result.error_code);
|
|
return BuildTextResponseBytes(BuildInputFailureJson(inject_result).dump());
|
|
}
|
|
|
|
Json json;
|
|
json["ok"] = true;
|
|
json["injected"] = "keyboard";
|
|
json["key_code"] = key_code;
|
|
json["is_down"] = is_down;
|
|
json["scan_code"] = scan_code;
|
|
json["extended"] = extended;
|
|
json["stage"] = interactive_stage;
|
|
json["desktop"] = WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
return BuildTextResponseBytes(json.dump());
|
|
}
|
|
|
|
SecureMouseRequest mouse_request;
|
|
if (ParseSecureInputMouseCommand(command, &mouse_request)) {
|
|
const InputInjectionResult inject_result = InjectMouseInput(mouse_request);
|
|
if (!inject_result.ok) {
|
|
LOG_WARN(
|
|
"Secure input helper input failed for mouse x={}, y={}, "
|
|
"wheel={}, flag={}, error='{}', stage='{}', target='{}', "
|
|
"current='{}', code={}",
|
|
mouse_request.x, mouse_request.y, mouse_request.wheel,
|
|
mouse_request.flag, inject_result.error,
|
|
mouse_request.interactive_stage, inject_result.desktop.target_desktop,
|
|
inject_result.desktop.current_desktop, inject_result.error_code);
|
|
return BuildTextResponseBytes(BuildInputFailureJson(inject_result).dump());
|
|
}
|
|
|
|
Json json;
|
|
json["ok"] = true;
|
|
json["injected"] = "mouse";
|
|
json["x"] = mouse_request.x;
|
|
json["y"] = mouse_request.y;
|
|
json["wheel"] = mouse_request.wheel;
|
|
json["flag"] = mouse_request.flag;
|
|
json["stage"] = mouse_request.interactive_stage;
|
|
json["desktop"] = WideToUtf8(GetCurrentThreadDesktopNameW());
|
|
return BuildTextResponseBytes(json.dump());
|
|
}
|
|
|
|
SecureCaptureRequest capture_request;
|
|
if (ParseSecureInputCaptureCommand(command, &capture_request)) {
|
|
return CaptureSecureDesktopFrame(capture_request, capture_buffers);
|
|
}
|
|
|
|
return BuildTextResponseBytes(BuildErrorJson("unknown_command"));
|
|
}
|
|
|
|
void HandleSecureInputHelperPipeClient(
|
|
HANDLE pipe, HANDLE event_handle,
|
|
std::shared_ptr<SecureSharedCaptureState> capture_state) {
|
|
SecureCaptureBuffers capture_buffers;
|
|
char buffer[1024] = {0};
|
|
DWORD bytes_read = 0;
|
|
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) &&
|
|
bytes_read > 0) {
|
|
std::vector<uint8_t> response = HandleSecureInputHelperCommand(
|
|
std::string(buffer, buffer + bytes_read), &capture_buffers,
|
|
capture_state.get());
|
|
DWORD bytes_written = 0;
|
|
if (!response.empty()) {
|
|
WriteFile(pipe, response.data(), static_cast<DWORD>(response.size()),
|
|
&bytes_written, nullptr);
|
|
FlushFileBuffers(pipe);
|
|
}
|
|
}
|
|
|
|
DisconnectNamedPipe(pipe);
|
|
if (event_handle != nullptr) {
|
|
CloseHandle(event_handle);
|
|
}
|
|
CloseHandle(pipe);
|
|
}
|
|
|
|
void SecureInputHelperIpcServerLoop(HANDLE stop_event, DWORD session_id) {
|
|
auto capture_state = std::make_shared<SecureSharedCaptureState>();
|
|
capture_state->session_id = session_id;
|
|
|
|
PipeSecurityAttributes security_attributes;
|
|
SECURITY_ATTRIBUTES* pipe_attributes = nullptr;
|
|
if (security_attributes.Initialize()) {
|
|
pipe_attributes = security_attributes.get();
|
|
}
|
|
|
|
std::wstring pipe_name =
|
|
crossdesk::GetCrossDeskSecureInputHelperPipeName(session_id);
|
|
while (stop_event == nullptr ||
|
|
WaitForSingleObject(stop_event, 0) != WAIT_OBJECT_0) {
|
|
HANDLE pipe = CreateNamedPipeW(
|
|
pipe_name.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
crossdesk::kCrossDeskSecureInputPipeBufferBytes, 4096, 0,
|
|
pipe_attributes);
|
|
if (pipe == INVALID_HANDLE_VALUE) {
|
|
LOG_ERROR("CreateNamedPipeW failed in secure input helper, error={}",
|
|
GetLastError());
|
|
if (stop_event != nullptr) {
|
|
WaitForSingleObject(stop_event, 1000);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
OVERLAPPED overlapped{};
|
|
overlapped.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
|
if (overlapped.hEvent == nullptr) {
|
|
LOG_ERROR("CreateEventW failed in secure input IPC loop, error={}",
|
|
GetLastError());
|
|
CloseHandle(pipe);
|
|
break;
|
|
}
|
|
|
|
BOOL connected = ConnectNamedPipe(pipe, &overlapped);
|
|
DWORD connect_error = connected ? ERROR_SUCCESS : GetLastError();
|
|
if (connected) {
|
|
SetEvent(overlapped.hEvent);
|
|
}
|
|
if (!connected && connect_error == ERROR_PIPE_CONNECTED) {
|
|
SetEvent(overlapped.hEvent);
|
|
connected = TRUE;
|
|
}
|
|
if (!connected && connect_error != ERROR_IO_PENDING) {
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
continue;
|
|
}
|
|
|
|
if (!connected) {
|
|
if (stop_event != nullptr) {
|
|
HANDLE wait_handles[] = {stop_event, overlapped.hEvent};
|
|
DWORD wait_result =
|
|
WaitForMultipleObjects(2, wait_handles, FALSE, INFINITE);
|
|
if (wait_result == WAIT_OBJECT_0) {
|
|
CancelIoEx(pipe, &overlapped);
|
|
CloseHandle(overlapped.hEvent);
|
|
CloseHandle(pipe);
|
|
break;
|
|
}
|
|
} else {
|
|
WaitForSingleObject(overlapped.hEvent, INFINITE);
|
|
}
|
|
}
|
|
|
|
std::thread(HandleSecureInputHelperPipeClient, pipe, overlapped.hEvent,
|
|
capture_state)
|
|
.detach();
|
|
}
|
|
|
|
StopSecureDesktopSharedCapture(capture_state.get());
|
|
}
|
|
|
|
void PrintUsage() {
|
|
std::cout << "CrossDesk session helper\n"
|
|
<< " --session-helper Run helper loop\n"
|
|
<< " --secure-input-helper Run Winlogon input helper\n"
|
|
<< " --session-id <id> Expected target session id\n"
|
|
<< " --stop-event <name> Global stop event name\n";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char* argv[]) {
|
|
EnablePerMonitorDpiAwareness();
|
|
|
|
InitializeHelperLogger();
|
|
|
|
bool run_helper = false;
|
|
bool run_secure_input_helper = false;
|
|
DWORD expected_session_id = 0xFFFFFFFF;
|
|
std::wstring stop_event_name;
|
|
for (int index = 1; index < argc; ++index) {
|
|
std::string argument = argv[index];
|
|
if (argument == "--session-helper") {
|
|
run_helper = true;
|
|
continue;
|
|
}
|
|
if (argument == "--secure-input-helper") {
|
|
run_secure_input_helper = true;
|
|
continue;
|
|
}
|
|
if (argument == "--session-id" && index + 1 < argc) {
|
|
expected_session_id = static_cast<DWORD>(std::stoul(argv[++index]));
|
|
continue;
|
|
}
|
|
if (argument == "--stop-event" && index + 1 < argc) {
|
|
stop_event_name = Utf8ToWide(argv[++index]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!run_helper && !run_secure_input_helper) {
|
|
PrintUsage();
|
|
return 0;
|
|
}
|
|
|
|
DWORD current_session_id = 0xFFFFFFFF;
|
|
ProcessIdToSessionId(GetCurrentProcessId(), ¤t_session_id);
|
|
HANDLE stop_event = nullptr;
|
|
if (!stop_event_name.empty()) {
|
|
stop_event = OpenEventW(SYNCHRONIZE, FALSE, stop_event_name.c_str());
|
|
if (stop_event == nullptr) {
|
|
LOG_ERROR("OpenEventW failed for stop event, error={}", GetLastError());
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if (run_secure_input_helper) {
|
|
LOG_INFO(
|
|
"Secure input helper starting: pid={}, current_session_id={}, "
|
|
"expected_session_id={}",
|
|
GetCurrentProcessId(), current_session_id, expected_session_id);
|
|
if (expected_session_id != 0xFFFFFFFF &&
|
|
expected_session_id != current_session_id) {
|
|
LOG_WARN("Secure input helper session mismatch: expected={}, current={}",
|
|
expected_session_id, current_session_id);
|
|
}
|
|
|
|
LOG_INFO("Secure input helper initial desktop: '{}'",
|
|
WideToUtf8(GetCurrentThreadDesktopNameW()));
|
|
SecureInputHelperIpcServerLoop(stop_event, current_session_id);
|
|
|
|
if (stop_event != nullptr) {
|
|
CloseHandle(stop_event);
|
|
}
|
|
LOG_INFO("Secure input helper exiting: session_id={}", current_session_id);
|
|
return 0;
|
|
}
|
|
|
|
LOG_INFO(
|
|
"Session helper starting: pid={}, current_session_id={}, "
|
|
"expected_session_id={}",
|
|
GetCurrentProcessId(), current_session_id, expected_session_id);
|
|
|
|
HelperState helper_state;
|
|
helper_state.session_id = current_session_id;
|
|
helper_state.process_id = GetCurrentProcessId();
|
|
helper_state.started_at_tick = GetTickCount64();
|
|
UpdateHelperState(&helper_state);
|
|
|
|
std::thread ipc_thread(HelperIpcServerLoop, stop_event, current_session_id,
|
|
&helper_state);
|
|
|
|
std::string last_desktop_name;
|
|
bool last_lock_app = false;
|
|
bool last_logon_ui = false;
|
|
bool last_secure_desktop = false;
|
|
bool last_session_locked = false;
|
|
std::string last_stage;
|
|
while (true) {
|
|
UpdateHelperState(&helper_state);
|
|
|
|
std::string desktop_name;
|
|
bool session_locked = false;
|
|
bool input_desktop_available = false;
|
|
bool lock_app_visible = false;
|
|
bool logon_ui_running = false;
|
|
bool secure_desktop_active = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(helper_state.mutex);
|
|
desktop_name = helper_state.input_desktop_name;
|
|
session_locked = helper_state.session_locked;
|
|
input_desktop_available = helper_state.input_desktop_available;
|
|
lock_app_visible = helper_state.lock_app_visible;
|
|
logon_ui_running = helper_state.logon_ui_visible;
|
|
secure_desktop_active = helper_state.secure_desktop_active;
|
|
}
|
|
const bool credential_ui_visible =
|
|
logon_ui_running || (session_locked && !input_desktop_available);
|
|
std::string stage = DetermineInteractiveStage(
|
|
lock_app_visible, credential_ui_visible, secure_desktop_active);
|
|
|
|
if (desktop_name != last_desktop_name ||
|
|
session_locked != last_session_locked ||
|
|
lock_app_visible != last_lock_app ||
|
|
logon_ui_running != last_logon_ui ||
|
|
secure_desktop_active != last_secure_desktop || stage != last_stage) {
|
|
LOG_INFO(
|
|
"Session helper state: session_id={}, input_desktop='{}', "
|
|
"session_locked={}, lock_app_visible={}, logon_ui_running={}, "
|
|
"secure_desktop_active={}, stage={}",
|
|
current_session_id, desktop_name, session_locked, lock_app_visible,
|
|
logon_ui_running, secure_desktop_active, stage);
|
|
last_desktop_name = desktop_name;
|
|
last_session_locked = session_locked;
|
|
last_lock_app = lock_app_visible;
|
|
last_logon_ui = logon_ui_running;
|
|
last_secure_desktop = secure_desktop_active;
|
|
last_stage = stage;
|
|
}
|
|
|
|
DWORD wait_result = stop_event != nullptr
|
|
? WaitForSingleObject(stop_event, 1000)
|
|
: WAIT_TIMEOUT;
|
|
if (wait_result == WAIT_OBJECT_0) {
|
|
break;
|
|
}
|
|
if (wait_result != WAIT_TIMEOUT && wait_result != WAIT_FAILED) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ipc_thread.joinable()) {
|
|
ipc_thread.join();
|
|
}
|
|
|
|
if (stop_event != nullptr) {
|
|
CloseHandle(stop_event);
|
|
}
|
|
|
|
LOG_INFO("Session helper exiting: session_id={}", current_session_id);
|
|
return 0;
|
|
}
|