Qt5 fork/execve创建子进程时如何获取窗口ID供fromWinId使用
结论
无法直接通过fork()或execv()获取子进程的窗口ID。
这两个系统调用仅负责进程创建、程序执行镜像替换的逻辑,完全不涉及X11窗口系统的资源交互。子进程的窗口是execv()加载的目标程序在自身UI初始化阶段,主动向X Server申请创建的资源,和fork/exec调用本身没有绑定关系,不可能从这两个调用的返回值中直接拿到窗口ID。
你枚举窗口列表为空,基本是两个原因导致的:
- 时序错误:fork/exec执行完成后立刻枚举窗口时,子进程还处于启动阶段,根本还没完成UI初始化、向X Server申请创建顶层窗口,此时枚举自然拿不到结果。
- 枚举逻辑缺陷:常规的窗口枚举如果没有给根窗口注册
SubstructureNotifyMask权限,默认只能遍历当前进程自身创建的窗口,无法感知其他进程新建的窗口;部分枚举实现没有递归遍历所有未映射的窗口节点,也会漏过刚创建的窗口。
可靠实现方案
不要用轮询枚举窗口+sleep等待的方案,这种方式稳定性差,还容易导致子进程窗口先闪屏再被嵌入,用X11事件监听的方式最可靠:
- 启动子进程前,先打开当前会话的X11 Display连接,给根窗口注册
SubstructureNotifyMask事件监听,这样所有新创建的顶层窗口都会触发事件通知,不需要轮询。 - 执行fork+exec启动子进程,记录好返回的子进程PID。
- 循环读取X11事件,捕获到
CreateNotify事件时,读取事件对应窗口的_NET_WM_PID属性,和你记录的子进程PID做比对,匹配成功的就是目标子进程创建的窗口。 - 继续监听事件,直到该目标窗口收到
MapNotify事件(即窗口完成映射、准备显示),此时再执行嵌入操作,否则会出现嵌入失败、窗口空白的问题。 - 按标准流程完成嵌入:
- 调用
QWindow::fromWinId(目标窗口ID)将原生X11窗口转换为Qt可识别的QWindow对象 - 调用
QWidget::createWindowContainer()将QWindow包装为可纳入Qt布局的QWidget控件 - 将容器控件添加到你的父窗口布局中即可。
- 调用
注意:如果嵌入的目标程序是Qt、GTK等UI框架开发的,启动阶段可能会创建多个临时隐藏窗口,除了PID匹配外,可以额外校验窗口的
WM_CLASS属性,避免拿到错误的临时窗口ID。
核心逻辑参考代码:
#include <X11/Xlib.h> #include <X11/Xatom.h> #include <unistd.h> #include <QWindow> #include <QWidget> // 父进程嵌入逻辑片段 pid_t child_pid = fork(); if (child_pid == 0) { // 子进程:可按需设置环境变量,避免子进程UI缩放、窗口类型配置影响嵌入效果 execl("./application.bin", "application.bin", NULL); _exit(1); } // 初始化X11事件监听 Display* x11_dpy = XOpenDisplay(NULL); Window root = DefaultRootWindow(x11_dpy); XSelectInput(x11_dpy, root, SubstructureNotifyMask); Window target_win = 0; XEvent ev; // 查找子进程对应的窗口 while (!target_win) { XNextEvent(x11_dpy, &ev); if (ev.type != CreateNotify) continue; Window candidate = ev.xcreatewindow.window; Atom pid_atom = XInternAtom(x11_dpy, "_NET_WM_PID", False); Atom ret_type; int ret_fmt; unsigned long nitem, ret_after; unsigned char* prop = nullptr; if (XGetWindowProperty(x11_dpy, candidate, pid_atom, 0, 1, False, XA_CARDINAL, &ret_type, &ret_fmt, &nitem, &ret_after, &prop) == Success) { if (prop && ret_fmt == 32 && nitem >=1) { pid_t win_pid = *reinterpret_cast<pid_t*>(prop); if (win_pid == child_pid) { target_win = candidate; } } XFree(prop); } } // 等待窗口完成映射 while (true) { XNextEvent(x11_dpy, &ev); if (ev.type == MapNotify && ev.xmap.window == target_win) { break; } } // 执行Qt嵌入 QWindow* embed_win = QWindow::fromWinId(reinterpret_cast<WId>(target_win)); QWidget* container = QWidget::createWindowContainer(embed_win); // 将container添加到你的父窗口布局中即可正常使用
补充说明
Qt5提供的createWindowContainer已经在X11平台做了完整的适配,不需要手动实现旧版Qt4 X11EmbedWidget对应的XEmbed协议交互,只要拿到正确的、完成映射的顶层窗口ID,就可以实现和旧版一致的嵌入效果。
内容的提问来源于stack exchange,提问作者Ank i zle
相关产品推荐
相关产品推荐

