Welcome to awesome bug tracking system.

FS#699 - hiding toplevel windows is not supported

Attached to Project: awesome
Opened by Andreas Kloeckner (inducer) - Tuesday, 15 December 2009, 03:12 GMT
Last edited by Uli Schlachter (psychon) - Friday, 08 January 2010, 23:20 GMT
Task Type Bug Report
Category Core
Status Closed
Assigned To Uli Schlachter (psychon)
Operating System All
Severity Low
Priority Normal
Reported Version 3.4.2
Due in Version Undecided
Due Date Undecided
Percent Complete 100%
Votes 0
Private No


I've attached a ten-line PyQt demo that tries to hide its main Window.

Expected result: no main window.

Actual result: a garbled carcass of a window that is immovable, doesn't redraw and can't be closed. (0.1 KiB)
This task depends upon

Closed by  Uli Schlachter (psychon)
Friday, 08 January 2010, 23:20 GMT
Reason for closing:  Fixed
Additional comments about closing:  commit 026247cf181c88a3331275ce7e40b30d1cbd2591
Author: Uli Schlachter <>
Date: Mon Dec 28 19:26:00 2009 +0100

Handle synthetic UnmapNotify events *correctly*

Second try:

Turns out I messed up with XCB_EVENT_SENT() and had a "!" too much. The old code
already tried to cope with this, but forgot to actually unmap the window which
it just set to withdrawn state.

This time I tested the patch *again* and now I found even less bugs than on my
last try.

P.S.: I suck.

Signed-off-by: Uli Schlachter <>
Signed-off-by: Julien Danjou <>
Comment by Uli Schlachter (psychon) - Tuesday, 15 December 2009, 20:06 GMT
No idea what QT is smoking, but I want some.

