Compare commits

...

6 Commits

12 changed files with 1109 additions and 130 deletions
@@ -4,6 +4,7 @@
#include <poll.h>
#include "keyboard_converter.h"
#include "platform.h"
#include "rd_log.h"
namespace crossdesk {
@@ -11,10 +12,28 @@ namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr;
static KeySym NormalizeKeySym(KeySym key_sym) {
if (key_sym >= XK_a && key_sym <= XK_z) {
return key_sym - XK_a + XK_A;
}
return key_sym;
}
static int KeyboardEventHandler(Display* display, XEvent* event) {
(void)display;
if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) {
KeySym keySym = XLookupKeysym(&event->xkey, 0);
int key_code = XKeysymToKeycode(display, keySym);
KeySym key_sym = NormalizeKeySym(XLookupKeysym(&event->xkey, 0));
auto key_it = x11KeySymToVkCode.find(static_cast<int>(key_sym));
if (key_it == x11KeySymToVkCode.end()) {
key_sym = NormalizeKeySym(XLookupKeysym(&event->xkey, 1));
key_it = x11KeySymToVkCode.find(static_cast<int>(key_sym));
}
if (key_it == x11KeySymToVkCode.end()) {
return 0;
}
int key_code = key_it->second;
bool is_key_down = (event->xkey.type == KeyPress);
if (g_on_key_action) {
@@ -25,7 +44,12 @@ static int KeyboardEventHandler(Display* display, XEvent* event) {
}
KeyboardCapturer::KeyboardCapturer()
: display_(nullptr), root_(0), running_(false) {
: display_(nullptr),
root_(0),
running_(false),
use_wayland_portal_(false),
wayland_init_attempted_(false),
dbus_connection_(nullptr) {
XInitThreads();
display_ = XOpenDisplay(nullptr);
if (!display_) {
@@ -35,6 +59,7 @@ KeyboardCapturer::KeyboardCapturer()
KeyboardCapturer::~KeyboardCapturer() {
Unhook();
CleanupWaylandPortal();
if (display_) {
XCloseDisplay(display_);
@@ -122,6 +147,22 @@ int KeyboardCapturer::Unhook() {
}
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
if (IsWaylandSession()) {
if (!use_wayland_portal_ && !wayland_init_attempted_) {
wayland_init_attempted_ = true;
if (InitWaylandPortal()) {
use_wayland_portal_ = true;
LOG_INFO("Keyboard controller initialized with Wayland portal backend");
} else {
LOG_WARN("Wayland keyboard control init failed, falling back to X11/XTest backend");
}
}
if (use_wayland_portal_) {
return SendWaylandKeyboardCommand(key_code, is_down);
}
}
if (!display_) {
LOG_ERROR("Display not initialized.");
return -1;
@@ -135,4 +176,4 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
}
return 0;
}
} // namespace crossdesk
} // namespace crossdesk
@@ -12,10 +12,16 @@
#include <X11/keysym.h>
#include <atomic>
#include <cstdint>
#include <functional>
#include <string>
#include <thread>
#include "device_controller.h"
struct DBusConnection;
struct DBusMessageIter;
namespace crossdesk {
class KeyboardCapturer : public DeviceController {
@@ -28,11 +34,25 @@ class KeyboardCapturer : public DeviceController {
virtual int Unhook();
virtual int SendKeyboardCommand(int key_code, bool is_down);
private:
bool InitWaylandPortal();
void CleanupWaylandPortal();
int SendWaylandKeyboardCommand(int key_code, bool is_down);
bool NotifyWaylandKeyboardKeysym(int keysym, uint32_t state);
bool NotifyWaylandKeyboardKeycode(int keycode, uint32_t state);
bool SendWaylandPortalVoidCall(const char* method_name,
const std::function<void(DBusMessageIter*)>&
append_args);
private:
Display* display_;
Window root_;
std::atomic<bool> running_;
std::thread event_thread_;
bool use_wayland_portal_ = false;
bool wayland_init_attempted_ = false;
DBusConnection* dbus_connection_ = nullptr;
std::string wayland_session_handle_;
};
} // namespace crossdesk
#endif
#endif
@@ -0,0 +1,711 @@
#include "keyboard_capturer.h"
#include <chrono>
#include <cstring>
#include <map>
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
#include <dbus/dbus.h>
#endif
#include "rd_log.h"
#include "wayland_portal_shared.h"
namespace crossdesk {
extern std::map<int, int> vkCodeToX11KeySym;
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
namespace {
constexpr const char* kPortalBusName = "org.freedesktop.portal.Desktop";
constexpr const char* kPortalObjectPath = "/org/freedesktop/portal/desktop";
constexpr const char* kPortalRemoteDesktopInterface =
"org.freedesktop.portal.RemoteDesktop";
constexpr const char* kPortalRequestInterface =
"org.freedesktop.portal.Request";
constexpr const char* kPortalRequestPathPrefix =
"/org/freedesktop/portal/desktop/request/";
constexpr const char* kPortalSessionPathPrefix =
"/org/freedesktop/portal/desktop/session/";
constexpr uint32_t kRemoteDesktopDeviceKeyboard = 1u;
constexpr uint32_t kKeyboardReleased = 0u;
constexpr uint32_t kKeyboardPressed = 1u;
int NormalizeFallbackKeysym(int keysym) {
if (keysym >= XK_A && keysym <= XK_Z) {
return keysym - XK_A + XK_a;
}
return keysym;
}
std::string MakeToken(const char* prefix) {
const auto now = std::chrono::steady_clock::now().time_since_epoch().count();
return std::string(prefix) + "_" + std::to_string(now);
}
void LogDbusError(const char* action, DBusError* error) {
if (error && dbus_error_is_set(error)) {
LOG_ERROR("{} failed: {} ({})", action,
error->message ? error->message : "unknown",
error->name ? error->name : "unknown");
} else {
LOG_ERROR("{} failed", action);
}
}
void AppendDictEntryString(DBusMessageIter* dict, const char* key,
const std::string& value) {
DBusMessageIter entry;
DBusMessageIter variant;
const char* key_cstr = key;
const char* value_cstr = value.c_str();
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, nullptr, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key_cstr);
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "s", &variant);
dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value_cstr);
dbus_message_iter_close_container(&entry, &variant);
dbus_message_iter_close_container(dict, &entry);
}
void AppendDictEntryUint32(DBusMessageIter* dict, const char* key,
uint32_t value) {
DBusMessageIter entry;
DBusMessageIter variant;
const char* key_cstr = key;
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, nullptr, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key_cstr);
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "u", &variant);
dbus_message_iter_append_basic(&variant, DBUS_TYPE_UINT32, &value);
dbus_message_iter_close_container(&entry, &variant);
dbus_message_iter_close_container(dict, &entry);
}
void AppendEmptyOptionsDict(DBusMessageIter* iter) {
DBusMessageIter options;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &options);
dbus_message_iter_close_container(iter, &options);
}
bool ReadPathLikeVariant(DBusMessageIter* variant, std::string* value) {
if (!variant || !value) {
return false;
}
const int type = dbus_message_iter_get_arg_type(variant);
if (type == DBUS_TYPE_OBJECT_PATH || type == DBUS_TYPE_STRING) {
const char* temp = nullptr;
dbus_message_iter_get_basic(variant, &temp);
if (temp && temp[0] != '\0') {
*value = temp;
return true;
}
}
return false;
}
bool ReadUint32Like(DBusMessageIter* iter, uint32_t* value) {
if (!iter || !value) {
return false;
}
const int type = dbus_message_iter_get_arg_type(iter);
if (type == DBUS_TYPE_UINT32) {
uint32_t temp = 0;
dbus_message_iter_get_basic(iter, &temp);
*value = temp;
return true;
}
if (type == DBUS_TYPE_INT32) {
int32_t temp = 0;
dbus_message_iter_get_basic(iter, &temp);
if (temp < 0) {
return false;
}
*value = static_cast<uint32_t>(temp);
return true;
}
return false;
}
std::string BuildSessionHandleFromRequestPath(
const std::string& request_path, const std::string& session_handle_token) {
if (request_path.rfind(kPortalRequestPathPrefix, 0) != 0 ||
session_handle_token.empty()) {
return "";
}
const size_t sender_start = strlen(kPortalRequestPathPrefix);
const size_t token_sep = request_path.find('/', sender_start);
if (token_sep == std::string::npos || token_sep <= sender_start) {
return "";
}
const std::string sender =
request_path.substr(sender_start, token_sep - sender_start);
if (sender.empty()) {
return "";
}
return std::string(kPortalSessionPathPrefix) + sender + "/" +
session_handle_token;
}
struct PortalResponseState {
std::string request_path;
bool received = false;
DBusMessage* message = nullptr;
};
DBusHandlerResult HandlePortalResponseSignal(DBusConnection* connection,
DBusMessage* message,
void* user_data) {
(void)connection;
auto* state = static_cast<PortalResponseState*>(user_data);
if (!state || !message) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (!dbus_message_is_signal(message, kPortalRequestInterface, "Response")) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
const char* path = dbus_message_get_path(message);
if (!path || state->request_path != path) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (state->message) {
dbus_message_unref(state->message);
state->message = nullptr;
}
state->message = dbus_message_ref(message);
state->received = true;
return DBUS_HANDLER_RESULT_HANDLED;
}
DBusMessage* WaitForPortalResponse(DBusConnection* connection,
const std::string& request_path,
int timeout_ms = 120000) {
if (!connection || request_path.empty()) {
return nullptr;
}
PortalResponseState state;
state.request_path = request_path;
DBusError error;
dbus_error_init(&error);
const std::string match_rule =
"type='signal',interface='" + std::string(kPortalRequestInterface) +
"',member='Response',path='" + request_path + "'";
dbus_bus_add_match(connection, match_rule.c_str(), &error);
if (dbus_error_is_set(&error)) {
LogDbusError("dbus_bus_add_match", &error);
dbus_error_free(&error);
return nullptr;
}
dbus_connection_add_filter(connection, HandlePortalResponseSignal, &state,
nullptr);
auto deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
while (!state.received && std::chrono::steady_clock::now() < deadline) {
dbus_connection_read_write(connection, 100);
while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) {
}
}
dbus_connection_remove_filter(connection, HandlePortalResponseSignal, &state);
DBusError remove_error;
dbus_error_init(&remove_error);
dbus_bus_remove_match(connection, match_rule.c_str(), &remove_error);
if (dbus_error_is_set(&remove_error)) {
dbus_error_free(&remove_error);
}
return state.message;
}
bool ExtractRequestPath(DBusMessage* reply, std::string* request_path) {
if (!reply || !request_path) {
return false;
}
const char* path = nullptr;
DBusError error;
dbus_error_init(&error);
const dbus_bool_t ok = dbus_message_get_args(
reply, &error, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
if (!ok || !path) {
LogDbusError("dbus_message_get_args(request_path)", &error);
dbus_error_free(&error);
return false;
}
*request_path = path;
return true;
}
bool ExtractPortalResponse(DBusMessage* message, uint32_t* response_code,
DBusMessageIter* results_array) {
if (!message || !response_code || !results_array) {
return false;
}
DBusMessageIter iter;
if (!dbus_message_iter_init(message, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) {
return false;
}
dbus_message_iter_get_basic(&iter, response_code);
if (!dbus_message_iter_next(&iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
return false;
}
*results_array = iter;
return true;
}
bool SendPortalRequestAndHandleResponse(
DBusConnection* connection, const char* interface_name,
const char* method_name, const char* action_name,
const std::function<bool(DBusMessage*)>& append_message_args,
const std::function<bool(uint32_t, DBusMessageIter*)>& handle_results,
std::string* request_path_out = nullptr) {
if (!connection || !interface_name || interface_name[0] == '\0' ||
!method_name || method_name[0] == '\0') {
return false;
}
DBusMessage* message = dbus_message_new_method_call(
kPortalBusName, kPortalObjectPath, interface_name, method_name);
if (!message) {
LOG_ERROR("Failed to allocate {} message", method_name);
return false;
}
if (append_message_args && !append_message_args(message)) {
dbus_message_unref(message);
LOG_ERROR("{} arguments are malformed", method_name);
return false;
}
DBusError error;
dbus_error_init(&error);
DBusMessage* reply =
dbus_connection_send_with_reply_and_block(connection, message, -1, &error);
dbus_message_unref(message);
if (!reply) {
LogDbusError(action_name ? action_name : method_name, &error);
dbus_error_free(&error);
return false;
}
std::string request_path;
const bool got_request_path = ExtractRequestPath(reply, &request_path);
dbus_message_unref(reply);
if (!got_request_path) {
return false;
}
if (request_path_out) {
*request_path_out = request_path;
}
DBusMessage* response = WaitForPortalResponse(connection, request_path);
if (!response) {
LOG_ERROR("Timed out waiting for {} response", method_name);
return false;
}
uint32_t response_code = 1;
DBusMessageIter results;
const bool parsed = ExtractPortalResponse(response, &response_code, &results);
if (!parsed) {
dbus_message_unref(response);
LOG_ERROR("{} response was malformed", method_name);
return false;
}
const bool ok = handle_results ? handle_results(response_code, &results)
: (response_code == 0);
dbus_message_unref(response);
return ok;
}
} // namespace
#endif
bool KeyboardCapturer::InitWaylandPortal() {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
CleanupWaylandPortal();
DBusError error;
dbus_error_init(&error);
DBusConnection* check_connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (!check_connection) {
LogDbusError("dbus_bus_get", &error);
dbus_error_free(&error);
return false;
}
const dbus_bool_t has_owner =
dbus_bus_name_has_owner(check_connection, kPortalBusName, &error);
if (dbus_error_is_set(&error)) {
LogDbusError("dbus_bus_name_has_owner", &error);
dbus_error_free(&error);
dbus_connection_unref(check_connection);
return false;
}
dbus_connection_unref(check_connection);
if (!has_owner) {
LOG_ERROR("xdg-desktop-portal is not available on session bus");
return false;
}
dbus_connection_ = dbus_bus_get_private(DBUS_BUS_SESSION, &error);
if (!dbus_connection_) {
LogDbusError("dbus_bus_get_private", &error);
dbus_error_free(&error);
return false;
}
dbus_connection_set_exit_on_disconnect(dbus_connection_, FALSE);
const std::string session_handle_token =
MakeToken("crossdesk_keyboard_session");
std::string request_path;
const bool create_ok = SendPortalRequestAndHandleResponse(
dbus_connection_, kPortalRemoteDesktopInterface, "CreateSession",
"CreateSession",
[&](DBusMessage* message) {
DBusMessageIter iter;
DBusMessageIter options;
dbus_message_iter_init_append(message, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}",
&options);
AppendDictEntryString(&options, "session_handle_token",
session_handle_token);
AppendDictEntryString(&options, "handle_token",
MakeToken("crossdesk_keyboard_req"));
dbus_message_iter_close_container(&iter, &options);
return true;
},
[&](uint32_t response_code, DBusMessageIter* results) {
if (response_code != 0) {
LOG_ERROR("RemoteDesktop.CreateSession denied, response={}",
response_code);
return false;
}
DBusMessageIter dict;
dbus_message_iter_recurse(results, &dict);
while (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
if (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
dbus_message_iter_recurse(&dict, &entry);
const char* key = nullptr;
dbus_message_iter_get_basic(&entry, &key);
if (key && dbus_message_iter_next(&entry) &&
dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_VARIANT &&
strcmp(key, "session_handle") == 0) {
DBusMessageIter variant;
std::string parsed_handle;
dbus_message_iter_recurse(&entry, &variant);
if (ReadPathLikeVariant(&variant, &parsed_handle) &&
!parsed_handle.empty()) {
wayland_session_handle_ = parsed_handle;
break;
}
}
}
dbus_message_iter_next(&dict);
}
return true;
},
&request_path);
if (!create_ok) {
CleanupWaylandPortal();
return false;
}
if (wayland_session_handle_.empty()) {
wayland_session_handle_ =
BuildSessionHandleFromRequestPath(request_path, session_handle_token);
}
if (wayland_session_handle_.empty()) {
LOG_ERROR("RemoteDesktop.CreateSession did not return session handle");
CleanupWaylandPortal();
return false;
}
const char* session_handle = wayland_session_handle_.c_str();
const bool select_ok = SendPortalRequestAndHandleResponse(
dbus_connection_, kPortalRemoteDesktopInterface, "SelectDevices",
"SelectDevices",
[&](DBusMessage* message) {
DBusMessageIter iter;
DBusMessageIter options;
dbus_message_iter_init_append(message, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
&session_handle);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}",
&options);
AppendDictEntryUint32(&options, "types", kRemoteDesktopDeviceKeyboard);
AppendDictEntryString(&options, "handle_token",
MakeToken("crossdesk_keyboard_req"));
dbus_message_iter_close_container(&iter, &options);
return true;
},
[](uint32_t response_code, DBusMessageIter*) {
if (response_code != 0) {
LOG_ERROR("RemoteDesktop.SelectDevices denied, response={}",
response_code);
return false;
}
return true;
});
if (!select_ok) {
CleanupWaylandPortal();
return false;
}
const char* parent_window = "";
bool keyboard_granted = false;
const bool start_ok = SendPortalRequestAndHandleResponse(
dbus_connection_, kPortalRemoteDesktopInterface, "Start", "Start",
[&](DBusMessage* message) {
DBusMessageIter iter;
DBusMessageIter options;
dbus_message_iter_init_append(message, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
&session_handle);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &parent_window);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}",
&options);
AppendDictEntryString(&options, "handle_token",
MakeToken("crossdesk_keyboard_req"));
dbus_message_iter_close_container(&iter, &options);
return true;
},
[&](uint32_t response_code, DBusMessageIter* results) {
if (response_code != 0) {
LOG_ERROR("RemoteDesktop.Start denied, response={}", response_code);
return false;
}
uint32_t granted_devices = 0;
DBusMessageIter dict;
dbus_message_iter_recurse(results, &dict);
while (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
if (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
dbus_message_iter_recurse(&dict, &entry);
const char* key = nullptr;
dbus_message_iter_get_basic(&entry, &key);
if (key && dbus_message_iter_next(&entry) &&
dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_VARIANT) {
DBusMessageIter variant;
dbus_message_iter_recurse(&entry, &variant);
if (strcmp(key, "devices") == 0) {
ReadUint32Like(&variant, &granted_devices);
}
}
}
dbus_message_iter_next(&dict);
}
keyboard_granted =
(granted_devices & kRemoteDesktopDeviceKeyboard) != 0;
if (!keyboard_granted) {
LOG_ERROR(
"RemoteDesktop.Start granted devices mask={}, keyboard not allowed",
granted_devices);
return false;
}
return true;
});
if (!start_ok) {
CleanupWaylandPortal();
return false;
}
if (!keyboard_granted) {
LOG_ERROR("RemoteDesktop session started without keyboard permission");
CleanupWaylandPortal();
return false;
}
return true;
#else
return false;
#endif
}
void KeyboardCapturer::CleanupWaylandPortal() {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
if (dbus_connection_) {
CloseWaylandPortalSessionAndConnection(dbus_connection_,
wayland_session_handle_,
"RemoteDesktop.Session.Close");
dbus_connection_ = nullptr;
}
#endif
use_wayland_portal_ = false;
wayland_session_handle_.clear();
}
int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down) {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
if (!dbus_connection_ || wayland_session_handle_.empty()) {
return -1;
}
const auto key_it = vkCodeToX11KeySym.find(key_code);
if (key_it == vkCodeToX11KeySym.end()) {
return 0;
}
const uint32_t key_state = is_down ? kKeyboardPressed : kKeyboardReleased;
const int keysym = key_it->second;
// Prefer keycode injection to preserve physical-key semantics and avoid
// implicit Shift interpretation for uppercase keysyms.
if (display_) {
const KeyCode x11_keycode =
XKeysymToKeycode(display_, static_cast<KeySym>(keysym));
if (x11_keycode > 8) {
const int evdev_keycode = static_cast<int>(x11_keycode) - 8;
if (NotifyWaylandKeyboardKeycode(evdev_keycode, key_state)) {
return 0;
}
}
}
const int fallback_keysym = NormalizeFallbackKeysym(keysym);
if (NotifyWaylandKeyboardKeysym(fallback_keysym, key_state)) {
return 0;
}
LOG_ERROR("Failed to send Wayland keyboard event, vk_code={}, is_down={}",
key_code, is_down);
return -3;
#else
(void)key_code;
(void)is_down;
return -1;
#endif
}
bool KeyboardCapturer::NotifyWaylandKeyboardKeysym(int keysym, uint32_t state) {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
return SendWaylandPortalVoidCall(
"NotifyKeyboardKeysym", [&](DBusMessageIter* iter) {
const char* session_handle = wayland_session_handle_.c_str();
int32_t key_sym = keysym;
uint32_t key_state = state;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
&session_handle);
AppendEmptyOptionsDict(iter);
dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &key_sym);
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &key_state);
});
#else
(void)keysym;
(void)state;
return false;
#endif
}
bool KeyboardCapturer::NotifyWaylandKeyboardKeycode(int keycode,
uint32_t state) {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
return SendWaylandPortalVoidCall(
"NotifyKeyboardKeycode", [&](DBusMessageIter* iter) {
const char* session_handle = wayland_session_handle_.c_str();
int32_t key_code = keycode;
uint32_t key_state = state;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
&session_handle);
AppendEmptyOptionsDict(iter);
dbus_message_iter_append_basic(iter, DBUS_TYPE_INT32, &key_code);
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &key_state);
});
#else
(void)keycode;
(void)state;
return false;
#endif
}
bool KeyboardCapturer::SendWaylandPortalVoidCall(
const char* method_name,
const std::function<void(DBusMessageIter*)>& append_args) {
#if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER
if (!dbus_connection_ || !method_name || method_name[0] == '\0') {
return false;
}
DBusMessage* message = dbus_message_new_method_call(
kPortalBusName, kPortalObjectPath, kPortalRemoteDesktopInterface,
method_name);
if (!message) {
LOG_ERROR("Failed to allocate {} message", method_name);
return false;
}
DBusMessageIter iter;
dbus_message_iter_init_append(message, &iter);
if (append_args) {
append_args(&iter);
}
DBusError error;
dbus_error_init(&error);
DBusMessage* reply = dbus_connection_send_with_reply_and_block(
dbus_connection_, message, 5000, &error);
dbus_message_unref(message);
if (!reply) {
LogDbusError(method_name, &error);
dbus_error_free(&error);
return false;
}
if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
const char* error_name = dbus_message_get_error_name(reply);
LOG_ERROR("{} returned DBus error: {}", method_name,
error_name ? error_name : "unknown");
dbus_message_unref(reply);
return false;
}
dbus_message_unref(reply);
return true;
#else
(void)method_name;
(void)append_args;
return false;
#endif
}
} // namespace crossdesk
@@ -1,5 +1,7 @@
#include "keyboard_capturer.h"
#include <unordered_map>
#include "keyboard_converter.h"
#include "rd_log.h"
@@ -7,9 +9,100 @@ namespace crossdesk {
static OnKeyAction g_on_key_action = nullptr;
static void* g_user_ptr = nullptr;
static std::unordered_map<int, int> g_unmapped_keycode_to_vk;
static int VkCodeFromUnicode(UniChar ch) {
if (ch >= 'a' && ch <= 'z') {
return static_cast<int>(ch - 'a' + 'A');
}
if (ch >= 'A' && ch <= 'Z') {
return static_cast<int>(ch);
}
if (ch >= '0' && ch <= '9') {
return static_cast<int>(ch);
}
switch (ch) {
case ' ':
return 0x20; // VK_SPACE
case '-':
case '_':
return 0xBD; // VK_OEM_MINUS
case '=':
case '+':
return 0xBB; // VK_OEM_PLUS
case '[':
case '{':
return 0xDB; // VK_OEM_4
case ']':
case '}':
return 0xDD; // VK_OEM_6
case '\\':
case '|':
return 0xDC; // VK_OEM_5
case ';':
case ':':
return 0xBA; // VK_OEM_1
case '\'':
case '"':
return 0xDE; // VK_OEM_7
case ',':
case '<':
return 0xBC; // VK_OEM_COMMA
case '.':
case '>':
return 0xBE; // VK_OEM_PERIOD
case '/':
case '?':
return 0xBF; // VK_OEM_2
case '`':
case '~':
return 0xC0; // VK_OEM_3
default:
return -1;
}
}
static int ResolveVkCodeFromMacEvent(CGEventRef event, CGKeyCode key_code,
bool is_key_down) {
auto key_it = CGKeyCodeToVkCode.find(key_code);
if (key_it != CGKeyCodeToVkCode.end()) {
if (is_key_down) {
g_unmapped_keycode_to_vk.erase(static_cast<int>(key_code));
}
return key_it->second;
}
int vk_code = -1;
UniChar chars[4] = {0};
UniCharCount char_count = 0;
CGEventKeyboardGetUnicodeString(event, 4, &char_count, chars);
if (char_count > 0) {
vk_code = VkCodeFromUnicode(chars[0]);
}
if (vk_code < 0) {
auto fallback_it =
g_unmapped_keycode_to_vk.find(static_cast<int>(key_code));
if (fallback_it != g_unmapped_keycode_to_vk.end()) {
vk_code = fallback_it->second;
}
}
if (vk_code >= 0) {
if (is_key_down) {
g_unmapped_keycode_to_vk[static_cast<int>(key_code)] = vk_code;
} else {
g_unmapped_keycode_to_vk.erase(static_cast<int>(key_code));
}
}
return vk_code;
}
CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
CGEventRef event, void* userInfo) {
(void)proxy;
if (!g_on_key_action) {
return event;
}
@@ -20,84 +113,74 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type,
return event;
}
int vk_code = 0;
if (type == kCGEventKeyDown || type == kCGEventKeyUp) {
const bool is_key_down = (type == kCGEventKeyDown);
CGKeyCode key_code = static_cast<CGKeyCode>(
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
if (CGKeyCodeToVkCode.find(key_code) != CGKeyCodeToVkCode.end()) {
g_on_key_action(CGKeyCodeToVkCode[key_code], type == kCGEventKeyDown,
g_user_ptr);
int vk_code = ResolveVkCodeFromMacEvent(event, key_code, is_key_down);
if (vk_code >= 0) {
g_on_key_action(vk_code, is_key_down, g_user_ptr);
}
} else if (type == kCGEventFlagsChanged) {
CGEventFlags current_flags = CGEventGetFlags(event);
CGKeyCode key_code = static_cast<CGKeyCode>(
CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode));
auto key_it = CGKeyCodeToVkCode.find(key_code);
if (key_it == CGKeyCodeToVkCode.end()) {
return nullptr;
}
const int vk_code = key_it->second;
// caps lock
bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0;
if (caps_lock_state != keyboard_capturer->caps_lock_flag_) {
keyboard_capturer->caps_lock_flag_ = caps_lock_state;
if (keyboard_capturer->caps_lock_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
g_on_key_action(vk_code, keyboard_capturer->caps_lock_flag_, g_user_ptr);
}
// shift
bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0;
if (shift_state != keyboard_capturer->shift_flag_) {
keyboard_capturer->shift_flag_ = shift_state;
if (keyboard_capturer->shift_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
g_on_key_action(vk_code, keyboard_capturer->shift_flag_, g_user_ptr);
}
// control
bool control_state = (current_flags & kCGEventFlagMaskControl) != 0;
if (control_state != keyboard_capturer->control_flag_) {
keyboard_capturer->control_flag_ = control_state;
if (keyboard_capturer->control_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
g_on_key_action(vk_code, keyboard_capturer->control_flag_, g_user_ptr);
}
// option
bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0;
if (option_state != keyboard_capturer->option_flag_) {
keyboard_capturer->option_flag_ = option_state;
if (keyboard_capturer->option_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
g_on_key_action(vk_code, keyboard_capturer->option_flag_, g_user_ptr);
}
// command
bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0;
if (command_state != keyboard_capturer->command_flag_) {
keyboard_capturer->command_flag_ = command_state;
if (keyboard_capturer->command_flag_) {
g_on_key_action(CGKeyCodeToVkCode[key_code], true, g_user_ptr);
} else {
g_on_key_action(CGKeyCodeToVkCode[key_code], false, g_user_ptr);
}
g_on_key_action(vk_code, keyboard_capturer->command_flag_, g_user_ptr);
}
}
return nullptr;
}
KeyboardCapturer::KeyboardCapturer() {}
KeyboardCapturer::KeyboardCapturer()
: event_tap_(nullptr), run_loop_source_(nullptr) {}
KeyboardCapturer::~KeyboardCapturer() {}
KeyboardCapturer::~KeyboardCapturer() { Unhook(); }
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
if (event_tap_) {
return 0;
}
g_unmapped_keycode_to_vk.clear();
g_on_key_action = on_key_action;
g_user_ptr = user_ptr;
@@ -115,15 +198,30 @@ int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
run_loop_source_ =
CFMachPortCreateRunLoopSource(kCFAllocatorDefault, event_tap_, 0);
if (!run_loop_source_) {
LOG_ERROR("CFMachPortCreateRunLoopSource failed");
CFRelease(event_tap_);
event_tap_ = nullptr;
return -1;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_,
kCFRunLoopCommonModes);
const CGEventFlags current_flags =
CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState);
caps_lock_flag_ = (current_flags & kCGEventFlagMaskAlphaShift) != 0;
shift_flag_ = (current_flags & kCGEventFlagMaskShift) != 0;
control_flag_ = (current_flags & kCGEventFlagMaskControl) != 0;
option_flag_ = (current_flags & kCGEventFlagMaskAlternate) != 0;
command_flag_ = (current_flags & kCGEventFlagMaskCommand) != 0;
CGEventTapEnable(event_tap_, true);
return 0;
}
int KeyboardCapturer::Unhook() {
g_unmapped_keycode_to_vk.clear();
g_on_key_action = nullptr;
g_user_ptr = nullptr;
@@ -170,9 +268,12 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) {
CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code];
CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down);
CGEventRef clearFlags =
CGEventCreateKeyboardEvent(NULL, (CGKeyCode)0, true);
CGEventSetFlags(clearFlags, 0);
if (!event) {
LOG_ERROR("CGEventCreateKeyboardEvent failed");
return -1;
}
CGEventSetFlags(event, 0);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
@@ -188,4 +289,4 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
return 0;
}
} // namespace crossdesk
} // namespace crossdesk
@@ -24,8 +24,8 @@ class KeyboardCapturer : public DeviceController {
virtual int SendKeyboardCommand(int key_code, bool is_down);
private:
CFMachPortRef event_tap_;
CFRunLoopSourceRef run_loop_source_;
CFMachPortRef event_tap_ = nullptr;
CFRunLoopSourceRef run_loop_source_ = nullptr;
public:
bool caps_lock_flag_ = false;
@@ -36,4 +36,4 @@ class KeyboardCapturer : public DeviceController {
int fn_key_code_ = 0x3F;
};
} // namespace crossdesk
#endif
#endif
@@ -54,11 +54,28 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
input.type = INPUT_KEYBOARD;
input.ki.wVk = (WORD)key_code;
if (!is_down) {
input.ki.dwFlags = KEYEVENTF_KEYUP;
const UINT scan_code =
MapVirtualKeyW(static_cast<UINT>(key_code), MAPVK_VK_TO_VSC_EX);
if (scan_code != 0) {
input.ki.wVk = 0;
input.ki.wScan = static_cast<WORD>(scan_code & 0xFF);
input.ki.dwFlags |= KEYEVENTF_SCANCODE;
if ((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) {
LOG_WARN("SendInput failed for key_code={}, is_down={}, err={}", key_code,
is_down, GetLastError());
return -1;
}
SendInput(1, &input, sizeof(INPUT));
return 0;
}
} // namespace crossdesk
} // namespace crossdesk
+73 -73
View File
@@ -73,13 +73,13 @@ std::map<int, int> vkCodeToCGKeyCode = {
{0x20, 0x31}, // Space
{0x08, 0x33}, // Backspace
{0x09, 0x30}, // Tab
{0x2C, 0x74}, // Print Screen
{0x2C, 0x69}, // Print Screen(F13)
{0x2D, 0x72}, // Insert
{0x2E, 0x75}, // Delete
{0x24, 0x73}, // Home
{0x23, 0x77}, // End
{0x21, 0x79}, // Page Up
{0x22, 0x7A}, // Page Down
{0x21, 0x74}, // Page Up
{0x22, 0x79}, // Page Down
// arrow keys
{0x25, 0x7B}, // Left Arrow
@@ -191,13 +191,13 @@ std::map<int, int> CGKeyCodeToVkCode = {
{0x31, 0x20}, // Space
{0x33, 0x08}, // Backspace
{0x30, 0x09}, // Tab
{0x74, 0x2C}, // Print Screen
{0x69, 0x2C}, // Print Screen(F13)
{0x72, 0x2D}, // Insert
{0x75, 0x2E}, // Delete
{0x73, 0x24}, // Home
{0x77, 0x23}, // End
{0x79, 0x21}, // Page Up
{0x7A, 0x22}, // Page Down
{0x74, 0x21}, // Page Up
{0x79, 0x22}, // Page Down
// arrow keys
{0x7B, 0x25}, // Left Arrow
@@ -326,21 +326,21 @@ std::map<int, int> vkCodeToX11KeySym = {
{0x28, 0xFF54}, // Down Arrow
// numpad
{0x60, 0x0030}, // Numpad 0
{0x61, 0x0031}, // Numpad 1
{0x62, 0x0032}, // Numpad 2
{0x63, 0x0033}, // Numpad 3
{0x64, 0x0034}, // Numpad 4
{0x65, 0x0035}, // Numpad 5
{0x66, 0x0036}, // Numpad 6
{0x67, 0x0037}, // Numpad 7
{0x68, 0x0038}, // Numpad 8
{0x69, 0x0039}, // Numpad 9
{0x6E, 0x003A}, // Numpad .
{0x6F, 0x002F}, // Numpad /
{0x6A, 0x002A}, // Numpad *
{0x6D, 0x002D}, // Numpad -
{0x6B, 0x002B}, // Numpad +
{0x60, 0xFFB0}, // Numpad 0
{0x61, 0xFFB1}, // Numpad 1
{0x62, 0xFFB2}, // Numpad 2
{0x63, 0xFFB3}, // Numpad 3
{0x64, 0xFFB4}, // Numpad 4
{0x65, 0xFFB5}, // Numpad 5
{0x66, 0xFFB6}, // Numpad 6
{0x67, 0xFFB7}, // Numpad 7
{0x68, 0xFFB8}, // Numpad 8
{0x69, 0xFFB9}, // Numpad 9
{0x6E, 0xFFAE}, // Numpad .
{0x6F, 0xFFAF}, // Numpad /
{0x6A, 0xFFAA}, // Numpad *
{0x6D, 0xFFAD}, // Numpad -
{0x6B, 0xFFAB}, // Numpad +
// symbol keys
{0xBA, 0x003B}, // ; (Semicolon)
@@ -454,21 +454,21 @@ std::map<int, int> x11KeySymToVkCode = {
{0xFF54, 0x28}, // Down Arrow
// numpad
{0x0030, 0x60}, // Numpad 0
{0x0031, 0x61}, // Numpad 1
{0x0032, 0x62}, // Numpad 2
{0x0033, 0x63}, // Numpad 3
{0x0034, 0x64}, // Numpad 4
{0x0035, 0x65}, // Numpad 5
{0x0036, 0x66}, // Numpad 6
{0x0037, 0x67}, // Numpad 7
{0x0038, 0x68}, // Numpad 8
{0x0039, 0x69}, // Numpad 9
{0x003A, 0x6E}, // Numpad .
{0x002F, 0x6F}, // Numpad /
{0x002A, 0x6A}, // Numpad *
{0x002D, 0x6D}, // Numpad -
{0x002B, 0x6B}, // Numpad +
{0xFFB0, 0x60}, // Numpad 0
{0xFFB1, 0x61}, // Numpad 1
{0xFFB2, 0x62}, // Numpad 2
{0xFFB3, 0x63}, // Numpad 3
{0xFFB4, 0x64}, // Numpad 4
{0xFFB5, 0x65}, // Numpad 5
{0xFFB6, 0x66}, // Numpad 6
{0xFFB7, 0x67}, // Numpad 7
{0xFFB8, 0x68}, // Numpad 8
{0xFFB9, 0x69}, // Numpad 9
{0xFFAE, 0x6E}, // Numpad .
{0xFFAF, 0x6F}, // Numpad /
{0xFFAA, 0x6A}, // Numpad *
{0xFFAD, 0x6D}, // Numpad -
{0xFFAB, 0x6B}, // Numpad +
// symbol keys
{0x003B, 0xBA}, // ; (Semicolon)
@@ -557,13 +557,13 @@ std::map<int, int> cgKeyCodeToX11KeySym = {
{0x31, 0x0020}, // Space
{0x33, 0xFF08}, // Backspace
{0x30, 0xFF09}, // Tab
{0x74, 0xFF15}, // Print Screen
{0x69, 0xFF15}, // Print Screen(F13)
{0x72, 0xFF63}, // Insert
{0x75, 0xFFFF}, // Delete
{0x73, 0xFF50}, // Home
{0x77, 0xFF57}, // End
{0x79, 0xFF55}, // Page Up
{0x7A, 0xFF56}, // Page Down
{0x74, 0xFF55}, // Page Up
{0x79, 0xFF56}, // Page Down
// arrow keys
{0x7B, 0xFF51}, // Left Arrow
@@ -572,21 +572,21 @@ std::map<int, int> cgKeyCodeToX11KeySym = {
{0x7D, 0xFF54}, // Down Arrow
// numpad
{0x52, 0x0030}, // Numpad 0
{0x53, 0x0031}, // Numpad 1
{0x54, 0x0032}, // Numpad 2
{0x55, 0x0033}, // Numpad 3
{0x56, 0x0034}, // Numpad 4
{0x57, 0x0035}, // Numpad 5
{0x58, 0x0036}, // Numpad 6
{0x59, 0x0037}, // Numpad 7
{0x5B, 0x0038}, // Numpad 8
{0x5C, 0x0039}, // Numpad 9
{0x41, 0x003A}, // Numpad .
{0x4B, 0x002F}, // Numpad /
{0x43, 0x002A}, // Numpad *
{0x4E, 0x002D}, // Numpad -
{0x45, 0x002B}, // Numpad +
{0x52, 0xFFB0}, // Numpad 0
{0x53, 0xFFB1}, // Numpad 1
{0x54, 0xFFB2}, // Numpad 2
{0x55, 0xFFB3}, // Numpad 3
{0x56, 0xFFB4}, // Numpad 4
{0x57, 0xFFB5}, // Numpad 5
{0x58, 0xFFB6}, // Numpad 6
{0x59, 0xFFB7}, // Numpad 7
{0x5B, 0xFFB8}, // Numpad 8
{0x5C, 0xFFB9}, // Numpad 9
{0x41, 0xFFAE}, // Numpad .
{0x4B, 0xFFAF}, // Numpad /
{0x43, 0xFFAA}, // Numpad *
{0x4E, 0xFFAD}, // Numpad -
{0x45, 0xFFAB}, // Numpad +
// symbol keys
{0x29, 0x003B}, // ; (Semicolon)
@@ -683,13 +683,13 @@ std::map<int, int> x11KeySymToCgKeyCode = {
{0x0020, 0x31}, // Space
{0xFF08, 0x33}, // Backspace
{0xFF09, 0x30}, // Tab
{0xFF15, 0x74}, // Print Screen
{0xFF15, 0x69}, // Print Screen(F13)
{0xFF63, 0x72}, // Insert
{0xFFFF, 0x75}, // Delete
{0xFF50, 0x73}, // Home
{0xFF57, 0x77}, // End
{0xFF55, 0x79}, // Page Up
{0xFF56, 0x7A}, // Page Down
{0xFF55, 0x74}, // Page Up
{0xFF56, 0x79}, // Page Down
// arrow keys
{0xFF51, 0x7B}, // Left Arrow
@@ -698,21 +698,21 @@ std::map<int, int> x11KeySymToCgKeyCode = {
{0xFF54, 0x7D}, // Down Arrow
// numpad
{0x0030, 0x52}, // Numpad 0
{0x0031, 0x53}, // Numpad 1
{0x0032, 0x54}, // Numpad 2
{0x0033, 0x55}, // Numpad 3
{0x0034, 0x56}, // Numpad 4
{0x0035, 0x57}, // Numpad 5
{0x0036, 0x58}, // Numpad 6
{0x0037, 0x59}, // Numpad 7
{0x0038, 0x5B}, // Numpad 8
{0x0039, 0x5C}, // Numpad 9
{0x003A, 0x41}, // Numpad .
{0x002F, 0x4B}, // Numpad /
{0x002A, 0x43}, // Numpad *
{0x002D, 0x4E}, // Numpad -
{0x002B, 0x45}, // Numpad +
{0xFFB0, 0x52}, // Numpad 0
{0xFFB1, 0x53}, // Numpad 1
{0xFFB2, 0x54}, // Numpad 2
{0xFFB3, 0x55}, // Numpad 3
{0xFFB4, 0x56}, // Numpad 4
{0xFFB5, 0x57}, // Numpad 5
{0xFFB6, 0x58}, // Numpad 6
{0xFFB7, 0x59}, // Numpad 7
{0xFFB8, 0x5B}, // Numpad 8
{0xFFB9, 0x5C}, // Numpad 9
{0xFFAE, 0x41}, // Numpad .
{0xFFAF, 0x4B}, // Numpad /
{0xFFAA, 0x43}, // Numpad *
{0xFFAD, 0x4E}, // Numpad -
{0xFFAB, 0x45}, // Numpad +
// symbol keys
{0x003B, 0x29}, // ; (Semicolon)
@@ -739,4 +739,4 @@ std::map<int, int> x11KeySymToCgKeyCode = {
{0xFFEC, 0x36}, // Right Command
};
} // namespace crossdesk
#endif
#endif
+2
View File
@@ -224,6 +224,8 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
}
AddAudioStream(props->peer_, props->audio_label_.c_str());
AddDataStream(props->peer_, props->data_label_.c_str(), false);
AddDataStream(props->peer_, props->mouse_label_.c_str(), false);
AddDataStream(props->peer_, props->keyboard_label_.c_str(), true);
AddDataStream(props->peer_, props->control_data_label_.c_str(), true);
AddDataStream(props->peer_, props->file_label_.c_str(), true);
AddDataStream(props->peer_, props->file_feedback_label_.c_str(), true);
+7 -3
View File
@@ -734,7 +734,7 @@ int Render::StartKeyboardCapturer() {
LOG_INFO("Start keyboard capturer");
}
return 0;
return keyboard_capturer_init_ret;
}
int Render::StopKeyboardCapturer() {
@@ -901,6 +901,8 @@ int Render::CreateConnectionPeer() {
AddAudioStream(peer_, audio_label_.c_str());
AddDataStream(peer_, data_label_.c_str(), false);
AddDataStream(peer_, mouse_label_.c_str(), false);
AddDataStream(peer_, keyboard_label_.c_str(), true);
AddDataStream(peer_, control_data_label_.c_str(), true);
AddDataStream(peer_, file_label_.c_str(), true);
AddDataStream(peer_, file_feedback_label_.c_str(), true);
@@ -1018,8 +1020,9 @@ void Render::UpdateInteractions() {
if (start_keyboard_capturer_ && focus_on_stream_window_) {
if (!keyboard_capturer_is_started_) {
StartKeyboardCapturer();
keyboard_capturer_is_started_ = true;
if (StartKeyboardCapturer() == 0) {
keyboard_capturer_is_started_ = true;
}
}
} else if (keyboard_capturer_is_started_) {
StopKeyboardCapturer();
@@ -2452,6 +2455,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();
focus_on_stream_window_ = false;
} else if (main_window_ &&
SDL_GetWindowID(main_window_) == event.window.windowID) {
+10
View File
@@ -20,6 +20,7 @@
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "IconsFontAwesome6.h"
@@ -83,6 +84,8 @@ class Render {
PeerPtr* peer_ = nullptr;
std::string audio_label_ = "control_audio";
std::string data_label_ = "data";
std::string mouse_label_ = "mouse";
std::string keyboard_label_ = "keyboard";
std::string file_label_ = "file";
std::string control_data_label_ = "control_data";
std::string file_feedback_label_ = "file_feedback";
@@ -320,6 +323,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();
int ProcessMouseEvent(const SDL_Event& event);
static void SdlCaptureAudioIn(void* userdata, Uint8* stream, int len);
@@ -504,6 +510,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_;
SDL_Event last_mouse_event;
SDL_AudioStream* output_stream_;
uint32_t STREAM_REFRESH_EVENT = 0;
@@ -603,6 +611,8 @@ class Render {
std::string video_secondary_label_ = "secondary_display";
std::string audio_label_ = "audio";
std::string data_label_ = "data";
std::string mouse_label_ = "mouse";
std::string keyboard_label_ = "keyboard";
std::string info_label_ = "info";
std::string control_data_label_ = "control_data";
std::string file_label_ = "file";
+59 -4
View File
@@ -81,6 +81,55 @@ void Render::OnSignalMessageCb(const char* message, size_t size,
}
}
bool Render::IsModifierVkKey(int key_code) {
switch (key_code) {
case 0x10: // VK_SHIFT
case 0x11: // VK_CONTROL
case 0x12: // VK_MENU(ALT)
case 0x5B: // VK_LWIN
case 0x5C: // VK_RWIN
case 0xA0: // VK_LSHIFT
case 0xA1: // VK_RSHIFT
case 0xA2: // VK_LCONTROL
case 0xA3: // VK_RCONTROL
case 0xA4: // VK_LMENU
case 0xA5: // VK_RMENU
return true;
default:
return false;
}
}
void Render::UpdatePressedModifierState(int key_code, bool is_down) {
if (!IsModifierVkKey(key_code)) {
return;
}
std::lock_guard<std::mutex> lock(pressed_modifier_keys_mutex_);
if (is_down) {
pressed_modifier_keys_.insert(key_code);
} else {
pressed_modifier_keys_.erase(key_code);
}
}
void Render::ForceReleasePressedModifiers() {
std::vector<int> pressed_keys;
{
std::lock_guard<std::mutex> lock(pressed_modifier_keys_mutex_);
if (pressed_modifier_keys_.empty()) {
return;
}
pressed_keys.assign(pressed_modifier_keys_.begin(),
pressed_modifier_keys_.end());
pressed_modifier_keys_.clear();
}
for (int key_code : pressed_keys) {
SendKeyCommand(key_code, false);
}
}
int Render::SendKeyCommand(int key_code, bool is_down) {
RemoteAction remote_action;
remote_action.type = ControlType::keyboard;
@@ -99,12 +148,18 @@ int Render::SendKeyCommand(int key_code, bool is_down) {
if (props->connection_status_ == ConnectionStatus::Connected &&
props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
int ret = SendReliableDataFrame(props->peer_, msg.c_str(), msg.size(),
props->keyboard_label_.c_str());
if (ret != 0) {
LOG_WARN("Send keyboard command failed, remote_id={}, ret={}",
target_id, ret);
}
}
}
}
UpdatePressedModifierState(key_code, is_down);
return 0;
}
@@ -227,7 +282,7 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
if (props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
props->mouse_label_.c_str());
}
} else if (SDL_EVENT_MOUSE_WHEEL == event.type &&
last_mouse_event.button.x >= render_rect.x &&
@@ -273,7 +328,7 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
if (props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
props->mouse_label_.c_str());
}
}
}
@@ -121,8 +121,16 @@ int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) {
width_ = attr.width;
height_ = attr.height;
if (width_ % 2 != 0 || height_ % 2 != 0) {
LOG_ERROR("Width and height must be even numbers");
if ((width_ & 1) != 0 || (height_ & 1) != 0) {
LOG_WARN(
"X11 root size {}x{} is not even, aligning down to {}x{} for NV12",
width_, height_, width_ & ~1, height_ & ~1);
width_ &= ~1;
height_ &= ~1;
}
if (width_ <= 1 || height_ <= 1) {
LOG_ERROR("Invalid capture size after alignment: {}x{}", width_, height_);
return -2;
}
@@ -289,6 +297,16 @@ void ScreenCapturerX11::OnFrame() {
src_argb = reinterpret_cast<uint8_t*>(image->data);
}
const size_t y_size =
static_cast<size_t>(width_) * static_cast<size_t>(height_);
const size_t uv_size = y_size / 2;
if (y_plane_.size() != y_size) {
y_plane_.resize(y_size);
}
if (uv_plane_.size() != uv_size) {
uv_plane_.resize(uv_size);
}
libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_,
uv_plane_.data(), width_, width_, height_);