linux下获取鼠标点击应用的窗口信息,进程信息
查询服务器类型:
shell
echo $XDG_SESSION_TYPE
一般有x11和Wayland
查询桌面类型:
shell
echo $XDG_CURRENT_DESKTOP
一般有GNome和KDE,国产有deepin,方案对比
| 对比项 | X11 方案(tdisplay 用的) | AT-SPI 方案(你要做的) |
|---|---|---|
| 实现层级 | 底层窗口管理系统 | 高层 GUI 无障碍接口 |
| 获取对象 | 只能看到窗口、进程、类名 | 可以看到每个控件、文本内容 |
| 精确度 | 只能识别窗口 | 可识别按钮、文本框、菜单项等 |
| 依赖库 | libX11, libXtst, libXi | libatspi, libatk-bridge, D-Bus |
| 性能 | 快、无需 Accessibility | 稍慢、依赖 D-Bus 服务 |
| 使用场景 | 审计系统、录屏、操作跟踪 | 屏幕阅读器、自动化测试 |
两个查询窗口id信息的工具命令
shell
xwininfo 捕获窗口,类似spi++
xprop -id wid 查询指定窗口id的信息
接下来介绍X11的方案实现:
shell
[鼠标点击事件]
│
▼
X Server 生成事件
│
│
└─ 我们使用 XRecord 捕获
│ (得到 root_x, root_y)
▼
坐标定位目标窗口
│
▼
获取鼠标点击的顶层窗口,判断root_x, root_y是否在范围内
│
▼
读取 _NET_WM_PID / _NET_WM_NAME
│
▼
获取应用程序名称 & 窗口属性
代码实现
c++
#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; //鼠标点击坐标x
short root_y; //鼠标点击坐标y
Window wid; //窗口id
std::string window_title; //窗口
pid_t pid;//进程id
std::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 ButtonPress
range->device_events.last = ButtonPress; // 5 ButtonRelease
XRecordClientSpec 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;
}
编译命令
shell
g++ -o ui_ctl ui_display.cpp -lX11 -lXtst -lpthread
运行结果