I started a new X server without any window manager (Xephyr to be exact) and ran this app under xtrace (had to install a shitload of python stuff for this :( ).
QT creates 6 (!!!!) windows (I skipped everything except CreateWindow, MapWindow, UnmapWindow and ConfigureWindow here):

000:<:0075: 40: Request(1): CreateWindow depth=0x00 window=0x00200001 parent=0x0000010b x=375 y=300 width=750 height=400 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0xff000000 border-pixel=0xff000000}
000:<:0076: 40: Request(1): CreateWindow depth=0x00 window=0x00200002 parent=0x0000010b x=0 y=0 width=1 height=1 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0x00000000 border-pixel=0x00000000}
000:<:007e: 20: Request(12): ConfigureWindow window=0x00200001 values={width=750 height=400}
000:<:0089: 16: Request(2): ChangeWindowAttributes window=0x00200001 value-list={bit-gravity=NorthWest(0x01)}
000:<:008a: 16: Request(2): ChangeWindowAttributes window=0x00200001 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:008d: 16: Request(2): ChangeWindowAttributes window=0x00200001 value-list={cursor=0x00200005}
000:<:008e: 16: Request(2): ChangeWindowAttributes window=0x00200001 value-list={background-pixel=0xff6a756e}
000:<:008f: 40: Request(1): CreateWindow depth=0x00 window=0x00200006 parent=0x00200001 x=0 y=0 width=100 height=30 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0xff000000 border-pixel=0xff000000}
000:<:0090: 16: Request(2): ChangeWindowAttributes window=0x00200006 value-list={bit-gravity=NorthWest(0x01)}
000:<:0091: 16: Request(2): ChangeWindowAttributes window=0x00200006 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:0092: 16: Request(2): ChangeWindowAttributes window=0x00200006 value-list={background-pixel=0xff6a756e}
000:<:0093: 40: Request(1): CreateWindow depth=0x00 window=0x00200007 parent=0x00200001 x=0 y=0 width=100 height=30 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0xff000000 border-pixel=0xff000000}
000:<:0094: 16: Request(2): ChangeWindowAttributes window=0x00200007 value-list={bit-gravity=NorthWest(0x01)}
000:<:0095: 16: Request(2): ChangeWindowAttributes window=0x00200007 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:0096: 16: Request(2): ChangeWindowAttributes window=0x00200007 value-list={background-pixel=0xff6a756e}
000:<:0097: 40: Request(1): CreateWindow depth=0x00 window=0x00200008 parent=0x00200001 x=0 y=0 width=100 height=30 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0xff000000 border-pixel=0xff000000}
000:<:0098: 16: Request(2): ChangeWindowAttributes window=0x00200008 value-list={bit-gravity=NorthWest(0x01)}
000:<:0099: 16: Request(2): ChangeWindowAttributes window=0x00200008 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:009a: 16: Request(2): ChangeWindowAttributes window=0x00200008 value-list={background-pixel=0xff6a756e}
000:<:009b: 40: Request(1): CreateWindow depth=0x00 window=0x00200009 parent=0x00200001 x=0 y=0 width=100 height=30 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0xff000000 border-pixel=0xff000000}
000:<:009c: 16: Request(2): ChangeWindowAttributes window=0x00200009 value-list={bit-gravity=NorthWest(0x01)}
000:<:009d: 16: Request(2): ChangeWindowAttributes window=0x00200009 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:009e: 16: Request(2): ChangeWindowAttributes window=0x00200009 value-list={background-pixel=0xff6a756e}
000:<:009f: 40: Request(1): CreateWindow depth=0x00 window=0x0020000a parent=0x00200001 x=0 y=0 width=100 height=30 border-width=0 class=CopyFromParent(0x0000) visual=CopyFromParent(0x00000000) value-list={background-pixel=0xff000000 border-pixel=0xff000000}
000:<:00a0: 16: Request(2): ChangeWindowAttributes window=0x0020000a value-list={bit-gravity=NorthWest(0x01)}
000:<:00a1: 16: Request(2): ChangeWindowAttributes window=0x0020000a value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00a2: 16: Request(2): ChangeWindowAttributes window=0x0020000a value-list={background-pixel=0xff6a756e}
000:<:00a3: 8: Request(10): UnmapWindow window=0x0020000a
000:<:00a4: 20: Request(12): ConfigureWindow window=0x0020000a values={width=100 height=11}
000:<:00a5: 16: Request(2): ChangeWindowAttributes window=0x0020000a value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,PointerMotion,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00a6: 16: Request(2): ChangeWindowAttributes window=0x0000010b value-list={event-mask=EnterWindow,LeaveWindow,KeymapState,PropertyChange}
000:>:00a6: Event ConfigureNotify(22) event=0x0020000a window=0x0020000a above-sibling=0x00200009 x=0 y=0 width=100 height=11 border-width=0 override-redirect=false(0x00)
000:<:00a7: 16: Request(2): ChangeWindowAttributes window=0x00200001 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,PointerMotion,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00a8: 16: Request(2): ChangeWindowAttributes window=0x00200006 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,PointerMotion,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00a9: 16: Request(2): ChangeWindowAttributes window=0x00200007 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,PointerMotion,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00aa: 16: Request(2): ChangeWindowAttributes window=0x00200008 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,PointerMotion,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00ab: 16: Request(2): ChangeWindowAttributes window=0x00200009 value-list={event-mask=KeyPress,KeyRelease,ButtonPress,ButtonRelease,EnterWindow,LeaveWindow,PointerMotion,ButtonMotion,KeymapState,Exposure,StructureNotify,FocusChange,PropertyChange}
000:<:00ac: 96: Request(18): ChangeProperty mode=Replace(0x00) window=0x00200001 property=0x28("WM_NORMAL_HINTS") type=0x29("WM_SIZE_HINTS") data=0x0000020a,0x00000000,0x00000000,0x000000c8,0x00000096,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000001;
000:<:00ad: 20: Request(12): ConfigureWindow window=0x00200001 values={width=200 height=150}
000:<:00ae: 28: Request(12): ConfigureWindow window=0x00200006 values={x=0 y=0 width=200 height=1}
000:<:00af: 28: Request(12): ConfigureWindow window=0x00200008 values={x=0 y=1 width=1 height=148}
000:<:00b0: 28: Request(12): ConfigureWindow window=0x00200009 values={x=200 y=1 width=1 height=148}
000:<:00b1: 28: Request(12): ConfigureWindow window=0x00200007 values={x=0 y=149 width=200 height=1}
000:<:00b3: 8: Request(8): MapWindow window=0x00200006
000:<:00b4: 8: Request(8): MapWindow window=0x00200007
000:<:00b5: 8: Request(8): MapWindow window=0x00200008
000:<:00b6: 8: Request(8): MapWindow window=0x00200009
000:<:00b9: 8: Request(8): MapWindow window=0x00200001
000:<:00ba: 8: Request(10): UnmapWindow window=0x00200001

