Reputation: 5301
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?
Upvotes: 3
Views: 1425
Reputation: 517
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
Upvotes: 4
Reputation: 5301
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.
Upvotes: 1
Reputation: 73
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);
...
Upvotes: 0