Skip to content

Keep spawned children off the mpv Wayland proxy - #653

Open
PurpleDizzy wants to merge 1 commit into
andrewrabert:mainfrom
PurpleDizzy:wayland-child-display
Open

Keep spawned children off the mpv Wayland proxy#653
PurpleDizzy wants to merge 1 commit into
andrewrabert:mainfrom
PurpleDizzy:wayland-child-display

Conversation

@PurpleDizzy

Copy link
Copy Markdown

Problem

Clicking Download, or any external link, makes the window vanish instantly.
No crash dialog, no signal, no core dump, nothing in dmesg. The app tears
itself down cleanly, which is what made this hard to spot.

It only reproduces when the xdg-open handler is not already running. A
warm browser hands the URL to its existing instance over the singleton socket
and exits before it ever creates a Wayland surface, so the bug looks
intermittent or config-specific.

Cause

start_proxy overrides the process-wide WAYLAND_DISPLAY so in-process
libmpv reaches the mpv proxy instead of the compositor. Every process we spawn
inherits it, so xdg-open's handler connects to the mpv proxy too, where the
first get_xdg_surface is demoted to a subsurface on the assumption that the
client is mpv. The browser then exports that surface via zxdg_exporter_v2,
the compositor rejects it, and the fatal wl_display error takes down the
connection we share with mpv.

From --log-level debug (niri, CEF 150.0.17, wayland-1 compositor and
wayland-2 proxy):

[MpvProxy] get_xdg_surface: demoting mpv surface server_id=Some(91)
[CEF] proxy: S_app dispatch: ... server sent error 0 on object
      zxdg_exporter_v2#229: exported surface had an invalid role
[CEF] Io error: Broken pipe (os error 32)
[CEF] proxy: S_mpv dispatch: the server hung up the connection
[mpv] vo/gpu-next/wayland: Error occurred on the display fd
[Main] MPV_EVENT_SHUTDOWN received

server_id=Some(91) is the browser, not mpv. mpv's own demote is
server_id=Some(8) at startup.

Fix

Record the compositor's WAYLAND_DISPLAY before the override and set it on
the spawned command, so children reach the compositor while libmpv still
reaches the proxy. open_url::open is the only runtime spawn on Linux, so
this covers downloads, external links, About paths and openConfigDir
together. Leaving it unrecorded (X11, or the proxy never started) keeps the
inherited env untouched.

Testing

  • Unit tests over Command::get_envs for the three cases: unrecorded host,
    a recorded display, and a host that had none.
  • just lint and just test pass.
  • Verified on niri with the browser fully closed: before the patch the app
    dies on every Download; after it, the browser cold-starts and the app stays
    up, with no second demoting line and no protocol error.

Note

This does not harden the proxy itself against non-mpv clients. Anything that
ends up with WAYLAND_DISPLAY pointing at the proxy can still take the app
down, so rejecting or passing through clients that aren't mpv may be worth
doing separately. I kept this change to the path that actually spawns them.

`start_proxy` overrides the process-wide `WAYLAND_DISPLAY` so in-process
libmpv reaches the proxy instead of the compositor, but every process we
spawn inherits it too. `xdg-open`'s handler therefore connects to the mpv
proxy, where the first `get_xdg_surface` is demoted to a subsurface on the
assumption that the client is mpv. A browser starting cold then exports
that surface via `zxdg_exporter_v2`, the compositor rejects it ("exported
surface had an invalid role"), and the resulting fatal `wl_display` error
takes down the connection we share with mpv. The window disappears with
no crash, no signal and no core.

Clicking Download or any external link reproduces it, but only when the
handler is not already running: a warm browser hands the URL to its
existing instance over the singleton socket and exits before it ever
creates a Wayland surface, which is what makes this look intermittent.

Record the compositor's `WAYLAND_DISPLAY` before the override and set it
on the spawned command, so children reach the compositor while libmpv
still reaches the proxy. `open_url::open` is the only runtime spawn on
Linux, so this covers downloads, external links, About paths and
openConfigDir together. Leaving it unrecorded (X11, or the proxy never
started) keeps the inherited env untouched.

Covered by unit tests over `Command::get_envs` for the three cases:
unrecorded host, a recorded display, and a host that had none.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant