diff --git a/src/common/windows_input_marker.h b/src/common/windows_input_marker.h new file mode 100644 index 0000000..c48cb8c --- /dev/null +++ b/src/common/windows_input_marker.h @@ -0,0 +1,15 @@ +#ifndef CROSSDESK_COMMON_WINDOWS_INPUT_MARKER_H_ +#define CROSSDESK_COMMON_WINDOWS_INPUT_MARKER_H_ + +#include + +namespace crossdesk { + +// SendInput copies dwExtraInfo into KBDLLHOOKSTRUCT. Tag CrossDesk-generated +// keyboard input so the controller hook can ignore only its own injections +// while still accepting input from accessibility tools or remote sessions. +inline constexpr std::uintptr_t kInjectedKeyboardInputMarker = 0x4353444B; + +} // namespace crossdesk + +#endif // CROSSDESK_COMMON_WINDOWS_INPUT_MARKER_H_ diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.cpp b/src/device_controller/keyboard/windows/keyboard_capturer.cpp index 8cfb4f3..a59599a 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/windows/keyboard_capturer.cpp @@ -1,36 +1,17 @@ #include "keyboard_capturer.h" +#include + #include "rd_log.h" +#include "windows_input_marker.h" namespace crossdesk { +namespace { -static OnKeyAction g_on_key_action = nullptr; -static void* g_user_ptr = nullptr; +constexpr wchar_t kRawInputWindowClassName[] = + L"CrossDeskKeyboardRawInputWindow"; -static int NormalizeModifierVkCode(const KBDLLHOOKSTRUCT* kb_data) { - if (kb_data == nullptr) { - return -1; - } - - if (kb_data->vkCode != VK_SHIFT && kb_data->vkCode != VK_CONTROL && - kb_data->vkCode != VK_MENU) { - return static_cast(kb_data->vkCode); - } - - UINT scan_code = static_cast(kb_data->scanCode & 0xFF); - if ((kb_data->flags & LLKHF_EXTENDED) != 0) { - scan_code |= 0xE000; - } - - const UINT normalized_vk = MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX); - if (normalized_vk != 0) { - return static_cast(normalized_vk); - } - - return static_cast(kb_data->vkCode); -} - -static bool PreferSideSpecificVkInjection(int key_code) { +bool PreferSideSpecificVkInjection(int key_code) { switch (key_code) { case VK_LSHIFT: case VK_RSHIFT: @@ -46,58 +27,247 @@ static bool PreferSideSpecificVkInjection(int key_code) { } } -LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { - if (nCode == HC_ACTION && g_on_key_action) { - KBDLLHOOKSTRUCT* kbData = reinterpret_cast(lParam); - if ((kbData->flags & LLKHF_INJECTED) != 0) { - return CallNextHookEx(NULL, nCode, wParam, lParam); - } - const int key_code = NormalizeModifierVkCode(kbData); - - if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { - g_on_key_action(key_code, true, kbData->scanCode, - (kbData->flags & LLKHF_EXTENDED) != 0, g_user_ptr); - } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { - g_on_key_action(key_code, false, kbData->scanCode, - (kbData->flags & LLKHF_EXTENDED) != 0, g_user_ptr); - } - return 1; - } - - return CallNextHookEx(NULL, nCode, wParam, lParam); -} +} // namespace KeyboardCapturer::KeyboardCapturer() {} -KeyboardCapturer::~KeyboardCapturer() {} +KeyboardCapturer::~KeyboardCapturer() { Unhook(); } int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) { - g_on_key_action = on_key_action; - g_user_ptr = user_ptr; + if (capture_thread_.joinable()) { + return 0; + } - keyboard_hook_ = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0); - if (!keyboard_hook_) { - LOG_ERROR("Failed to install keyboard hook"); + on_key_action_ = on_key_action; + user_ptr_ = user_ptr; + { + std::lock_guard lock(capture_state_mutex_); + capture_thread_id_ = 0; + capture_start_complete_ = false; + capture_start_succeeded_ = false; + } + + capture_thread_ = std::thread(&KeyboardCapturer::RawInputThreadMain, this); + + std::unique_lock lock(capture_state_mutex_); + capture_start_condition_.wait( + lock, [this] { return capture_start_complete_; }); + const bool capture_started = capture_start_succeeded_; + lock.unlock(); + + if (!capture_started) { + if (capture_thread_.joinable()) { + capture_thread_.join(); + } + on_key_action_ = nullptr; + user_ptr_ = nullptr; return -1; } return 0; } int KeyboardCapturer::Unhook() { - if (keyboard_hook_) { - g_on_key_action = nullptr; - g_user_ptr = nullptr; - UnhookWindowsHookEx(keyboard_hook_); - keyboard_hook_ = nullptr; + DWORD capture_thread_id = 0; + { + std::lock_guard lock(capture_state_mutex_); + capture_thread_id = capture_thread_id_; } + if (capture_thread_id != 0 && + !PostThreadMessageW(capture_thread_id, WM_QUIT, 0, 0)) { + LOG_WARN("Failed to stop keyboard raw input thread, thread_id={}, error={}", + capture_thread_id, GetLastError()); + } + if (capture_thread_.joinable()) { + capture_thread_.join(); + } + + on_key_action_ = nullptr; + user_ptr_ = nullptr; return 0; } -// apply remote keyboard commands to the local machine +void KeyboardCapturer::RawInputThreadMain() { + const DWORD thread_id = GetCurrentThreadId(); + + MSG message{}; + PeekMessageW(&message, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + const bool capture_started = CreateRawInputWindow(); + { + std::lock_guard lock(capture_state_mutex_); + capture_thread_id_ = thread_id; + capture_start_succeeded_ = capture_started; + capture_start_complete_ = true; + } + capture_start_condition_.notify_one(); + + if (!capture_started) { + std::lock_guard lock(capture_state_mutex_); + capture_thread_id_ = 0; + return; + } + + LOG_INFO("Keyboard raw input capture started, thread_id={}", thread_id); + while (true) { + const BOOL get_message_result = GetMessageW(&message, nullptr, 0, 0); + if (get_message_result <= 0) { + if (get_message_result < 0) { + LOG_WARN("Keyboard raw input message loop failed, thread_id={}, " + "error={}", + thread_id, GetLastError()); + } + break; + } + TranslateMessage(&message); + DispatchMessageW(&message); + } + + DestroyRawInputWindow(); + { + std::lock_guard lock(capture_state_mutex_); + capture_thread_id_ = 0; + } + LOG_INFO("Keyboard raw input capture stopped, thread_id={}", thread_id); +} + +bool KeyboardCapturer::CreateRawInputWindow() { + const HINSTANCE instance = GetModuleHandleW(nullptr); + WNDCLASSEXW window_class{}; + window_class.cbSize = sizeof(window_class); + window_class.lpfnWndProc = &KeyboardCapturer::RawInputWindowProc; + window_class.hInstance = instance; + window_class.lpszClassName = kRawInputWindowClassName; + + if (RegisterClassExW(&window_class) == 0) { + const DWORD error = GetLastError(); + if (error != ERROR_CLASS_ALREADY_EXISTS) { + LOG_WARN("Failed to register keyboard raw input window class, error={}", + error); + return false; + } + } + + raw_input_window_ = CreateWindowExW( + 0, kRawInputWindowClassName, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, + instance, this); + if (!raw_input_window_) { + LOG_WARN("Failed to create keyboard raw input window, error={}", + GetLastError()); + return false; + } + + RAWINPUTDEVICE keyboard_device{}; + keyboard_device.usUsagePage = HID_USAGE_PAGE_GENERIC; + keyboard_device.usUsage = HID_USAGE_GENERIC_KEYBOARD; + keyboard_device.dwFlags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK; + keyboard_device.hwndTarget = raw_input_window_; + if (!RegisterRawInputDevices(&keyboard_device, 1, + sizeof(keyboard_device))) { + LOG_WARN("Failed to register keyboard raw input, error={}", GetLastError()); + DestroyWindow(raw_input_window_); + raw_input_window_ = nullptr; + return false; + } + + raw_input_registered_ = true; + return true; +} + +void KeyboardCapturer::DestroyRawInputWindow() { + if (raw_input_registered_) { + RAWINPUTDEVICE keyboard_device{}; + keyboard_device.usUsagePage = HID_USAGE_PAGE_GENERIC; + keyboard_device.usUsage = HID_USAGE_GENERIC_KEYBOARD; + keyboard_device.dwFlags = RIDEV_REMOVE; + keyboard_device.hwndTarget = nullptr; + if (!RegisterRawInputDevices(&keyboard_device, 1, + sizeof(keyboard_device))) { + LOG_WARN("Failed to unregister keyboard raw input, error={}", + GetLastError()); + } + raw_input_registered_ = false; + } + if (raw_input_window_) { + DestroyWindow(raw_input_window_); + raw_input_window_ = nullptr; + } +} + +LRESULT CALLBACK KeyboardCapturer::RawInputWindowProc( + HWND window, UINT message, WPARAM w_param, LPARAM l_param) { + KeyboardCapturer* capturer = reinterpret_cast( + GetWindowLongPtrW(window, GWLP_USERDATA)); + if (message == WM_NCCREATE) { + auto* create = reinterpret_cast(l_param); + capturer = static_cast(create->lpCreateParams); + SetWindowLongPtrW(window, GWLP_USERDATA, + reinterpret_cast(capturer)); + } else if (message == WM_INPUT && capturer) { + capturer->HandleRawInput(reinterpret_cast(l_param)); + } else if (message == WM_NCDESTROY) { + SetWindowLongPtrW(window, GWLP_USERDATA, 0); + } + return DefWindowProcW(window, message, w_param, l_param); +} + +void KeyboardCapturer::HandleRawInput(HRAWINPUT raw_input_handle) { + RAWINPUT input{}; + UINT input_size = sizeof(input); + const UINT bytes_read = + GetRawInputData(raw_input_handle, RID_INPUT, &input, &input_size, + sizeof(RAWINPUTHEADER)); + if (bytes_read == static_cast(-1) || + bytes_read < sizeof(RAWINPUTHEADER) || + input.header.dwType != RIM_TYPEKEYBOARD) { + return; + } + + const RAWKEYBOARD& keyboard = input.data.keyboard; + if (keyboard.VKey == 0xFF || + keyboard.ExtraInformation == + static_cast(kInjectedKeyboardInputMarker)) { + return; + } + + bool is_down = false; + if (keyboard.Message == WM_KEYDOWN || keyboard.Message == WM_SYSKEYDOWN) { + is_down = true; + } else if (keyboard.Message != WM_KEYUP && + keyboard.Message != WM_SYSKEYUP) { + return; + } + + const bool extended = (keyboard.Flags & (RI_KEY_E0 | RI_KEY_E1)) != 0; + UINT mapped_scan_code = keyboard.MakeCode; + if ((keyboard.Flags & RI_KEY_E0) != 0) { + mapped_scan_code |= 0xE000; + } else if ((keyboard.Flags & RI_KEY_E1) != 0) { + mapped_scan_code |= 0xE100; + } + if (mapped_scan_code == 0xE11D || mapped_scan_code == 0xE02A) { + return; + } + + int key_code = static_cast(keyboard.VKey); + if (key_code == VK_SHIFT || key_code == VK_CONTROL || key_code == VK_MENU) { + const UINT normalized = + MapVirtualKeyW(mapped_scan_code, MAPVK_VSC_TO_VK_EX); + if (normalized != 0) { + key_code = static_cast(normalized); + } + } + + if (on_key_action_) { + on_key_action_(key_code, is_down, keyboard.MakeCode, extended, user_ptr_); + } +} + +// Apply remote keyboard commands to the local machine. int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { INPUT input = {0}; input.type = INPUT_KEYBOARD; + input.ki.dwExtraInfo = + static_cast(kInjectedKeyboardInputMarker); const bool prefer_vk = PreferSideSpecificVkInjection(key_code); const UINT resolved_scan_code = @@ -113,7 +283,7 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } } else { - input.ki.wVk = (WORD)key_code; + input.ki.wVk = static_cast(key_code); if (prefer_vk && resolved_scan_code != 0) { input.ki.wScan = static_cast(resolved_scan_code & 0xFF); @@ -134,7 +304,7 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, input.ki.dwFlags |= KEYEVENTF_KEYUP; } - UINT sent = SendInput(1, &input, sizeof(INPUT)); + const UINT sent = SendInput(1, &input, sizeof(INPUT)); if (sent != 1) { LOG_WARN("SendInput failed for key_code={}, is_down={}, err={}", key_code, is_down, GetLastError()); diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.h b/src/device_controller/keyboard/windows/keyboard_capturer.h index f1c4a7b..9f64e0d 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.h +++ b/src/device_controller/keyboard/windows/keyboard_capturer.h @@ -9,6 +9,10 @@ #include +#include +#include +#include + #include "device_controller.h" namespace crossdesk { @@ -26,8 +30,25 @@ class KeyboardCapturer : public DeviceController { bool extended = false); private: - HHOOK keyboard_hook_ = nullptr; + static LRESULT CALLBACK RawInputWindowProc(HWND window, UINT message, + WPARAM w_param, LPARAM l_param); + + void RawInputThreadMain(); + bool CreateRawInputWindow(); + void DestroyRawInputWindow(); + void HandleRawInput(HRAWINPUT raw_input_handle); + + OnKeyAction on_key_action_ = nullptr; + void* user_ptr_ = nullptr; + HWND raw_input_window_ = nullptr; + bool raw_input_registered_ = false; + std::thread capture_thread_; + DWORD capture_thread_id_ = 0; + bool capture_start_complete_ = false; + bool capture_start_succeeded_ = false; + std::mutex capture_state_mutex_; + std::condition_variable capture_start_condition_; }; } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/src/gui/application/gui_application.cpp b/src/gui/application/gui_application.cpp index de77ba7..aa914a4 100644 --- a/src/gui/application/gui_application.cpp +++ b/src/gui/application/gui_application.cpp @@ -3042,9 +3042,8 @@ void GuiApplication::SendKeyInput(const std::string& text, bool pressed, controlled_remote_id_ = props->remote_id_; focused_remote_id_ = props->remote_id_; - // Native hooks see the same physical key before Slint does. When a native - // hook is active, forwarding the FocusScope callback as well would duplicate - // the event on platforms whose hook does not consume the local key. + // Native capture forwards the event independently. Slint keyboard events + // are used only by platforms whose native capture backend is unavailable. if (keyboard_capturer_is_started_ && !keyboard_capturer_uses_window_events_) { return; } diff --git a/src/gui/features/devices/session_device_manager.cpp b/src/gui/features/devices/session_device_manager.cpp index e2fa37e..7d4fd34 100644 --- a/src/gui/features/devices/session_device_manager.cpp +++ b/src/gui/features/devices/session_device_manager.cpp @@ -11,6 +11,7 @@ namespace crossdesk { namespace { constexpr uint64_t kCaptureResumeKeyFrameGapMs = 500; +constexpr size_t kMaxCapturedKeyboardInputs = 512; } // namespace @@ -256,18 +257,18 @@ int SessionDeviceManager::StartKeyboardCapturer() { [](int key_code, bool is_down, uint32_t scan_code, bool extended, void *user_ptr) { if (user_ptr) { - auto *runtime = static_cast(user_ptr); - runtime->keyboard_.SendKeyCommand(key_code, is_down, scan_code, - extended); + auto *devices = static_cast(user_ptr); + devices->QueueCapturedKeyboardInput(key_code, is_down, scan_code, + extended); } }, - &owner_); + this); if (hook_ret != 0) { owner_.keyboard_capturer_uses_window_events_ = true; LOG_WARN( "Start keyboard capturer failed, falling back to Slint keyboard events"); } else { - LOG_INFO("Start keyboard capturer with native hook"); + LOG_INFO("Start keyboard capturer with native input"); } return 0; } @@ -281,6 +282,7 @@ int SessionDeviceManager::StopKeyboardCapturer() { if (keyboard_capturer_) { keyboard_capturer_->Unhook(); + ClearCapturedKeyboardInput(); LOG_INFO("Stop keyboard capturer"); } return 0; @@ -410,6 +412,7 @@ void SessionDeviceManager::UpdateInteractions() { owner_.keyboard_capturer_is_started_ = true; } if (owner_.keyboard_capturer_is_started_) { + DrainCapturedKeyboardInput(); owner_.keyboard_.SendHeartbeat(false); } } else if (owner_.keyboard_capturer_is_started_) { @@ -421,6 +424,36 @@ void SessionDeviceManager::UpdateInteractions() { owner_.keyboard_.CheckRemoteTimeouts(); } +void SessionDeviceManager::QueueCapturedKeyboardInput(int key_code, + bool is_down, + uint32_t scan_code, + bool extended) { + std::lock_guard lock(captured_keyboard_inputs_mutex_); + if (captured_keyboard_inputs_.size() >= kMaxCapturedKeyboardInputs) { + captured_keyboard_inputs_.pop_front(); + LOG_WARN("Captured keyboard input queue overflow, dropping oldest event"); + } + captured_keyboard_inputs_.push_back( + CapturedKeyboardInput{key_code, is_down, scan_code, extended}); +} + +void SessionDeviceManager::DrainCapturedKeyboardInput() { + std::deque inputs; + { + std::lock_guard lock(captured_keyboard_inputs_mutex_); + inputs.swap(captured_keyboard_inputs_); + } + for (const CapturedKeyboardInput &input : inputs) { + owner_.keyboard_.SendKeyCommand(input.key_code, input.is_down, + input.scan_code, input.extended); + } +} + +void SessionDeviceManager::ClearCapturedKeyboardInput() { + std::lock_guard lock(captured_keyboard_inputs_mutex_); + captured_keyboard_inputs_.clear(); +} + bool SessionDeviceManager::SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { diff --git a/src/gui/features/devices/session_device_manager.h b/src/gui/features/devices/session_device_manager.h index 5db858d..145a0a3 100644 --- a/src/gui/features/devices/session_device_manager.h +++ b/src/gui/features/devices/session_device_manager.h @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include @@ -53,6 +55,18 @@ public: const std::vector &display_info_list() const; private: + struct CapturedKeyboardInput { + int key_code = 0; + bool is_down = false; + uint32_t scan_code = 0; + bool extended = false; + }; + + void QueueCapturedKeyboardInput(int key_code, bool is_down, + uint32_t scan_code, bool extended); + void DrainCapturedKeyboardInput(); + void ClearCapturedKeyboardInput(); + GuiRuntime &owner_; SDL_AudioStream *output_stream_ = nullptr; ScreenCapturerFactory *screen_capturer_factory_ = nullptr; @@ -63,6 +77,8 @@ private: MouseController *mouse_controller_ = nullptr; KeyboardCapturer *keyboard_capturer_ = nullptr; std::vector display_info_list_; + std::deque captured_keyboard_inputs_; + std::mutex captured_keyboard_inputs_mutex_; uint64_t last_frame_time_ = 0; std::string last_video_frame_stream_id_; }; diff --git a/src/gui/features/input/keyboard_controller.cpp b/src/gui/features/input/keyboard_controller.cpp index 4ee0c65..adac4a1 100644 --- a/src/gui/features/input/keyboard_controller.cpp +++ b/src/gui/features/input/keyboard_controller.cpp @@ -1,14 +1,14 @@ #include "features/input/keyboard_controller.h" +#include + #include #include +#include #include #include #include -#include -#include - #include "minirtc.h" #include "rd_log.h" #include "runtime/gui_runtime.h" @@ -45,8 +45,8 @@ int NormalizeWindowsModifierVk(int key_code, uint32_t scan_code, #endif } -void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t *scan_code_out, - bool *extended_out) { +void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t* scan_code_out, + bool* extended_out) { if (!scan_code_out || !extended_out) { return; } @@ -65,7 +65,7 @@ void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t *scan_code_out, #if _WIN32 constexpr uint32_t kSecureDesktopInputLogIntervalMs = 2000; -void LogSecureDesktopInputBlocked(uint32_t *last_tick, const char *stage) { +void LogSecureDesktopInputBlocked(uint32_t* last_tick, const char* stage) { const uint32_t now = static_cast(SDL_GetTicks()); if (*last_tick != 0 && now - *last_tick < kSecureDesktopInputLogIntervalMs) { return; @@ -77,8 +77,8 @@ void LogSecureDesktopInputBlocked(uint32_t *last_tick, const char *stage) { stage ? stage : ""); } -bool IsTransientSecureDesktopInputFailure(const nlohmann::json &response, - const RemoteAction &action) { +bool IsTransientSecureDesktopInputFailure(const nlohmann::json& response, + const RemoteAction& action) { return response.is_object() && response.value("error", std::string()) == "send_input_failed" && response.value("code", 0u) == ERROR_ACCESS_DENIED && @@ -87,9 +87,9 @@ bool IsTransientSecureDesktopInputFailure(const nlohmann::json &response, } #endif -} // namespace +} // namespace -KeyboardController::KeyboardController(GuiRuntime &owner) : owner_(owner) {} +KeyboardController::KeyboardController(GuiRuntime& owner) : owner_(owner) {} void KeyboardController::TrackPressedKey(int key_code, bool is_down, uint32_t scan_code, bool extended) { @@ -106,13 +106,13 @@ void KeyboardController::ForceReleasePressedKeys() { { std::lock_guard lock(pressed_keys_mutex_); pressed_keys.reserve(pressed_keys_.size()); - for (const auto &[_, key] : pressed_keys_) { + for (const auto& [_, key] : pressed_keys_) { pressed_keys.push_back(key); } pressed_keys_.clear(); } - for (const PressedKey &key : pressed_keys) { + for (const PressedKey& key : pressed_keys) { SendKeyCommand(key.key_code, false, key.scan_code, key.extended); } SendHeartbeat(true); @@ -130,7 +130,7 @@ void KeyboardController::SendHeartbeat(bool force) { { std::lock_guard lock(pressed_keys_mutex_); size_t index = 0; - for (const auto &[_, key] : pressed_keys_) { + for (const auto& [_, key] : pressed_keys_) { if (index >= kMaxKeyboardStateKeys) { LOG_WARN("Keyboard heartbeat truncated, pressed_keys={}", pressed_keys_.size()); @@ -223,9 +223,10 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down, action.k.flag = is_down ? KeyFlag::key_down : KeyFlag::key_up; if (!json.is_discarded() && IsTransientSecureDesktopInputFailure(json, action)) { - LOG_INFO("Secure desktop keyboard injection transient failure, " - "key_code={}, is_down={}, response={}", - key_code, is_down, response); + LOG_INFO( + "Secure desktop keyboard injection transient failure, " + "key_code={}, is_down={}, response={}", + key_code, is_down, response); return true; } @@ -245,15 +246,15 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down, extended); } -void KeyboardController::ApplyRemoteEvent(const std::string &remote_id, - const RemoteAction &action) { +void KeyboardController::ApplyRemoteEvent(const std::string& remote_id, + const RemoteAction& action) { const int key_code = static_cast(action.k.key_value); const bool is_down = action.k.flag == KeyFlag::key_down; const bool injected = InjectRemoteKey(key_code, is_down, action.k.scan_code, action.k.extended); std::lock_guard lock(remote_states_mutex_); - auto &state = remote_states_[remote_id]; + auto& state = remote_states_[remote_id]; state.last_seen_tick = static_cast(SDL_GetTicks()); if (is_down && injected) { state.pressed_keys[key_code] = @@ -263,13 +264,13 @@ void KeyboardController::ApplyRemoteEvent(const std::string &remote_id, } } -void KeyboardController::ApplyRemoteState(const std::string &remote_id, - const RemoteAction &action) { +void KeyboardController::ApplyRemoteState(const std::string& remote_id, + const RemoteAction& action) { std::vector keys_to_release; std::vector keys_to_press; { std::lock_guard lock(remote_states_mutex_); - auto &state = remote_states_[remote_id]; + auto& state = remote_states_[remote_id]; if (action.ks.seq != 0 && state.last_seq != 0 && static_cast(action.ks.seq - state.last_seq) <= 0) { return; @@ -283,24 +284,24 @@ void KeyboardController::ApplyRemoteState(const std::string &remote_id, const size_t count = (std::min)(action.ks.pressed_count, kMaxKeyboardStateKeys); for (size_t index = 0; index < count; ++index) { - const auto &key = action.ks.pressed_keys[index]; + const auto& key = action.ks.pressed_keys[index]; const int key_code = static_cast(key.key_value); desired_keys[key_code] = PressedKey{key_code, key.scan_code, key.extended}; } - for (const auto &[key_code, key] : state.pressed_keys) { + for (const auto& [key_code, key] : state.pressed_keys) { if (desired_keys.find(key_code) == desired_keys.end()) { keys_to_release.push_back(key); } } - for (const auto &[key_code, key] : desired_keys) { + for (const auto& [key_code, key] : desired_keys) { if (state.pressed_keys.find(key_code) == state.pressed_keys.end()) { keys_to_press.push_back(key); } } } - for (const PressedKey &key : keys_to_release) { + for (const PressedKey& key : keys_to_release) { if (InjectRemoteKey(key.key_code, false, key.scan_code, key.extended)) { std::lock_guard lock(remote_states_mutex_); const auto state_it = remote_states_.find(remote_id); @@ -309,7 +310,7 @@ void KeyboardController::ApplyRemoteState(const std::string &remote_id, } } } - for (const PressedKey &key : keys_to_press) { + for (const PressedKey& key : keys_to_press) { if (InjectRemoteKey(key.key_code, true, key.scan_code, key.extended)) { std::lock_guard lock(remote_states_mutex_); remote_states_[remote_id].pressed_keys[key.key_code] = key; @@ -317,8 +318,8 @@ void KeyboardController::ApplyRemoteState(const std::string &remote_id, } } -void KeyboardController::ReleaseRemotePressedKeys(const std::string &remote_id, - const char *reason) { +void KeyboardController::ReleaseRemotePressedKeys(const std::string& remote_id, + const char* reason) { std::vector keys_to_release; { std::lock_guard lock(remote_states_mutex_); @@ -326,7 +327,7 @@ void KeyboardController::ReleaseRemotePressedKeys(const std::string &remote_id, if (state_it == remote_states_.end()) { return; } - for (const auto &[_, key] : state_it->second.pressed_keys) { + for (const auto& [_, key] : state_it->second.pressed_keys) { keys_to_release.push_back(key); } remote_states_.erase(state_it); @@ -336,7 +337,7 @@ void KeyboardController::ReleaseRemotePressedKeys(const std::string &remote_id, LOG_WARN("Releasing {} remote keyboard keys for remote_id={}, reason={}", keys_to_release.size(), remote_id, reason ? reason : "unknown"); } - for (const PressedKey &key : keys_to_release) { + for (const PressedKey& key : keys_to_release) { InjectRemoteKey(key.key_code, false, key.scan_code, key.extended); } } @@ -346,7 +347,7 @@ void KeyboardController::CheckRemoteTimeouts() { std::vector timed_out_remotes; { std::lock_guard lock(remote_states_mutex_); - for (const auto &[remote_id, state] : remote_states_) { + for (const auto& [remote_id, state] : remote_states_) { if (state.keyboard_state_seen && !state.pressed_keys.empty() && state.last_seen_tick != 0 && now - state.last_seen_tick > kRemoteReleaseTimeoutMs) { @@ -354,9 +355,9 @@ void KeyboardController::CheckRemoteTimeouts() { } } } - for (const std::string &remote_id : timed_out_remotes) { + for (const std::string& remote_id : timed_out_remotes) { ReleaseRemotePressedKeys(remote_id, "keyboard_heartbeat_timeout"); } } -} // namespace crossdesk +} // namespace crossdesk diff --git a/src/service/windows/session_helper_main.cpp b/src/service/windows/session_helper_main.cpp index cd6c288..8b44a8f 100644 --- a/src/service/windows/session_helper_main.cpp +++ b/src/service/windows/session_helper_main.cpp @@ -24,6 +24,7 @@ #include "path_manager.h" #include "rd_log.h" #include "session_helper_shared.h" +#include "windows_input_marker.h" namespace { @@ -828,6 +829,8 @@ InputInjectionResult InjectKeyboardInput( INPUT input = {0}; input.type = INPUT_KEYBOARD; + input.ki.dwExtraInfo = static_cast( + crossdesk::kInjectedKeyboardInputMarker); const bool prefer_vk = PreferSideSpecificVkInjection(key_code); const UINT resolved_scan_code = diff --git a/xmake/targets.lua b/xmake/targets.lua index a95f494..bbfd7ac 100644 --- a/xmake/targets.lua +++ b/xmake/targets.lua @@ -315,7 +315,7 @@ function setup_targets() add_links("Advapi32", "User32", "Wtsapi32", "Gdi32") add_files("src/service/windows/session_helper_main.cpp") add_files(crossdesk_windows_resource) - add_includedirs("src/service/windows", {public = true}) + add_includedirs("src/common", "src/service/windows", {public = true}) end target("crossdesk")