How to wait until window is mapped and viewable
Asked Answered
D

2

7

What is the proper way to wait until an X11 window is mapped and viewable? Precisely, I want to wait until I can safely call XSetInputFocus() without running into any risks of the X server backfiring with the following error:

// X Error of failed request:  BadMatch (invalid parameter attributes)
// Major opcode of failed request:  42 (X_SetInputFocus)    

Currently this error happens quite often, especially on slow X servers or when trying to open a new window right after having changed the monitor resolution using libXrandr.

I already have a solution for this problem but it is pretty hacky because it polls the window attribute so I'd like to know whether or not there is a cleaner version.

Here is my current approach:

static Bool predicate(Display *display, XEvent *ev, XPointer arg)
{
    return(ev->type == MapNotify);
}

static void waitmapnotify(struct osdisplayinfo *osd)
{
    XEvent ev;
    XWindowAttributes xwa;

    XPeekIfEvent(osd->display, &ev, predicate, NULL);

    do {
        XGetWindowAttributes(osd->display, osd->window, &xwa);
        usleep(1);
    } while(xwa.map_state != IsViewable);   
}

This code works fine but it is hacky so I'm putting it up for debate here - just in case there is a cleaner way of doing this.

Dingman answered 10/6, 2014 at 18:54 Comment(0)
A
2

Select SubstructureNotifyMask on the root window. You should get an event each time a top-level window is mapped, unmapped, moved, raised, resized etc. These are the events that potentially change visibility of top-level windows. This program prints a message whenever such an event happens:

#include <X11/Xlib.h>
#include <stdio.h>
int main ()
{
  Display* d = XOpenDisplay(0);
  int cnt = 0;
  XEvent ev;    
  XSelectInput (d, RootWindow(d, DefaultScreen(d)), SubstructureNotifyMask);    
  while (1)
  {
    XNextEvent(d, &ev);
    printf ("Got an event %d!\n", cnt++);
    // <----- do your XGetWindowAttributes(...) check here
  }
}

Note that you may not get events about your own windows getting mapped. This is because the WM is likely to reparent top-level windows to be children not of the root, but of intermediate decoration windows.

There are two ways to cope with the situation:

  1. Check if your window parent, the parent of the parent, ... etc is the mapped window of the event.
  2. Add XSelectInput (d, yourwindow, StructureNotifyMask); to the mix.

Note the first select has SubstructureNotifyMask and the second one StructureNotifyMask, a different mask.

Adrianaadriane answered 11/6, 2014 at 11:55 Comment(10)
Thanks, but this doesn't work for me. I've tried it and it just sits there in an endless loop. Besides, even if it worked, I'd be wondering what would happen if the state changed to IsViewable right before the call to XNextEvent()? Then XNextEvent() would sit and wait until some other window got mapped, unmapped, moved, raised, resized, etc, wouldn't it? So I'd probably run into my app getting blocked.Dingman
You might have a WM with a virtual root. I don't have much experience with them. You need to find what the virtual root is and use that.Adrianaadriane
IsViewable changes not by itself, but because (and thus after) something gets changed in the root substructure.Adrianaadriane
If you have X implemented ob top of some other window system (Mac/Windows) this approach will not work.Adrianaadriane
It's just a standard Mint installation (Maya). I'll try it again in a small test program and see how it behaves in there. But isn't your solution possibly dangerous considering that IsViewable could be set by the X server right before XNextEvent() is called, thus blocking my app until another event arrives (which might take some time).Dingman
Um, it's not a problem if IsViewable is set right before the event. That's in fact the desired sequence. Setting it after the event would be problematic.Adrianaadriane
I've played a little bit more with this now and it definitely doesn't work. The problem with this code is that XNextEvent() will sit and wait and block my window from becoming mapped, e.g. using the code above directly after calling XMapRaised() leads to XNextEvent() blocking until a window event from a different X11 program occurs. i.e. my program won't continue until I activate another window or start another program. This is slightly confusing because one would expect that mapping would occur asynchronously some time after calling XMapRaised() but that doesn't seem to be the case...Dingman
...using the code above after XMapRaised() really blocks my program and program flow only continues after a different program has caused some window event. Then the XNextEvent() call will be woken up and my program will continue. My own call to XMapRaised() won't wake up XNextEvent()!Dingman
Thanks, but it still doesn't work. The second solution does not lead to any changes. The program is still stuck in XNextEvent() and won't wake up from its sleep until a window event from a different program comes in. Concerning your first suggestion ("Check if your window parent, the parent of the parent, ... etc is the mapped window of the event"): I don't understand how this could be of any use because XNextEvent() never returns anyway so how should I check an event that I never get?Dingman
Works for me, don't know what could be the problem.Adrianaadriane
G
0

As far as I know X11 lib does not expose any callback mechanics for the X11 Event handling. (you can easily build your own once you understand the event filtering model)

You might want to loop on the X11 event queue as I guest this should be more efficient being designed for that purpose. Moreover you can configure the events filter, so that you get only the events that are of interest for your specific window.

A useful (though dated) link might be: Linux Journal X11 Tutorial Check page two for an example on installing filters and getting events from the X11 queue.

Gorgeous answered 10/6, 2014 at 22:39 Comment(3)
The problem is that AFAICS there is no event that is sent to signal that the window is now viewable. There is the "MapNotify" and I'm already waiting on this (see above) but it is not enough. I also need to wait for "IsViewable" to be set in the attributes but AFAICS there is no event which signals when this attribute is set/changes.Dingman
When I was "refreshing" my memory on Xlib I found the following event: x.org/releases/X11R7.7/doc/libX11/libX11/… that, AFAIA, should be triggered by the window visibility state change. Does it make sense for your scenario?Gorgeous
I tried VisibilityNotify but unfortunately it is only sent when showing a window again that has previously been minimized. VisibilityNotify is not sent when a window is created and first shown.Dingman

© 2022 - 2024 — McMap. All rights reserved.