3

After creating a display, I make a window with XCreateWindow. Then, as described here, I call XMapWindow and then immediately XUnmapWindow; this lets the X server know about the window so that commands (e.g. XMoveWindow) don't silently fail.

The window is invisible at this point, as it should be. I can stop execution with e.g. getchar. Definitely invisible.

Then I call glXCreateContext, and the window appears, just as if I had called XMapWindow again! Sorcery! I have stopped execution immediately before and immediately after, so I know it's glXCreateContext.


This makes no sense. I skimmed the documentation, but there's really no way this could possibly happen. Any guesses?


EDIT: Here's a simple example:

//Compile with "g++ <filename>.cpp -std=c++11 -lX11 -lGL"

#include <cassert>
#include <cstdio>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include <GL/glx.h>


static Display* display;


static void _x11_map_window(Window window) {
    printf("Mapping window %lu; RETURN to continue\n",window); getchar();
    XMapWindow(display, window);
    printf("Mapped window!  RETURN to continue\n"); getchar();
}
static void _x11_unmap_window(Window window) {
    printf("Unmapping window %lu; RETURN to continue\n",window); getchar();
    XUnmapWindow(display, window);
    printf("Unmapped window!  RETURN to continue\n"); getchar();
}


int main(int argc, char* argv[]) {
    /* ##### MAKE DISPLAY ##### */
    display = XOpenDisplay(nullptr);


    /* ##### MAKE VISUAL INFO. ##### */
    int attributes[] = { //can't be const b/c X11 doesn't like it.  Not sure if that's intentional or just stupid.
        GLX_RGBA, //apparently nothing comes after this?
        GLX_RED_SIZE,    8,
        GLX_GREEN_SIZE,  8,
        GLX_BLUE_SIZE,   8,
        GLX_ALPHA_SIZE,  8,
        //Ideally, the size would be 32 (or at least 24), but I have actually seen
        //  this size (on a modern OS even).
        GLX_DEPTH_SIZE, 16,
        GLX_DOUBLEBUFFER, True,
        None
    };

    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wold-style-cast" //Because of X11's cruft in "DefaultScreen".
    XVisualInfo* visual_info = glXChooseVisual(display, DefaultScreen(display), attributes);
    #pragma GCC diagnostic pop
    assert(visual_info!=nullptr);


    /* ##### MAKE WINDOW ##### */
    Window parent = XDefaultRootWindow(display);

    Colormap colormap = XCreateColormap(display, parent, visual_info->visual, AllocNone);

    XSetWindowAttributes window_attributes_set;
    window_attributes_set.colormap = colormap;
    window_attributes_set.background_pixel = 0; //This and next b/c of http://stackoverflow.com/questions/3645632/how-to-create-a-window-with-a-bit-depth-of-32
    window_attributes_set.border_pixel = 0;     //especially resulting in BadMatch error on Raspberry Pi.  Also changes bit fields below in XCreateWindow.
    window_attributes_set.event_mask = ExposureMask | KeyPressMask;

    int position[2]={50,50}, dimensions[2]={128,128};
    Window window = XCreateWindow(
        display, parent,
        position[0],position[1], static_cast<unsigned int>(dimensions[0]),static_cast<unsigned int>(dimensions[1]), //Note: the documentation must be wrong; this thing wants unsigned ints.
        0u,
        visual_info->depth,
        InputOutput,
        visual_info->visual,
        //CWColormap|CWEventMask,
        CWBackPixel|CWColormap|CWBorderPixel | CWEventMask,
        &window_attributes_set
    );
    assert(window!=0);
    printf("Created window %lu\n",window);
    XStoreName(display, window, "[default title]");
    XSelectInput(display, window,
        //http://www.tronche.com/gui/x/xlib/events/mask.html#NoEventMask
        //http://www.tronche.com/gui/x/xlib/events/processing-overview.html
        ExposureMask |
        KeyPressMask | KeyReleaseMask |
        ButtonPressMask | ButtonReleaseMask | //ButtonMotionMask |
        //EnterWindowMask | LeaveWindowMask |
        PointerMotionMask |
        //KeymapStateMask | FocusChangeMask | ColormapChangeMask |
        StructureNotifyMask //Resizing, etc.
        //PropertyChangeMask
    );

    Atom wm_delete = XInternAtom(display, "WM_DELETE_WINDOW", True);
    XSetWMProtocols(display, window, &wm_delete, 1);

    XMoveWindow(display, window, 100,100);

    //As described here: http://stackoverflow.com/questions/14801536/xmovewindow-not-working-before-xmapwindow
    //  "the X server doesn't have to know about a window before it is mapped for the first time".  Hence,
    //  map the window and then unmap it so that the X server knows about it.  This is important because some
    //  functions silently fail (e.g. XMoveWindow) when the X server is oblivious.
    _x11_map_window(window);
    _x11_unmap_window(window);


    /* ##### MAKE RENDER CONTEXT ##### */
    GLXContext render_context = glXCreateContext(display, visual_info, nullptr, True);
    assert(render_context!=nullptr);


    /* ##### MORE STUFF WOULD GO HERE ##### */
    while (1);

    return 0;
}

