[fix] fix Wayland keyboard capture by using SDL key events

This commit is contained in:
dijunkun
2026-04-21 14:47:10 +08:00
parent ffa94986d5
commit 97e48bfe71
4 changed files with 261 additions and 39 deletions

View File

@@ -98,6 +98,7 @@ std::map<int, int> vkCodeToCGKeyCode = {
{0x67, 0x59}, // Numpad 7
{0x68, 0x5B}, // Numpad 8
{0x69, 0x5C}, // Numpad 9
{0x90, 0x47}, // Num Lock / Keypad Clear
{0x6E, 0x41}, // Numpad .
{0x6F, 0x4B}, // Numpad /
{0x6A, 0x43}, // Numpad *
@@ -216,6 +217,7 @@ std::map<int, int> CGKeyCodeToVkCode = {
{0x59, 0x67}, // Numpad 7
{0x5B, 0x68}, // Numpad 8
{0x5C, 0x69}, // Numpad 9
{0x47, 0x90}, // Num Lock / Keypad Clear
{0x41, 0x6E}, // Numpad .
{0x4B, 0x6F}, // Numpad /
{0x43, 0x6A}, // Numpad *
@@ -336,6 +338,7 @@ std::map<int, int> vkCodeToX11KeySym = {
{0x67, 0xFFB7}, // Numpad 7
{0x68, 0xFFB8}, // Numpad 8
{0x69, 0xFFB9}, // Numpad 9
{0x90, 0xFF7F}, // Num Lock
{0x6E, 0xFFAE}, // Numpad .
{0x6F, 0xFFAF}, // Numpad /
{0x6A, 0xFFAA}, // Numpad *
@@ -464,6 +467,7 @@ std::map<int, int> x11KeySymToVkCode = {
{0xFFB7, 0x67}, // Numpad 7
{0xFFB8, 0x68}, // Numpad 8
{0xFFB9, 0x69}, // Numpad 9
{0xFF7F, 0x90}, // Num Lock
{0xFFAE, 0x6E}, // Numpad .
{0xFFAF, 0x6F}, // Numpad /
{0xFFAA, 0x6A}, // Numpad *
@@ -582,6 +586,7 @@ std::map<int, int> cgKeyCodeToX11KeySym = {
{0x59, 0xFFB7}, // Numpad 7
{0x5B, 0xFFB8}, // Numpad 8
{0x5C, 0xFFB9}, // Numpad 9
{0x47, 0xFF7F}, // Num Lock / Keypad Clear
{0x41, 0xFFAE}, // Numpad .
{0x4B, 0xFFAF}, // Numpad /
{0x43, 0xFFAA}, // Numpad *
@@ -708,6 +713,7 @@ std::map<int, int> x11KeySymToCgKeyCode = {
{0xFFB7, 0x59}, // Numpad 7
{0xFFB8, 0x5B}, // Numpad 8
{0xFFB9, 0x5C}, // Numpad 9
{0xFF7F, 0x47}, // Num Lock / Keypad Clear
{0xFFAE, 0x41}, // Numpad .
{0xFFAF, 0x4B}, // Numpad /
{0xFFAA, 0x43}, // Numpad *

View File

@@ -98,8 +98,7 @@ RemoteAction BuildWindowsServiceStatusAction(
action.ss.available = status.available;
std::strncpy(action.ss.interactive_stage, status.interactive_stage.c_str(),
sizeof(action.ss.interactive_stage) - 1);
action.ss.interactive_stage[sizeof(action.ss.interactive_stage) - 1] =
'\0';
action.ss.interactive_stage[sizeof(action.ss.interactive_stage) - 1] = '\0';
return action;
}
@@ -795,6 +794,13 @@ int Render::StopMouseController() {
}
int Render::StartKeyboardCapturer() {
#if defined(__linux__) && !defined(__APPLE__)
if (IsWaylandSession()) {
LOG_INFO("Start keyboard capturer with SDL Wayland backend");
return 0;
}
#endif
if (!keyboard_capturer_) {
LOG_INFO("keyboard capturer is nullptr");
return -1;
@@ -818,6 +824,13 @@ int Render::StartKeyboardCapturer() {
}
int Render::StopKeyboardCapturer() {
#if defined(__linux__) && !defined(__APPLE__)
if (IsWaylandSession()) {
LOG_INFO("Stop keyboard capturer with SDL Wayland backend");
return 0;
}
#endif
if (keyboard_capturer_) {
keyboard_capturer_->Unhook();
LOG_INFO("Stop keyboard capturer");
@@ -1883,19 +1896,18 @@ void Render::HandleWindowsServiceIntegration() {
return;
}
const bool has_connected_remote = std::any_of(
connection_status_.begin(), connection_status_.end(),
[](const auto& entry) {
return entry.second == ConnectionStatus::Connected;
});
const bool has_connected_remote =
std::any_of(connection_status_.begin(), connection_status_.end(),
[](const auto& entry) {
return entry.second == ConnectionStatus::Connected;
});
if (!has_connected_remote) {
ResetLocalWindowsServiceState(false);
return;
}
bool force_broadcast = false;
if (pending_windows_service_sas_.exchange(false,
std::memory_order_relaxed)) {
if (pending_windows_service_sas_.exchange(false, std::memory_order_relaxed)) {
const std::string response =
QueryCrossDeskService("sas", kWindowsServiceSasTimeoutMs);
auto json = nlohmann::json::parse(response, nullptr, false);
@@ -1931,10 +1943,12 @@ void Render::HandleWindowsServiceIntegration() {
status.error_code != last_logged_service_error_code);
if (availability_changed || error_changed) {
if (status.available) {
LOG_INFO("Local Windows service available for secure desktop integration");
LOG_INFO(
"Local Windows service available for secure desktop integration");
} else {
LOG_WARN(
"Local Windows service unavailable, secure desktop integration disabled: error={}, code={}",
"Local Windows service unavailable, secure desktop integration "
"disabled: error={}, code={}",
status.error, status.error_code);
}
last_logged_service_available = status.available;
@@ -1944,7 +1958,8 @@ void Render::HandleWindowsServiceIntegration() {
} else if (last_logged_service_available ||
last_logged_service_error != "invalid_service_status_json") {
LOG_WARN(
"Local Windows service status query failed, secure desktop integration disabled");
"Local Windows service status query failed, secure desktop integration "
"disabled");
last_logged_service_available = false;
last_logged_service_error = "invalid_service_status_json";
last_logged_service_error_code = 0;
@@ -2670,7 +2685,7 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
case SDL_EVENT_WINDOW_FOCUS_LOST:
if (stream_window_ &&
SDL_GetWindowID(stream_window_) == event.window.windowID) {
ForceReleasePressedModifiers();
ForceReleasePressedKeys();
focus_on_stream_window_ = false;
} else if (main_window_ &&
SDL_GetWindowID(main_window_) == event.window.windowID) {
@@ -2702,6 +2717,15 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
break;
}
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
if (keyboard_capturer_is_started_ && focus_on_stream_window_ &&
stream_window_ &&
SDL_GetWindowID(stream_window_) == event.key.windowID) {
ProcessKeyboardEvent(event);
}
break;
default:
if (event.type == STREAM_REFRESH_EVENT) {
auto* props = static_cast<SubStreamWindowProperties*>(event.user.data1);

View File

@@ -287,7 +287,7 @@ class Render {
void ResetRemoteServiceStatus(SubStreamWindowProperties& props);
void ApplyRemoteServiceStatus(SubStreamWindowProperties& props,
const ServiceStatus& status);
RemoteUnlockState GetRemoteUnlockState(
RemoteUnlockState GetRemoteUnlockState(
const SubStreamWindowProperties& props) const;
#ifdef __APPLE__
int RequestPermissionWindow();
@@ -342,8 +342,9 @@ class Render {
private:
int SendKeyCommand(int key_code, bool is_down);
static bool IsModifierVkKey(int key_code);
void UpdatePressedModifierState(int key_code, bool is_down);
void ForceReleasePressedModifiers();
void TrackPressedKeyState(int key_code, bool is_down);
void ForceReleasePressedKeys();
int ProcessKeyboardEvent(const SDL_Event& event);
int ProcessMouseEvent(const SDL_Event& event);
static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len);
@@ -532,8 +533,8 @@ class Render {
std::string controlled_remote_id_ = "";
std::string focused_remote_id_ = "";
std::string remote_client_id_ = "";
std::unordered_set<int> pressed_modifier_keys_;
std::mutex pressed_modifier_keys_mutex_;
std::unordered_set<int> pressed_keyboard_keys_;
std::mutex pressed_keyboard_keys_mutex_;
SDL_Event last_mouse_event;
SDL_AudioStream* output_stream_;
uint32_t STREAM_REFRESH_EVENT = 0;

View File

@@ -28,6 +28,178 @@ namespace crossdesk {
namespace {
int TranslateSdlKeypadScancodeToVk(SDL_Scancode scancode) {
switch (scancode) {
case SDL_SCANCODE_NUMLOCKCLEAR:
return 0x90;
case SDL_SCANCODE_KP_ENTER:
return 0x0D;
case SDL_SCANCODE_KP_0:
return 0x60;
case SDL_SCANCODE_KP_1:
return 0x61;
case SDL_SCANCODE_KP_2:
return 0x62;
case SDL_SCANCODE_KP_3:
return 0x63;
case SDL_SCANCODE_KP_4:
return 0x64;
case SDL_SCANCODE_KP_5:
return 0x65;
case SDL_SCANCODE_KP_6:
return 0x66;
case SDL_SCANCODE_KP_7:
return 0x67;
case SDL_SCANCODE_KP_8:
return 0x68;
case SDL_SCANCODE_KP_9:
return 0x69;
case SDL_SCANCODE_KP_PERIOD:
case SDL_SCANCODE_KP_COMMA:
return 0x6E;
case SDL_SCANCODE_KP_DIVIDE:
return 0x6F;
case SDL_SCANCODE_KP_MULTIPLY:
return 0x6A;
case SDL_SCANCODE_KP_MINUS:
return 0x6D;
case SDL_SCANCODE_KP_PLUS:
return 0x6B;
case SDL_SCANCODE_KP_EQUALS:
return 0xBB;
default:
return -1;
}
}
int TranslateSdlKeyboardEventToVk(const SDL_KeyboardEvent& event) {
const int keypad_key_code = TranslateSdlKeypadScancodeToVk(event.scancode);
if (keypad_key_code >= 0) {
return keypad_key_code;
}
const int key = static_cast<int>(event.key);
if (key >= 'a' && key <= 'z') {
return key - 'a' + 0x41;
}
if (key >= 'A' && key <= 'Z') {
return key;
}
if (key >= '0' && key <= '9') {
return key;
}
switch (key) {
case ';':
return 0xBA;
case '\'':
return 0xDE;
case '`':
return 0xC0;
case ',':
return 0xBC;
case '.':
return 0xBE;
case '/':
return 0xBF;
case '\\':
return 0xDC;
case '[':
return 0xDB;
case ']':
return 0xDD;
case '-':
return 0xBD;
case '=':
return 0xBB;
default:
break;
}
switch (event.scancode) {
case SDL_SCANCODE_ESCAPE:
return 0x1B;
case SDL_SCANCODE_RETURN:
return 0x0D;
case SDL_SCANCODE_SPACE:
return 0x20;
case SDL_SCANCODE_BACKSPACE:
return 0x08;
case SDL_SCANCODE_TAB:
return 0x09;
case SDL_SCANCODE_PRINTSCREEN:
return 0x2C;
case SDL_SCANCODE_SCROLLLOCK:
return 0x91;
case SDL_SCANCODE_PAUSE:
return 0x13;
case SDL_SCANCODE_INSERT:
return 0x2D;
case SDL_SCANCODE_DELETE:
return 0x2E;
case SDL_SCANCODE_HOME:
return 0x24;
case SDL_SCANCODE_END:
return 0x23;
case SDL_SCANCODE_PAGEUP:
return 0x21;
case SDL_SCANCODE_PAGEDOWN:
return 0x22;
case SDL_SCANCODE_LEFT:
return 0x25;
case SDL_SCANCODE_RIGHT:
return 0x27;
case SDL_SCANCODE_UP:
return 0x26;
case SDL_SCANCODE_DOWN:
return 0x28;
case SDL_SCANCODE_F1:
return 0x70;
case SDL_SCANCODE_F2:
return 0x71;
case SDL_SCANCODE_F3:
return 0x72;
case SDL_SCANCODE_F4:
return 0x73;
case SDL_SCANCODE_F5:
return 0x74;
case SDL_SCANCODE_F6:
return 0x75;
case SDL_SCANCODE_F7:
return 0x76;
case SDL_SCANCODE_F8:
return 0x77;
case SDL_SCANCODE_F9:
return 0x78;
case SDL_SCANCODE_F10:
return 0x79;
case SDL_SCANCODE_F11:
return 0x7A;
case SDL_SCANCODE_F12:
return 0x7B;
case SDL_SCANCODE_CAPSLOCK:
return 0x14;
case SDL_SCANCODE_LSHIFT:
return 0xA0;
case SDL_SCANCODE_RSHIFT:
return 0xA1;
case SDL_SCANCODE_LCTRL:
return 0xA2;
case SDL_SCANCODE_RCTRL:
return 0xA3;
case SDL_SCANCODE_LALT:
return 0xA4;
case SDL_SCANCODE_RALT:
return 0xA5;
case SDL_SCANCODE_LGUI:
return 0x5B;
case SDL_SCANCODE_RGUI:
return 0x5C;
default:
return -1;
}
}
#if _WIN32
constexpr uint32_t kSecureDesktopInputLogIntervalMs = 2000;
@@ -36,8 +208,7 @@ bool BuildAbsoluteMousePosition(const std::vector<DisplayInfo>& displays,
float normalized_y, int* absolute_x_out,
int* absolute_y_out) {
if (absolute_x_out == nullptr || absolute_y_out == nullptr ||
display_index < 0 ||
display_index >= static_cast<int>(displays.size())) {
display_index < 0 || display_index >= static_cast<int>(displays.size())) {
return false;
}
@@ -152,29 +323,29 @@ bool Render::IsModifierVkKey(int key_code) {
}
}
void Render::UpdatePressedModifierState(int key_code, bool is_down) {
if (!IsModifierVkKey(key_code)) {
void Render::TrackPressedKeyState(int key_code, bool is_down) {
if (!IsWaylandSession() && !IsModifierVkKey(key_code)) {
return;
}
std::lock_guard<std::mutex> lock(pressed_modifier_keys_mutex_);
std::lock_guard<std::mutex> lock(pressed_keyboard_keys_mutex_);
if (is_down) {
pressed_modifier_keys_.insert(key_code);
pressed_keyboard_keys_.insert(key_code);
} else {
pressed_modifier_keys_.erase(key_code);
pressed_keyboard_keys_.erase(key_code);
}
}
void Render::ForceReleasePressedModifiers() {
void Render::ForceReleasePressedKeys() {
std::vector<int> pressed_keys;
{
std::lock_guard<std::mutex> lock(pressed_modifier_keys_mutex_);
if (pressed_modifier_keys_.empty()) {
std::lock_guard<std::mutex> lock(pressed_keyboard_keys_mutex_);
if (pressed_keyboard_keys_.empty()) {
return;
}
pressed_keys.assign(pressed_modifier_keys_.begin(),
pressed_modifier_keys_.end());
pressed_modifier_keys_.clear();
pressed_keys.assign(pressed_keyboard_keys_.begin(),
pressed_keyboard_keys_.end());
pressed_keyboard_keys_.clear();
}
for (int key_code : pressed_keys) {
@@ -210,11 +381,28 @@ int Render::SendKeyCommand(int key_code, bool is_down) {
}
}
UpdatePressedModifierState(key_code, is_down);
TrackPressedKeyState(key_code, is_down);
return 0;
}
int Render::ProcessKeyboardEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_KEY_DOWN && event.type != SDL_EVENT_KEY_UP) {
return -1;
}
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat) {
return 0;
}
const int key_code = TranslateSdlKeyboardEventToVk(event.key);
if (key_code < 0) {
return 0;
}
return SendKeyCommand(key_code, event.type == SDL_EVENT_KEY_DOWN);
}
int Render::ProcessMouseEvent(const SDL_Event& event) {
controlled_remote_id_ = "";
RemoteAction remote_action;
@@ -292,7 +480,8 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
}
if (is_pointer_position_event && cursor_x >= render_rect.x &&
cursor_x <= render_rect.x + render_rect.w && cursor_y >= render_rect.y &&
cursor_x <= render_rect.x + render_rect.w &&
cursor_y >= render_rect.y &&
cursor_y <= render_rect.y + render_rect.h) {
controlled_remote_id_ = it.first;
last_mouse_event.motion.x = cursor_x;
@@ -827,9 +1016,9 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
remote_action.m.x, remote_action.m.y,
&absolute_x, &absolute_y)) {
LOG_WARN(
"Secure desktop mouse injection skipped, invalid display mapping: display_index={}, x={}, y={}",
render->selected_display_, remote_action.m.x,
remote_action.m.y);
"Secure desktop mouse injection skipped, invalid display "
"mapping: display_index={}, x={}, y={}",
render->selected_display_, remote_action.m.x, remote_action.m.y);
return;
}
@@ -842,7 +1031,8 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
&render->last_local_secure_input_block_log_tick_, "local",
render->local_interactive_stage_.c_str());
LOG_WARN(
"Secure desktop mouse injection failed, x={}, y={}, wheel={}, flag={}, response={}",
"Secure desktop mouse injection failed, x={}, y={}, wheel={}, "
"flag={}, response={}",
absolute_x, absolute_y, remote_action.m.s,
static_cast<int>(remote_action.m.flag), response);
}
@@ -1153,8 +1343,9 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
// Keep Wayland capture session warm to avoid black screen on
// subsequent reconnects.
render->start_screen_capturer_ = true;
LOG_INFO("Keeping Wayland screen capturer running after "
"disconnect to preserve reconnect stability");
LOG_INFO(
"Keeping Wayland screen capturer running after "
"disconnect to preserve reconnect stability");
} else {
render->start_screen_capturer_ = false;
}