当前位置: 首页 > news >正文

Linux桌面X11服务-XRecord方案捕获鼠标点击的应用窗口

linux下获取鼠标点击应用的窗口信息,进程信息

查询服务器类型:

echo $XDG_SESSION_TYPE

一般有x11和Wayland

查询桌面类型:

echo $XDG_CURRENT_DESKTOP

一般有GNome和KDE,国产有deepin,方案对比

对比项X11 方案(tdisplay 用的)AT-SPI 方案(你要做的)
实现层级底层窗口管理系统高层 GUI 无障碍接口
获取对象只能看到窗口、进程、类名可以看到每个控件、文本内容
精确度只能识别窗口可识别按钮、文本框、菜单项等
依赖库libX11, libXtst, libXilibatspi, libatk-bridge, D-Bus
性能快、无需 Accessibility稍慢、依赖 D-Bus 服务
使用场景审计系统、录屏、操作跟踪屏幕阅读器、自动化测试

两个查询窗口id信息的工具命令

xwininfo 捕获窗口,类似spi++
xprop -id wid  查询指定窗口id的信息

接下来介绍X11的方案实现:

[鼠标点击事件]│▼
X Server 生成事件││└─ 我们使用 XRecord 捕获│   (得到 root_x, root_y)▼坐标定位目标窗口│▼获取鼠标点击的顶层窗口,判断root_x, root_y是否在范围内│▼读取 _NET_WM_PID / _NET_WM_NAME│▼获取应用程序名称 & 窗口属性

