Repository: qtile/qtile

Issue:
Mirror widget does not get events: mouse_entered/mouse_leave
### The issue:

```
% qtile --version
0.21.0
```
I noticed that the mirrored widget does not trigger the mouse_enter/mouse_leave callbacks. They only work on one screen.

I think this is a bug.

### Required:

- [X] I have searched past issues to see if this bug has already been reported.


Discussion:

Resolving patch:
diff --git a/libqtile/backend/base.py b/libqtile/backend/base.py
--- a/libqtile/backend/base.py
+++ b/libqtile/backend/base.py
@@ -637,10 +637,11 @@ def __init__(self, qtile: Qtile, win: Internal, width: int, height: int):
         self._height = height
 
         self.surface: cairocffi.RecordingSurface
+        self.last_surface: cairocffi.RecordingSurface
         self.ctx: cairocffi.Context
         self._reset_surface()
 
-        self.mirrors: dict[Drawer, bool] = {}
+        self.mirrors: set[Drawer] = set()
 
         self.current_rect = (0, 0, 0, 0)
         self.previous_rect = (-1, -1, -1, -1)
@@ -653,16 +654,7 @@ def finalize(self):
 
     def add_mirror(self, mirror: Drawer):
         """Keep details of other drawers that are mirroring this one."""
-        self.mirrors[mirror] = False
-
-    def reset_mirrors(self):
-        """Reset the drawn status of mirrors."""
-        self.mirrors = {m: False for m in self.mirrors}
-
-    @property
-    def mirrors_drawn(self) -> bool:
-        """Returns True if all mirrors have been drawn with the current surface."""
-        return all(v for v in self.mirrors.values())
+        self.mirrors.add(mirror)
 
     @property
     def width(self) -> int:
@@ -688,14 +680,6 @@ def _reset_surface(self):
         )
         self.ctx = self.new_ctx()
 
-    def _check_surface_reset(self):
-        """
-        Checks to see if the widget is not being reflected and
-        then clears RecordingSurface of operations.
-        """
-        if not self.mirrors:
-            self._reset_surface()
-
     @property
     def needs_update(self) -> bool:
         # We can't test for the surface's ink_extents here on its own as a completely
@@ -710,13 +694,8 @@ def needs_update(self) -> bool:
         return ink_changed or rect_changed
 
     def paint_to(self, drawer: Drawer) -> None:
-        drawer.ctx.set_source_surface(self.surface)
+        drawer.ctx.set_source_surface(self.last_surface)
         drawer.ctx.paint()
-        self.mirrors[drawer] = True
-
-        if self.mirrors_drawn:
-            self._reset_surface()
-            self.reset_mirrors()
 
     def _rounded_rect(self, x, y, width, height, linewidth):
         aspect = 1.0
@@ -790,9 +769,14 @@ def draw(
         """
         if self._enabled:
             self._draw(offsetx, offsety, width, height)
+            if self.mirrors:
+                # mypy is tripping over CONTENT_COLOR_ALPHA here despite it working earlier in this file!
+                self.last_surface = cairocffi.RecordingSurface(cairocffi.CONTENT_COLOR_ALPHA, None)  # type: ignore
+                ctx = cairocffi.Context(self.last_surface)
+                ctx.set_source_surface(self.surface)
+                ctx.paint()
 
-        # Check to see if RecordingSurface can be cleared.
-        self._check_surface_reset()
+        self._reset_surface()
 
     def _draw(
         self,
diff --git a/libqtile/widget/base.py b/libqtile/widget/base.py
--- a/libqtile/widget/base.py
+++ b/libqtile/widget/base.py
@@ -844,6 +844,8 @@ def __init__(self, reflection, **config):
         reflection.draw = self.hook(reflection.draw)
         self.reflects = reflection
         self._length = 0
+        if self.reflects.length_type == bar.STRETCH:
+            self.length_type = bar.STRETCH
 
     def _configure(self, qtile, bar):
         _Widget._configure(self, qtile, bar)
@@ -854,7 +856,9 @@ def _configure(self, qtile, bar):
 
     @property
     def length(self):
-        return self.reflects.length
+        if self.length_type != bar.STRETCH:
+            return self.reflects.length
+        return self._length
 
     @length.setter
     def length(self, value):
@@ -863,22 +867,27 @@ def length(self, value):
     def hook(self, draw):
         def _():
             draw()
-            self.draw()
+            if self.length_type == bar.STRETCH:
+                self.bar.draw()
+            else:
+                self.draw()
 
         return _
 
     def draw(self):
-        if self._length != self.reflects.length:
+        if self.length_type != bar.STRETCH and self._length != self.reflects.length:
             self._length = self.length
             self.bar.draw()
         else:
-            # We only update the mirror's drawer if the parent widget has
-            # contents in its RecordingSurface. If this is False then the widget
-            # wil just show the existing drawer contents.
-            if self.reflects.drawer.needs_update:
-                self.drawer.clear(self.background or self.bar.background)
-                self.reflects.drawer.paint_to(self.drawer)
+            self.drawer.clear(self.reflects.background or self.bar.background)
+            self.reflects.drawer.paint_to(self.drawer)
             self.drawer.draw(offsetx=self.offset, offsety=self.offsety, width=self.width)
 
     def button_press(self, x, y, button):
         self.reflects.button_press(x, y, button)
+
+    def mouse_enter(self, x, y):
+        self.reflects.mouse_enter(x, y)
+
+    def mouse_leave(self, x, y):
+        self.reflects.mouse_leave(x, y)


Test patch: