【问题标题】:XReparentWindow works sporadicallyXReparentWindow 偶尔工作
【发布时间】:2021-10-05 07:58:44
【问题描述】:

我正在试验 XReparentWindow,最终目标是将多个进程的窗口聚合到一个“驾驶舱”模拟进程中。 XReparentWindow 的实验偶尔会起作用;有时窗口会成功重新设置父级,有时则不会。当重新设置父对象失败时,(not) 抓取的窗口闪烁一秒钟,然后照常进行,抓取器显示未定义的窗口内容。每隔一段时间就成功一次(总是尝试两次以暴力解决问题)。

编辑 1:在 XReparentWindow 之后检查 XQueryTree 的输出显示抓取的窗口已正确重新设置父级,但似乎将其帧原点保持在从显示屏上抓取的位置,而不是移动到抓取器窗口。

抓取的窗口来自实时 OpenGL 渲染应用程序,从源代码编译。应用程序不会以任何方式预测抓取(也许应该?)。我也尝试过使用glxgears 和 GNOME 终端,结果相同。

实验代码,将窗口抓取作为程序参数(例如使用xwininfo | grep "Window id"):

#include <X11/Xlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h> // usleep

int main(int argc, char** argv) {
  assert(argc==2);
  Window window, extwin;
  sscanf(argv[1], "%p", &extwin);
  Display* display = XOpenDisplay(0);
  window = XCreateWindow(display, RootWindow(display, 0), 0, 0, 500, 500, 0, DefaultDepth(display, 0), InputOutput, DefaultVisual(display, 0), 0, 0);
  XMapWindow(display, window);
  XReparentWindow(display, extwin, window, 0, 0);
  while(1) {
    XFlush(display);
    usleep(3e5);
  }
  return 0;
}

(代码是从受限环境中手动导出的。对于导出过程中的任何拼写错误,我们深表歉意。)

期待有关下一步尝试的建议。

编辑 2:使用 xev 捕获抓取窗口的事件流后,我注意到一些奇怪的事情;在重新设置为抓取器窗口后,它会在不到一秒后重新设置为根窗口(受限环境,键入在其他窗口上看到的具有预期意义的内容):

UnmapNotify event ...
ReparentNotify event ... parent 0x4000001 (grabber window)
MapNotify event ...
ConfigureNotify event ... synthetic YES (what is this?)
UnmapNotify event ...
ReparentNotify event ... parent 0xed (reparenting back to parent window, but why?)
MapNotify event ...
VisibilityNotify event ...
Expose event ...
PropertyNotify event ... _NET_WM_DESKTOP state PropertyDelete
PropertyNotify event ... _NET_WM_STATE state PropertyDelete
PropertyNotify event ... WM_STATE state PropertyNewValue

我退出程序并再次尝试第二次,此时继续的输出是:

UnmapNotify event ...
ReparentNotify event ... parent 0x4000001 (grabber window)
MapNotify event ...
VisibilityNotify event ...
Expose event ...

发生了什么事?

