1261 lines
46 KiB
C++
1261 lines
46 KiB
C++
#pragma once
|
||
#include "common/commands.h"
|
||
#include "client/IOCPClient.h"
|
||
#include "LinuxConfig.h"
|
||
#include "ClipboardHandler.h"
|
||
#include "FileTransferV2.h"
|
||
#include <dlfcn.h>
|
||
#include <sys/stat.h>
|
||
#include <thread>
|
||
#include <mutex>
|
||
#include <atomic>
|
||
#include <vector>
|
||
#include <cstring>
|
||
#include <stdexcept>
|
||
|
||
// 客户端 ID(定义在 main.cpp)
|
||
extern uint64_t g_myClientID;
|
||
|
||
// Linux 端 BITMAPINFOHEADER 定义,与 Windows 完全一致
|
||
#pragma pack(push, 1)
|
||
struct BITMAPINFOHEADER_LNX {
|
||
uint32_t biSize; // 40
|
||
int32_t biWidth;
|
||
int32_t biHeight;
|
||
uint16_t biPlanes; // 1
|
||
uint16_t biBitCount; // 32
|
||
uint32_t biCompression; // 0 (BI_RGB)
|
||
uint32_t biSizeImage;
|
||
int32_t biXPelsPerMeter; // 0
|
||
int32_t biYPelsPerMeter; // 0
|
||
uint32_t biClrUsed; // 0
|
||
uint32_t biClrImportant; // 0
|
||
};
|
||
#pragma pack(pop)
|
||
|
||
// Linux 本地 MSG64 定义(与 Windows MSG64 内存布局完全一致)
|
||
// 用于解析服务端发来的鼠标/键盘控制命令
|
||
#pragma pack(push, 1)
|
||
struct MSG64_LNX {
|
||
uint64_t hwnd;
|
||
uint64_t message;
|
||
uint64_t wParam;
|
||
uint64_t lParam;
|
||
uint64_t time;
|
||
int32_t pt_x;
|
||
int32_t pt_y;
|
||
};
|
||
#pragma pack(pop)
|
||
|
||
// X11 类型前向声明(避免 #include <X11/Xlib.h>)
|
||
typedef struct _XDisplay Display;
|
||
typedef unsigned long XID;
|
||
typedef XID Window;
|
||
typedef XID Drawable;
|
||
typedef XID Pixmap;
|
||
typedef struct _XGC *GC;
|
||
|
||
struct XImage {
|
||
int width, height;
|
||
int xoffset;
|
||
int format;
|
||
char *data;
|
||
int byte_order;
|
||
int bitmap_unit;
|
||
int bitmap_bit_order;
|
||
int bitmap_pad;
|
||
int depth;
|
||
int bytes_per_line;
|
||
int bits_per_pixel;
|
||
unsigned long red_mask;
|
||
unsigned long green_mask;
|
||
unsigned long blue_mask;
|
||
// 后续字段省略,XDestroyImage 通过函数指针释放
|
||
void *obdata;
|
||
struct funcs {
|
||
void *p[8]; // create_image, destroy_image, ...
|
||
} f;
|
||
};
|
||
|
||
// X11 错误处理(防止 BadMatch 等错误导致进程退出)
|
||
static int x11_error_handler(Display* dpy, void* evt)
|
||
{
|
||
// 忽略所有 X11 错误,由调用方检查返回值
|
||
return 0;
|
||
}
|
||
|
||
// XCreateGC 需要的 XGCValues 结构体(布局与 Xlib 一致)
|
||
struct XGCValues_LNX {
|
||
int function;
|
||
unsigned long plane_mask;
|
||
unsigned long foreground;
|
||
unsigned long background;
|
||
int line_width;
|
||
int line_style;
|
||
int cap_style;
|
||
int join_style;
|
||
int fill_style;
|
||
int fill_rule;
|
||
int arc_mode;
|
||
unsigned long tile; // Pixmap = XID
|
||
unsigned long stipple; // Pixmap = XID
|
||
int ts_x_origin;
|
||
int ts_y_origin;
|
||
unsigned long font; // Font = XID
|
||
int subwindow_mode;
|
||
};
|
||
|
||
// X11 GC 常量
|
||
#define GCSubwindowMode (1L<<15)
|
||
#define IncludeInferiors 1
|
||
|
||
// ============== 屏幕算法常量 ==============
|
||
#define ALGORITHM_GRAY 0
|
||
#define ALGORITHM_DIFF 1
|
||
#define ALGORITHM_H264 2
|
||
#define ALGORITHM_RGB565 3
|
||
|
||
// 算法支持表(编译时常量,日后支持 H264 时改为 true)
|
||
static const bool g_SupportedAlgo[] = {
|
||
true, // ALGORITHM_GRAY = 0
|
||
true, // ALGORITHM_DIFF = 1
|
||
false, // ALGORITHM_H264 = 2
|
||
true, // ALGORITHM_RGB565 = 3
|
||
};
|
||
|
||
// 不支持的算法降级为 RGB565
|
||
inline uint8_t GetEffectiveAlgorithm(uint8_t algo) {
|
||
if (algo > 3 || !g_SupportedAlgo[algo]) {
|
||
return ALGORITHM_RGB565;
|
||
}
|
||
return algo;
|
||
}
|
||
|
||
// ============== 颜色转换函数 ==============
|
||
|
||
// BGRA → 灰度 (Y = 0.299R + 0.587G + 0.114B)
|
||
// 使用整数近似: Y = (306*R + 601*G + 117*B) >> 10
|
||
inline void ConvertBGRAtoGray(const uint8_t* src, uint8_t* dst, uint32_t pixelCount)
|
||
{
|
||
for (uint32_t i = 0; i < pixelCount; i++) {
|
||
uint8_t b = src[i * 4 + 0];
|
||
uint8_t g = src[i * 4 + 1];
|
||
uint8_t r = src[i * 4 + 2];
|
||
dst[i] = (uint8_t)((306 * r + 601 * g + 117 * b) >> 10);
|
||
}
|
||
}
|
||
|
||
// BGRA → RGB565 (R:5位, G:6位, B:5位)
|
||
// 格式: RRRRRGGG GGGBBBBB
|
||
inline void ConvertBGRAtoRGB565(const uint8_t* src, uint16_t* dst, uint32_t pixelCount)
|
||
{
|
||
for (uint32_t i = 0; i < pixelCount; i++) {
|
||
uint8_t b = src[i * 4 + 0];
|
||
uint8_t g = src[i * 4 + 1];
|
||
uint8_t r = src[i * 4 + 2];
|
||
uint16_t r5 = (r >> 3) & 0x1F;
|
||
uint16_t g6 = (g >> 2) & 0x3F;
|
||
uint16_t b5 = (b >> 3) & 0x1F;
|
||
dst[i] = (r5 << 11) | (g6 << 5) | b5;
|
||
}
|
||
}
|
||
|
||
// ============== Windows 消息常量(用于解析服务端控制命令)==============
|
||
#define WM_MOUSEMOVE 0x0200
|
||
#define WM_LBUTTONDOWN 0x0201
|
||
#define WM_LBUTTONUP 0x0202
|
||
#define WM_LBUTTONDBLCLK 0x0203
|
||
#define WM_RBUTTONDOWN 0x0204
|
||
#define WM_RBUTTONUP 0x0205
|
||
#define WM_RBUTTONDBLCLK 0x0206
|
||
#define WM_MBUTTONDOWN 0x0207
|
||
#define WM_MBUTTONUP 0x0208
|
||
#define WM_MBUTTONDBLCLK 0x0209
|
||
#define WM_MOUSEWHEEL 0x020A
|
||
|
||
#define WM_KEYDOWN 0x0100
|
||
#define WM_KEYUP 0x0101
|
||
#define WM_SYSKEYDOWN 0x0104
|
||
#define WM_SYSKEYUP 0x0105
|
||
|
||
// Windows 滚轮增量宏
|
||
#define GET_WHEEL_DELTA_WPARAM(wParam) ((short)((wParam) >> 16))
|
||
|
||
// X11 Bool 常量
|
||
#ifndef True
|
||
#define True 1
|
||
#endif
|
||
#ifndef False
|
||
#define False 0
|
||
#endif
|
||
|
||
// X11 按钮常量
|
||
#define Button1 1 // 左键
|
||
#define Button2 2 // 中键
|
||
#define Button3 3 // 右键
|
||
#define Button4 4 // 滚轮上
|
||
#define Button5 5 // 滚轮下
|
||
|
||
// X11 KeySym 常量(常用键)
|
||
#define XK_BackSpace 0xff08
|
||
#define XK_Tab 0xff09
|
||
#define XK_Return 0xff0d
|
||
#define XK_Escape 0xff1b
|
||
#define XK_Delete 0xffff
|
||
#define XK_Home 0xff50
|
||
#define XK_Left 0xff51
|
||
#define XK_Up 0xff52
|
||
#define XK_Right 0xff53
|
||
#define XK_Down 0xff54
|
||
#define XK_Page_Up 0xff55
|
||
#define XK_Page_Down 0xff56
|
||
#define XK_End 0xff57
|
||
#define XK_Insert 0xff63
|
||
#define XK_Shift_L 0xffe1
|
||
#define XK_Shift_R 0xffe2
|
||
#define XK_Control_L 0xffe3
|
||
#define XK_Control_R 0xffe4
|
||
#define XK_Caps_Lock 0xffe5
|
||
#define XK_Alt_L 0xffe9
|
||
#define XK_Alt_R 0xffea
|
||
#define XK_Super_L 0xffeb // Win键
|
||
#define XK_Super_R 0xffec
|
||
#define XK_F1 0xffbe
|
||
#define XK_F2 0xffbf
|
||
#define XK_F3 0xffc0
|
||
#define XK_F4 0xffc1
|
||
#define XK_F5 0xffc2
|
||
#define XK_F6 0xffc3
|
||
#define XK_F7 0xffc4
|
||
#define XK_F8 0xffc5
|
||
#define XK_F9 0xffc6
|
||
#define XK_F10 0xffc7
|
||
#define XK_F11 0xffc8
|
||
#define XK_F12 0xffc9
|
||
#define XK_Num_Lock 0xff7f
|
||
#define XK_Scroll_Lock 0xff14
|
||
#define XK_KP_0 0xffb0
|
||
#define XK_KP_1 0xffb1
|
||
#define XK_KP_2 0xffb2
|
||
#define XK_KP_3 0xffb3
|
||
#define XK_KP_4 0xffb4
|
||
#define XK_KP_5 0xffb5
|
||
#define XK_KP_6 0xffb6
|
||
#define XK_KP_7 0xffb7
|
||
#define XK_KP_8 0xffb8
|
||
#define XK_KP_9 0xffb9
|
||
#define XK_KP_Multiply 0xffaa
|
||
#define XK_KP_Add 0xffab
|
||
#define XK_KP_Subtract 0xffad
|
||
#define XK_KP_Decimal 0xffae
|
||
#define XK_KP_Divide 0xffaf
|
||
#define XK_KP_Enter 0xff8d
|
||
#define XK_Print 0xff61
|
||
#define XK_Pause 0xff13
|
||
#define XK_space 0x0020
|
||
|
||
// Windows VK 码到 X11 KeySym 的映射表
|
||
static unsigned long VKtoKeySym(unsigned int vk)
|
||
{
|
||
// 字母键 A-Z (VK 0x41-0x5A)
|
||
if (vk >= 0x41 && vk <= 0x5A)
|
||
return vk + 0x20; // 'a'-'z' 的 ASCII/KeySym
|
||
|
||
// 数字键 0-9 (VK 0x30-0x39)
|
||
if (vk >= 0x30 && vk <= 0x39)
|
||
return vk; // '0'-'9' 的 ASCII/KeySym
|
||
|
||
// 小键盘数字 VK_NUMPAD0-9 (0x60-0x69)
|
||
if (vk >= 0x60 && vk <= 0x69)
|
||
return XK_KP_0 + (vk - 0x60);
|
||
|
||
// F1-F12 (VK 0x70-0x7B)
|
||
if (vk >= 0x70 && vk <= 0x7B)
|
||
return XK_F1 + (vk - 0x70);
|
||
|
||
// 特殊键映射
|
||
switch (vk) {
|
||
case 0x08:
|
||
return XK_BackSpace; // VK_BACK
|
||
case 0x09:
|
||
return XK_Tab; // VK_TAB
|
||
case 0x0D:
|
||
return XK_Return; // VK_RETURN
|
||
case 0x10:
|
||
return XK_Shift_L; // VK_SHIFT
|
||
case 0x11:
|
||
return XK_Control_L; // VK_CONTROL
|
||
case 0x12:
|
||
return XK_Alt_L; // VK_MENU (Alt)
|
||
case 0x13:
|
||
return XK_Pause; // VK_PAUSE
|
||
case 0x14:
|
||
return XK_Caps_Lock; // VK_CAPITAL
|
||
case 0x1B:
|
||
return XK_Escape; // VK_ESCAPE
|
||
case 0x20:
|
||
return XK_space; // VK_SPACE
|
||
case 0x21:
|
||
return XK_Page_Up; // VK_PRIOR
|
||
case 0x22:
|
||
return XK_Page_Down; // VK_NEXT
|
||
case 0x23:
|
||
return XK_End; // VK_END
|
||
case 0x24:
|
||
return XK_Home; // VK_HOME
|
||
case 0x25:
|
||
return XK_Left; // VK_LEFT
|
||
case 0x26:
|
||
return XK_Up; // VK_UP
|
||
case 0x27:
|
||
return XK_Right; // VK_RIGHT
|
||
case 0x28:
|
||
return XK_Down; // VK_DOWN
|
||
case 0x2C:
|
||
return XK_Print; // VK_SNAPSHOT
|
||
case 0x2D:
|
||
return XK_Insert; // VK_INSERT
|
||
case 0x2E:
|
||
return XK_Delete; // VK_DELETE
|
||
case 0x5B:
|
||
return XK_Super_L; // VK_LWIN
|
||
case 0x5C:
|
||
return XK_Super_R; // VK_RWIN
|
||
case 0x6A:
|
||
return XK_KP_Multiply; // VK_MULTIPLY
|
||
case 0x6B:
|
||
return XK_KP_Add; // VK_ADD
|
||
case 0x6D:
|
||
return XK_KP_Subtract; // VK_SUBTRACT
|
||
case 0x6E:
|
||
return XK_KP_Decimal; // VK_DECIMAL
|
||
case 0x6F:
|
||
return XK_KP_Divide; // VK_DIVIDE
|
||
case 0x90:
|
||
return XK_Num_Lock; // VK_NUMLOCK
|
||
case 0x91:
|
||
return XK_Scroll_Lock; // VK_SCROLL
|
||
case 0xA0:
|
||
return XK_Shift_L; // VK_LSHIFT
|
||
case 0xA1:
|
||
return XK_Shift_R; // VK_RSHIFT
|
||
case 0xA2:
|
||
return XK_Control_L; // VK_LCONTROL
|
||
case 0xA3:
|
||
return XK_Control_R; // VK_RCONTROL
|
||
case 0xA4:
|
||
return XK_Alt_L; // VK_LMENU
|
||
case 0xA5:
|
||
return XK_Alt_R; // VK_RMENU
|
||
// 符号键(美式键盘布局)
|
||
case 0xBA:
|
||
return 0x003b; // VK_OEM_1 (;:)
|
||
case 0xBB:
|
||
return 0x003d; // VK_OEM_PLUS (=+)
|
||
case 0xBC:
|
||
return 0x002c; // VK_OEM_COMMA (,<)
|
||
case 0xBD:
|
||
return 0x002d; // VK_OEM_MINUS (-_)
|
||
case 0xBE:
|
||
return 0x002e; // VK_OEM_PERIOD (.>)
|
||
case 0xBF:
|
||
return 0x002f; // VK_OEM_2 (/?)
|
||
case 0xC0:
|
||
return 0x0060; // VK_OEM_3 (`~)
|
||
case 0xDB:
|
||
return 0x005b; // VK_OEM_4 ([{)
|
||
case 0xDC:
|
||
return 0x005c; // VK_OEM_5 (\|)
|
||
case 0xDD:
|
||
return 0x005d; // VK_OEM_6 (]})
|
||
case 0xDE:
|
||
return 0x0027; // VK_OEM_7 ('")
|
||
default:
|
||
return 0; // 未知键
|
||
}
|
||
}
|
||
|
||
// X11 函数指针类型
|
||
typedef Display* (*fn_XOpenDisplay)(const char*);
|
||
typedef int (*fn_XCloseDisplay)(Display*);
|
||
typedef XImage* (*fn_XGetImage)(Display*, Drawable, int, int, unsigned int, unsigned int, unsigned long, int);
|
||
typedef int (*fn_XDestroyImage)(XImage*);
|
||
typedef int (*fn_XSetErrorHandler)(int (*)(Display*, void*));
|
||
typedef Pixmap (*fn_XCreatePixmap)(Display*, Drawable, unsigned int, unsigned int, unsigned int);
|
||
typedef int (*fn_XFreePixmap)(Display*, Pixmap);
|
||
typedef GC (*fn_XCreateGC)(Display*, Drawable, unsigned long, void*);
|
||
typedef int (*fn_XFreeGC)(Display*, GC);
|
||
typedef int (*fn_XCopyArea)(Display*, Drawable, Drawable, GC, int, int, unsigned int, unsigned int, int, int);
|
||
typedef int (*fn_XDefaultDepth)(Display*, int);
|
||
typedef int (*fn_XSync)(Display*, int);
|
||
typedef unsigned long (*fn_XKeysymToKeycode)(Display*, unsigned long);
|
||
typedef int (*fn_XFlush)(Display*);
|
||
typedef int (*fn_XClearArea)(Display*, Window, int, int, unsigned int, unsigned int, int);
|
||
|
||
// XTest 扩展函数指针类型(用于模拟鼠标/键盘输入)
|
||
typedef int (*fn_XTestFakeMotionEvent)(Display*, int, int, int, unsigned long);
|
||
typedef int (*fn_XTestFakeButtonEvent)(Display*, unsigned int, int, unsigned long);
|
||
typedef int (*fn_XTestFakeKeyEvent)(Display*, unsigned int, int, unsigned long);
|
||
|
||
// X11 动态加载包装
|
||
class X11Loader
|
||
{
|
||
public:
|
||
fn_XOpenDisplay pXOpenDisplay;
|
||
fn_XCloseDisplay pXCloseDisplay;
|
||
fn_XGetImage pXGetImage;
|
||
fn_XDestroyImage pXDestroyImage;
|
||
|
||
// Xlib 宏的替代:通过偏移读取 Display 内部结构
|
||
// 这些函数通过 dlsym 获取
|
||
typedef int (*fn_XDefaultScreen)(Display*);
|
||
typedef int (*fn_XDisplayWidth)(Display*, int);
|
||
typedef int (*fn_XDisplayHeight)(Display*, int);
|
||
typedef Window (*fn_XRootWindow)(Display*, int);
|
||
|
||
fn_XDefaultScreen pXDefaultScreen;
|
||
fn_XDisplayWidth pXDisplayWidth;
|
||
fn_XDisplayHeight pXDisplayHeight;
|
||
fn_XRootWindow pXRootWindow;
|
||
|
||
// Pixmap 相关(解决合成窗口管理器下 XGetImage BadMatch 问题)
|
||
fn_XSetErrorHandler pXSetErrorHandler;
|
||
fn_XCreatePixmap pXCreatePixmap;
|
||
fn_XFreePixmap pXFreePixmap;
|
||
fn_XCreateGC pXCreateGC;
|
||
fn_XFreeGC pXFreeGC;
|
||
fn_XCopyArea pXCopyArea;
|
||
fn_XDefaultDepth pXDefaultDepth;
|
||
fn_XSync pXSync;
|
||
fn_XKeysymToKeycode pXKeysymToKeycode;
|
||
fn_XFlush pXFlush;
|
||
fn_XClearArea pXClearArea;
|
||
|
||
// XTest 扩展(用于模拟输入)
|
||
fn_XTestFakeMotionEvent pXTestFakeMotionEvent;
|
||
fn_XTestFakeButtonEvent pXTestFakeButtonEvent;
|
||
fn_XTestFakeKeyEvent pXTestFakeKeyEvent;
|
||
|
||
X11Loader() : m_handle(nullptr), m_xtst_handle(nullptr)
|
||
{
|
||
pXOpenDisplay = nullptr;
|
||
pXCloseDisplay = nullptr;
|
||
pXGetImage = nullptr;
|
||
pXDestroyImage = nullptr;
|
||
pXDefaultScreen = nullptr;
|
||
pXDisplayWidth = nullptr;
|
||
pXDisplayHeight = nullptr;
|
||
pXRootWindow = nullptr;
|
||
pXSetErrorHandler = nullptr;
|
||
pXCreatePixmap = nullptr;
|
||
pXFreePixmap = nullptr;
|
||
pXCreateGC = nullptr;
|
||
pXFreeGC = nullptr;
|
||
pXCopyArea = nullptr;
|
||
pXDefaultDepth = nullptr;
|
||
pXSync = nullptr;
|
||
pXKeysymToKeycode = nullptr;
|
||
pXFlush = nullptr;
|
||
pXClearArea = nullptr;
|
||
pXTestFakeMotionEvent = nullptr;
|
||
pXTestFakeButtonEvent = nullptr;
|
||
pXTestFakeKeyEvent = nullptr;
|
||
}
|
||
|
||
bool Load()
|
||
{
|
||
m_handle = dlopen("libX11.so.6", RTLD_LAZY);
|
||
if (!m_handle) m_handle = dlopen("libX11.so", RTLD_LAZY);
|
||
if (!m_handle) return false;
|
||
|
||
pXOpenDisplay = (fn_XOpenDisplay)dlsym(m_handle, "XOpenDisplay");
|
||
pXCloseDisplay = (fn_XCloseDisplay)dlsym(m_handle, "XCloseDisplay");
|
||
pXGetImage = (fn_XGetImage)dlsym(m_handle, "XGetImage");
|
||
pXDestroyImage = (fn_XDestroyImage)dlsym(m_handle, "XDestroyImage");
|
||
pXDefaultScreen = (fn_XDefaultScreen)dlsym(m_handle, "XDefaultScreen");
|
||
pXDisplayWidth = (fn_XDisplayWidth)dlsym(m_handle, "XDisplayWidth");
|
||
pXDisplayHeight = (fn_XDisplayHeight)dlsym(m_handle, "XDisplayHeight");
|
||
pXRootWindow = (fn_XRootWindow)dlsym(m_handle, "XRootWindow");
|
||
|
||
// Pixmap 相关函数
|
||
pXSetErrorHandler = (fn_XSetErrorHandler)dlsym(m_handle, "XSetErrorHandler");
|
||
pXCreatePixmap = (fn_XCreatePixmap)dlsym(m_handle, "XCreatePixmap");
|
||
pXFreePixmap = (fn_XFreePixmap)dlsym(m_handle, "XFreePixmap");
|
||
pXCreateGC = (fn_XCreateGC)dlsym(m_handle, "XCreateGC");
|
||
pXFreeGC = (fn_XFreeGC)dlsym(m_handle, "XFreeGC");
|
||
pXCopyArea = (fn_XCopyArea)dlsym(m_handle, "XCopyArea");
|
||
pXDefaultDepth = (fn_XDefaultDepth)dlsym(m_handle, "XDefaultDepth");
|
||
pXSync = (fn_XSync)dlsym(m_handle, "XSync");
|
||
pXKeysymToKeycode = (fn_XKeysymToKeycode)dlsym(m_handle, "XKeysymToKeycode");
|
||
pXFlush = (fn_XFlush)dlsym(m_handle, "XFlush");
|
||
pXClearArea = (fn_XClearArea)dlsym(m_handle, "XClearArea");
|
||
|
||
// 加载 XTest 扩展库(用于模拟鼠标/键盘输入)
|
||
m_xtst_handle = dlopen("libXtst.so.6", RTLD_LAZY);
|
||
if (!m_xtst_handle) m_xtst_handle = dlopen("libXtst.so", RTLD_LAZY);
|
||
if (m_xtst_handle) {
|
||
pXTestFakeMotionEvent = (fn_XTestFakeMotionEvent)dlsym(m_xtst_handle, "XTestFakeMotionEvent");
|
||
pXTestFakeButtonEvent = (fn_XTestFakeButtonEvent)dlsym(m_xtst_handle, "XTestFakeButtonEvent");
|
||
pXTestFakeKeyEvent = (fn_XTestFakeKeyEvent)dlsym(m_xtst_handle, "XTestFakeKeyEvent");
|
||
}
|
||
|
||
// 基本 X11 函数必须全部存在;XTest 函数可选(没有时无法控制输入)
|
||
return pXOpenDisplay && pXCloseDisplay && pXGetImage && pXDestroyImage &&
|
||
pXDefaultScreen && pXDisplayWidth && pXDisplayHeight && pXRootWindow &&
|
||
pXSetErrorHandler && pXCreatePixmap && pXFreePixmap &&
|
||
pXCreateGC && pXFreeGC && pXCopyArea && pXDefaultDepth && pXSync &&
|
||
pXKeysymToKeycode && pXFlush;
|
||
}
|
||
|
||
// 检查 XTest 扩展是否可用
|
||
bool HasXTest() const
|
||
{
|
||
return pXTestFakeMotionEvent && pXTestFakeButtonEvent && pXTestFakeKeyEvent;
|
||
}
|
||
|
||
~X11Loader()
|
||
{
|
||
if (m_xtst_handle) {
|
||
dlclose(m_xtst_handle);
|
||
m_xtst_handle = nullptr;
|
||
}
|
||
if (m_handle) {
|
||
dlclose(m_handle);
|
||
m_handle = nullptr;
|
||
}
|
||
}
|
||
|
||
private:
|
||
void* m_handle;
|
||
void* m_xtst_handle;
|
||
};
|
||
|
||
class ScreenHandler : public IOCPManager
|
||
{
|
||
public:
|
||
ScreenHandler(IOCPClient* client)
|
||
: m_client(client), m_running(false), m_destroyed(false), m_display(nullptr),
|
||
m_inputDisplay(nullptr),
|
||
m_width(0), m_height(0),
|
||
m_pixmap(0), m_gc(nullptr), m_xtestWarned(false),
|
||
m_bAlgorithm(ALGORITHM_DIFF), m_maxFPS(10), m_qualityLevel(QUALITY_ADAPTIVE)
|
||
{
|
||
if (!client) {
|
||
throw std::invalid_argument("IOCPClient pointer cannot be null");
|
||
}
|
||
|
||
// 加载保存的质量等级配置
|
||
LoadQualitySettings();
|
||
|
||
// 动态加载 X11
|
||
if (!m_x11.Load()) {
|
||
throw std::runtime_error("Failed to load libX11.so (X11 not installed)");
|
||
}
|
||
|
||
// 打开 X11 Display(截屏专用)
|
||
m_display = m_x11.pXOpenDisplay(nullptr);
|
||
if (!m_display) {
|
||
throw std::runtime_error("Failed to open X11 display (no desktop environment?)");
|
||
}
|
||
|
||
// 打开独立的 X11 Display(输入控制专用)
|
||
// X11 Display 不是线程安全的,截屏线程和回调线程必须使用各自独立的连接
|
||
if (m_x11.HasXTest()) {
|
||
m_inputDisplay = m_x11.pXOpenDisplay(nullptr);
|
||
}
|
||
|
||
// 设置自定义错误处理器,防止 BadMatch 等错误导致进程退出
|
||
m_x11.pXSetErrorHandler(x11_error_handler);
|
||
|
||
// 获取默认屏幕信息
|
||
int screen = m_x11.pXDefaultScreen(m_display);
|
||
m_width = m_x11.pXDisplayWidth(m_display, screen);
|
||
m_height = m_x11.pXDisplayHeight(m_display, screen);
|
||
m_root = m_x11.pXRootWindow(m_display, screen);
|
||
|
||
// 创建离屏 Pixmap 和 GC(解决合成窗口管理器下 XGetImage 的 BadMatch 问题)
|
||
int depth = m_x11.pXDefaultDepth(m_display, screen);
|
||
m_pixmap = m_x11.pXCreatePixmap(m_display, m_root, m_width, m_height, depth);
|
||
|
||
// GC 必须设置 subwindow_mode = IncludeInferiors,
|
||
// 否则 XCopyArea 会裁剪掉子窗口(应用程序窗口),只拷贝 root 背景
|
||
XGCValues_LNX gcv;
|
||
memset(&gcv, 0, sizeof(gcv));
|
||
gcv.subwindow_mode = IncludeInferiors;
|
||
m_gc = m_x11.pXCreateGC(m_display, m_root, GCSubwindowMode, &gcv);
|
||
if (!m_pixmap || !m_gc) {
|
||
throw std::runtime_error("Failed to create X11 Pixmap/GC for screen capture");
|
||
}
|
||
|
||
// 初始化 BITMAPINFOHEADER
|
||
memset(&m_bmpHeader, 0, sizeof(m_bmpHeader));
|
||
m_bmpHeader.biSize = sizeof(BITMAPINFOHEADER_LNX);
|
||
m_bmpHeader.biWidth = m_width;
|
||
m_bmpHeader.biHeight = m_height;
|
||
m_bmpHeader.biPlanes = 1;
|
||
m_bmpHeader.biBitCount = 32;
|
||
m_bmpHeader.biCompression = 0; // BI_RGB
|
||
m_bmpHeader.biSizeImage = m_width * m_height * 4;
|
||
|
||
// 分配帧缓冲
|
||
m_prevFrame.resize(m_bmpHeader.biSizeImage, 0);
|
||
m_currFrame.resize(m_bmpHeader.biSizeImage, 0);
|
||
|
||
// 差异帧缓冲: token(1) + algo(1) + cursorXY(8) + cursorType(1) + 最大差异数据
|
||
m_diffBuffer.resize(1 + 1 + 8 + 1 + m_bmpHeader.biSizeImage * 2);
|
||
}
|
||
|
||
~ScreenHandler()
|
||
{
|
||
// Mark as destroyed to prevent Start() from creating new thread
|
||
m_destroyed = true;
|
||
m_running = false;
|
||
|
||
// Lock to ensure Start() is not in the middle of creating thread
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_threadMutex);
|
||
if (m_captureThread.joinable()) {
|
||
m_captureThread.join();
|
||
}
|
||
}
|
||
if (m_inputDisplay && m_x11.pXCloseDisplay) {
|
||
m_x11.pXCloseDisplay(m_inputDisplay);
|
||
m_inputDisplay = nullptr;
|
||
}
|
||
if (m_display) {
|
||
if (m_gc && m_x11.pXFreeGC) m_x11.pXFreeGC(m_display, m_gc);
|
||
if (m_pixmap && m_x11.pXFreePixmap) m_x11.pXFreePixmap(m_display, m_pixmap);
|
||
m_pixmap = 0;
|
||
m_gc = nullptr;
|
||
// 强制全屏重绘,恢复 VMware SVGA 等虚拟显卡驱动的显示状态
|
||
// XClearArea(display, window, x, y, w, h, exposures)
|
||
// w=0, h=0 表示整个窗口;exposures=True 触发 Expose 事件强制所有窗口重绘
|
||
if (m_x11.pXClearArea) {
|
||
m_x11.pXClearArea(m_display, m_root, 0, 0, 0, 0, True);
|
||
}
|
||
m_x11.pXSync(m_display, 0);
|
||
if (m_x11.pXCloseDisplay) m_x11.pXCloseDisplay(m_display);
|
||
m_display = nullptr;
|
||
}
|
||
Mprintf(">>> ScreenHandler destroyed, display refreshed\n");
|
||
}
|
||
|
||
void Start()
|
||
{
|
||
// Prevent starting if destructor has begun
|
||
if (m_destroyed) return;
|
||
|
||
// Lock to prevent race with destructor
|
||
std::lock_guard<std::mutex> lock(m_threadMutex);
|
||
|
||
// Double-check after acquiring lock
|
||
if (m_destroyed) return;
|
||
|
||
// Prevent starting if thread is already running or joinable
|
||
if (m_captureThread.joinable()) return;
|
||
|
||
bool expected = false;
|
||
if (!m_running.compare_exchange_strong(expected, true)) return;
|
||
|
||
m_captureThread = std::thread(&ScreenHandler::CaptureLoop, this);
|
||
}
|
||
|
||
// 发送 BITMAPINFOHEADER + ScreenSettings(需在连接后、等待 COMMAND_NEXT 前调用)
|
||
void SendBitmapInfo()
|
||
{
|
||
const uint32_t ulLength = 1 + sizeof(BITMAPINFOHEADER_LNX) + 2 * sizeof(uint64_t) + sizeof(ScreenSettings);
|
||
std::vector<uint8_t> buf(ulLength, 0);
|
||
buf[0] = TOKEN_BITMAPINFO;
|
||
memcpy(&buf[1], &m_bmpHeader, sizeof(BITMAPINFOHEADER_LNX));
|
||
// 第一个 uint64_t 是 clientID,用于服务端识别客户端
|
||
uint64_t clientID = g_myClientID;
|
||
uint64_t zero = 0;
|
||
memcpy(&buf[1 + sizeof(BITMAPINFOHEADER_LNX)], &clientID, sizeof(uint64_t));
|
||
memcpy(&buf[1 + sizeof(BITMAPINFOHEADER_LNX) + sizeof(uint64_t)], &zero, sizeof(uint64_t));
|
||
ScreenSettings settings = {};
|
||
settings.MaxFPS = m_maxFPS.load();
|
||
settings.QualityLevel = m_qualityLevel; // 上报保存的质量等级
|
||
memcpy(&buf[1 + sizeof(BITMAPINFOHEADER_LNX) + 2 * sizeof(uint64_t)], &settings, sizeof(ScreenSettings));
|
||
m_client->Send2Server((char*)buf.data(), ulLength);
|
||
Mprintf(">>> SendBitmapInfo: clientID=%llu, QualityLevel=%d\n", clientID, m_qualityLevel);
|
||
}
|
||
|
||
virtual VOID OnReceive(PBYTE data, ULONG size)
|
||
{
|
||
if (!size) return;
|
||
|
||
switch (data[0]) {
|
||
case COMMAND_NEXT:
|
||
Start();
|
||
break;
|
||
|
||
case COMMAND_SCREEN_CONTROL:
|
||
// 处理鼠标/键盘控制命令
|
||
if (size >= 1 + sizeof(MSG64_LNX)) {
|
||
HandleInputEvent((MSG64_LNX*)(data + 1));
|
||
}
|
||
break;
|
||
|
||
case CMD_QUALITY_LEVEL:
|
||
// 质量等级调整: [cmd:1][level:1][persist:1]
|
||
if (size >= 2) {
|
||
int8_t level = (int8_t)data[1];
|
||
int persist = (size >= 3) ? data[2] : 0;
|
||
ApplyQualityLevel(level, persist);
|
||
}
|
||
break;
|
||
|
||
case COMMAND_SCREEN_SET_CLIPBOARD:
|
||
// 服务端设置剪贴板: [cmd:1][text:N]
|
||
if (size > 1) {
|
||
if (!ClipboardHandler::IsAvailable()) {
|
||
Mprintf("*** Clipboard unavailable (install xclip/xsel, need DISPLAY)\n");
|
||
} else if (ClipboardHandler::SetTextRaw((const char*)(data + 1), size - 1)) {
|
||
Mprintf(">>> Clipboard SET: %zu bytes\n", size - 1);
|
||
} else {
|
||
Mprintf("*** Clipboard SET failed\n");
|
||
}
|
||
}
|
||
break;
|
||
|
||
case COMMAND_SCREEN_GET_CLIPBOARD:
|
||
// 服务端请求剪贴板: [cmd:1][hash:64][hmac:16]
|
||
// 返回: [TOKEN_CLIPBOARD_TEXT:1][text:N] 或 [COMMAND_GET_FOLDER:1][files]
|
||
{
|
||
if (!ClipboardHandler::IsAvailable()) {
|
||
Mprintf("*** Clipboard unavailable (install xclip/xsel, need DISPLAY)\n");
|
||
uint8_t empty = TOKEN_CLIPBOARD_TEXT;
|
||
m_client->Send2Server((char*)&empty, 1);
|
||
break;
|
||
}
|
||
|
||
// 优先检查剪贴板中的文件
|
||
auto files = ClipboardHandler::GetFiles();
|
||
if (!files.empty()) {
|
||
// 返回 COMMAND_GET_FOLDER + 文件列表(多字符串格式:file1\0file2\0\0)
|
||
std::vector<uint8_t> buf;
|
||
buf.push_back(COMMAND_GET_FOLDER);
|
||
for (const auto& f : files) {
|
||
// 文件路径需要转换为 GBK 编码(服务端预期)
|
||
std::string gbkPath = FileTransferV2::utf8ToGbk(f);
|
||
buf.insert(buf.end(), gbkPath.begin(), gbkPath.end());
|
||
buf.push_back(0); // 每个路径后的 null 终止符
|
||
}
|
||
buf.push_back(0); // 结束标记
|
||
m_client->Send2Server((char*)buf.data(), buf.size());
|
||
Mprintf(">>> Clipboard GET: %zu files\n", files.size());
|
||
break;
|
||
}
|
||
|
||
// 没有文件,返回文本
|
||
std::string text = ClipboardHandler::GetText();
|
||
if (!text.empty()) {
|
||
std::vector<uint8_t> buf(1 + text.size());
|
||
buf[0] = TOKEN_CLIPBOARD_TEXT;
|
||
memcpy(&buf[1], text.data(), text.size());
|
||
m_client->Send2Server((char*)buf.data(), buf.size());
|
||
Mprintf(">>> Clipboard GET: %zu bytes text\n", text.size());
|
||
} else {
|
||
// 返回空剪贴板
|
||
uint8_t empty = TOKEN_CLIPBOARD_TEXT;
|
||
m_client->Send2Server((char*)&empty, 1);
|
||
Mprintf(">>> Clipboard GET: empty\n");
|
||
}
|
||
}
|
||
break;
|
||
|
||
case COMMAND_GET_FILE:
|
||
// 服务端请求下载文件: [cmd:1][targetDir\0][file1\0file2\0...\0]
|
||
// 使用 V2 协议上传文件
|
||
{
|
||
if (size < 3) break;
|
||
|
||
// 解析目标目录(GBK 编码)
|
||
const char* ptr = (const char*)(data + 1);
|
||
const char* end = (const char*)(data + size);
|
||
std::string targetDirGbk = ptr;
|
||
std::string targetDir = FileTransferV2::gbkToUtf8(targetDirGbk);
|
||
ptr += targetDirGbk.length() + 1;
|
||
|
||
// 解析文件列表
|
||
std::vector<std::string> files;
|
||
while (ptr < end && *ptr != '\0') {
|
||
std::string fileGbk = ptr;
|
||
files.push_back(FileTransferV2::gbkToUtf8(fileGbk));
|
||
ptr += fileGbk.length() + 1;
|
||
}
|
||
|
||
// 如果没有文件列表,从剪贴板获取
|
||
if (files.empty()) {
|
||
files = ClipboardHandler::GetFiles();
|
||
}
|
||
|
||
if (!files.empty() && !targetDir.empty()) {
|
||
Mprintf(">>> COMMAND_GET_FILE: %zu files -> %s\n", files.size(), targetDir.c_str());
|
||
|
||
// 使用 V2 协议发送文件
|
||
extern uint64_t g_myClientID;
|
||
std::thread([this, files, targetDir]() {
|
||
// 收集所有文件(展开目录)
|
||
std::vector<std::string> allFiles;
|
||
std::vector<std::string> rootCandidates;
|
||
|
||
for (const auto& path : files) {
|
||
struct stat st;
|
||
if (stat(path.c_str(), &st) != 0) continue;
|
||
|
||
if (S_ISDIR(st.st_mode)) {
|
||
std::string dirPath = path;
|
||
if (dirPath.back() != '/') dirPath += '/';
|
||
size_t pos = dirPath.rfind('/', dirPath.length() - 2);
|
||
std::string parentPath = (pos != std::string::npos) ? dirPath.substr(0, pos + 1) : dirPath;
|
||
rootCandidates.push_back(parentPath);
|
||
FileTransferV2::CollectFiles(dirPath, allFiles);
|
||
} else {
|
||
rootCandidates.push_back(path);
|
||
allFiles.push_back(path);
|
||
}
|
||
}
|
||
|
||
if (allFiles.empty()) {
|
||
Mprintf("*** No files to send\n");
|
||
return;
|
||
}
|
||
|
||
std::string commonRoot = FileTransferV2::GetCommonRoot(rootCandidates);
|
||
Mprintf(">>> Sending %zu files, root=%s\n", allFiles.size(), commonRoot.c_str());
|
||
|
||
FileTransferV2::SendFilesV2(allFiles, targetDir, commonRoot, m_client, g_myClientID);
|
||
}).detach();
|
||
} else {
|
||
Mprintf("*** COMMAND_GET_FILE: no files or empty target\n");
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 加载保存的质量设置
|
||
void LoadQualitySettings()
|
||
{
|
||
m_qualityLevel = (int8_t)m_config.GetInt("QualityLevel", QUALITY_ADAPTIVE);
|
||
Mprintf(">>> LoadQualitySettings: level=%d\n", m_qualityLevel);
|
||
|
||
// 如果有保存的具体等级,立即应用
|
||
if (m_qualityLevel >= 0 && m_qualityLevel < QUALITY_COUNT) {
|
||
const QualityProfile& profile = GetQualityProfile(m_qualityLevel);
|
||
m_maxFPS.store(profile.maxFPS);
|
||
m_bAlgorithm.store(GetEffectiveAlgorithm(profile.algorithm));
|
||
}
|
||
}
|
||
|
||
// 应用质量等级配置
|
||
void ApplyQualityLevel(int8_t level, int persist = 0)
|
||
{
|
||
m_qualityLevel = level;
|
||
|
||
// 保存到配置文件
|
||
if (persist) {
|
||
m_config.SetInt("QualityLevel", level);
|
||
Mprintf(">>> Quality saved: level=%d\n", level);
|
||
}
|
||
|
||
if (level == QUALITY_DISABLED) {
|
||
// 关闭模式:保持当前设置
|
||
Mprintf(">>> Quality: Disabled (keep current)\n");
|
||
return;
|
||
}
|
||
|
||
if (level >= 0 && level < QUALITY_COUNT) {
|
||
// 具体等级:从配置表获取并应用
|
||
const QualityProfile& profile = GetQualityProfile(level);
|
||
|
||
// 应用帧率
|
||
m_maxFPS.store(profile.maxFPS);
|
||
|
||
// 应用算法(带降级处理)
|
||
uint8_t algo = GetEffectiveAlgorithm(profile.algorithm);
|
||
m_bAlgorithm.store(algo);
|
||
|
||
Mprintf(">>> Quality: Level=%d, FPS=%d, Algo=%d->%d\n",
|
||
level, profile.maxFPS, profile.algorithm, algo);
|
||
} else {
|
||
// 自适应模式 (level=-1):由服务端动态调整,不做处理
|
||
Mprintf(">>> Quality: Adaptive mode\n");
|
||
}
|
||
}
|
||
|
||
// 处理来自服务端的鼠标/键盘输入事件
|
||
// 使用独立的 m_inputDisplay,避免与截屏线程的 m_display 产生竞争
|
||
void HandleInputEvent(const MSG64_LNX* msg)
|
||
{
|
||
if (!m_inputDisplay || !m_x11.HasXTest()) {
|
||
if (!m_xtestWarned) {
|
||
Mprintf("*** XTest not available, cannot handle input ***\n");
|
||
m_xtestWarned = true;
|
||
}
|
||
return;
|
||
}
|
||
|
||
unsigned int message = (unsigned int)msg->message;
|
||
// 从 lParam 提取坐标 (MAKELPARAM 格式: low 16 bits = x, high 16 bits = y)
|
||
// 注意:不使用 pt_x/pt_y,因为 Windows MSG64 结构可能有填充导致偏移不一致
|
||
int x = (int)(msg->lParam & 0xFFFF);
|
||
int y = (int)((msg->lParam >> 16) & 0xFFFF);
|
||
|
||
switch (message) {
|
||
// ================== 鼠标事件 ==================
|
||
case WM_MOUSEMOVE:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_LBUTTONDOWN:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, True, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_LBUTTONUP:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_LBUTTONDBLCLK:
|
||
// 双击:快速按下释放两次
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, False, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button1, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_RBUTTONDOWN:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, True, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_RBUTTONUP:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_RBUTTONDBLCLK:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, False, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button3, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_MBUTTONDOWN:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, True, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_MBUTTONUP:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_MBUTTONDBLCLK:
|
||
m_x11.pXTestFakeMotionEvent(m_inputDisplay, -1, x, y, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, False, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, Button2, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
|
||
case WM_MOUSEWHEEL: {
|
||
short delta = GET_WHEEL_DELTA_WPARAM(msg->wParam);
|
||
// 滚轮:正值向上(Button4),负值向下(Button5)
|
||
unsigned int button = (delta > 0) ? Button4 : Button5;
|
||
int clicks = abs(delta) / 120; // 标准滚轮增量是120
|
||
if (clicks < 1) clicks = 1;
|
||
for (int i = 0; i < clicks; i++) {
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, button, True, 0);
|
||
m_x11.pXTestFakeButtonEvent(m_inputDisplay, button, False, 0);
|
||
}
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
break;
|
||
}
|
||
|
||
// ================== 键盘事件 ==================
|
||
case WM_KEYDOWN:
|
||
case WM_SYSKEYDOWN: {
|
||
unsigned int vk = (unsigned int)msg->wParam;
|
||
unsigned long keysym = VKtoKeySym(vk);
|
||
if (keysym) {
|
||
unsigned int keycode = m_x11.pXKeysymToKeycode(m_inputDisplay, keysym);
|
||
if (keycode) {
|
||
m_x11.pXTestFakeKeyEvent(m_inputDisplay, keycode, True, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
case WM_KEYUP:
|
||
case WM_SYSKEYUP: {
|
||
unsigned int vk = (unsigned int)msg->wParam;
|
||
unsigned long keysym = VKtoKeySym(vk);
|
||
if (keysym) {
|
||
unsigned int keycode = m_x11.pXKeysymToKeycode(m_inputDisplay, keysym);
|
||
if (keycode) {
|
||
m_x11.pXTestFakeKeyEvent(m_inputDisplay, keycode, False, 0);
|
||
m_x11.pXFlush(m_inputDisplay);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
private:
|
||
IOCPClient* m_client;
|
||
std::atomic<bool> m_running;
|
||
std::atomic<bool> m_destroyed; // Flag to prevent Start() after destruction begins
|
||
std::mutex m_threadMutex; // Protects m_captureThread lifecycle
|
||
std::thread m_captureThread;
|
||
bool m_xtestWarned;
|
||
|
||
// X11 动态加载
|
||
X11Loader m_x11;
|
||
Display* m_display; // 截屏线程专用
|
||
Display* m_inputDisplay; // 输入控制专用(OnReceive 回调线程)
|
||
Window m_root;
|
||
Pixmap m_pixmap;
|
||
GC m_gc;
|
||
int m_width;
|
||
int m_height;
|
||
|
||
// 协议
|
||
BITMAPINFOHEADER_LNX m_bmpHeader;
|
||
std::vector<uint8_t> m_prevFrame;
|
||
std::vector<uint8_t> m_currFrame;
|
||
std::vector<uint8_t> m_diffBuffer;
|
||
|
||
// 自适应质量控制
|
||
std::atomic<uint8_t> m_bAlgorithm; // 当前算法 (ALGORITHM_DIFF/RGB565/GRAY)
|
||
std::atomic<int> m_maxFPS; // 最大帧率
|
||
int8_t m_qualityLevel; // 当前质量等级 (-1=自适应, 0-5=具体等级)
|
||
LinuxConfig m_config; // 配置持久化 (~/.config/ghost/config.conf)
|
||
|
||
// X11 截屏,输出 BGRA 格式(自底向上,与 BMP 一致)
|
||
// 使用 XCopyArea 将 root window 拷贝到离屏 Pixmap,再对 Pixmap 调用 XGetImage
|
||
// 这样可以避免合成窗口管理器(Mutter 等)导致的 BadMatch 错误
|
||
bool CaptureScreen(std::vector<uint8_t>& buffer)
|
||
{
|
||
// 先将 root window 内容拷贝到离屏 Pixmap
|
||
m_x11.pXCopyArea(m_display, m_root, m_pixmap, m_gc, 0, 0, m_width, m_height, 0, 0);
|
||
m_x11.pXSync(m_display, 0); // 等待拷贝完成
|
||
|
||
// AllPlanes = ~0UL, ZPixmap = 2
|
||
XImage* img = m_x11.pXGetImage(m_display, m_pixmap, 0, 0, m_width, m_height, ~0UL, 2);
|
||
if (!img) return false;
|
||
|
||
// X11 ZPixmap 通常是 BGRA (bits_per_pixel=32),但行序是自顶向下
|
||
// BMP 期望自底向上,需要翻转
|
||
int rowBytes = m_width * 4;
|
||
for (int y = 0; y < m_height; y++) {
|
||
int srcRow = y;
|
||
int dstRow = m_height - 1 - y; // 翻转
|
||
uint8_t* src = (uint8_t*)img->data + srcRow * img->bytes_per_line;
|
||
uint8_t* dst = buffer.data() + dstRow * rowBytes;
|
||
|
||
if (img->bits_per_pixel == 32) {
|
||
// X11 通常是 BGRX,需要确保 alpha 通道
|
||
for (int x = 0; x < m_width; x++) {
|
||
dst[x * 4 + 0] = src[x * 4 + 0]; // B
|
||
dst[x * 4 + 1] = src[x * 4 + 1]; // G
|
||
dst[x * 4 + 2] = src[x * 4 + 2]; // R
|
||
dst[x * 4 + 3] = 0xFF; // A
|
||
}
|
||
} else {
|
||
// 不支持非 32 位格式
|
||
m_x11.pXDestroyImage(img);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
m_x11.pXDestroyImage(img);
|
||
return true;
|
||
}
|
||
|
||
// 发送第一帧完整截图
|
||
void SendFirstScreen()
|
||
{
|
||
if (!CaptureScreen(m_currFrame)) return;
|
||
|
||
uint32_t imgSize = m_bmpHeader.biSizeImage;
|
||
std::vector<uint8_t> buf(1 + imgSize);
|
||
buf[0] = TOKEN_FIRSTSCREEN;
|
||
memcpy(&buf[1], m_currFrame.data(), imgSize);
|
||
|
||
m_client->Send2Server((char*)buf.data(), buf.size());
|
||
|
||
// 保存为上一帧
|
||
m_prevFrame = m_currFrame;
|
||
}
|
||
|
||
// 计算差异并发送 TOKEN_NEXTSCREEN
|
||
// 差异格式: 每个变化区域 = offset(4字节) + length(4字节) + pixel data
|
||
void SendDiffFrame()
|
||
{
|
||
if (!CaptureScreen(m_currFrame)) return;
|
||
|
||
uint8_t* out = m_diffBuffer.data();
|
||
out[0] = TOKEN_NEXTSCREEN;
|
||
uint8_t* data = out + 1;
|
||
|
||
// 写入算法类型(使用当前生效的算法)
|
||
uint8_t algo = m_bAlgorithm.load();
|
||
memcpy(data, &algo, sizeof(uint8_t));
|
||
|
||
// 写入光标位置 (Linux 端简单置 0)
|
||
int32_t cursorX = 0, cursorY = 0;
|
||
memcpy(data + 1, &cursorX, sizeof(int32_t));
|
||
memcpy(data + 1 + sizeof(int32_t), &cursorY, sizeof(int32_t));
|
||
|
||
// 写入光标类型
|
||
uint8_t cursorType = 0;
|
||
memcpy(data + 1 + 2 * sizeof(int32_t), &cursorType, sizeof(uint8_t));
|
||
|
||
uint32_t headerSize = 1 + 2 * sizeof(int32_t) + 1; // algo + cursor + cursorType
|
||
uint8_t* diffData = data + headerSize;
|
||
uint32_t diffLen = CompareBitmap(m_currFrame.data(), m_prevFrame.data(),
|
||
diffData, m_bmpHeader.biSizeImage, algo);
|
||
|
||
uint32_t totalLen = 1 + headerSize + diffLen;
|
||
m_client->Send2Server((char*)out, totalLen);
|
||
|
||
// 更新上一帧
|
||
std::swap(m_prevFrame, m_currFrame);
|
||
}
|
||
|
||
// 差异比较算法(支持 DIFF/RGB565/GRAY)
|
||
// 输出格式: [byteOffset(4) + length(4) + pixel data] ...
|
||
// DIFF: length = 字节数, data = BGRA 原始数据
|
||
// RGB565: length = 像素数, data = RGB565 格式
|
||
// GRAY: length = 像素数, data = 灰度值
|
||
uint32_t CompareBitmap(const uint8_t* curr, const uint8_t* prev,
|
||
uint8_t* outBuf, uint32_t totalBytes, uint8_t algo)
|
||
{
|
||
const uint32_t bytesPerPixel = 4;
|
||
const uint32_t totalPixels = totalBytes / bytesPerPixel;
|
||
const uint32_t gapThreshold = 8; // 8 像素间隙容忍
|
||
|
||
// 根据算法确定长度字段的除数
|
||
// GRAY/RGB565: 长度字段存像素数 (ratio=4)
|
||
// DIFF: 长度字段存字节数 (ratio=1)
|
||
const uint32_t ratio = (algo == ALGORITHM_GRAY || algo == ALGORITHM_RGB565) ? 4 : 1;
|
||
|
||
uint32_t outOffset = 0;
|
||
uint32_t i = 0;
|
||
|
||
while (i < totalPixels) {
|
||
// 跳过相同像素
|
||
while (i < totalPixels &&
|
||
*(uint32_t*)(curr + i * 4) == *(uint32_t*)(prev + i * 4)) {
|
||
i++;
|
||
}
|
||
if (i >= totalPixels) break;
|
||
|
||
// 找到变化区域的起始
|
||
uint32_t start = i;
|
||
uint32_t lastDiff = i;
|
||
|
||
// 扫描直到连续 gapThreshold 个像素相同
|
||
while (i < totalPixels) {
|
||
if (*(uint32_t*)(curr + i * 4) != *(uint32_t*)(prev + i * 4)) {
|
||
lastDiff = i;
|
||
} else if (i - lastDiff > gapThreshold) {
|
||
break;
|
||
}
|
||
i++;
|
||
}
|
||
|
||
uint32_t end = lastDiff + 1;
|
||
uint32_t count = end - start; // 像素数
|
||
uint32_t byteOffset = start * bytesPerPixel;
|
||
uint32_t byteCount = count * bytesPerPixel;
|
||
|
||
// 写入 byteOffset
|
||
memcpy(outBuf + outOffset, &byteOffset, sizeof(uint32_t));
|
||
outOffset += sizeof(uint32_t);
|
||
|
||
// 写入 length(根据算法不同)
|
||
uint32_t lengthField = byteCount / ratio;
|
||
memcpy(outBuf + outOffset, &lengthField, sizeof(uint32_t));
|
||
outOffset += sizeof(uint32_t);
|
||
|
||
// 写入像素数据(根据算法转换)
|
||
const uint8_t* srcData = curr + byteOffset;
|
||
if (algo == ALGORITHM_RGB565) {
|
||
ConvertBGRAtoRGB565(srcData, (uint16_t*)(outBuf + outOffset), count);
|
||
outOffset += count * 2; // RGB565: 2 字节/像素
|
||
} else if (algo == ALGORITHM_GRAY) {
|
||
ConvertBGRAtoGray(srcData, outBuf + outOffset, count);
|
||
outOffset += count; // GRAY: 1 字节/像素
|
||
} else {
|
||
// DIFF: 原样复制 BGRA
|
||
memcpy(outBuf + outOffset, srcData, byteCount);
|
||
outOffset += byteCount; // DIFF: 4 字节/像素
|
||
}
|
||
}
|
||
|
||
return outOffset;
|
||
}
|
||
|
||
|
||
// 获取单调时钟毫秒数(墙钟时间,不受系统时间调整影响)
|
||
static uint64_t GetTickMs()
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||
}
|
||
|
||
// 截屏主循环
|
||
void CaptureLoop()
|
||
{
|
||
try {
|
||
Mprintf(">>> ScreenHandler CaptureLoop started (%dx%d)\n", m_width, m_height);
|
||
|
||
// 发送第一帧
|
||
SendFirstScreen();
|
||
|
||
while (m_running) {
|
||
uint64_t start = GetTickMs();
|
||
|
||
SendDiffFrame();
|
||
|
||
// 动态计算帧间隔(根据当前 maxFPS)
|
||
int fps = m_maxFPS.load();
|
||
if (fps <= 0) fps = 10; // 防止除零
|
||
int sleepMs = 1000 / fps;
|
||
|
||
int elapsed = (int)(GetTickMs() - start);
|
||
int wait = sleepMs - elapsed;
|
||
if (wait > 0) {
|
||
usleep(wait * 1000);
|
||
}
|
||
}
|
||
|
||
Mprintf(">>> ScreenHandler CaptureLoop stopped\n");
|
||
} catch (const std::exception& e) {
|
||
Mprintf("*** CaptureLoop exception: %s ***\n", e.what());
|
||
} catch (...) {
|
||
Mprintf("*** CaptureLoop unknown exception ***\n");
|
||
}
|
||
}
|
||
};
|