XReparentWindow works sporadically
Asked Answered
R

3

3

I'm experimenting with XReparentWindow with the end goal to aggregate windows of multiple processes into one "cockpit" simulating process. Experiments with XReparentWindow works sporadically; sometimes the window is reparented successfully, sometimes not. When unsuccessfully reparented the (not) grabbed window flickers for a second and then proceedes as usual, and the grabber show undefined window content. It is successfull every other time (tempted to brute-force the problem away by always trying two times).

Edit 1: Checking output of XQueryTree right after XReparentWindow shows the grabbed window is properly reparented, but would appear to keep its frame origin where grabbed from on display rather than being moved to the grabber window.

The grabbed window is from a real-time OpenGL rendering application, compiled from source. The application does not anticipate the grabbing in any way (maybe it should?). I have also tried grabbing glxgears and a GNOME Terminal, same result.

The experimental code, taking window to grab as program argument (e.g. using 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;
}

(Code is manually exported from a restricted environment. Sorry for any typos made during export.)

Looking forward for suggestions of what to try out next.

Edit 2: Having captured the event stream of the grabbed window using xev I notice something odd; after being reparented to the grabber window, it reparents itself back to root window after less than a second (restricted environment, typing what's seen on other window with anticipated significance):

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

I quit the program and try again a second time, at which the output that continues is:

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

What is going on?

Rachael answered 15/2, 2019 at 9:30 Comment(0)
Y
4

I am a newbie in the GUI world and I don't know X11 internals. But I've just read a very interesting documentation (https://www.x.org/releases/current/doc/libX11/libX11/libX11.html)

Most of the functions in Xlib just add requests to an output buffer. These requests later execute asynchronously on the X server. Functions that return values of information stored in the server do not return (that is, they block) until an explicit reply is received or an error occurs. You can provide an error handler, which will be called when the error is reported.

If a client does not want a request to execute asynchronously, it can follow the request with a call to XSync, which blocks until all previously buffered asynchronous events have been sent and acted on. As an important side effect, the output buffer in Xlib is always flushed by a call to any function that returns a value from the server or waits for input.

So I guess what you have is a race condition between reparenting and something.

This works for me:

// 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;
}

Also it's possible to use xdotool:

xdotool windowunmap $CHID
xdotool windowreparent $CHID $NEWPID
xdotool windowmap --sync $CHID
Ylla answered 3/10, 2021 at 23:33 Comment(3)
I have no machine for testing this, but it does make sense. I take it you experienced the same original problem on your machine before coming up with this solution?Rachael
Yes, XReparentWindow just reparented my window to the root window instead of the window I wanted. Also, I've added two lines of code (usleep and XSync): for some reason it had worked without them before I rebooted, but now I have to have them.Ylla
Why do you set XSync(d, False) twice?Sarcastic
R
1

Brute force solution, grabbing the window repeatedly:

#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;
}

Assuming this only happens once the clause can be disabled after two calls to reparent. Works on my machine. Would appreciate full explaination of what is really going on.

Rachael answered 19/2, 2019 at 14:8 Comment(0)
N
0

I have never tried with an OpenGL application and do not have the environment here. Maybe try first with a simple X app (like xclock) and observe if you get the same behaviour. If yes, that's your code, if no, probably OpenGL interaction.

From your snippset, two comments though:

In the while loop, you should consume the X events

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

Then the XAddToSaveSet function does not work properly. You will need to use the XFixes in order to properly restore that window in case of a crash.

#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);
...
Nerva answered 19/2, 2019 at 0:1 Comment(3)
XNextEvent would seem to have no effect, other than capturing my attempts to kill the application using Ctrl+C. XAddToSaveSet seems to work fine, maybe my xlib is having a good day, but in any case removing it all together does not affect the reparenting problem. Removing XAddToSaveSet from the question to keep it simpler.Rachael
My bad, the capturing of ctrl+c came from grabbing a GNOME Terminal instead of glxgears. Should never test two things at the same time...Rachael
If you are running GNOME that means you have a window manager running. Maybe it is the one trying to get the window back (your second ReparentNotify event ... ). Not sure how to tell GNOME to leave you alone...Nerva

© 2022 - 2024 — McMap. All rights reserved.