【问题讨论】:

    标签: x11 gnome xlib


    【解决方案1】:

    暴力破解,反复抓取窗口:

    #include <X11/Xlib.h>
    #include <stdio.h>
    #include <assert.h>
    #include <unistd.h> // usleep
    
    int main(int argc, char** argv) {
      assert(argc==2);
      Window window, extwin;
      sscanf(argv[1], "%p", &extwin);
      Display* display = XOpenDisplay(0);
      window = XCreateWindow(display, RootWindow(display, 0), 0, 0, 500, 500, 0, DefaultDepth(display, 0), InputOutput, DefaultVisual(display, 0), 0, 0);
      XMapWindow(display, window);
      while(1) {
        Window root, parent, *ch;
        unsigned int nch;
        XQueryTree(display, extwin, &root, &parent, &ch, &nch);
        if(parent!=window) {
          XReparentWindow(display, extwin, window, 0, 0);
        }
        if(nch>0) { XFree(ch); }
        XFlush(display);
        usleep(3e5);
      }
      return 0;
    }
    

    假设只有在两次调用 reparent 后可以禁用子句时才会发生这种情况。在我的机器上工作。希望能全面解释到底发生了什么。

    【讨论】:

      【解决方案2】:

      我是 GUI 领域的新手,我不了解 X11 的内部结构。但我刚刚阅读了一个非常有趣的文档 (https://www.x.org/releases/current/doc/libX11/libX11/libX11.html)

      Xlib 中的大多数函数只是将请求添加到输出缓冲区。 这些请求稍后在 X 服务器上异步执行。返回存储在服务器中的信息值的函数在收到显式回复或发生错误之前不会返回(也就是说,它们会阻塞)。您可以提供一个错误处理程序,在报告错误时将调用该处理程序。

      如果客户端不希望请求异步执行,它可以在请求之后调用XSync,这会阻塞直到所有先前缓冲的异步事件都已发送并执行。作为一个重要的副作用,Xlib 中的输出缓冲区总是通过调用从服务器返回值或等待输入的任何函数来刷新。

      所以我猜你所拥有的是重新养育和其他东西之间的竞争条件。

      这对我有用:

      // gcc -lX11 -lXcomposite a.c && ./a.out 0x1a00001
      // IDs can be gotten from
      // `wmctrl -l` (shows only the parent windows) or `xwininfo`.
      #include <stdio.h>
      #include <stdlib.h>
      #include <stdint.h>
      #include <stdbool.h>
      #include <unistd.h>
      #include <X11/Xlib.h> // -lX11
      #include <X11/Xutil.h>
      #include <X11/Xatom.h>
      #include <X11/extensions/Xcomposite.h> // optional, -lXcomposite
      
      void reparent (Display *d, Window child, Window new_parent)
      {
          XUnmapWindow(d, child);
          XMapWindow(d, new_parent);
          XSync(d, False);
          XReparentWindow(d, child, new_parent, 0, 0);
          XMapWindow(d, child);
          // 1 ms seems to be enough even during `nice -n -19 stress -c $cpuThreadsCount` (pacman -S stress) on linux-tkg-pds.
          // Probably can be decreased even further.
          usleep(1e3);
          XSync(d, False);
      }
      
      int main (int argc, char **argv)
      {
          Display *d = XOpenDisplay(XDisplayName(NULL));
          int s = DefaultScreen(d);
          Window root = RootWindow(d, s);
          if (argc != 2)
          {
              printf("Wrong number of arguments, exiting.");
              exit(1);
          }
      
          Window child = strtol(argv[1], NULL, 0);
          Window new_parent = XCreateSimpleWindow(
              d, root, 0, 0, 500, 500, 0, 0, 0
          );
      
          // (Optional)
          // Allow grabbing by `ffmpeg -f x11grab -window_id`
          // while being on the same virtual screen
          // AND (focused or unfocused)
          // AND (seen or unseen)
          // AND no other window is both fullscreen and focused.
          XCompositeRedirectWindow(d, child, CompositeRedirectAutomatic);
      
          // After `new_parent` is destroyed (when the program exists),
          // don't make `child` unmapped/invisible.
          XAddToSaveSet(d, child);
      
          reparent(d, child, new_parent);
      
          XEvent e;
          while (1)
          {
              XNextEvent(d, &e);
          }
      
          return 0;
      }
      

      也可以使用xdotool:

      xdotool windowunmap $CHID
      xdotool windowreparent $CHID $NEWPID
      xdotool windowmap --sync $CHID
      

      【讨论】:

      • 我没有机器可以测试这个,但它确实有意义。我认为您在提出此解决方案之前在您的机器上遇到了同样的原始问题?
      • 是的,XReparentWindow 只是将我的窗口重新设置为根窗口,而不是我想要的窗口。另外,我添加了两行代码(usleep 和 XSync):由于某种原因,在我重新启动之前它在没有它们的情况下也可以工作,但现在我必须拥有它们。
      【解决方案3】:

      我从未尝试过使用 OpenGL 应用程序并且这里没有环境。 也许首先尝试使用一个简单的 X 应用程序(如 xclock)并观察您是否得到相同的行为。 如果是,那是你的代码,如果不是,可能是 OpenGL 交互。

      从您的代码片段中,有两个 cmets:

      while 循环中,您应该使用 X 事件

      XEvent e;    
      while(1) {
           XNextEvent(d, &e);
      }
      

      那么XAddToSaveSet函数就不能正常工作了。 您需要使用 XFixes 才能在发生崩溃时正确恢复该窗口。

      #include <X11/extensions/Xfixes.h>
      
      ...
      
      // The Xorg API is buggy in certain areas.
      // Need to use the XFixes extensions to address them
      // Initializes these extensions
      int event_base_return = 0;
      int error_base_return = 0;
      Bool result = XFixesQueryExtension(display, &event_base_return);
      printf("XFixesQueryExtension result: %d. eventbase: %d - errorbase: %d\n", result, event_base_return, error_base_return);
      
      // We actually only need version 1.0. But if 4.0 is not there then something is really wrong
      int major = 4;
      int minor = 0;
      result = XFixesQueryVersion(display, &major, &minor);
      printf("XFixesQueryVersion result: %d - version: %d.%d\n", result, major, minor);
      
      ...
      XReparentWindow(display, childWindowId, parentWindowId, 0, 0);
      XFixesChangeSaveSet(display, childWindowId, SetModeInsert, SaveSetRoot, SaveSetUnmap);
      ...
      

      【讨论】:

      • XNextEvent 似乎没有任何效果,除了捕获我使用 Ctrl+C 杀死应用程序的尝试。 XAddToSaveSet 似乎工作正常,也许我的 xlib 过得愉快,但无论如何将其全部删除不会影响重新设置问题。从问题中删除 XAddToSaveSet 以使其更简单。
      • 我的错,ctrl+c 的捕获来自于抓取 GNOME 终端而不是 glxgears。永远不要同时测试两件事......
      • 如果您正在运行 GNOME,这意味着您正在运行一个窗口管理器。也许它是试图让窗口恢复的那个(你的第二个 ReparentNotify 事件......)。不知道如何告诉 GNOME 让你一个人呆着......
      猜你喜欢
      • 2016-04-08
      • 1970-01-01
      • 2014-01-08
      • 2020-11-10
      • 2016-01-21
      • 2013-07-19
      • 2013-05-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多