[feat] mouse/keyboard control and screen capture supported by using X11 on Linux platform

This commit is contained in:
dijunkun
2025-05-07 19:37:41 +08:00
parent 93bd5b2660
commit 250fd49406
12 changed files with 363 additions and 454 deletions

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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_);
}

View File

@@ -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;
};