(One window made invisible which never was visible in the first place, then 5 other windows made visible and one hidden again)
Comment by Uli Schlachter (psychon) - Tuesday, 15 December 2009, 20:36 GMT
I made a c++ version of this, bug happens here too.
   t.cpp (0.2 KiB)
Comment by anrxc (anrxc) - Thursday, 17 December 2009, 21:56 GMT
PyQT4 version attached below. I lost all input control in awesome, had to switch to the console to kill the window. Run in twm after that as requested by psychon and there window is movable, input is active, and window can be killed.
Comment by Andreas Kloeckner (inducer) - Thursday, 17 December 2009, 22:14 GMT
Btw, this is not just an academic exercise. This occurs in real life when using the program VisIt, see
Comment by Uli Schlachter (psychon) - Friday, 18 December 2009, 18:44 GMT
I wrote yet another test app. This one waits 10 sec, then shows the window, waits another 10 sec to hide it and after 10 more seconds the app exits.
Bug does not happen here. I feel like blaming Qt....
   t.cpp (0.3 KiB)
Comment by Andreas Kloeckner (inducer) - Friday, 18 December 2009, 19:18 GMT
I doubt it's Qt's fault. Even in your first trace, there's an UnmapWindow at the end, however the window clearly doesn't get unmapped.
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 13:23 GMT
I downloaded a vanilla dwm, compiled it and started it in Xephyr.
Both .cpp test apps show the exact same behavior in dwm as they do in awesome.

@inducer: Well, there are 2 UnmapWindow (technically only one) and 5 MapWindow. That leaves us with at least 3 windows still being mapped.
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 13:32 GMT
I just had an idea: Could this be a race condition?

- Qt's show() is called, it sends a MapWindow command.
- Immediately afterwards, hide() is called and a UnmapWindow is sent.
- The X server sees the MapWindow request, but because a WM is running, it sends a MapRequest to the WM and doesn't do anything else.
- The X server receives an UnmapWindow command, but since the window isn't mapped yet, this command does nothing.
- The WM gets the MapRequest and thus maps the window (MapWindow again).

Because the UnmapWindow was discarded, the window stays visible.

Thoughts? Does this sound realistic? Why doesn't this happen with other WMs? (Anybody can test *if* this happens with other WMs?)

This would also explain why my second .cpp file doesn't cause this bug to happen.
Comment by Julien Danjou (jd) - Tuesday, 22 December 2009, 13:37 GMT
Sounds realistic. We probably miss a couple of xcb_{un,}grab_server() in the manage/unmanage function.
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 13:49 GMT
I don't think we can do much about this. If my guess really is the case, by the time we receive the MapRequest, lots of things could already have happened. I'll try to find out why this doesnt happen with other WMs.
Suggestions for a lightweight WM to test with?
Comment by Julien Danjou (jd) - Tuesday, 22 December 2009, 13:54 GMT
evilwm, dwm, wmaker?
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 14:15 GMT
dwm: As mentioned above, bug happens here, too.

evilwm: Bug happens, too.
evilwm receives a MapRequest and then a generated UnmapNotify (wtf? what is Qt doing?). It reparents and maps Qt's window and then maps its own frame-window.

