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):

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.

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....

- 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.

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?

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)

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?

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.

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?