代码实现

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <queue>
#include <atomic>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <X11/extensions/record.h>
#include <X11/extensions/Xfixes.h>
#include <X11/Xproto.h>
#include <X11/Xmd.h>
#include <X11/Xatom.h>
#include <unistd.h>
#include <vector>
#include <pwd.h>
#include "scopedtimer.h"std::vector<Window> get_all_windows(Display*display);
// ==================== 鼠标点击信息结构 ====================
struct ClickEvent {unsigned char button;  // 鼠标按钮号short root_x; //鼠标点击坐标xshort root_y;  //鼠标点击坐标yWindow wid;  //窗口idstd::string window_title; //窗口pid_t pid;//进程idstd::string pid_name;//进程名
};std::vector<Window> global_windows;
// 事件队列与互斥锁
std::queue<ClickEvent> click_queue;
std::mutex queue_mutex;
std::atomic<bool> should_exit(false);Display *global_display = nullptr;
Display *global_display_event = nullptr;
// ==================== 获取窗口标题 ====================
std::string get_window_title(Display *display, Window window){if(!window) return "";Atom utf8_string = XInternAtom(display, "UTF8_STRING",True);Atom net_wm_name = XInternAtom(display, "_NET_WM_NAME", True);if(net_wm_name!=None){Atom actual_type;int actual_format;unsigned long nitems, bytes_after;unsigned char *prop = nullptr;if(XGetWindowProperty(display,window,net_wm_name,0,(~0L),False,utf8_string!=None?utf8_string:XA_STRING,&actual_type,&actual_format,&nitems,&bytes_after,&prop) == Success && prop){std::string title(reinterpret_cast<char*>(prop),nitems);XFree(prop);return title;}}XTextProperty prop;if(XGetWMName(display, window,&prop) && prop.value){std::string title(reinterpret_cast<char*>(prop.value));XFree(prop.value);return title;}return "";
}//return 非0值表示成功
pid_t get_window_pid(Display *display, Window window){pid_t pid;if(!window) return 0;Atom net_wm_pid = XInternAtom(display, "_NET_WM_PID", True);if(net_wm_pid!=None){Atom actual_type;int actual_format;unsigned long nitems, bytes_after;unsigned char *prop = nullptr;if(XGetWindowProperty(display,window,net_wm_pid,0,(~0L),False,XA_CARDINAL,&actual_type,&actual_format,&nitems,&bytes_after,&prop) == Success && prop){pid = *(unsigned long *)prop;XFree(prop);return pid;}}return 0;
}std::string get_proc_name(pid_t pid){if(pid <= 0)return "";std::string path = "cat /proc/" + std::to_string(pid) + "/cmdline";std::string proc_name;FILE *fstream = nullptr;char buff[1024];memset(buff, 0, sizeof(buff));if(NULL != (fstream = popen(path.c_str(), "r"))){while(NULL!=fgets(buff, sizeof(buff), fstream)){proc_name += buff;}}if(fstream!=nullptr)pclose(fstream);return proc_name;
}
Window find_window_at(Display* display, int x, int y){//std::vector<Window> windows = get_all_windows(display);std::vector<Window> windows = global_windows;for(Window win : windows){XWindowAttributes attr;if(!XGetWindowAttributes(display, win, &attr) || attr.map_state != IsViewable){continue;}int win_x, win_y;Window child;XTranslateCoordinates(display, win, DefaultRootWindow(display),0,0,&win_x,&win_y,&child);if(x >= win_x && x<= win_x + attr.width && y>=win_y &&y<=win_y+attr.height){return win;}}return 0;
}std::vector<Window> get_all_windows(Display*display){std::vector<Window> windows;Atom client_list_atom = XInternAtom(display, "_NET_CLIENT_LIST_STACKING", false);if(client_list_atom != None){Atom actual_type;int actual_format;unsigned long nitems, bytes_after;unsigned char *prop = NULL;int result = XGetWindowProperty(display,DefaultRootWindow(display),client_list_atom,0,(~0L),False,XA_WINDOW,&actual_type,&actual_format,&nitems,&bytes_after,&prop);if(result == Success && prop){Window *data = reinterpret_cast<Window*>(prop);for(long i=nitems - 1; i>=0; i--){Window w = data[i];Atom wm_window_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE",False);Atom desktop_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DESKTOP",False);Atom ddk_dock_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False);Atom actual_type2;int actual_format2;unsigned long nitems2, bytes_after2;Atom *type_data = nullptr;if(XGetWindowProperty(display,w,wm_window_type,0,32,False,XA_ATOM,&actual_type2,&actual_format2,&nitems2,&bytes_after2,reinterpret_cast<unsigned char**>(&type_data)) == Success){bool is_desktop = false;for(unsigned long j = 0; j < nitems2; j++){if(type_data[j] == desktop_type || type_data[j] == ddk_dock_type){is_desktop = true;break;}}XFree(type_data);if(!is_desktop){windows.emplace_back(w);}}}XFree(prop);}}return windows;
}std::string get_event_button(int btn_no){const char* btn_name = "未知";switch (btn_no) {case 1: btn_name = "左键"; break;case 2: btn_name = "中键"; break;case 3: btn_name = "右键"; break;case 4: btn_name = "滚轮上"; break;case 5: btn_name = "滚轮下"; break;}return btn_name;
}
// ==================== 队列线程处理鼠标点击 ====================
void click_processor_thread() {while (!should_exit.load()) {ClickEvent ev;{std::lock_guard<std::mutex> lock(queue_mutex);if (click_queue.empty()) {std::this_thread::sleep_for(std::chrono::milliseconds(5));continue;}ev = click_queue.front();click_queue.pop();}printf("处理鼠标事件开始,队列还有事件个数 %ld 个\n",click_queue.size());fflush(stdout);scoped_timer s_timer("获取鼠标事件,处理时间 ");Window target_window = 0;if((target_window = find_window_at(global_display_event, ev.root_x, ev.root_y))!=0){printf("\n=== 鼠标点击事件 ===\n");printf("点击了窗口中 0x%lx\n",target_window);std::string title = get_window_title(global_display_event, target_window);printf("窗口标题:%s\n",title.c_str());pid_t pid = get_window_pid(global_display_event, target_window);std::string proc_name = get_proc_name(pid);printf("窗口关联进程id: %d, 进程名:%s\n", pid, proc_name.c_str());fflush(stdout);}}
}// ==================== XRecord 回调 ====================
extern "C" void event_callback(XPointer closure, XRecordInterceptData *data) {if (data->category != XRecordFromServer) {XRecordFreeData(data);return;}static int click_count = 0;const unsigned char* d = data->data;xEvent *xevent = reinterpret_cast<xEvent*>(data->data);if (data->data_len < 2) {XRecordFreeData(data);return;}if (xevent->u.u.type == ButtonPress && xevent->u.u.detail == 1) { // ButtonPress 只捕获左键按下printf("鼠标 [%s] 点击了...%d\n",get_event_button(xevent->u.u.detail).c_str(), click_count++);//回调函数获取坐标short x = xevent->u.keyButtonPointer.rootX;short y = xevent->u.keyButtonPointer.rootY;printf("获取鼠标点击坐标(%d, %d)...\n",x, y);std::lock_guard<std::mutex> lock(queue_mutex);ClickEvent event = {.button = xevent->u.u.type,.root_x = x,.root_y = y};click_queue.push({event});fflush(stdout);}XRecordFreeData(data);
}void print_window_id(Window wid){Window root, parent;Window *children;unsigned int nchildren;int status = XQueryTree(global_display, wid, &root, &parent, &children, &nchildren);if(status){printf("根窗口: 0x%lx\n", root);printf("父窗口: 0x%lx\n", parent);for (unsigned int i=0; i<nchildren; i++){printf("子窗口 %d: 0x%lx\n", i, children[i]);char * window_name;int ret = XFetchName(global_display, children[i], &window_name);if(ret){printf("子窗口 %d, 标题: %s: 0x%lx\n", i, window_name, children[i]);}}fflush(stdout);XFree(children);}else{fprintf(stderr, "查询窗口树失败\n");}return ;
}
// ==================== main ====================
int main() {Display *display = XOpenDisplay(NULL);if (!display) {std::cerr << "❌ 无法打开 X11 显示" << std::endl;return 1;}global_display = display;global_display_event = XOpenDisplay(NULL);if (!global_display_event) {std::cerr << "❌ 无法打开 X11 global_display 显示" << std::endl;return 1;}int major, minor;if (!XRecordQueryVersion(display, &major, &minor)) {std::cerr << "❌ XRecord 扩展不可用" << std::endl;XCloseDisplay(display);return 1;}XRecordRange *range = XRecordAllocRange();if (!range) {std::cerr << "❌ 无法分配 XRecordRange" << std::endl;XCloseDisplay(display);return 1;}memset(range, 0, sizeof(XRecordRange));//只记录鼠标按下的事件range->device_events.first = ButtonPress; // 4 ButtonPressrange->device_events.last  = ButtonPress; // 5 ButtonReleaseXRecordClientSpec client = XRecordAllClients;XRecordContext context = XRecordCreateContext(display, 0, &client, 1, &range, 1);if (!context) {std::cerr << "❌ 无法创建 XRecord 上下文" << std::endl;XFree(range);XCloseDisplay(display);return 1;}XFree(range);XSync(display, False);std::cout << "🖱️ 鼠标点击监听器已启动..." << std::endl;std::cout << "点击任意窗口获取信息,按 Ctrl+C 退出" << std::endl;// 启动线程处理队列std::thread processor(click_processor_thread);global_windows = get_all_windows(display);for(int i=0; i<global_windows.size(); i++){printf("窗口%d,id:0x%lx\n", i, global_windows[i]);}fflush(stdout);// 阻塞监听XRecordEnableContext(display, context, event_callback, NULL);// 程序退出should_exit.store(true);processor.join();XRecordFreeContext(display, context);XCloseDisplay(display);return 0;
}

编译命令

g++ -o ui_ctl ui_display.cpp  -lX11 -lXtst -lpthread

运行结果

在这里插入图片描述

http://www.dtcms.com/a/519615.html

相关文章:

  • 021数据结构之并查集——算法备赛
  • 网站制作售后免费在线代理网站
  • Vue组件的一些底层细节
  • 2. =>的用法 C#例子 WPF例子
  • 在C#中出现WinForm原控件Chart卡顿问题
  • Spring Boot 3零基础教程,WEB 开发 内嵌服务器底层源码分析 笔记48
  • 网站开发案例分析成都制作网页
  • 导入的 Google(Chrome)书签默认不会自动显示在「书签栏」,而是放在一个文件夹里。下面是详细步骤,帮你把 导入的全部书签添加到书签栏
  • 一小时内使用NVIDIA Nemotron创建你自己的Bash计算机使用智能体
  • Chrome开发者工具
  • 虚拟机 Ubuntu 中安装 Google Chrome 浏览器
  • Docker/K8s部署MySQL的创新实践与优化技巧大纲
  • 网站建设管理流程避免网站侵权
  • 如何在Visual Studio中配置C++环境?
  • 珠海翻译公司高效翻译服务 2025年10月
  • 网站后台管理系统怎么登陆鄂州网站建设与设计
  • 建设系统网站企业密信下载app下载官网
  • 算法面经常考题整理(1)机器学习
  • 使用java如何进行接口测试
  • 机器学习-方差与偏差
  • 甘肃省网站建设咨询seo最好的网站源码
  • 3.序列式容器-heap
  • Module JDK is not defined 警告解决
  • 柞水县住房和城乡建设局网站网站建设客户分析调查表文档
  • html`contenteditable`
  • 【语音识别】语音识别的发展历程
  • 【C++ 类与对象 (下)】:进阶特性与编译器优化的深度实战
  • 加速智能体开发:从 Serverless 运行时到 Serverless AI 运行时
  • 怎么在服务器建立网站wordpress getcategorylink
  • uniapp textarea标签 在ios真机上出现高度拉长问题