Repository: qtile/qtile

Issue:
The "Hide" button of Chrome's screen sharing sticky window doesn't work
This window won't go away:

![image](https://user-images.githubusercontent.com/835072/99949439-5548e500-2d7b-11eb-89c5-7b84bb179990.png)

xprop:

```
_NET_WM_DESKTOP(CARDINAL) = 1
WM_STATE(WM_STATE):
		window state: Normal
		icon window: 0x0
_NET_WM_WINDOW_OPACITY(CARDINAL) = 4109694196
_NET_WM_STATE(ATOM) = _NET_WM_STATE_STICKY, _NET_WM_STATE_ABOVE
_NET_WM_USER_TIME(CARDINAL) = 495685613
WM_NORMAL_HINTS(WM_SIZE_HINTS):
		program specified location: 1041, 1384
		program specified minimum size: 478 by 56
		program specified maximum size: 478 by 56
WM_NAME(UTF8_STRING) = "meet.google.com is sharing your screen."
_NET_WM_NAME(UTF8_STRING) = "meet.google.com is sharing your screen."
_MOTIF_WM_HINTS(_MOTIF_WM_HINTS) = 0x2, 0x0, 0x0, 0x0, 0x0
XdndAware(ATOM) = BITMAP
_NET_WM_ICON(CARDINAL) = 	Icon (128 x 128): ...
_NET_WM_BYPASS_COMPOSITOR(CARDINAL) = 2
_NET_WM_SYNC_REQUEST_COUNTER(CARDINAL) = 20979044, 20979045
_GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED(CARDINAL) = 1
_NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL
_NET_WM_PID(CARDINAL) = 26321
WM_CLIENT_MACHINE(STRING) = "saiga"
WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW, _NET_WM_PING, _NET_WM_SYNC_REQUEST
```


Discussion:
On Mon, Nov 23, 2020 at 02:04:14AM -0800, Guillaume Gelin wrote:
> This window won't go away:
> 
> ![image](https://user-images.githubusercontent.com/835072/99949439-5548e500-2d7b-11eb-89c5-7b84bb179990.png)
> 
> xprop:
> 
> ```
> _NET_WM_DESKTOP(CARDINAL) = 1
> WM_STATE(WM_STATE):
> 		window state: Normal
> 		icon window: 0x0
> _NET_WM_WINDOW_OPACITY(CARDINAL) = 4109694196
> _NET_WM_STATE(ATOM) = _NET_WM_STATE_STICKY, _NET_WM_STATE_ABOVE
> _NET_WM_USER_TIME(CARDINAL) = 495685613
> WM_NORMAL_HINTS(WM_SIZE_HINTS):
> 		program specified location: 1041, 1384
> 		program specified minimum size: 478 by 56
> 		program specified maximum size: 478 by 56
> WM_NAME(UTF8_STRING) = "meet.google.com is sharing your screen."
> _NET_WM_NAME(UTF8_STRING) = "meet.google.com is sharing your screen."
> _MOTIF_WM_HINTS(_MOTIF_WM_HINTS) = 0x2, 0x0, 0x0, 0x0, 0x0
> XdndAware(ATOM) = BITMAP
> _NET_WM_ICON(CARDINAL) = 	Icon (128 x 128): ...
> _NET_WM_BYPASS_COMPOSITOR(CARDINAL) = 2
> _NET_WM_SYNC_REQUEST_COUNTER(CARDINAL) = 20979044, 20979045
> _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED(CARDINAL) = 1
> _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL
> _NET_WM_PID(CARDINAL) = 26321
> WM_CLIENT_MACHINE(STRING) = "saiga"
> WM_PROTOCOLS(ATOM): protocols  WM_DELETE_WINDOW, _NET_WM_PING, _NET_WM_SYNC_REQUEST
> ```

If you run a compositor, does that fix it? I'm wondering about
_NET_WM_SYNC_REQUEST.

I'm already running picom. :)
Huh, the rest of that looks ok (i.e. like it should really be displaying the window). Maybe it's because it is using WM_SYNC and BYPASS_COMPOSITOR? We don't implement WM_SYNC at all, so I usually tell people to just run a compositor for that...
FWIW it seems that it doesn't hide on i3 neither.

Resolving patch:
diff --git a/libqtile/window.py b/libqtile/window.py
--- a/libqtile/window.py
+++ b/libqtile/window.py
@@ -416,6 +416,7 @@ def hide(self):
         # We don't want to get the UnmapNotify for this unmap
         with self.disable_mask(xcffib.xproto.EventMask.StructureNotify):
             self.window.unmap()
+        self.state = IconicState
         self.hidden = True
 
     def unhide(self):
@@ -991,7 +992,6 @@ def getposition(self):
 
     def _reconfigure_floating(self, new_float_state=FLOATING):
         if new_float_state == MINIMIZED:
-            self.state = IconicState
             self.hide()
         else:
             width = max(self.width, self.hints.get('min_width', 0))
@@ -1201,6 +1201,14 @@ def handle_ClientMessage(self, event):  # noqa: N802
                     logger.warning("Invalid value for focus_on_window_activation: {}".format(focus_behavior))
         elif atoms["_NET_CLOSE_WINDOW"] == opcode:
             self.kill()
+        elif atoms["WM_CHANGE_STATE"] == opcode:
+            state = data.data32[0]
+            if state == NormalState:
+                self.minimized = False
+            elif state == IconicState:
+                self.minimized = True
+        else:
+            logger.info("Unhandled client message: %s", atoms.get_name(opcode))
 
     def handle_PropertyNotify(self, e):  # noqa: N802
         name = self.qtile.conn.atoms.get_name(e.atom)


Test patch:
diff --git a/test/test_manager.py b/test/test_manager.py
--- a/test/test_manager.py
+++ b/test/test_manager.py
@@ -37,6 +37,7 @@
 import libqtile.hook
 import libqtile.layout
 import libqtile.widget
+import libqtile.window
 from libqtile.backend.x11 import xcbq
 from libqtile.command_client import SelectError
 from libqtile.command_interface import CommandError, CommandException
@@ -387,6 +388,32 @@ def test_kill_via_message(manager):
     assert_window_died(manager.c, window_info)
 
 
+@manager_config
+@no_xinerama
+def test_change_state_via_message(manager):
+    manager.test_window("one")
+    window_info = manager.c.window.info()
+    conn = xcbq.Connection(manager.display)
+
+    data = xcffib.xproto.ClientMessageData.synthetic([libqtile.window.IconicState, 0, 0, 0, 0], "IIIII")
+    ev = xcffib.xproto.ClientMessageEvent.synthetic(
+        32, window_info["id"], conn.atoms['WM_CHANGE_STATE'], data
+    )
+    conn.default_screen.root.send_event(ev, mask=xcffib.xproto.EventMask.SubstructureRedirect)
+    conn.xsync()
+    assert manager.c.window.info()["minimized"]
+
+    data = xcffib.xproto.ClientMessageData.synthetic([libqtile.window.NormalState, 0, 0, 0, 0], "IIIII")
+    ev = xcffib.xproto.ClientMessageEvent.synthetic(
+        32, window_info["id"], conn.atoms['WM_CHANGE_STATE'], data
+    )
+    conn.default_screen.root.send_event(ev, mask=xcffib.xproto.EventMask.SubstructureRedirect)
+    conn.xsync()
+    assert not manager.c.window.info()["minimized"]
+
+    conn.finalize()
+
+
 @manager_config
 @no_xinerama
 def test_regression_groupswitch(manager):