Feature: Implement Linux cursor type detection using XFixes extension

This commit is contained in:
yuanyuanxiang
2026-05-03 12:58:29 +02:00
parent ca37fa419a
commit a8b0932080

View File

@@ -12,6 +12,7 @@
#include <atomic> #include <atomic>
#include <vector> #include <vector>
#include <cstring> #include <cstring>
#include <cstdlib>
#include <stdexcept> #include <stdexcept>
#include <memory> #include <memory>
@@ -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 函数指针类型 // X11 函数指针类型
typedef Display* (*fn_XOpenDisplay)(const char*); typedef Display* (*fn_XOpenDisplay)(const char*);
typedef int (*fn_XCloseDisplay)(Display*); typedef int (*fn_XCloseDisplay)(Display*);
@@ -405,12 +416,18 @@ typedef int (*fn_XSync)(Display*, int);
typedef unsigned long (*fn_XKeysymToKeycode)(Display*, unsigned long); typedef unsigned long (*fn_XKeysymToKeycode)(Display*, unsigned long);
typedef int (*fn_XFlush)(Display*); typedef int (*fn_XFlush)(Display*);
typedef int (*fn_XClearArea)(Display*, Window, int, int, unsigned int, unsigned int, int); 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 扩展函数指针类型(用于模拟鼠标/键盘输入) // XTest 扩展函数指针类型(用于模拟鼠标/键盘输入)
typedef int (*fn_XTestFakeMotionEvent)(Display*, int, int, int, unsigned long); typedef int (*fn_XTestFakeMotionEvent)(Display*, int, int, int, unsigned long);
typedef int (*fn_XTestFakeButtonEvent)(Display*, unsigned int, int, unsigned long); typedef int (*fn_XTestFakeButtonEvent)(Display*, unsigned int, int, unsigned long);
typedef int (*fn_XTestFakeKeyEvent)(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 动态加载包装 // X11 动态加载包装
class X11Loader class X11Loader
{ {
@@ -444,13 +461,19 @@ public:
fn_XKeysymToKeycode pXKeysymToKeycode; fn_XKeysymToKeycode pXKeysymToKeycode;
fn_XFlush pXFlush; fn_XFlush pXFlush;
fn_XClearArea pXClearArea; fn_XClearArea pXClearArea;
fn_XQueryPointer pXQueryPointer;
fn_XFree pXFree;
// XTest 扩展(用于模拟输入) // XTest 扩展(用于模拟输入)
fn_XTestFakeMotionEvent pXTestFakeMotionEvent; fn_XTestFakeMotionEvent pXTestFakeMotionEvent;
fn_XTestFakeButtonEvent pXTestFakeButtonEvent; fn_XTestFakeButtonEvent pXTestFakeButtonEvent;
fn_XTestFakeKeyEvent pXTestFakeKeyEvent; 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; pXOpenDisplay = nullptr;
pXCloseDisplay = nullptr; pXCloseDisplay = nullptr;
@@ -471,9 +494,13 @@ public:
pXKeysymToKeycode = nullptr; pXKeysymToKeycode = nullptr;
pXFlush = nullptr; pXFlush = nullptr;
pXClearArea = nullptr; pXClearArea = nullptr;
pXQueryPointer = nullptr;
pXFree = nullptr;
pXTestFakeMotionEvent = nullptr; pXTestFakeMotionEvent = nullptr;
pXTestFakeButtonEvent = nullptr; pXTestFakeButtonEvent = nullptr;
pXTestFakeKeyEvent = nullptr; pXTestFakeKeyEvent = nullptr;
pXFixesQueryExtension = nullptr;
pXFixesGetCursorImage = nullptr;
} }
bool Load() bool Load()
@@ -503,6 +530,8 @@ public:
pXKeysymToKeycode = (fn_XKeysymToKeycode)dlsym(m_handle, "XKeysymToKeycode"); pXKeysymToKeycode = (fn_XKeysymToKeycode)dlsym(m_handle, "XKeysymToKeycode");
pXFlush = (fn_XFlush)dlsym(m_handle, "XFlush"); pXFlush = (fn_XFlush)dlsym(m_handle, "XFlush");
pXClearArea = (fn_XClearArea)dlsym(m_handle, "XClearArea"); pXClearArea = (fn_XClearArea)dlsym(m_handle, "XClearArea");
pXQueryPointer = (fn_XQueryPointer)dlsym(m_handle, "XQueryPointer");
pXFree = (fn_XFree)dlsym(m_handle, "XFree");
// 加载 XTest 扩展库(用于模拟鼠标/键盘输入) // 加载 XTest 扩展库(用于模拟鼠标/键盘输入)
m_xtst_handle = dlopen("libXtst.so.6", RTLD_LAZY); m_xtst_handle = dlopen("libXtst.so.6", RTLD_LAZY);
@@ -513,7 +542,15 @@ public:
pXTestFakeKeyEvent = (fn_XTestFakeKeyEvent)dlsym(m_xtst_handle, "XTestFakeKeyEvent"); 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 && return pXOpenDisplay && pXCloseDisplay && pXGetImage && pXDestroyImage &&
pXDefaultScreen && pXDisplayWidth && pXDisplayHeight && pXRootWindow && pXDefaultScreen && pXDisplayWidth && pXDisplayHeight && pXRootWindow &&
pXSetErrorHandler && pXCreatePixmap && pXFreePixmap && pXSetErrorHandler && pXCreatePixmap && pXFreePixmap &&
@@ -527,8 +564,18 @@ public:
return pXTestFakeMotionEvent && pXTestFakeButtonEvent && pXTestFakeKeyEvent; return pXTestFakeMotionEvent && pXTestFakeButtonEvent && pXTestFakeKeyEvent;
} }
// 检查 XFixes 扩展是否可用
bool HasXFixes() const
{
return pXFixesGetCursorImage != nullptr;
}
~X11Loader() ~X11Loader()
{ {
if (m_xfixes_handle) {
dlclose(m_xfixes_handle);
m_xfixes_handle = nullptr;
}
if (m_xtst_handle) { if (m_xtst_handle) {
dlclose(m_xtst_handle); dlclose(m_xtst_handle);
m_xtst_handle = nullptr; m_xtst_handle = nullptr;
@@ -542,6 +589,7 @@ public:
private: private:
void* m_handle; void* m_handle;
void* m_xtst_handle; void* m_xtst_handle;
void* m_xfixes_handle;
}; };
class ScreenHandler : public IOCPManager class ScreenHandler : public IOCPManager
@@ -1165,13 +1213,14 @@ private:
uint8_t algo = m_bAlgorithm.load(); uint8_t algo = m_bAlgorithm.load();
memcpy(data, &algo, sizeof(uint8_t)); memcpy(data, &algo, sizeof(uint8_t));
// 写入光标位置 (Linux 端简单置 0) // 写入光标位置
int32_t cursorX = 0, cursorY = 0; int32_t cursorX = 0, cursorY = 0;
GetCursorPosition(cursorX, cursorY);
memcpy(data + 1, &cursorX, sizeof(int32_t)); memcpy(data + 1, &cursorX, sizeof(int32_t));
memcpy(data + 1 + sizeof(int32_t), &cursorY, sizeof(int32_t)); memcpy(data + 1 + sizeof(int32_t), &cursorY, sizeof(int32_t));
// 写入光标类型 // 写入光标类型 (使用 XFixes 检测)
uint8_t cursorType = 0; uint8_t cursorType = GetCursorTypeIndex();
memcpy(data + 1 + 2 * sizeof(int32_t), &cursorType, sizeof(uint8_t)); memcpy(data + 1 + 2 * sizeof(int32_t), &cursorType, sizeof(uint8_t));
uint32_t headerSize = 1 + 2 * sizeof(int32_t) + 1; // algo + cursor + cursorType uint32_t headerSize = 1 + 2 * sizeof(int32_t) + 1; // algo + cursor + cursorType
@@ -1231,9 +1280,10 @@ private:
packet[1] = ALGORITHM_H264; packet[1] = ALGORITHM_H264;
int32_t cursorX = 0, cursorY = 0; int32_t cursorX = 0, cursorY = 0;
GetCursorPosition(cursorX, cursorY);
memcpy(&packet[2], &cursorX, sizeof(int32_t)); memcpy(&packet[2], &cursorX, sizeof(int32_t));
memcpy(&packet[6], &cursorY, sizeof(int32_t)); memcpy(&packet[6], &cursorY, sizeof(int32_t));
packet[10] = 0; // cursorType packet[10] = GetCursorTypeIndex(); // 使用 XFixes 检测光标类型
memcpy(&packet[headerSize], encodedData, encodedSize); memcpy(&packet[headerSize], encodedData, encodedSize);
m_client->Send2Server((char*)packet.data(), packet.size()); m_client->Send2Server((char*)packet.data(), packet.size());
@@ -1322,6 +1372,118 @@ private:
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 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() void CaptureLoop()
{ {