Support presenting to a native Wayland surface on Qt#233
Open
hmaarrfk wants to merge 2 commits into
Open
Conversation
<details><summary>Claude's draft</summary> On Wayland, rendercanvas put Qt on XWayland, and the code to present to a native Wayland surface was disabled with an `if False`. That code could not work: it paired the `wl_surface` from `winId()` with a display obtained from `get_alt_wayland_display()`, i.e. a fresh `wl_display_connect()`. A surface is a proxy owned by the connection that created it, so libwayland aborts with "Proxy and queue point to different wl_displays" as soon as a buffer is attached. Note that creating the surface succeeds, so this only shows when frames are actually presented. Qt can tell us what display it is connected to, and that display is the only one its surface is valid on. So: * Get the real `wl_display` from Qt (`QNativeInterface::QWaylandApplication` on Qt6, the platform native interface on Qt5). * Decide the platform from the qpa plugin that Qt actually uses, rather than from `sys.platform`: under a Wayland session Qt may still be on XWayland, in which case `winId()` is an X11 window. * Only put Qt on XWayland when the Qt lib cannot provide its display, so that those libs keep using the x11 code-path. Qt selects its platform when the application is instantiated, so this is decided at import-time. An explicit QT_QPA_PLATFORM is no longer overridden. Also release the wgpu surface when the canvas is closed, instead of leaving it to the garbage collector. On Wayland the surface is owned by the display- connection of the toolkit, so releasing it after the toolkit has torn that connection down is a use-after-free (during interpreter shutdown). Tested on Wayland with a headless weston and lavapipe: presenting to screen works, both for a top-level canvas and an embedded QRenderWidget. Only PySide6 was available to test with. The other backends are unchanged: they have no way to obtain their display, so they stay on XWayland. Resume this Claude session: ``` cd /home/mark/git/pygfx/rendercanvas claude --resume 0a03c1b5-9aa0-4d24-b370-c3799f1a1abe ``` </details>
hmaarrfk
force-pushed
the
qt-wayland-native
branch
from
July 17, 2026 02:16
ba7d4d1 to
61aad67
Compare
Contributor
Author
hmaarrfk
marked this pull request as ready for review
July 17, 2026 16:30
Contributor
Author
|
This is ready for review |
hmaarrfk
commented
Jul 17, 2026
Comment on lines
+178
to
+186
| # Release the surface now, instead of leaving it to the garbage | ||
| # collector. The surface only makes sense as long as the window | ||
| # exists, and on Wayland it is owned by the display-connection of | ||
| # the toolkit, so releasing it after the toolkit has torn that | ||
| # connection down is a use-after-free. Dropping our reference (above) | ||
| # already does this, but only if no other references are left. | ||
| release = getattr(wgpu_context, "_release", None) # private method | ||
| if release is not None: | ||
| release() |
Contributor
Author
There was a problem hiding this comment.
@almarklein this is the shadiest part abut this code.
Contributor
Author
There was a problem hiding this comment.
https://github.com/pygfx/wgpu-py/blob/main/wgpu/backends/wgpu_native/_api.py#L1049
but AI was convinced that sometimes this method did not exist.
I would love to see native support for qt-wayland especially since x11 is being moved away from so quickly.
hmaarrfk
force-pushed
the
qt-wayland-native
branch
from
July 17, 2026 18:26
61aad67 to
a07ff83
Compare
<details><summary>Claude's draft</summary>
The qt backend was not tested on Linux at all: the entries in `test-backends`
all run on Windows, so the (already present) step to install xvfb never runs.
Now that Qt presents to a native Wayland surface, the Wayland and x11
code-paths need testing.
Adds a `test-qt-linux` job with three cases:
* wayland: a Wayland session (headless weston) with a Qt that can provide its
wl_display, so Qt runs on Wayland natively.
* wayland-old-qt: a Wayland session (headless weston with XWayland enabled) and
PySide6 6.9, which cannot provide its wl_display, so rendercanvas puts it on
XWayland. This exercises the real fallback target rather than a bare X server.
* x11: a system that only has x11.
Each case asserts what qpa platform Qt ends up on before running the tests,
because otherwise a case that silently falls back would still pass and test
nothing. Verified that the assertion does fail the step when the platform is
not the expected one.
Note that `QNativeInterface::QWaylandApplication` is only exposed by PySide6
since 6.10 (checked 6.5 up to 6.11), so the XWayland path is what most
installs use today, which is why it is worth a case of its own.
The jobs were checked by running the same steps in ubuntu/python docker
images. This showed that Qt's xcb plugin needs libxcb-cursor0, libxcb-icccm4,
libxcb-keysyms1 and libxkbcommon-x11-0, none of which the existing dependency
list installs. Its wayland plugin needs none of these, which is why only the
x11 cases were affected. Note that Qt's own hint ("xcb-cursor0 or
libxcb-cursor0 is needed") is printed whenever the xcb plugin fails to load,
also when the missing library is a different one.
Resume this Claude session:
```
cd /home/mark/git/pygfx/rendercanvas
claude --resume 0a03c1b5-9aa0-4d24-b370-c3799f1a1abe
```
</details>
hmaarrfk
force-pushed
the
qt-wayland-native
branch
from
July 17, 2026 18:31
a07ff83 to
b81ee29
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


