diff --git a/linux/ScreenHandler.h b/linux/ScreenHandler.h index 7e58ad2..3847a4c 100644 --- a/linux/ScreenHandler.h +++ b/linux/ScreenHandler.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -389,6 +390,16 @@ static unsigned long VKtoKeySym(unsigned int vk) } } +// XFixes cursor image structure (for cursor type detection) +struct XFixesCursorImage { + short x, y; + unsigned short width, height; + unsigned short xhot, yhot; + unsigned long cursor_serial; + unsigned long* pixels; + // Atom cursor_name; // Only in XFixes 2.0+ +}; + // X11 函数指针类型 typedef Display* (*fn_XOpenDisplay)(const char*); typedef int (*fn_XCloseDisplay)(Display*); @@ -405,12 +416,18 @@ 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); +typedef int (*fn_XQueryPointer)(Display*, Window, Window*, Window*, int*, int*, int*, int*, unsigned int*); +typedef int (*fn_XFree)(void*); // 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); +// XFixes 扩展函数指针类型(用于光标类型检测) +typedef int (*fn_XFixesQueryExtension)(Display*, int*, int*); +typedef XFixesCursorImage* (*fn_XFixesGetCursorImage)(Display*); + // X11 动态加载包装 class X11Loader { @@ -444,13 +461,19 @@ public: fn_XKeysymToKeycode pXKeysymToKeycode; fn_XFlush pXFlush; fn_XClearArea pXClearArea; + fn_XQueryPointer pXQueryPointer; + fn_XFree pXFree; // XTest 扩展(用于模拟输入) fn_XTestFakeMotionEvent pXTestFakeMotionEvent; fn_XTestFakeButtonEvent pXTestFakeButtonEvent; fn_XTestFakeKeyEvent pXTestFakeKeyEvent; - X11Loader() : m_handle(nullptr), m_xtst_handle(nullptr) + // XFixes 扩展(用于光标类型检测) + fn_XFixesQueryExtension pXFixesQueryExtension; + fn_XFixesGetCursorImage pXFixesGetCursorImage; + + X11Loader() : m_handle(nullptr), m_xtst_handle(nullptr), m_xfixes_handle(nullptr) { pXOpenDisplay = nullptr; pXCloseDisplay = nullptr; @@ -471,9 +494,13 @@ public: pXKeysymToKeycode = nullptr; pXFlush = nullptr; pXClearArea = nullptr; + pXQueryPointer = nullptr; + pXFree = nullptr; pXTestFakeMotionEvent = nullptr; pXTestFakeButtonEvent = nullptr; pXTestFakeKeyEvent = nullptr; + pXFixesQueryExtension = nullptr; + pXFixesGetCursorImage = nullptr; } bool Load() @@ -503,6 +530,8 @@ public: pXKeysymToKeycode = (fn_XKeysymToKeycode)dlsym(m_handle, "XKeysymToKeycode"); pXFlush = (fn_XFlush)dlsym(m_handle, "XFlush"); pXClearArea = (fn_XClearArea)dlsym(m_handle, "XClearArea"); + pXQueryPointer = (fn_XQueryPointer)dlsym(m_handle, "XQueryPointer"); + pXFree = (fn_XFree)dlsym(m_handle, "XFree"); // 加载 XTest 扩展库(用于模拟鼠标/键盘输入) m_xtst_handle = dlopen("libXtst.so.6", RTLD_LAZY); @@ -513,7 +542,15 @@ public: pXTestFakeKeyEvent = (fn_XTestFakeKeyEvent)dlsym(m_xtst_handle, "XTestFakeKeyEvent"); } - // 基本 X11 函数必须全部存在;XTest 函数可选(没有时无法控制输入) + // 加载 XFixes 扩展库(用于光标类型检测) + m_xfixes_handle = dlopen("libXfixes.so.3", RTLD_LAZY); + if (!m_xfixes_handle) m_xfixes_handle = dlopen("libXfixes.so", RTLD_LAZY); + if (m_xfixes_handle) { + pXFixesQueryExtension = (fn_XFixesQueryExtension)dlsym(m_xfixes_handle, "XFixesQueryExtension"); + pXFixesGetCursorImage = (fn_XFixesGetCursorImage)dlsym(m_xfixes_handle, "XFixesGetCursorImage"); + } + + // 基本 X11 函数必须全部存在;XTest/XFixes 函数可选 return pXOpenDisplay && pXCloseDisplay && pXGetImage && pXDestroyImage && pXDefaultScreen && pXDisplayWidth && pXDisplayHeight && pXRootWindow && pXSetErrorHandler && pXCreatePixmap && pXFreePixmap && @@ -527,8 +564,18 @@ public: return pXTestFakeMotionEvent && pXTestFakeButtonEvent && pXTestFakeKeyEvent; } + // 检查 XFixes 扩展是否可用 + bool HasXFixes() const + { + return pXFixesGetCursorImage != nullptr; + } + ~X11Loader() { + if (m_xfixes_handle) { + dlclose(m_xfixes_handle); + m_xfixes_handle = nullptr; + } if (m_xtst_handle) { dlclose(m_xtst_handle); m_xtst_handle = nullptr; @@ -542,6 +589,7 @@ public: private: void* m_handle; void* m_xtst_handle; + void* m_xfixes_handle; }; class ScreenHandler : public IOCPManager @@ -1165,13 +1213,14 @@ private: uint8_t algo = m_bAlgorithm.load(); memcpy(data, &algo, sizeof(uint8_t)); - // 写入光标位置 (Linux 端简单置 0) + // 写入光标位置 int32_t cursorX = 0, cursorY = 0; + GetCursorPosition(cursorX, cursorY); memcpy(data + 1, &cursorX, sizeof(int32_t)); memcpy(data + 1 + sizeof(int32_t), &cursorY, sizeof(int32_t)); - // 写入光标类型 - uint8_t cursorType = 0; + // 写入光标类型 (使用 XFixes 检测) + uint8_t cursorType = GetCursorTypeIndex(); memcpy(data + 1 + 2 * sizeof(int32_t), &cursorType, sizeof(uint8_t)); uint32_t headerSize = 1 + 2 * sizeof(int32_t) + 1; // algo + cursor + cursorType @@ -1231,9 +1280,10 @@ private: packet[1] = ALGORITHM_H264; int32_t cursorX = 0, cursorY = 0; + GetCursorPosition(cursorX, cursorY); memcpy(&packet[2], &cursorX, sizeof(int32_t)); memcpy(&packet[6], &cursorY, sizeof(int32_t)); - packet[10] = 0; // cursorType + packet[10] = GetCursorTypeIndex(); // 使用 XFixes 检测光标类型 memcpy(&packet[headerSize], encodedData, encodedSize); m_client->Send2Server((char*)packet.data(), packet.size()); @@ -1322,6 +1372,118 @@ private: return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; } + // 获取光标位置 + void GetCursorPosition(int32_t& x, int32_t& y) + { + x = 0; + y = 0; + + // 检查是否正在运行和资源是否有效 + if (!m_running.load() || m_destroyed.load()) { + static bool warned = false; + if (!warned) { + Mprintf("*** GetCursorPosition: skipped (running=%d, destroyed=%d)\n", + m_running.load(), m_destroyed.load()); + warned = true; + } + return; + } + + Display* display = m_display; // 局部拷贝 + if (!display || !m_x11.pXQueryPointer) { + static bool warned = false; + if (!warned) { + Mprintf("*** GetCursorPosition: display=%p, pXQueryPointer=%p\n", + (void*)display, (void*)m_x11.pXQueryPointer); + warned = true; + } + return; + } + + Window root_return, child_return; + int root_x, root_y, win_x, win_y; + unsigned int mask; + + if (m_x11.pXQueryPointer(display, m_root, &root_return, &child_return, + &root_x, &root_y, &win_x, &win_y, &mask)) { + x = root_x; + y = root_y; + + // Clamp to screen bounds + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x >= m_width) x = m_width - 1; + if (y >= m_height) y = m_height - 1; + } + } + + // 获取光标类型索引(映射到 Windows 光标类型) + // Windows cursor type indices (from CursorInfo.h): + // 0: IDC_APPSTARTING, 1: IDC_ARROW, 2: IDC_CROSS, 3: IDC_HAND, + // 4: IDC_HELP, 5: IDC_IBEAM, 6: IDC_ICON, 7: IDC_NO, + // 8: IDC_SIZE, 9: IDC_SIZEALL, 10: IDC_SIZENESW, 11: IDC_SIZENS, + // 12: IDC_SIZENWSE, 13: IDC_SIZEWE, 14: IDC_UPARROW, 15: IDC_WAIT + uint8_t GetCursorTypeIndex() + { + // Cache result and throttle to avoid performance impact + static uint8_t cachedIndex = 1; // Default: IDC_ARROW + static uint64_t lastCheckTime = 0; + static unsigned long lastCursorSerial = 0; + + // Throttle: check at most every 100ms + uint64_t now = GetTickMs(); + if ((now - lastCheckTime) < 100) { + return cachedIndex; + } + lastCheckTime = now; + + // Check if XFixes is available and XFree is loaded + if (!m_x11.HasXFixes() || !m_x11.pXFree || !m_display) { + return 1; // IDC_ARROW + } + + // Get current cursor image + XFixesCursorImage* cursorImg = m_x11.pXFixesGetCursorImage(m_display); + if (!cursorImg) { + return cachedIndex; + } + + // Check if cursor changed (using serial number) + if (cursorImg->cursor_serial == lastCursorSerial) { + // Cursor hasn't changed, use cached value + // Note: We need to free the cursor image + // XFixes allocates this with Xlib's allocator + m_x11.pXFree(cursorImg); + return cachedIndex; + } + lastCursorSerial = cursorImg->cursor_serial; + + // Analyze cursor characteristics to determine type + uint8_t index = 1; // Default to IDC_ARROW + + unsigned short w = cursorImg->width; + unsigned short h = cursorImg->height; + unsigned short xhot = cursorImg->xhot; + unsigned short yhot = cursorImg->yhot; + + // Heuristic-based cursor type detection (conservative approach): + // Only detect distinctive cursor types to minimize false positives + + // IBEAM (text cursor): very narrow, tall cursor + if (w <= 8 && h >= 12 && xhot <= w/2 + 1) { + index = 5; // IDC_IBEAM + } + // HAND (pointing): hotspot at top-left area (finger tip) + else if (w >= 18 && h >= 20 && xhot <= 10 && yhot <= 5) { + index = 3; // IDC_HAND + } + // All other cursors default to ARROW + + cachedIndex = index; + m_x11.pXFree(cursorImg); + return index; + } + // 截屏主循环 void CaptureLoop() {