Repository: qtile/qtile

Issue:
wl-copy crashes Qtile

# Issue description

wl-copy makes Qtile under Wayland crash:

1. call `wl-copy`
2. qtile makes space in the tiling layout for a new window which doesn't appear
3. freezes for a second or so
4. crashes to TTY

I think we aren't managing clipboards correctly (though interestingly this happens with `wl-copy -p` too)

# Qtile version

7cd0722f16afe36f15927fc6892117b701899aac (always, really)

# Stack traces

When I stick `faulthandler.enable()` in `bin/qtile` it prints this:
```
Current thread 0x00007ffb12958740 (most recent call first):
  File "/home/mcol/git/pywayland/pywayland/server/eventloop.py", line 281 in dispatch
  File "/home/mcol/git/pywayland/pywayland/utils.py", line 30 in wrapper
  File "/home/mcol/git/qtile/libqtile/backend/wayland/core.py", line 465 in _poll
  File "/usr/lib/python3.9/asyncio/events.py", line 80 in _run
  File "/usr/lib/python3.9/asyncio/base_events.py", line 1890 in _run_once
  File "/usr/lib/python3.9/asyncio/base_events.py", line 596 in run_forever
  File "/usr/lib/python3.9/asyncio/base_events.py", line 629 in run_until_complete
  File "/usr/lib/python3.9/asyncio/runners.py", line 44 in run
  File "/home/mcol/git/qtile/libqtile/core/manager.py", line 209 in loop
  File "/home/mcol/git/qtile/libqtile/scripts/start.py", line 89 in start
  File "/home/mcol/git/qtile/libqtile/scripts/main.py", line 55 in main
  File "/home/mcol/git/qtile/bin/qtile", line 39 in <module>
```

# Configuration

Default.



Discussion:
Output when run with `WAYLAND_DEBUG=1`:
```
[3848660.176]  -> wl_data_device@9.selection(nil)
[3848660.190]  -> zwp_primary_selection_device_v1@13.selection(nil)
[3848660.453]  -> zwp_primary_selection_source_v1@10.cancelled()
[3848660.498]  -> xdg_toplevel@22.configure(930, 960, array)
[3848660.523]  -> xdg_surface@21.configure(32)
[3848660.944]  -> wl_surface@3.frame(new id wl_callback@15)
[3848661.126]  -> wl_surface@3.attach(wl_buffer@19, 0, 0)
[3848661.155]  -> wl_surface@3.damage_buffer(0, 0, 2147483647, 2147483647)
[3848661.181]  -> wl_surface@3.commit()
qtile: types/xdg_shell/wlr_xdg_surface.c:170: surface_send_configure: Assertion `0 && "not reached"' failed.
Fatal Python error: Aborted
```

Resolving patch:
diff --git a/libqtile/backend/wayland/core.py b/libqtile/backend/wayland/core.py
--- a/libqtile/backend/wayland/core.py
+++ b/libqtile/backend/wayland/core.py
@@ -96,6 +96,9 @@ def __init__(self):
         self.fd = None
         self._hovered_internal: Optional[window.Internal] = None
 
+        # These windows have not been mapped yet; they'll get managed when mapped
+        self.pending_windows: List[window.WindowType] = []
+
         # mapped_windows contains just regular windows
         self.mapped_windows: List[window.WindowType] = []  # Ascending in Z
         # stacked_windows also contains layer_shell windows from the current output
@@ -251,16 +254,10 @@ def _on_request_cursor(self, _listener, event: seat.PointerRequestSetCursorEvent
 
     def _on_new_xdg_surface(self, _listener, surface: XdgSurface):
         logger.debug("Signal: xdg_shell new_surface_event")
-        assert self.qtile is not None
-
-        if surface.role != XdgSurfaceRole.TOPLEVEL:
-            return
-
-        wid = self.new_wid()
-        win = window.Window(self, self.qtile, surface, wid)
-        logger.info(f"Managing new top-level window with window ID: {wid}")
-        self._poll()  # Give the window the chance to map itself before focussing it
-        self.qtile.manage(win)
+        if surface.role == XdgSurfaceRole.TOPLEVEL:
+            assert self.qtile is not None
+            win = window.Window(self, self.qtile, surface, self.new_wid())
+            self.pending_windows.append(win)
 
     def _on_cursor_axis(self, _listener, event: pointer.PointerEventAxis):
         self.seat.pointer_notify_axis(
diff --git a/libqtile/backend/wayland/window.py b/libqtile/backend/wayland/window.py
--- a/libqtile/backend/wayland/window.py
+++ b/libqtile/backend/wayland/window.py
@@ -158,6 +158,12 @@ def mapped(self, mapped: bool) -> None:
 
     def _on_map(self, _listener, _data):
         logger.debug("Signal: window map")
+
+        if self in self.core.pending_windows:
+            self.core.pending_windows.remove(self)
+            logger.debug(f"Managing new top-level window with window ID: {self.wid}")
+            self.qtile.manage(self)
+
         if self.group.screen:
             self.mapped = True
             self.core.focus_window(self)


Test patch: