An open menu silences the tooltips behind it - #206
Conversation
GTK's tooltip machinery doesn't follow the pointer grab a menu takes: it works off the widget it picks under the pointer, so sweeping across the window behind an open context menu still pops tooltips for controls the click can no longer reach — bubbles that land over or behind the menu depending on how the compositor stacks the two surfaces. GTK4 has no gtk-enable-tooltips switch left to flip, but a widget is only asked for a tooltip while its has-tooltip flag is set, so clearing that flag outside the open menu is the mute and restoring it on close is the unmute. Two emission hooks on map/unmap catch every popover, including the ones we don't build ourselves (AdwTabView's tab menu, every GtkMenuButton's), and a query-tooltip hook covers widgets that appear while a menu is already up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FD7QTvXtBfAUDCkLa3eqZe
9e9c66b to
e84868b
Compare
|
@claude review |
|
Claude finished @ghackett's task in 2m 38s —— View job Review
Solid, well-documented approach — hooking Cross-window muting bug
# collins/tooltipmute.py:67-77
toplevels = Gtk.Window.get_toplevels()
for i in range(toplevels.get_n_items()):
window = toplevels.get_item(i)
_mute_tree(window)
window.trigger_tooltip_query()Collins isn't single-window: It also compounds on the unmute side: Suggested fix direction: key the mute state (and the Minor
Everything else — the |
|
Addressed in 208 (this one had already merged). On the cross-window finding: the premise that another window is "fully What the review is right about is that the code said none of this, and took the |
A context menu grabs the pointer, but GTK's tooltip machinery doesn't follow
the grab.
gtk_main_do_eventhands the tooltip code the widget it pickedunder the pointer, not the widget the grab routes the event to, so with a menu
open you can still sweep the pointer across the window behind it and pop
tooltips — for controls the click can no longer even reach. The bubble lands
over or behind the menu depending on how the compositor stacks the two
surfaces.
Now nothing outside an open menu answers a tooltip query. The menu's own
tooltips are untouched — those are the ones that belong on screen.
Both shots are composited: the window render never contains a popover (its own
surface) and never contains a tooltip (GtkTooltipWindow is private), so the
menu and the bubble are rendered separately and pasted on. The bubble is drawn
by a widget with GTK's own
tooltipCSS node, positioned where GTK hangs atooltip — a faithful reconstruction of the stray tooltip rather than a capture
of one, since a headless run has no pointer to hover with.
How it works
GTK4 dropped the
gtk-enable-tooltipssetting, so there's no switch to flip.What there is:
gtk_tooltip_run_requeryonly asks a widget for a tooltip whileits
has-tooltipflag is set. Clearing that flag is the mute, restoring itwhen the last menu closes is the unmute.
collins/tooltipmute.pyhangs it all off emission hooks rather than a call ateach popup site, so menus we don't build ourselves — AdwTabView's tab menu,
every GtkMenuButton's, VTE's — are covered too:
map/unmapon any autohiding popover open and close the mute. Only apopover that autohides takes the grab that makes the UI behind it
untouchable; one that doesn't leaves that UI live, tooltips included.
requery per window so a bubble already on screen retracts. (Usually moot —
the button press that opens a context menu hides the tooltip itself — but a
menu opened from the keyboard gets no such press.)
query-tooltiphook, live only while something is muted, catches widgetsbuilt after the sweep. GTK asks a widget for its tooltip twice: once when the
pointer settles on it, then again half a second later when it's about to show
the bubble. By that second ask the hook has muted the widget and its
ancestors, so the requery walks off the top of the window empty-handed.
Verification
No unit test: the module is GTK all the way down, and CI's pytest runner has no
typelibs. Checked instead with headless probes driving a real Collins instance
(staged data, isolated app id) — with a sidebar row's context menu open:
after the menu closes;
a tooltip, and re-armed on close;
The sweep costs ~1.6ms on an 893-widget window, once per menu open.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FD7QTvXtBfAUDCkLa3eqZe