Also demonstrates the failure of either XCreateWindow or XMoveWindow to set the window position before map/unmap.

genpfault
  • 51,148
  • 11
  • 85
  • 139
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • Try calling `XFlush` instead of `glXCreateContext`, does the window still appear? – n. m. could be an AI Feb 10 '15 at 12:25
  • @n.m. Yes, which confuses me since `XUnmapWindow` comes after `XMapWindow`? – geometrian Feb 10 '15 at 18:06
  • Well so `glXCreateContext` indeed has nothing to do with it. It just flushes the connection. Any other function that flushes the connection would do the same. You have `XMapWindow` somewhere which is not paired with `XUnmapWindow`. BTW it is totally unnecessary to map your window to move it etc. Things don't fail silently in X11. – n. m. could be an AI Feb 10 '15 at 19:56
  • @n.m. I am >99% sure there is no mismatched pairing. The functions occur in _exactly_ two places, which for testing are prefixed immediately before and after by a `printf`. I have examined this output (or, it's short enough that I can step through every API call manually). Re: silent failure, see the linked question? – geometrian Feb 10 '15 at 21:21
  • 1
    Linked post is totally wrong, the window is placed this way not because "the x server doesn't know about the window", but for entirely different reason. [See how the X server knows perfectly well about unmapped windows](http://pastebin.com/Tnb3Ws3u). – n. m. could be an AI Feb 10 '15 at 22:35
  • 1
    Well, I can debug only so much of invisible code. – n. m. could be an AI Feb 10 '15 at 22:38
  • @n.m. to address both, see edit. – geometrian Feb 11 '15 at 20:00
  • 1
    Addressed 1: There is no "failure of x11 to move unmapped windows because the server doesn't know about unmapped windows". The **window manager** is free to totally ignore requested window coordinates when mapping windows. An override-redirected window appears exactly when requested as I have demonstrated, because no WM is involved. To enforce coordinates, you need to talk to the WM and tell it your desired coordinates through WM hints (weird but true). – n. m. could be an AI Feb 11 '15 at 20:22
  • 1
    Addressed 2: the linked program normally does not show any window at all on my screen. If I insert XFlushWindow after mapping and unmapping, a window appears and then disappears. It does not reappear after calling the GL function. I have no idea what's going on here. Perhaps you can try an X11 protocol analyser like x11trace. How many XMapWindow request do you see in the dump? I only see one. – n. m. could be an AI Feb 11 '15 at 20:48
  • About setting position through WM hints: use XSetWMNormalHints with `hints.x = desired_x; hints.y = desired_y; hints.flags = USPosition;`. All available literature says these fields are obsolete. Nonetheless they work, and nothing else does. – n. m. could be an AI Feb 11 '15 at 21:01
  • @n.m. If you add an answer to that effect on the linked post, I'll change the accept. For this question, got [xtrace output](http://pastebin.com/9yXpm36t); appears to only have one map window. It looks like the expose events are generated after the unmap. Could this be the problem? Also, if I add `XFlush` among the map/unmap calls, the window appears and then disappears as I'd expect. – geometrian Feb 12 '15 at 18:25
  • It is actually a duplicate; I forgot I have already answered such questions no less than twice. [one](http://stackoverflow.com/questions/17114738/how-to-make-x11-window-span-multiple-monitors/17118962#17118962) [two](http://stackoverflow.com/questions/25391791/x11-configurenotify-always-returning-x-y-0-0/25392859#25392859) – n. m. could be an AI Feb 12 '15 at 19:22

1 Answers1

1

Investigating this has been difficult, but I've resolved it and the TL;DR is:

  1. This isn't actually about glXCreateContext(...). This is about an apparent timing bug in certain implementations of X.
  2. This issue was exposed by a workaround I had written on the basis of misinformation. The thing I wanted to do should instead be accomplished by means of a different workaround.

Description of Underlying Issues

When a window is created, the window manager wraps it in a new window, as long as the override redirect isn't set (attributes .override_redirect and flag CWOverrideRedirect on window creation). This is so that it can do things like add a frame and buttons.

Unfortunately, the window manager can (and does, at least before the window is mapped) use this as an excuse to ignore behavior such as XMoveWindow(...). This has led to the misconception that one should map and then unmap the window so that the X server "knows about it".

This exposes the apparent bug. On the system in question (stock Ubuntu in VirtualBox), mapping and then unmapping the window immediately after causes the window to remain mapped.

I tried many things, such as putting XFlush(...) or XSync(...) calls around the map/unmap calls (which also allowed me to show that glXCreateContext(...) is not at issue). However, what finally got it to work as-expected was to add sleeps. A delay of 0.1 seconds made the window appear and disappear. A delay of 0.01 seconds made the window remain mapped. This was rather frustrating to figure out (I had the aforementioned getchar()s and printf(...)s, and this introduced enough latency while debugging that the problem couldn't be reproduced).

The following (probably non-minimal) code works as-written, but removing the nanosleep(...) calls will cause the problem:

struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 200000000;

XFlush(_display);
nanosleep(&ts, nullptr);
XMapWindow(display, window);
XFlush(display);
nanosleep(&ts, nullptr);
XUnmapWindow(display, window);
XFlush(_display);

Presumably the delay allows catching up to the map/unmap events or something. I'm not sure this is a full-fledged bug, but it's certainly a usability defect. (If this gives you enough information to explain what's going on here, feel free to edit this answer.)

However, as-mentioned, this workaround is based on a misconception! The X server already knows about the new window. It's just flagrantly ignoring you. To fix this, we can hint to the windowing system that it shouldn't be so rude. Since this doesn't rely on map/unmap, the erroneous behavior doesn't happen anymore.

geometrian
  • 14,775
  • 10
  • 56
  • 132
  • To me, this appears to be another case of ignoring [ICCCM 4.2.2](https://tronche.com/gui/x/icccm/sec-4.html#s-4.2.2). You simply cannot assume that after `XMapWindow` returns, the window is mapped. You are racing against the window manager here, and adding sleeps is a very bad cure for race conditions.. The correct solution is to properly wait for the `VisibilityNotify` event which tells you when the window is actually mapped. – derhass May 30 '18 at 23:19
  • @derhass Indeed, even if I hadn't succeeded in finding the correct workaround, adding sleeps as a workaround on top of this workaround would have been unacceptable. I don't see why waiting for `VisibilityNotify` should be necessary though? I.e., why can't I issue unmap before the map has finished clearing? – geometrian May 31 '18 at 00:58
  • 1
    The description for [`XUnmapWindow()`](https://tronche.com/gui/x/xlib/window/XUnmapWindow.html) states "If the specified window is already unmapped, `XUnmapWindow()` has no effect." If the mapping is deferred by the WM, the X Server currently sees the window as unmapped window, and ignores the request. – derhass May 31 '18 at 08:10
  • @imallett hi, sorry for necroposting, but I face with the same problem. From calling `XMapWindow(...)` until calling `glXCreateContext(...)` I can call `XMove/ResizeWindow()` and it has effect. But after `glXCreateContext` I can't use this functions, only if I call `XGetWindowProperty()`. How can I struggle this trouble? What causes such strange behavior? Thank you in advance! –  Sep 25 '18 at 17:01
  • @imallett solution based on setting x and y in wmnormalhints, should work, but x and y options now is obsolete. –  Sep 26 '18 at 08:36
  • For me next works: after `XMoveWindow` or `XResizeWindow` I past `XFlush`. –  Sep 27 '18 at 18:19