From 250fd494064bf1a7794cd352ff0e086e5f0620e4 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 7 May 2025 19:37:41 +0800 Subject: [PATCH] [feat] mouse/keyboard control and screen capture supported by using X11 on Linux platform --- .../keyboard/linux/keyboard_capturer.cpp | 64 ++++- .../keyboard/linux/keyboard_capturer.h | 7 + .../{keyboard/mac => }/keyboard_converter.h | 64 +++++ .../mouse/linux/mouse_controller.cpp | 225 ++++++++---------- .../mouse/linux/mouse_controller.h | 23 +- .../linux/screen_capturer_x11.cpp | 204 +++++++--------- .../linux/screen_capturer_x11.h | 93 ++++---- src/screen_capturer/linux/x11_session.h | 37 --- .../linux/x11_session_impl.cpp | 49 ---- src/screen_capturer/linux/x11_session_impl.h | 44 ---- thirdparty/projectx | 2 +- xmake.lua | 5 +- 12 files changed, 363 insertions(+), 454 deletions(-) rename src/device_controller/{keyboard/mac => }/keyboard_converter.h (68%) delete mode 100644 src/screen_capturer/linux/x11_session.h delete mode 100644 src/screen_capturer/linux/x11_session_impl.cpp delete mode 100644 src/screen_capturer/linux/x11_session_impl.h diff --git a/src/device_controller/keyboard/linux/keyboard_capturer.cpp b/src/device_controller/keyboard/linux/keyboard_capturer.cpp index 70b6688..fbd1019 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/linux/keyboard_capturer.cpp @@ -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; -} \ No newline at end of file +} diff --git a/src/device_controller/keyboard/linux/keyboard_capturer.h b/src/device_controller/keyboard/linux/keyboard_capturer.h index 0bec57b..cab6422 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer.h +++ b/src/device_controller/keyboard/linux/keyboard_capturer.h @@ -7,6 +7,10 @@ #ifndef _KEYBOARD_CAPTURER_H_ #define _KEYBOARD_CAPTURER_H_ +#include +#include +#include + #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 \ No newline at end of file diff --git a/src/device_controller/keyboard/mac/keyboard_converter.h b/src/device_controller/keyboard_converter.h similarity index 68% rename from src/device_controller/keyboard/mac/keyboard_converter.h rename to src/device_controller/keyboard_converter.h index ba6c2c4..888be28 100644 --- a/src/device_controller/keyboard/mac/keyboard_converter.h +++ b/src/device_controller/keyboard_converter.h @@ -245,4 +245,68 @@ std::map CGKeyCodeToVkCode = { {0x36, 0x5C}, // Right Command }; +// Windows vkCode to X11 KeySym +std::map 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 x11KeySymToVkCode = []() { + std::map result; + for (const auto& pair : vkCodeToX11KeySym) { + result[pair.second] = pair.first; + } + return result; +}(); + +// macOS CGKeyCode to X11 KeySym +std::map 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 x11KeySymToCgKeyCode = []() { + std::map result; + for (const auto& pair : cgKeyCodeToX11KeySym) { + result[pair.second] = pair.first; + } + return result; +}(); + #endif \ No newline at end of file diff --git a/src/device_controller/mouse/linux/mouse_controller.cpp b/src/device_controller/mouse/linux/mouse_controller.cpp index d6f9355..839eea5 100644 --- a/src/device_controller/mouse/linux/mouse_controller.cpp +++ b/src/device_controller/mouse/linux/mouse_controller.cpp @@ -1,151 +1,120 @@ #include "mouse_controller.h" +#include + #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(remote_action.m.x * screen_width_), + static_cast(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_); +} \ No newline at end of file diff --git a/src/device_controller/mouse/linux/mouse_controller.h b/src/device_controller/mouse/linux/mouse_controller.h index cfdde1a..73a3d17 100644 --- a/src/device_controller/mouse/linux/mouse_controller.h +++ b/src/device_controller/mouse/linux/mouse_controller.h @@ -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 -#include -#include -#include -#include +#include +#include #include #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; }; diff --git a/src/screen_capturer/linux/screen_capturer_x11.cpp b/src/screen_capturer/linux/screen_capturer_x11.cpp index ba60ccf..646a1a9 100644 --- a/src/screen_capturer/linux/screen_capturer_x11.cpp +++ b/src/screen_capturer/linux/screen_capturer_x11.cpp @@ -1,160 +1,118 @@ #include "screen_capturer_x11.h" -#include +#include +#include +#include "libyuv.h" #include "rd_log.h" -#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 -unsigned char nv12_buffer_[NV12_BUFFER_SIZE]; - ScreenCapturerX11::ScreenCapturerX11() {} -ScreenCapturerX11::~ScreenCapturerX11() { - if (inited_ && capture_thread_.joinable()) { - capture_thread_.join(); - inited_ = false; - } -} +ScreenCapturerX11::~ScreenCapturerX11() { Destroy(); } int ScreenCapturerX11::Init(const int fps, cb_desktop_data cb) { - if (cb) { - _on_data = cb; + display_ = XOpenDisplay(nullptr); + if (!display_) { + LOG_ERROR("Cannot connect to X server"); + return -1; + } + + root_ = DefaultRootWindow(display_); + XWindowAttributes attr; + XGetWindowAttributes(display_, root_, &attr); + + width_ = attr.width; + height_ = attr.height; + + if (width_ % 2 != 0 || height_ % 2 != 0) { + LOG_ERROR("Width and height must be even numbers"); + return -2; } fps_ = fps; + callback_ = cb; - av_log_set_level(AV_LOG_QUIET); - - pFormatCtx_ = avformat_alloc_context(); - - avdevice_register_all(); - - // grabbing frame rate - av_dict_set(&options_, "framerate", "30", 0); - // show remote cursor - av_dict_set(&options_, "capture_cursor", "0", 0); - // Make the grabbed area follow the mouse - // av_dict_set(&options_, "follow_mouse", "centered", 0); - // Video frame size. The default is to capture the full screen - // av_dict_set(&options_, "video_size", "1280x720", 0); - std::string capture_method = "x11grab"; - ifmt_ = (AVInputFormat *)av_find_input_format(capture_method.c_str()); - if (!ifmt_) { - LOG_ERROR("Couldn't find_input_format [{}]", capture_method.c_str()); - } - - const char *display = std::getenv("DISPLAY"); - // Grab at position 10,20 - if (display) { - if (avformat_open_input(&pFormatCtx_, display, ifmt_, &options_) != 0) { - LOG_ERROR("Couldn't open input stream {}", display); - return -1; - } else { - LOG_INFO("Open input stream [{}]", display); - } - } else { - LOG_ERROR("DISPLAY environment variable not set"); - return -1; - } - - if (avformat_find_stream_info(pFormatCtx_, NULL) < 0) { - LOG_ERROR("Couldn't find stream information"); - return -1; - } - - videoindex_ = -1; - for (i_ = 0; i_ < pFormatCtx_->nb_streams; i_++) - if (pFormatCtx_->streams[i_]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - videoindex_ = i_; - break; - } - if (videoindex_ == -1) { - LOG_ERROR("Didn't find a video stream"); - return -1; - } - - pCodecParam_ = pFormatCtx_->streams[videoindex_]->codecpar; - - pCodecCtx_ = avcodec_alloc_context3(NULL); - avcodec_parameters_to_context(pCodecCtx_, pCodecParam_); - - pCodec_ = const_cast(avcodec_find_decoder(pCodecCtx_->codec_id)); - if (pCodec_ == NULL) { - LOG_ERROR("Codec not found"); - return -1; - } - if (avcodec_open2(pCodecCtx_, pCodec_, NULL) < 0) { - LOG_ERROR("Could not open codec"); - return -1; - } - - const int screen_w = pFormatCtx_->streams[videoindex_]->codecpar->width; - const int screen_h = pFormatCtx_->streams[videoindex_]->codecpar->height; - - pFrame_ = av_frame_alloc(); - pFrameNv12_ = av_frame_alloc(); - - pFrame_->width = screen_w; - pFrame_->height = screen_h; - pFrameNv12_->width = 1280; - pFrameNv12_->height = 720; - - packet_ = (AVPacket *)av_malloc(sizeof(AVPacket)); - - img_convert_ctx_ = sws_getContext( - pFrame_->width, pFrame_->height, pCodecCtx_->pix_fmt, pFrameNv12_->width, - pFrameNv12_->height, AV_PIX_FMT_NV12, SWS_BICUBIC, NULL, NULL, NULL); - - inited_ = true; + y_plane_.resize(width_ * height_); + uv_plane_.resize((width_ / 2) * (height_ / 2) * 2); return 0; } int ScreenCapturerX11::Destroy() { - running_ = false; + Stop(); + CleanUp(); return 0; } int ScreenCapturerX11::Start() { + if (running_) return 0; running_ = true; - capture_thread_ = std::thread([this]() { + paused_ = false; + thread_ = std::thread([this]() { while (running_) { - if (av_read_frame(pFormatCtx_, packet_) >= 0) { - if (packet_->stream_index == videoindex_) { - avcodec_send_packet(pCodecCtx_, packet_); - av_packet_unref(packet_); - got_picture_ = avcodec_receive_frame(pCodecCtx_, pFrame_); - - if (!got_picture_) { - av_image_fill_arrays(pFrameNv12_->data, pFrameNv12_->linesize, - nv12_buffer_, AV_PIX_FMT_NV12, - pFrameNv12_->width, pFrameNv12_->height, 1); - - sws_scale(img_convert_ctx_, pFrame_->data, pFrame_->linesize, 0, - pFrame_->height, pFrameNv12_->data, - pFrameNv12_->linesize); - - _on_data((unsigned char *)nv12_buffer_, - pFrameNv12_->width * pFrameNv12_->height * 3 / 2, - pFrameNv12_->width, pFrameNv12_->height); - } - } - } + if (!paused_) OnFrame(); } }); - return 0; } int ScreenCapturerX11::Stop() { + if (!running_) return 0; running_ = false; + if (thread_.joinable()) thread_.join(); return 0; } -int ScreenCapturerX11::Pause() { return 0; } +int ScreenCapturerX11::Pause() { + paused_ = true; + return 0; +} -int ScreenCapturerX11::Resume() { return 0; } +int ScreenCapturerX11::Resume() { + paused_ = false; + return 0; +} -void ScreenCapturerX11::OnFrame() {} +void ScreenCapturerX11::OnFrame() { + if (!display_) return; -void ScreenCapturerX11::CleanUp() {} + XImage* image = + XGetImage(display_, root_, 0, 0, width_, height_, AllPlanes, ZPixmap); + if (!image) return; + + bool needs_copy = image->bytes_per_line != width_ * 4; + std::vector argb_buf; + uint8_t* src_argb = nullptr; + + if (needs_copy) { + argb_buf.resize(width_ * height_ * 4); + for (int y = 0; y < height_; ++y) { + memcpy(&argb_buf[y * width_ * 4], image->data + y * image->bytes_per_line, + width_ * 4); + } + src_argb = argb_buf.data(); + } else { + src_argb = reinterpret_cast(image->data); + } + + libyuv::ARGBToNV12(src_argb, width_ * 4, y_plane_.data(), width_, + uv_plane_.data(), width_, width_, height_); + + std::vector nv12; + nv12.reserve(y_plane_.size() + uv_plane_.size()); + nv12.insert(nv12.end(), y_plane_.begin(), y_plane_.end()); + nv12.insert(nv12.end(), uv_plane_.begin(), uv_plane_.end()); + + if (callback_) { + callback_(nv12.data(), width_ * height_ * 3 / 2, width_, height_); + } + + XDestroyImage(image); +} + +void ScreenCapturerX11::CleanUp() { + if (display_) { + XCloseDisplay(display_); + display_ = nullptr; + } +} \ No newline at end of file diff --git a/src/screen_capturer/linux/screen_capturer_x11.h b/src/screen_capturer/linux/screen_capturer_x11.h index 3be21b0..7b7b123 100644 --- a/src/screen_capturer/linux/screen_capturer_x11.h +++ b/src/screen_capturer/linux/screen_capturer_x11.h @@ -1,23 +1,33 @@ +/* + * @Author: DI JUNKUN + * @Date: 2025-05-07 + * Copyright (c) 2025 by DI JUNKUN, All Rights Reserved. + */ + #ifndef _SCREEN_CAPTURER_X11_H_ #define _SCREEN_CAPTURER_X11_H_ -#include -#include -#include -#include +#include +#include -#include "screen_capturer.h" -#ifdef __cplusplus -extern "C" { -#endif -#include -#include -#include -#include -#include -#ifdef __cplusplus +#include +#include +#include +#include +#include +#include + +class ScreenCapturer { + public: + typedef std::function + cb_desktop_data; + + virtual ~ScreenCapturer() {} + virtual int Init(const int fps, cb_desktop_data cb) = 0; + virtual int Destroy() = 0; + virtual int Start() = 0; + virtual int Stop() = 0; }; -#endif class ScreenCapturerX11 : public ScreenCapturer { public: @@ -25,10 +35,10 @@ class ScreenCapturerX11 : public ScreenCapturer { ~ScreenCapturerX11(); public: - virtual int Init(const int fps, cb_desktop_data cb) override; - virtual int Destroy() override; - virtual int Start() override; - virtual int Stop() override; + int Init(const int fps, cb_desktop_data cb) override; + int Destroy() override; + int Start() override; + int Stop() override; int Pause(); int Resume(); @@ -39,40 +49,19 @@ class ScreenCapturerX11 : public ScreenCapturer { void CleanUp(); private: - std::atomic_bool _running; - std::atomic_bool _paused; - std::atomic_bool _inited; + Display* display_ = nullptr; + Window root_ = 0; + int width_ = 0; + int height_ = 0; + std::thread thread_; + std::atomic running_{false}; + std::atomic paused_{false}; + int fps_ = 30; + cb_desktop_data callback_; - std::thread _thread; - - std::string _device_name; - - int _fps; - - cb_desktop_data _on_data; - - private: - int i_ = 0; - int videoindex_ = 0; - int got_picture_ = 0; - int fps_ = 0; - bool inited_ = false; - - // ffmpeg - AVFormatContext *pFormatCtx_ = nullptr; - AVCodecContext *pCodecCtx_ = nullptr; - AVCodec *pCodec_ = nullptr; - AVCodecParameters *pCodecParam_ = nullptr; - AVDictionary *options_ = nullptr; - AVInputFormat *ifmt_ = nullptr; - AVFrame *pFrame_ = nullptr; - AVFrame *pFrameNv12_ = nullptr; - AVPacket *packet_ = nullptr; - struct SwsContext *img_convert_ctx_ = nullptr; - - // thread - std::thread capture_thread_; - std::atomic_bool running_; + // 缓冲区 + std::vector y_plane_; + std::vector uv_plane_; }; #endif \ No newline at end of file diff --git a/src/screen_capturer/linux/x11_session.h b/src/screen_capturer/linux/x11_session.h deleted file mode 100644 index 9225c09..0000000 --- a/src/screen_capturer/linux/x11_session.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef _X11_SESSION_H_ -#define _X11_SESSION_H_ - -class X11Session { - public: - struct x11_session_frame { - unsigned int width; - unsigned int height; - unsigned int row_pitch; - - const unsigned char *data; - }; - - class x11_session_observer { - public: - virtual ~x11_session_observer() {} - virtual void OnFrame(const x11_session_frame &frame) = 0; - }; - - public: - virtual void Release() = 0; - - virtual int Initialize() = 0; - - virtual void RegisterObserver(x11_session_observer *observer) = 0; - - virtual int Start() = 0; - virtual int Stop() = 0; - - virtual int Pause() = 0; - virtual int Resume() = 0; - - protected: - virtual ~X11Session(){}; -}; - -#endif \ No newline at end of file diff --git a/src/screen_capturer/linux/x11_session_impl.cpp b/src/screen_capturer/linux/x11_session_impl.cpp deleted file mode 100644 index 54aa7e9..0000000 --- a/src/screen_capturer/linux/x11_session_impl.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "x11_session_impl.h" - -#include -#include -#include -#include - -#define CHECK_INIT \ - if (!is_initialized_) { \ - std::cout << "AE_NEED_INIT" << std::endl; \ - return 4; \ - } - -X11SessionImpl::X11SessionImpl() {} - -X11SessionImpl::~X11SessionImpl() { - Stop(); - CleanUp(); -} - -void X11SessionImpl::Release() { delete this; } - -int X11SessionImpl::Initialize() { return 0; } - -void X11SessionImpl::RegisterObserver(x11_session_observer *observer) { - observer_ = observer; -} - -int X11SessionImpl::Start() { - if (is_running_) return 0; - - int error = 1; - - CHECK_INIT; - - return error; -} - -int X11SessionImpl::Stop() { return 0; } - -int X11SessionImpl::Pause() { return 0; } - -int X11SessionImpl::Resume() { return 0; } - -void X11SessionImpl::OnFrame() {} - -void X11SessionImpl::OnClosed() {} - -void X11SessionImpl::CleanUp() {} \ No newline at end of file diff --git a/src/screen_capturer/linux/x11_session_impl.h b/src/screen_capturer/linux/x11_session_impl.h deleted file mode 100644 index 075c174..0000000 --- a/src/screen_capturer/linux/x11_session_impl.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef _WGC_SESSION_IMPL_H_ -#define _WGC_SESSION_IMPL_H_ - -#include -#include - -#include "x11_session.h" - -class X11SessionImpl : public X11Session { - public: - X11SessionImpl(); - ~X11SessionImpl() override; - - public: - void Release() override; - - int Initialize() override; - - void RegisterObserver(x11_session_observer *observer) override; - - int Start() override; - int Stop() override; - - int Pause() override; - int Resume() override; - - private: - void OnFrame(); - void OnClosed(); - - void CleanUp(); - - // void message_func(); - - private: - std::mutex lock_; - bool is_initialized_ = false; - bool is_running_ = false; - bool is_paused_ = false; - - x11_session_observer *observer_ = nullptr; -}; - -#endif \ No newline at end of file diff --git a/thirdparty/projectx b/thirdparty/projectx index a007ba6..881cecc 160000 --- a/thirdparty/projectx +++ b/thirdparty/projectx @@ -1 +1 @@ -Subproject commit a007ba65dd575fdef7f8cdd2f143464c6e9e4683 +Subproject commit 881cecc3f9d5d0f5f7a9bba47b8b5e0560ac6b77 diff --git a/xmake.lua b/xmake.lua index f1399a5..1c1013a 100644 --- a/xmake.lua +++ b/xmake.lua @@ -27,6 +27,7 @@ if is_os("windows") then "Imm32", "iphlpapi") add_cxflags("/WX") elseif is_os("linux") then + add_requires("libyuv") add_requires("ffmpeg 5.1.2", {system = false}) add_syslinks("pthread", "dl") add_linkdirs("thirdparty/projectx/thirdparty/nvcodec/lib/x64") @@ -34,7 +35,7 @@ elseif is_os("linux") then add_ldflags("-lavformat", "-lavdevice", "-lavfilter", "-lavcodec", "-lswscale", "-lavutil", "-lswresample", "-lasound", "-lxcb-shape", "-lxcb-xfixes", "-lsndio", "-lxcb", - "-lxcb-shm", "-lXext", "-lX11", "-lXv", "-ldl", "-lpthread", + "-lxcb-shm", "-lXext", "-lX11", "-lXv", "-lXtst", "-ldl", "-lpthread", {force = true}) add_cxflags("-Wno-unused-variable") elseif is_os("macosx") then @@ -79,7 +80,7 @@ target("screen_capturer") add_includedirs("src/screen_capturer/macosx/avfoundation", "src/screen_capturer/macosx/screen_capturer_kit", {public = true}) elseif is_os("linux") then - add_packages("ffmpeg") + add_packages("libyuv", "ffmpeg") add_files("src/screen_capturer/linux/*.cpp") add_includedirs("src/screen_capturer/linux", {public = true}) end