xref: #36
got to test this on an actual linux machine soon running wayland. Curious to see this in action, but the commits look good.
Claude's notes
What this does
On Wayland, rendercanvas puts Qt on XWayland, and the code to present to a native Wayland surface is disabled with an
if False. That code could not have worked: it pairs thewl_surfacefromwinId()with a display fromget_alt_wayland_display(), i.e. a freshwl_display_connect(). Awl_surfaceis a proxy owned by the connection that created it, so libwayland aborts withProxy and queue point to different wl_displaysas soon as a buffer is attached. The existing comment ("this segfaults, so it looks like the real display object is needed?") diagnosed this correctly.Worth noting for anyone testing this: creating the surface succeeds with the wrong display, and so does
wgpuSurfaceGetCapabilities. It only aborts once frames are actually presented, so a probe has to render.Qt can say what display it is connected to, and that display is the only one its surface is valid on. So this:
wl_displayfrom Qt (QNativeInterface::QWaylandApplicationon Qt6, the platform native interface on Qt5);sys.platform, since under a Wayland session Qt may still be on XWayland, wherewinId()is an X11 window;A second bug this uncovered
WgpuContextToScreen._rc_closeunconfigured the wgpu context but never released the surface, leaving it to the garbage collector. On Wayland the surface belongs to the toolkit's display-connection, so during interpreter shutdownwgpuSurfaceReleaseran after Qt had torn that connection down — a use-after-free (gdb showed the freed map was Qt'swl_display+ 0x90). x11 never hit this because an X11 window id is just a number that outlives any connection. This was unreachable before, so it is newly-exposed rather than a regression.Which Qt versions get native Wayland
QNativeInterface.QWaylandApplicationturns out to be recent. Checked each PySide6 minor:QNativeInterfaceQWaylandApplicationSo most installs today keep the current behaviour, and this is a no-op for them. It also means the check has to be for
QWaylandApplicationspecifically:QNativeInterfacealone exists since 6.7 and would be a false positive on 6.7–6.9.Behaviour changes worth a look
QT_QPA_PLATFORMis no longer overridden. PreviouslyQT_QPA_PLATFORM=xcbwas set unconditionally under Wayland, so a user asking forwaylandwas silently overruled. Forcing still works in both directions._get_surface_ids→None→ bitmap path is now only reachable when aQApplicationalready exists at import (already_had_app_on_import) with a Qt lib that cannot provide its display. It is too late to force xcb there, so degrading to bitmap beats crashing.wgpucontext.pyis shared with glfw. Releasing the surface at close rather than at GC is correct for every backend, but it is not Qt-only code. Happy to split it out.Testing
Tested against a headless weston with lavapipe (software Vulkan), on PySide6 only, since that is all that was installed locally. Presenting to screen works for both a top-level canvas and an embedded
QRenderWidget, and the teardown crash is gone.tests/test_backend_qt.pypasses on native Wayland and on xcb. The rest of the suite is unchanged (the 3test_metafailures locally are a pre-existing git-version mismatch, identical on a stashed baseline).The second commit adds a
test-qt-linuxjob covering the three cases: Wayland with a capable Qt, Wayland with PySide6 6.9 (must fall back to XWayland), and x11-only. Each case asserts which qpa platform Qt ends up on before running the tests, because a case that silently fell back would otherwise still pass and test nothing. I checked the assertion actually fails the step when the platform is wrong.The jobs were validated by running the same steps in ubuntu/python docker images, which turned up two things worth knowing: the pip PySide6 wheel does ship
libqwayland.so, and Qt's xcb plugin needslibxcb-cursor0(Qt >= 6.5), which the existing dependency list does not install.Not addressed
glfw and wx are untouched and stay on XWayland, so they are not equivalent to Qt after this. Neither exposes the display of its connection (glfw could via
glfw.get_wayland_display(), wx has no way at all). glfw on Wayland was not testable here:glfw.init()hangs under headless weston because libdecor cannot initialise GTK, before rendercanvas is involved.