fix: Wayland popup error (Qt.Tool instead of Qt.Popup) #4

Merged
bjoern merged 2 commits from fix/wayland-popup into main 2026-06-28 18:40:22 +02:00
Member

Fix: Wayland "Failed to create grabbing popup" error

The error

qt.qpa.wayland: Failed to create grabbing popup. Ensure popup
QWidgetWindow(...) has a transientParent set and that parent window
has received input.

Root cause

The UsagePanel used Qt.Popup window flags. On Wayland, popup surfaces require a transient parent window — but QSystemTrayIcon doesn't create a window we can parent to. KDE Plasma 6 runs Wayland by default, so this hits every click.

The fix

  • Changed window flags from Qt.Popup to Qt.ToolQt.Tool doesn't use the Wayland popup surface protocol, so no transient parent needed
  • Added a QApplication event filter on TrayApp that closes the panel when the user clicks outside it (replaces Qt.Popup's automatic pointer grab, which is what Wayland was rejecting)
  • Panel calls raise_() after show() to ensure it appears on top

45 tests still passing.

## Fix: Wayland "Failed to create grabbing popup" error ### The error ``` qt.qpa.wayland: Failed to create grabbing popup. Ensure popup QWidgetWindow(...) has a transientParent set and that parent window has received input. ``` ### Root cause The `UsagePanel` used `Qt.Popup` window flags. On Wayland, popup surfaces require a transient parent window — but `QSystemTrayIcon` doesn't create a window we can parent to. KDE Plasma 6 runs Wayland by default, so this hits every click. ### The fix - Changed window flags from `Qt.Popup` to `Qt.Tool` — `Qt.Tool` doesn't use the Wayland popup surface protocol, so no transient parent needed - Added a `QApplication` event filter on `TrayApp` that closes the panel when the user clicks outside it (replaces `Qt.Popup`'s automatic pointer grab, which is what Wayland was rejecting) - Panel calls `raise_()` after `show()` to ensure it appears on top 45 tests still passing.
fix: Wayland popup error — use Qt.Tool instead of Qt.Popup
All checks were successful
CI / test (pull_request) Successful in 55s
c924e15c1c
Wayland's popup surface protocol requires a transient parent window.
QSystemTrayIcon doesn't provide one, causing:
  qt.qpa.wayland: Failed to create grabbing popup

Fix:
- Change UsagePanel window flags from Qt.Popup to Qt.Tool
- Qt.Tool doesn't need a transient parent on Wayland
- Added QApplication event filter for click-outside-to-close
  (replaces Qt.Popup's automatic grab that doesn't work on Wayland)
- Panel raises to front after show()
Author
Member

🤖 Hermes automated review: minor comments

Reviewed base 1ef78c0 → head c924e15 (1 file, +27/−3).

CI/CD: CI / test (55s) passed for head c924e15 — local build/test skipped per CI policy. (I also ran the suite locally: 45/45 passed in 0.29s, consistent with CI.)

Core fix is correct

The root-cause analysis is sound. On Wayland, Qt.Popup creates an xdg_popup surface, which the protocol requires to have a transient parent (xdg-shell get_popup needs a parent xdg_surface). QSystemTrayIcon's StatusNotifierItem is not an xdg_surface you can parent to, so KDE Plasma 6 (Wayland default) rejects the grab every time. Switching to Qt.Tool sidesteps the popup protocol entirely — a Qt.Tool window is a regular toplevel — so the Wayland error disappears. This is the standard fix for this class of problem.

The replacement of Qt.Popup's automatic pointer grab with an application-level eventFilter (main.py ~TrayApp.eventFilter) is the right pattern:

  • Filter is installed on self._app → receives events for all objects. ✓
  • Checks event.type() == event.Type.MouseButtonPress and not self._panel.underMouse() before hiding. ✓
  • Returns True to consume the event — this is the documented Qt event-filter convention and correctly reproduces Qt.Popup's "click outside closes and is swallowed" semantics. ✓
  • Tray-icon activations arrive through the StatusNotifierItem D-Bus path, not as QApplication mouse events, so the filter does not interfere with _on_tray_activated/toggle behavior. ✓
  • raise_() after show() is correct for a Qt.Tool | Qt.WindowStaysOnTopHint window on both X11 and Wayland. ✓

No security concerns: no secrets, no shell/eval, no network/serialization changes — purely a window-management tweak.

Minor (non-blocking)

  • UsagePanel.closed = Signal() is dead codemain.py:178: the signal is declared on the class but is never emit()ed anywhere (notably not from eventFilter's hide path, nor from a closeEvent override) and is never connected by TrayApp. Either wire it up (self._panel.hide()self._panel.closed.emit()) so callers can react to dismissal, or remove the declaration. As-is it's an unused attribute that suggests an API that doesn't exist.
  • Docstring/code mismatchmain.py:172-174 docstring says "Click-outside-to-close is handled via application focus events," but the actual implementation intercepts MouseButtonPress events (mouse-button events, not FocusIn/FocusOut/ApplicationStateChange). The mouse-press approach is arguably better than focus events on Wayland (focus events are unreliable for frameless top-level windows), so the code is fine — just update the docstring to say "mouse-button events" so it matches.
  • Panel toggling via eventFilter vs. tray click (informational, not a bug): when the panel is visible and the user left-clicks the tray icon, the eventFilter does not fire (tray clicks bypass QApplication mouse dispatch), so _on_tray_activated_show_panel correctly sees isVisible() and hides it. No conflict. Noting this because the two close-paths could look like a race on first read; they aren't.

Verdict: Clean, well-targeted Wayland fix with correct root-cause analysis. No blocking issues — just the dead closed signal and a docstring nit. CI green, tests green.


Automated daily review. I never merge PRs. Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the MCP integration cannot create inline review comments or approval states.

## 🤖 Hermes automated review: minor comments Reviewed base `1ef78c0` → head `c924e15` (1 file, +27/−3). **CI/CD:** CI / test ✅ (55s) passed for head `c924e15` — local build/test skipped per CI policy. (I also ran the suite locally: **45/45 passed** in 0.29s, consistent with CI.) ### Core fix is correct ✅ The root-cause analysis is sound. On Wayland, `Qt.Popup` creates an `xdg_popup` surface, which the protocol requires to have a transient parent (xdg-shell `get_popup` needs a parent `xdg_surface`). `QSystemTrayIcon`'s StatusNotifierItem is not an `xdg_surface` you can parent to, so KDE Plasma 6 (Wayland default) rejects the grab every time. Switching to `Qt.Tool` sidesteps the popup protocol entirely — a `Qt.Tool` window is a regular toplevel — so the Wayland error disappears. This is the standard fix for this class of problem. The replacement of `Qt.Popup`'s automatic pointer grab with an application-level `eventFilter` (`main.py` ~`TrayApp.eventFilter`) is the right pattern: - Filter is installed on `self._app` → receives events for all objects. ✓ - Checks `event.type() == event.Type.MouseButtonPress` and `not self._panel.underMouse()` before hiding. ✓ - Returns `True` to consume the event — this is the documented Qt event-filter convention and correctly reproduces `Qt.Popup`'s "click outside closes and is swallowed" semantics. ✓ - Tray-icon activations arrive through the StatusNotifierItem D-Bus path, not as `QApplication` mouse events, so the filter does not interfere with `_on_tray_activated`/toggle behavior. ✓ - `raise_()` after `show()` is correct for a `Qt.Tool | Qt.WindowStaysOnTopHint` window on both X11 and Wayland. ✓ No security concerns: no secrets, no shell/eval, no network/serialization changes — purely a window-management tweak. ### Minor (non-blocking) - **`UsagePanel.closed = Signal()` is dead code** — `main.py:178`: the signal is declared on the class but is never `emit()`ed anywhere (notably not from `eventFilter`'s hide path, nor from a `closeEvent` override) and is never connected by `TrayApp`. Either wire it up (`self._panel.hide()` → `self._panel.closed.emit()`) so callers can react to dismissal, or remove the declaration. As-is it's an unused attribute that suggests an API that doesn't exist. - **Docstring/code mismatch** — `main.py:172-174` docstring says *"Click-outside-to-close is handled via application focus events,"* but the actual implementation intercepts `MouseButtonPress` events (mouse-button events, not `FocusIn`/`FocusOut`/`ApplicationStateChange`). The mouse-press approach is arguably better than focus events on Wayland (focus events are unreliable for frameless top-level windows), so the code is fine — just update the docstring to say "mouse-button events" so it matches. - **Panel toggling via eventFilter vs. tray click** (informational, not a bug): when the panel is visible and the user left-clicks the tray icon, the `eventFilter` does *not* fire (tray clicks bypass `QApplication` mouse dispatch), so `_on_tray_activated` → `_show_panel` correctly sees `isVisible()` and hides it. No conflict. Noting this because the two close-paths could look like a race on first read; they aren't. **Verdict:** Clean, well-targeted Wayland fix with correct root-cause analysis. No blocking issues — just the dead `closed` signal and a docstring nit. CI green, tests green. --- *Automated daily review. I never merge PRs. Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the MCP integration cannot create inline review comments or approval states.*
fix: remove dead 'closed' signal, fix docstring wording
All checks were successful
CI / test (pull_request) Successful in 21s
01a662bbff
- Remove unused UsagePanel.closed = Signal() (never emitted/connected)
- Fix docstring: 'focus events' → 'mouse-button events' to match
  the actual eventFilter implementation
bjoern merged commit e95fde0b7e into main 2026-06-28 18:40:22 +02:00
bjoern deleted branch fix/wayland-popup 2026-06-28 18:40:22 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/zai-tray-checker!4
No description provided.