wmaker: bug *does* *not* happen. No idea why yet.
I'll investigate.
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 14:50 GMT
Hm, no idea what wmaker does differently. The Qt test app (client 004 in the attached trace) sends a MapWindow and a UnmapWindow at 1261491545.483 sequence 00bd and 1261491545.484 sequence 00c5.
For some reason that I don't understand, wmaker (client 000) then later unmaps the test window at 1261491545.502 sequence 3e05.

There are 859 lines of output between these two events. :(

*But* there is still a weird icon in wmaker's weird-icon-area (No idea what that's supposed to be, but there is one icon per open client (I had some xterm running)
   trace (184.7 KiB)
Comment by Andreas Kloeckner (inducer) - Tuesday, 22 December 2009, 15:09 GMT
Metacity and kwin are also both fine.
Comment by Julien Danjou (jd) - Tuesday, 22 December 2009, 15:23 GMT
Actually, I think taking a look on when wmaker does grab and ungrab the server and mimicking it may be enough to have a fix.
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 20:22 GMT
I looked at wmaker's source and I think I figured it out.
At 1261491545.484 000:>:3b83: receives a *generated* UnmapNotify. Due to this, it sets the window's state to withdrawn, unmaps it etc.

awesome, on the hand, end up in event_handle_unmapnotify(). Here, generated events are just plainly ignored. Also, I'm not sure if awesome (tries to) unmap a window in respsonse to an UnmapNotify....

(No idea how much of this is intended, it might be that wmaker just does this accidentally; I think next I'll take a look at Qt's source, oh joy...)
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 20:47 GMT
Hm, ok, here's the qt side of things:

src/gui/kernel/qwidget_x11.cpp, QWidgetPrivate::hide_sys() calls XWithdrawWindow() which unmaps the window and sends the synthetic UnmapNotify to the WM.
I guess most WMs unmap a window when they receive a synthetic UnmapNotify?
Comment by Uli Schlachter (psychon) - Tuesday, 22 December 2009, 21:07 GMT
Ok, from XWithdrawWindow() I googled my way through its man page until I reached the ICCCM (InterClient Communication Conventions Manual). Chapter 4 is "Client-to-Window-Manage Communication". Chapter 4.1.4 explains "Changing Window State".
For Normal to Withdrawn state, one can read:

Normal ? Withdrawn ? The client should unmap the window and follow it with a synthetic UnmapNotify event as described later in this section.

And later:

Advice to Implementors
For compatibility with obsolete clients, window managers should trigger the transi-
tion to the Withdrawn state on the real UnmapNotify rather than waiting for the syn-
thetic one. They should also trigger the transition if they receive a synthetic Unmap-
Notify on a window for which they have not yet received a real UnmapNotify.

So ICCCM mandates that we unmap windows when we receive a synthetic UnmapNotify on the root window. This is something we aren't doing currently.

So this really is a bug in awesome (and evilwm, dwm, ....).
Comment by Andreas Kloeckner (inducer) - Wednesday, 23 December 2009, 17:29 GMT
Thanks very much for taking the time to track this down!

Comment by Uli Schlachter (psychon) - Monday, 28 December 2009, 19:06 GMT
Ok, I'm a little confused on a way to go here. My latest commit (which sadly was already merged) definitely is buggy and has to be removed ASAP (@jd: Feel free to revert).

To fix this bug we have to xcb_unmap_window() in the UnmapNotify handle right after calling client_unmanage(). But we should also trigger the switch to withdrawn state if a client just unmaps one of its windows according to ICCCM (this should also fix some other bugs where windows still show up on the tasklist even though they shouldn't).

The first part is easy: As I said, just add a call to xcb_unmap_window() (I verified this to actually work).
This last part is where it's getting tricky. We have to client_unmanage() on a real UnmapNotify. But not if this event is from awesome, else we lose clients on tag switch (this is where my first patch failed badly, oh and I fucked up an if).

So how do we find out if it was awesome who generated the unmap or a client? If we just ignored real unmap notifies as we did before, how many apps would break?