Feature: Implement Linux cursor type detection using XFixes extension
This commit is contained in:
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user