Stop the indicator from swallowing clicks in its corner

WA_TransparentForMouseEvents does nothing for a top-level window: Qt
takes the click and then drops it, so it still never reaches whatever is
underneath. Since the window stays mapped while idle, that turned its
corner of the screen into a dead zone for good. Qt::WindowTransparentForInput
is the one that leaves the window without an input region at all.

The dismissable one has to keep taking clicks, and the flag is read once
when the window is created, so it shrinks to a point while concealed
instead. Resizing keeps the surface alive, which is the whole reason
concealing does not simply hide it.
This commit is contained in:
yusufipk
2026-08-10 15:59:35 +03:00
parent d506e0d3d1
commit 77b26e76be
2 changed files with 39 additions and 5 deletions
+20 -5
View File
@@ -65,16 +65,23 @@ class Overlay(QWidget):
# It is the window manager that would otherwise move this out of # It is the window manager that would otherwise move this out of
# the corner. macOS has no such hint, and Qt warns about it. # the corner. macOS has no such hint, and Qt warns about it.
flags |= Qt.WindowType.X11BypassWindowManagerHint flags |= Qt.WindowType.X11BypassWindowManagerHint
self.setWindowFlags(flags)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
# One that can be clicked away has to receive the click, which means it # One that can be clicked away has to receive the click, which means it
# also swallows one aimed at whatever is underneath it. The rest stay # also swallows one aimed at whatever is underneath it. The rest stay
# transparent to the mouse, as an indicator should be. # transparent to the mouse, as an indicator should be. It has to be this
# flag and not WA_TransparentForMouseEvents: on a top-level window the
# attribute only makes Qt drop the event it already took, so the click
# never reaches the window below. The flag is the one that tells the
# display server the window has no input region at all. It is read when
# the window is created and cannot be turned off later without the
# window being torn down and built again, which is why the dismissable
# one has to shrink itself instead (see _conceal).
if dismissable: if dismissable:
self.setCursor(Qt.CursorShape.PointingHandCursor) self.setCursor(Qt.CursorShape.PointingHandCursor)
else: else:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) flags |= Qt.WindowType.WindowTransparentForInput
self.setWindowFlags(flags)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
self.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.resize(MIN_WIDTH, HEIGHT) self.resize(MIN_WIDTH, HEIGHT)
@@ -190,11 +197,19 @@ class Overlay(QWidget):
to be a real repaint, not just zero opacity: with the animation stopped to be a real repaint, not just zero opacity: with the animation stopped
nothing else damages the surface, and the stale frame would sit on the nothing else damages the surface, and the stale frame would sit on the
screen until some other event made the compositor redraw it. screen until some other event made the compositor redraw it.
A window that stays mapped also stays clickable, though, and the one
that can be dismissed is the one that takes clicks. Left at full size it
would turn its corner of the screen into a dead zone long after there
was anything to see there, so it shrinks to a point. Resizing keeps the
surface alive, unlike hiding it.
""" """
self._anim.stop() self._anim.stop()
self.state = "hidden" self.state = "hidden"
self._concealed = True self._concealed = True
self.repaint() self.repaint()
if self.dismissable:
self.resize(1, 1)
def _resize_to_content(self): def _resize_to_content(self):
if self.state in LIVE: if self.state in LIVE:
+19
View File
@@ -355,6 +355,25 @@ class Overlay(DikteTest):
self.assertTrue(flags & Qt.WindowType.WindowDoesNotAcceptFocus) self.assertTrue(flags & Qt.WindowType.WindowDoesNotAcceptFocus)
self.assertTrue(flags & Qt.WindowType.WindowStaysOnTopHint) self.assertTrue(flags & Qt.WindowType.WindowStaysOnTopHint)
def test_it_lets_a_click_through_to_whatever_is_under_it(self):
"""It stays mapped while idle, so without this its corner of the screen
would stop taking clicks for good. The widget attribute is not enough:
on a top-level window it only makes Qt drop the event it already took."""
from PyQt6.QtCore import Qt
flags = self.overlay().windowFlags()
self.assertTrue(flags & Qt.WindowType.WindowTransparentForInput)
def test_the_one_that_takes_clicks_shrinks_out_of_the_way(self):
"""It has to stay clickable, so it cannot be transparent to input; it
gets out of the way by leaving nothing there to click instead."""
widget = self.overlay(dismissable=True)
widget.show_busy("Asking Claude…")
self.assertGreater(widget.width(), 1)
widget.dismiss()
self.assertEqual((widget.width(), widget.height()), (1, 1))
widget.show_busy("Asking Claude…")
self.assertGreater(widget.width(), 1)
def test_recording_then_working_then_done(self): def test_recording_then_working_then_done(self):
widget = self.overlay() widget = self.overlay()
widget.show_recording() widget.show_recording()