Wine-NSPA – NSPA Wayland Embed Protocol

This page documents the Wayland host/plugin embedding path for winelib hosts that need Wine plugin surfaces to live inside a native Wayland application.

Table of Contents

  1. Overview
  2. Public contract
  3. Display adoption
  4. Per-window embed lifecycle
  5. Input and RT locking
  6. Relationship to X11 embedding
  7. Current consumers
  8. References

1. Overview

The Wayland embed path lets a winelib host render Wine plugin windows as wl_subsurface children of a host-owned wl_surface. It is the Wayland counterpart to the X11 embed protocol, but the ownership model is stricter: Wayland only allows subsurface parentage when both surfaces live on the same wl_display connection.

Wine-NSPA solves that by letting the host publish its wl_display before winewayland.drv initializes. In host mode, the driver adopts that display, binds its own Wayland globals on the same connection, and creates plugin surfaces as children of host surfaces.

Wayland host/plugin embedding: one process, one wl_display, two ownership domains winelib host JUCE-NSPA Wayland peer owns the parent `wl_surface` drives the event loop calls `wine_nspa_wayland_set_display()` process-local handoff `WINE_NSPA_WAYLAND_HOST` `WINE_NSPA_WAYLAND_DISPLAY` value is PID-stamped pre-driver initialization contract `winewayland.drv` adopts host `wl_display` binds compositor / subcompositor / seat does not open a second connection Wine-side window state remains authoritative plugin HWND surface fresh Wine `wl_surface` role changes to foreign subsurface position set parent-relative host-owned parent same display identity is verified foreign-surface input enters are ignored host keeps parent lifecycle authority

2. Public contract

The supported interface lives in include/wine/nspa_wayland_embed.h.

Symbol Meaning
WINE_NSPA_WAYLAND_HOST host-mode marker; tells winewayland.drv to wait for display adoption instead of opening its own connection
WINE_NSPA_WAYLAND_DISPLAY PID-stamped pointer handoff for the host wl_display
wine_nspa_wayland_set_display() host helper that publishes the current process id and wl_display pointer
WM_WAYLANDDRV_NSPA_EMBED_WINDOW request that a Wine HWND become a wl_subsurface of a host wl_surface
WM_WAYLANDDRV_NSPA_EMBED_DONE async completion message posted after the subsurface install path runs

The display handoff is process-local. Child Wine helper processes that inherit the environment do not reuse the parent’s pointer because the value is checked against getpid().


3. Display adoption

Wayland embedding has to avoid a load-order race. A driver export would be too late because winewayland.drv initializes as soon as it is loaded. The host therefore publishes the display pointer before the first Wine GUI call.

The host-side sequence is:


setenv ("WINE_NSPA_WAYLAND_HOST", "1", 1);
wine_nspa_wayland_set_display (juceWaylandDisplay);

Then winewayland.drv initializes against the host connection:

  1. read WINE_NSPA_WAYLAND_HOST
  2. parse WINE_NSPA_WAYLAND_DISPLAY
  3. accept it only if the PID matches the current process
  4. bind wl_compositor, wl_subcompositor, wl_shm, xdg_wm_base, wp_viewporter, and wl_seat on that display
  5. skip the normal wl_display_connect() path for this process

When the environment is absent or invalid, the Wayland driver follows the normal standalone Wine path.


4. Per-window embed lifecycle

Once the plugin has an HWND, the host sends WM_WAYLANDDRV_NSPA_EMBED_WINDOW to that window.

Message field Meaning
wParam host parent wl_surface*
lParam packed parent-relative peerX, peerY coordinates

The driver path:

  1. resolves wayland_win_data for the HWND
  2. verifies that the foreign parent belongs to the adopted wl_display
  3. destroys any existing Wine surface for that HWND
  4. creates a fresh role-less wl_surface
  5. assigns it as a wl_subsurface of the host parent
  6. records embedded state and parent-relative coordinates
  7. posts WM_WAYLANDDRV_NSPA_EMBED_DONE

The fresh-surface step is important. Wayland surface roles are not mutable once a buffer has committed in a role such as xdg_toplevel, so the embed message must run before plugin editor attach paths cause a first commit.

Per-window Wayland embed lifecycle host peer owns parent `wl_surface` has adopted display live sends embed message driver message `WM_WAYLANDDRV_NSPA_EMBED_WINDOW` parent surface + peer coords before plugin attach commits winewayland.drv verify same display create fresh surface assign foreign subsurface role plugin editor renders into Wine surface host surface is parent completion message follows Role timing rule the host sends the embed request before VST/CLAP editor attach calls so the Wine surface can become a subsurface before the plugin commits a buffer

5. Input and RT locking

Two follow-on changes make the host-mode path usable for plugin editors in an RT-oriented process.

Area Current behavior
Foreign-surface enter events pointer and keyboard enter events on host-owned foreign surfaces are ignored by winewayland.drv; the host remains owner of its own surface focus
Driver mutexes winewayland.drv internal pthread mutexes are converted to Wine-NSPA pi_mutex_t so Wayland driver locks follow the same priority-inheritance policy as the rest of the RT stack
Initialization guard the lazy Wayland initialization path avoids a non-PI pthread mutex on the RT thread

This matters because host-mode embedding puts native host surfaces and Wine plugin surfaces in one process and one Wayland connection. The Wine driver must not claim input focus for the host shell, and it must not introduce ordinary pthread-mutex inversion into the RT process.


6. Relationship to X11 embedding

The X11 and Wayland protocols solve the same application problem with different window-system primitives.

Area X11 path Wayland path
Parent type X11 Window id wl_surface*
Wine child type Wine X11 child window Wine wl_surface
Attach primitive XReparentWindow() + embedded-state bookkeeping wl_subsurface under a foreign host surface
Connection rule host and Wine can use independent X11 clients host and Wine must share one wl_display
Display handoff not needed host publishes wl_display before driver init
Completion message WM_X11DRV_NSPA_EMBED_DONE WM_WAYLANDDRV_NSPA_EMBED_DONE

The consumer pump discipline is shared: embedded editors need input-first queue passes and a bounded general drain so plugin redraw or timer traffic cannot starve the native host loop.


7. Current consumers

Consumer Use
JUCE-NSPA provides the Wayland peer surface and exposes the host wl_display used by Wine-NSPA’s Wayland driver
Lulada enables the JUCE-NSPA Wayland backend for the application shell and embeds Windows plugin editors through the Wine-NSPA Wayland path on Wayland sessions

Yabridge-NSPA still uses the X11 embed path because its editor bridge keeps a wrapper X11 window as the immediate foreign parent.


8. References