Repository: qtile/qtile

Issue:
Issue with floating windows on screen edges
### The bug:

When moving windows offscreen, if their geometric center goes beyond the right and bottom parts of a screen the console is flooded with error messages. Tested on Xephyr and on regular X server.

This is caused by `XWindow.tweak_float` resulting in a call to `_find_closest_closest` (which checks points, not windows) with the window center. However, there is a screen pre-filtering stage that treats the coordinates as top left corners of the window. In this corner case, `candidate_screens` is empty causing IndexError. There is another behaviour when

Proposed fixes:
 - remove the pre-filtering (the listcomp on line `libqtile/core/manager.py:674`)
 - replace `libqtile/core/manager.py:678` with `closest_screen = candidate_screens and candidate_screens[0] or None`

Willing to provide a fix after feedback.

Demo:

![offscreen_float](https://user-images.githubusercontent.com/5336394/139753473-768cbad6-560b-4832-b750-c559a46cea30.gif)

Qtile version: 0.18.1, 0.18.2.dev71+g9598a9fb.d20211101 (upstream)

In place of logs, relevant stacktrace:
```
2021-11-01 23:51:43,102 libqtile manager.py:process_button_motion():L750  Mouse command error set_position_floating: Traceback (most recent call last):
  File "/home/golui/Development/External/qtile/libqtile/command/interface.py", line 313, in call
    return SUCCESS, cmd(*args, **kwargs)
  File "/home/golui/Development/External/qtile/libqtile/backend/x11/window.py", line 1748, in cmd_set_position_floating
    self.tweak_float(x=x, y=y)
  File "/home/golui/Development/External/qtile/libqtile/backend/x11/window.py", line 1428, in tweak_float
    screen = self.qtile.find_closest_screen(
  File "/home/golui/Development/External/qtile/libqtile/core/manager.py", line 654, in find_closest_screen
    return self._find_closest_closest(x, y, x_match + y_match)
  File "/home/golui/Development/External/qtile/libqtile/core/manager.py", line 678, in _find_closest_closest
    closest_screen = candidate_screens[0]
IndexError: list index out of range
```

### 

- [X] I have searched past issues to see if this bug has already been reported.


Discussion:

Resolving patch:
diff --git a/libqtile/core/manager.py b/libqtile/core/manager.py
--- a/libqtile/core/manager.py
+++ b/libqtile/core/manager.py
@@ -50,7 +50,7 @@
 from libqtile.group import _Group
 from libqtile.log_utils import logger
 from libqtile.scratchpad import ScratchPad
-from libqtile.utils import get_cache_dir, send_notification
+from libqtile.utils import get_cache_dir, lget, send_notification
 from libqtile.widget.base import _Widget
 
 if TYPE_CHECKING:
@@ -625,7 +625,7 @@ def find_screen(self, x: int, y: int) -> Optional[Screen]:
             return result[0]
         return None
 
-    def find_closest_screen(self, x: int, y: int) -> Screen:
+    def find_closest_screen(self, x: int, y: int) -> Optional[Screen]:
         """
         If find_screen returns None, then this basically extends a
         screen vertically and horizontally and see if x,y lies in the
@@ -653,15 +653,16 @@ def find_closest_screen(self, x: int, y: int) -> Screen:
             return y_match[0]
         return self._find_closest_closest(x, y, x_match + y_match)
 
-    def _find_closest_closest(self, x: int, y: int, candidate_screens: List[Screen]) -> Screen:
+    def _find_closest_closest(
+        self, x: int, y: int, candidate_screens: List[Screen]
+    ) -> Optional[Screen]:
         """
         if find_closest_screen can't determine one, we've got multiple
         screens, so figure out who is closer.  We'll calculate using
         the square of the distance from the center of a screen.
 
         Note that this could return None if x, y is right/below all
-        screens (shouldn't happen but we don't do anything about it
-        here other than returning None)
+        screens.
         """
         closest_distance: Optional[float] = None  # because mypy only considers first value
         if not candidate_screens:
@@ -672,7 +673,7 @@ def _find_closest_closest(self, x: int, y: int, candidate_screens: List[Screen])
         candidate_screens = [
             s for s in candidate_screens if x < s.x + s.width and y < s.y + s.height
         ]
-        closest_screen = candidate_screens[0]
+        closest_screen = lget(candidate_screens, 0)
         for s in candidate_screens:
             middle_x = s.x + s.width / 2
             middle_y = s.y + s.height / 2


Test patch: