mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-08-02 23:41:52 +08:00
[fix] restore Linux keyboard injection
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
#include "keyboard_converter.h"
|
#include "keyboard_converter.h"
|
||||||
|
#include "linux_evdev_keycode.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "windows_key_metadata.h"
|
#include "windows_key_metadata.h"
|
||||||
@@ -17,6 +18,40 @@ namespace crossdesk {
|
|||||||
static OnKeyAction g_on_key_action = nullptr;
|
static OnKeyAction g_on_key_action = nullptr;
|
||||||
static void* g_user_ptr = nullptr;
|
static void* g_user_ptr = nullptr;
|
||||||
|
|
||||||
|
static KeyCode ResolveX11Keycode(Display* display, int key_code,
|
||||||
|
uint32_t scan_code, bool extended) {
|
||||||
|
if (!display) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key_it = vkCodeToX11KeySym.find(key_code);
|
||||||
|
if (key_it != vkCodeToX11KeySym.end()) {
|
||||||
|
const KeyCode x11_keycode =
|
||||||
|
XKeysymToKeycode(display, static_cast<KeySym>(key_it->second));
|
||||||
|
if (x11_keycode != 0) {
|
||||||
|
return x11_keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some controllers can preserve the physical Windows scan code even when
|
||||||
|
// they cannot resolve a Windows virtual-key value. Xorg's evdev keycodes use
|
||||||
|
// the Linux input code plus the protocol-reserved offset of 8.
|
||||||
|
const int evdev_keycode =
|
||||||
|
ResolveLinuxEvdevKeycodeFromWindowsKey(key_code, scan_code, extended);
|
||||||
|
if (evdev_keycode < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int min_keycode = 0;
|
||||||
|
int max_keycode = 0;
|
||||||
|
XDisplayKeycodes(display, &min_keycode, &max_keycode);
|
||||||
|
const int x11_keycode = evdev_keycode + 8;
|
||||||
|
if (x11_keycode < min_keycode || x11_keycode > max_keycode) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<KeyCode>(x11_keycode);
|
||||||
|
}
|
||||||
|
|
||||||
static KeySym NormalizeKeySym(KeySym key_sym) {
|
static KeySym NormalizeKeySym(KeySym key_sym) {
|
||||||
if (key_sym >= XK_a && key_sym <= XK_z) {
|
if (key_sym >= XK_a && key_sym <= XK_z) {
|
||||||
return key_sym - XK_a + XK_A;
|
return key_sym - XK_a + XK_A;
|
||||||
@@ -62,7 +97,16 @@ KeyboardCapturer::KeyboardCapturer()
|
|||||||
display_ = XOpenDisplay(nullptr);
|
display_ = XOpenDisplay(nullptr);
|
||||||
if (!display_) {
|
if (!display_) {
|
||||||
LOG_ERROR("Failed to open X display.");
|
LOG_ERROR("Failed to open X display.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int event_base = 0;
|
||||||
|
int error_base = 0;
|
||||||
|
int major_version = 0;
|
||||||
|
int minor_version = 0;
|
||||||
|
x11_xtest_available_ =
|
||||||
|
XTestQueryExtension(display_, &event_base, &error_base, &major_version,
|
||||||
|
&minor_version) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardCapturer::~KeyboardCapturer() {
|
KeyboardCapturer::~KeyboardCapturer() {
|
||||||
@@ -70,8 +114,10 @@ KeyboardCapturer::~KeyboardCapturer() {
|
|||||||
CleanupWaylandPortal();
|
CleanupWaylandPortal();
|
||||||
|
|
||||||
if (display_) {
|
if (display_) {
|
||||||
|
std::lock_guard<std::mutex> lock(x11_injection_mutex_);
|
||||||
XCloseDisplay(display_);
|
XCloseDisplay(display_);
|
||||||
display_ = nullptr;
|
display_ = nullptr;
|
||||||
|
x11_xtest_available_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +202,6 @@ int KeyboardCapturer::Unhook() {
|
|||||||
|
|
||||||
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
||||||
uint32_t scan_code, bool extended) {
|
uint32_t scan_code, bool extended) {
|
||||||
(void)scan_code;
|
|
||||||
(void)extended;
|
|
||||||
if (IsWaylandSession()) {
|
if (IsWaylandSession()) {
|
||||||
if (!use_wayland_portal_ && !wayland_init_attempted_) {
|
if (!use_wayland_portal_ && !wayland_init_attempted_) {
|
||||||
wayland_init_attempted_ = true;
|
wayland_init_attempted_ = true;
|
||||||
@@ -181,12 +225,33 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) {
|
std::lock_guard<std::mutex> lock(x11_injection_mutex_);
|
||||||
int x11_key_code = vkCodeToX11KeySym[key_code];
|
if (!x11_xtest_available_) {
|
||||||
KeyCode keycode = XKeysymToKeycode(display_, x11_key_code);
|
LOG_ERROR("XTest extension not available for keyboard injection");
|
||||||
XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime);
|
return -2;
|
||||||
XFlush(display_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KeyCode x11_keycode =
|
||||||
|
ResolveX11Keycode(display_, key_code, scan_code, extended);
|
||||||
|
if (x11_keycode == 0) {
|
||||||
|
LOG_WARN(
|
||||||
|
"Cannot map remote keyboard event to X11 keycode, vk_code={}, "
|
||||||
|
"scan_code={}, extended={}",
|
||||||
|
key_code, scan_code, extended);
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!XTestFakeKeyEvent(display_, x11_keycode, is_down, CurrentTime)) {
|
||||||
|
LOG_ERROR(
|
||||||
|
"XTest keyboard injection failed, vk_code={}, scan_code={}, "
|
||||||
|
"extended={}, x11_keycode={}, is_down={}",
|
||||||
|
key_code, scan_code, extended, static_cast<int>(x11_keycode), is_down);
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the request before reporting success to the keyboard-state
|
||||||
|
// reconciler. This also makes X11 protocol errors observable immediately.
|
||||||
|
XSync(display_, False);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -49,6 +50,8 @@ class KeyboardCapturer : public DeviceController {
|
|||||||
unsigned long root_;
|
unsigned long root_;
|
||||||
std::atomic<bool> running_;
|
std::atomic<bool> running_;
|
||||||
std::thread event_thread_;
|
std::thread event_thread_;
|
||||||
|
std::mutex x11_injection_mutex_;
|
||||||
|
bool x11_xtest_available_ = false;
|
||||||
bool use_wayland_portal_ = false;
|
bool use_wayland_portal_ = false;
|
||||||
bool wayland_init_attempted_ = false;
|
bool wayland_init_attempted_ = false;
|
||||||
DBusConnection* dbus_connection_ = nullptr;
|
DBusConnection* dbus_connection_ = nullptr;
|
||||||
|
|||||||
@@ -611,6 +611,7 @@ int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down,
|
|||||||
// Prefer keycode injection to preserve physical-key semantics and avoid
|
// Prefer keycode injection to preserve physical-key semantics and avoid
|
||||||
// implicit Shift interpretation for uppercase keysyms.
|
// implicit Shift interpretation for uppercase keysyms.
|
||||||
if (display_) {
|
if (display_) {
|
||||||
|
std::lock_guard<std::mutex> lock(x11_injection_mutex_);
|
||||||
const int keysym = key_it->second;
|
const int keysym = key_it->second;
|
||||||
const KeyCode x11_keycode =
|
const KeyCode x11_keycode =
|
||||||
XKeysymToKeycode(display_, static_cast<KeySym>(keysym));
|
XKeysymToKeycode(display_, static_cast<KeySym>(keysym));
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "keyboard_capturer.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Display* receiver = XOpenDisplay(nullptr);
|
||||||
|
if (!receiver) {
|
||||||
|
std::fprintf(stderr, "open display failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int screen = DefaultScreen(receiver);
|
||||||
|
Window window = XCreateSimpleWindow(receiver, RootWindow(receiver, screen),
|
||||||
|
0, 0, 100, 100, 0, 0, 0);
|
||||||
|
XSelectInput(receiver, window, KeyPressMask | KeyReleaseMask);
|
||||||
|
XMapWindow(receiver, window);
|
||||||
|
XSync(receiver, False);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
XSetInputFocus(receiver, window, RevertToParent, CurrentTime);
|
||||||
|
XSync(receiver, False);
|
||||||
|
|
||||||
|
crossdesk::KeyboardCapturer keyboard;
|
||||||
|
// Exercise the scan-code fallback: the controller supplied no usable VK,
|
||||||
|
// but did preserve the set-1 scan code for the A key.
|
||||||
|
const int down = keyboard.SendKeyboardCommand(0, true, 0x1E, false);
|
||||||
|
const int up = keyboard.SendKeyboardCommand(0, false, 0x1E, false);
|
||||||
|
|
||||||
|
bool saw_down = false;
|
||||||
|
bool saw_up = false;
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::seconds(2);
|
||||||
|
while (std::chrono::steady_clock::now() < deadline &&
|
||||||
|
(!saw_down || !saw_up)) {
|
||||||
|
while (XPending(receiver) > 0) {
|
||||||
|
XEvent event{};
|
||||||
|
XNextEvent(receiver, &event);
|
||||||
|
saw_down = saw_down || event.type == KeyPress;
|
||||||
|
saw_up = saw_up || event.type == KeyRelease;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
XDestroyWindow(receiver, window);
|
||||||
|
XCloseDisplay(receiver);
|
||||||
|
std::printf("down_ret=%d up_ret=%d saw_down=%d saw_up=%d\n", down, up,
|
||||||
|
saw_down, saw_up);
|
||||||
|
return down == 0 && up == 0 && saw_down && saw_up ? 0 : 2;
|
||||||
|
}
|
||||||
@@ -197,6 +197,14 @@ function setup_targets()
|
|||||||
"src/device_controller/keyboard/linux", {public = true})
|
"src/device_controller/keyboard/linux", {public = true})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if is_os("linux") then
|
||||||
|
target("linux_keyboard_x11_integration_test")
|
||||||
|
set_kind("binary")
|
||||||
|
set_default(false)
|
||||||
|
add_deps("device_controller")
|
||||||
|
add_files("tests/linux_keyboard_x11_integration_test.cpp")
|
||||||
|
end
|
||||||
|
|
||||||
target("thumbnail")
|
target("thumbnail")
|
||||||
set_kind("object")
|
set_kind("object")
|
||||||
add_packages("libyuv", "openssl3")
|
add_packages("libyuv", "openssl3")
|
||||||
|
|||||||
Reference in New Issue
Block a user