mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-27 04:35:34 +08:00
[feat] mouse/keyboard control and screen capture supported by using X11 on Linux platform
This commit is contained in:
@@ -1,15 +1,69 @@
|
||||
#include "keyboard_capturer.h"
|
||||
|
||||
KeyboardCapturer::KeyboardCapturer() {}
|
||||
#include "keyboard_converter.h"
|
||||
#include "rd_log.h"
|
||||
|
||||
KeyboardCapturer::~KeyboardCapturer() {}
|
||||
static OnKeyAction g_on_key_action = nullptr;
|
||||
static void* g_user_ptr = nullptr;
|
||||
|
||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void *user_ptr) {
|
||||
static int KeyboardEventHandler(Display* display, XEvent* event) {
|
||||
if (event->xkey.type == KeyPress || event->xkey.type == KeyRelease) {
|
||||
KeySym keySym = XKeycodeToKeysym(display, event->xkey.keycode, 0);
|
||||
int key_code = XKeysymToKeycode(display, keySym);
|
||||
bool is_key_down = (event->xkey.type == KeyPress);
|
||||
|
||||
if (g_on_key_action) {
|
||||
g_on_key_action(key_code, is_key_down, g_user_ptr);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() { return 0; }
|
||||
KeyboardCapturer::KeyboardCapturer() : display_(nullptr), running_(true) {
|
||||
display_ = XOpenDisplay(nullptr);
|
||||
if (!display_) {
|
||||
LOG_ERROR("Failed to open X display.");
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardCapturer::~KeyboardCapturer() {
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
}
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Hook(OnKeyAction on_key_action, void* user_ptr) {
|
||||
g_on_key_action = on_key_action;
|
||||
g_user_ptr = user_ptr;
|
||||
|
||||
XSelectInput(display_, DefaultRootWindow(display_),
|
||||
KeyPressMask | KeyReleaseMask);
|
||||
|
||||
while (running_) {
|
||||
XEvent event;
|
||||
XNextEvent(display_, &event);
|
||||
KeyboardEventHandler(display_, &event);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::Unhook() {
|
||||
running_ = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) {
|
||||
if (!display_) {
|
||||
LOG_ERROR("Display not initialized.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (vkCodeToX11KeySym.find(key_code) != vkCodeToX11KeySym.end()) {
|
||||
int x11_key_code = vkCodeToX11KeySym[key_code];
|
||||
KeyCode keycode = XKeysymToKeycode(display_, x11_key_code);
|
||||
XTestFakeKeyEvent(display_, keycode, is_down, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
#ifndef _KEYBOARD_CAPTURER_H_
|
||||
#define _KEYBOARD_CAPTURER_H_
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
#include <X11/keysym.h>
|
||||
|
||||
#include "device_controller.h"
|
||||
|
||||
class KeyboardCapturer : public DeviceController {
|
||||
@@ -20,6 +24,9 @@ class KeyboardCapturer : public DeviceController {
|
||||
virtual int SendKeyboardCommand(int key_code, bool is_down);
|
||||
|
||||
private:
|
||||
Display *display_;
|
||||
Window root_;
|
||||
bool running_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -245,4 +245,68 @@ std::map<int, int> CGKeyCodeToVkCode = {
|
||||
{0x36, 0x5C}, // Right Command
|
||||
};
|
||||
|
||||
// Windows vkCode to X11 KeySym
|
||||
std::map<int, int> vkCodeToX11KeySym = {
|
||||
{0x41, XK_A}, {0x42, XK_B}, {0x43, XK_C},
|
||||
{0x44, XK_D}, {0x45, XK_E}, {0x46, XK_F},
|
||||
{0x47, XK_G}, {0x48, XK_H}, {0x49, XK_I},
|
||||
{0x4A, XK_J}, {0x4B, XK_K}, {0x4C, XK_L},
|
||||
{0x4D, XK_M}, {0x4E, XK_N}, {0x4F, XK_O},
|
||||
{0x50, XK_P}, {0x51, XK_Q}, {0x52, XK_R},
|
||||
{0x53, XK_S}, {0x54, XK_T}, {0x55, XK_U},
|
||||
{0x56, XK_V}, {0x57, XK_W}, {0x58, XK_X},
|
||||
{0x59, XK_Y}, {0x5A, XK_Z}, {0x30, XK_0},
|
||||
{0x31, XK_1}, {0x32, XK_2}, {0x33, XK_3},
|
||||
{0x34, XK_4}, {0x35, XK_5}, {0x36, XK_6},
|
||||
{0x37, XK_7}, {0x38, XK_8}, {0x39, XK_9},
|
||||
{0x1B, XK_Escape}, {0x0D, XK_Return}, {0x20, XK_space},
|
||||
{0x08, XK_BackSpace}, {0x09, XK_Tab}, {0x25, XK_Left},
|
||||
{0x27, XK_Right}, {0x26, XK_Up}, {0x28, XK_Down},
|
||||
{0x70, XK_F1}, {0x71, XK_F2}, {0x72, XK_F3},
|
||||
{0x73, XK_F4}, {0x74, XK_F5}, {0x75, XK_F6},
|
||||
{0x76, XK_F7}, {0x77, XK_F8}, {0x78, XK_F9},
|
||||
{0x79, XK_F10}, {0x7A, XK_F11}, {0x7B, XK_F12},
|
||||
};
|
||||
|
||||
// X11 KeySym to Windows vkCode
|
||||
std::map<int, int> x11KeySymToVkCode = []() {
|
||||
std::map<int, int> result;
|
||||
for (const auto& pair : vkCodeToX11KeySym) {
|
||||
result[pair.second] = pair.first;
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
// macOS CGKeyCode to X11 KeySym
|
||||
std::map<int, int> cgKeyCodeToX11KeySym = {
|
||||
{0x00, XK_A}, {0x0B, XK_B}, {0x08, XK_C},
|
||||
{0x02, XK_D}, {0x0E, XK_E}, {0x03, XK_F},
|
||||
{0x05, XK_G}, {0x04, XK_H}, {0x22, XK_I},
|
||||
{0x26, XK_J}, {0x28, XK_K}, {0x25, XK_L},
|
||||
{0x2E, XK_M}, {0x2D, XK_N}, {0x1F, XK_O},
|
||||
{0x23, XK_P}, {0x0C, XK_Q}, {0x0F, XK_R},
|
||||
{0x01, XK_S}, {0x11, XK_T}, {0x20, XK_U},
|
||||
{0x09, XK_V}, {0x0D, XK_W}, {0x07, XK_X},
|
||||
{0x10, XK_Y}, {0x06, XK_Z}, {0x12, XK_1},
|
||||
{0x13, XK_2}, {0x14, XK_3}, {0x15, XK_4},
|
||||
{0x17, XK_5}, {0x16, XK_6}, {0x1A, XK_7},
|
||||
{0x1C, XK_8}, {0x19, XK_9}, {0x1D, XK_0},
|
||||
{0x35, XK_Escape}, {0x24, XK_Return}, {0x31, XK_space},
|
||||
{0x33, XK_BackSpace}, {0x30, XK_Tab}, {0x7B, XK_Left},
|
||||
{0x7C, XK_Right}, {0x7E, XK_Up}, {0x7D, XK_Down},
|
||||
{0x7A, XK_F1}, {0x78, XK_F2}, {0x63, XK_F3},
|
||||
{0x76, XK_F4}, {0x60, XK_F5}, {0x61, XK_F6},
|
||||
{0x62, XK_F7}, {0x64, XK_F8}, {0x65, XK_F9},
|
||||
{0x6D, XK_F10}, {0x67, XK_F11}, {0x6F, XK_F12},
|
||||
};
|
||||
|
||||
// X11 KeySym to macOS CGKeyCode
|
||||
std::map<int, int> x11KeySymToCgKeyCode = []() {
|
||||
std::map<int, int> result;
|
||||
for (const auto& pair : cgKeyCodeToX11KeySym) {
|
||||
result[pair.second] = pair.first;
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
#endif
|
||||
@@ -1,151 +1,120 @@
|
||||
#include "mouse_controller.h"
|
||||
|
||||
#include <X11/extensions/XTest.h>
|
||||
|
||||
#include "rd_log.h"
|
||||
|
||||
MouseController::MouseController() {}
|
||||
|
||||
MouseController::~MouseController() {
|
||||
if (uinput_fd_) {
|
||||
ioctl(uinput_fd_, UI_DEV_DESTROY);
|
||||
close(uinput_fd_);
|
||||
}
|
||||
}
|
||||
MouseController::~MouseController() { Destroy(); }
|
||||
|
||||
int MouseController::Init(int screen_width, int screen_height) {
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
|
||||
uinput_fd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
|
||||
if (uinput_fd_ < 0) {
|
||||
LOG_ERROR("Cannot open device: /dev/uinput");
|
||||
display_ = XOpenDisplay(NULL);
|
||||
if (!display_) {
|
||||
LOG_ERROR("Cannot connect to X server");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ioctl(uinput_fd_, UI_SET_EVBIT, EV_KEY);
|
||||
ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_RIGHT);
|
||||
ioctl(uinput_fd_, UI_SET_KEYBIT, BTN_LEFT);
|
||||
ioctl(uinput_fd_, UI_SET_EVBIT, EV_ABS);
|
||||
ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_X);
|
||||
ioctl(uinput_fd_, UI_SET_ABSBIT, ABS_Y);
|
||||
ioctl(uinput_fd_, UI_SET_EVBIT, EV_REL);
|
||||
root_ = DefaultRootWindow(display_);
|
||||
screen_width_ = screen_width;
|
||||
screen_height_ = screen_height;
|
||||
|
||||
struct uinput_user_dev uidev;
|
||||
memset(&uidev, 0, sizeof(uidev));
|
||||
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "VirtualMouse");
|
||||
uidev.id.bustype = BUS_USB;
|
||||
uidev.id.version = 1;
|
||||
uidev.id.vendor = 0x1;
|
||||
uidev.id.product = 0x1;
|
||||
uidev.absmin[ABS_X] = 0;
|
||||
uidev.absmax[ABS_X] = screen_width_;
|
||||
uidev.absmin[ABS_Y] = 0;
|
||||
uidev.absmax[ABS_Y] = screen_height_;
|
||||
|
||||
int res_uidev = write(uinput_fd_, &uidev, sizeof(uidev));
|
||||
ioctl(uinput_fd_, UI_DEV_CREATE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MouseController::Destroy() { return 0; }
|
||||
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action) {
|
||||
int mouse_pos_x = remote_action.m.x * screen_width_;
|
||||
int mouse_pos_y = remote_action.m.y * screen_height_;
|
||||
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, NULL);
|
||||
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::left_down:
|
||||
SimulateKeyDown(uinput_fd_, BTN_LEFT);
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
SimulateKeyUp(uinput_fd_, BTN_LEFT);
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
SimulateKeyDown(uinput_fd_, BTN_RIGHT);
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
SimulateKeyUp(uinput_fd_, BTN_RIGHT);
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
SimulateKeyDown(uinput_fd_, BTN_MIDDLE);
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
SimulateKeyUp(uinput_fd_, BTN_MIDDLE);
|
||||
break;
|
||||
case MouseFlag::wheel_vertical:
|
||||
event.type = EV_REL;
|
||||
event.code = REL_WHEEL;
|
||||
event.value = remote_action.m.s;
|
||||
(void)write(uinput_fd_, &event, sizeof(event));
|
||||
break;
|
||||
case MouseFlag::wheel_horizontal:
|
||||
event.type = EV_REL;
|
||||
event.code = REL_HWHEEL;
|
||||
event.value = remote_action.m.s;
|
||||
(void)write(uinput_fd_, &event, sizeof(event));
|
||||
break;
|
||||
default:
|
||||
SetMousePosition(uinput_fd_, mouse_pos_x, mouse_pos_y);
|
||||
break;
|
||||
}
|
||||
int event_base, error_base, major_version, minor_version;
|
||||
if (!XTestQueryExtension(display_, &event_base, &error_base, &major_version,
|
||||
&minor_version)) {
|
||||
LOG_ERROR("XTest extension not available");
|
||||
XCloseDisplay(display_);
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyDown(int fd, int kval) {
|
||||
int res_ev = 0;
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, 0);
|
||||
|
||||
event.type = EV_KEY;
|
||||
event.value = 1;
|
||||
event.code = kval;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
|
||||
event.type = EV_SYN;
|
||||
event.value = 0;
|
||||
event.code = SYN_REPORT;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
int MouseController::Destroy() {
|
||||
if (display_) {
|
||||
XCloseDisplay(display_);
|
||||
display_ = nullptr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyUp(int fd, int kval) {
|
||||
int res_ev = 0;
|
||||
struct input_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
gettimeofday(&event.time, 0);
|
||||
int MouseController::SendMouseCommand(RemoteAction remote_action) {
|
||||
switch (remote_action.type) {
|
||||
case mouse:
|
||||
switch (remote_action.m.flag) {
|
||||
case MouseFlag::move:
|
||||
SetMousePosition(
|
||||
static_cast<int>(remote_action.m.x * screen_width_),
|
||||
static_cast<int>(remote_action.m.y * screen_height_));
|
||||
break;
|
||||
case MouseFlag::left_down:
|
||||
XTestFakeButtonEvent(display_, 1, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::left_up:
|
||||
XTestFakeButtonEvent(display_, 1, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::right_down:
|
||||
XTestFakeButtonEvent(display_, 3, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::right_up:
|
||||
XTestFakeButtonEvent(display_, 3, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::middle_down:
|
||||
XTestFakeButtonEvent(display_, 2, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::middle_up:
|
||||
XTestFakeButtonEvent(display_, 2, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
break;
|
||||
case MouseFlag::wheel_vertical: {
|
||||
if (remote_action.m.s > 0) {
|
||||
SimulateMouseWheel(4, remote_action.m.s);
|
||||
} else if (remote_action.m.s < 0) {
|
||||
SimulateMouseWheel(5, -remote_action.m.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MouseFlag::wheel_horizontal: {
|
||||
if (remote_action.m.s > 0) {
|
||||
SimulateMouseWheel(6, remote_action.m.s);
|
||||
} else if (remote_action.m.s < 0) {
|
||||
SimulateMouseWheel(7, -remote_action.m.s);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
event.type = EV_KEY;
|
||||
event.value = 0;
|
||||
event.code = kval;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
|
||||
event.type = EV_SYN;
|
||||
event.value = 0;
|
||||
event.code = SYN_REPORT;
|
||||
res_ev = write(fd, &event, sizeof(event));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MouseController::SetMousePosition(int fd, int x, int y) {
|
||||
struct input_event ev[2], ev_sync;
|
||||
memset(ev, 0, sizeof(ev));
|
||||
memset(&ev_sync, 0, sizeof(ev_sync));
|
||||
|
||||
ev[0].type = EV_ABS;
|
||||
ev[0].code = ABS_X;
|
||||
ev[0].value = x;
|
||||
ev[1].type = EV_ABS;
|
||||
ev[1].code = ABS_Y;
|
||||
ev[1].value = y;
|
||||
int res_w = write(fd, ev, sizeof(ev));
|
||||
|
||||
ev_sync.type = EV_SYN;
|
||||
ev_sync.value = 0;
|
||||
ev_sync.code = 0;
|
||||
int res_ev_sync = write(fd, &ev_sync, sizeof(ev_sync));
|
||||
void MouseController::SetMousePosition(int x, int y) {
|
||||
XWarpPointer(display_, None, root_, 0, 0, 0, 0, x, y);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyDown(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, True, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateKeyUp(int kval) {
|
||||
XTestFakeKeyEvent(display_, kval, False, CurrentTime);
|
||||
XFlush(display_);
|
||||
}
|
||||
|
||||
void MouseController::SimulateMouseWheel(int direction_button, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
XTestFakeButtonEvent(display_, direction_button, True, CurrentTime);
|
||||
XTestFakeButtonEvent(display_, direction_button, False, CurrentTime);
|
||||
}
|
||||
XFlush(display_);
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
/*
|
||||
* @Author: DI JUNKUN
|
||||
* @Date: 2023-12-14
|
||||
* Copyright (c) 2023 by DI JUNKUN, All Rights Reserved.
|
||||
* @Date: 2025-05-07
|
||||
* Copyright (c) 2025 by DI JUNKUN, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _MOUSE_CONTROLLER_H_
|
||||
#define _MOUSE_CONTROLLER_H_
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/uinput.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "device_controller.h"
|
||||
@@ -27,13 +24,13 @@ class MouseController : public DeviceController {
|
||||
virtual int SendMouseCommand(RemoteAction remote_action);
|
||||
|
||||
private:
|
||||
void SimulateKeyDown(int fd, int kval);
|
||||
void SimulateKeyUp(int fd, int kval);
|
||||
void SetMousePosition(int fd, int x, int y);
|
||||
void SimulateKeyDown(int kval);
|
||||
void SimulateKeyUp(int kval);
|
||||
void SetMousePosition(int x, int y);
|
||||
void SimulateMouseWheel(int direction_button, int count);
|
||||
|
||||
private:
|
||||
int uinput_fd_;
|
||||
struct uinput_user_dev uinput_dev_;
|
||||
Display* display_ = nullptr;
|
||||
Window root_ = 0;
|
||||
int screen_width_ = 0;
|
||||
int screen_height_ = 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user