Wine windowing, Wayland, X11, and input

Trace window and input integration through supported display backends.

14 min readChapter 162 of 168Intermediate → advanced
In this chapter

Before you start#

Review Wayland objects and the wire, Wayland surfaces and events, X11 and Xwayland, and Wine architecture. This source walkthrough pins Wine 10.0 commit b073859675060c9211fcbccfd90e4e87520dc2c2; its event timeline is synthetic, not an observed desktop session.

Learning goals#

Distinguish a Windows window from a compositor surface, follow Wine's driver dispatch and a Wayland configure, explain input and buffer lifetime, and analyze a destroy-versus-event race without inspecting the host desktop.

Source walkthrough#

A resize crosses two event systems
A resize crosses two event systems1. Windows application to Wine window driver: Request or process window change. 2. Compositor to Wine window driver: Configure a surface. 3. Wine window driver to Compositor: Acknowledge and commit an appropriate buffer. 4. Wine window driver to Windows application (return): Queue applicable Windows messages. 5. Compositor to Wine window driver (return): Release buffer when finished.Windows applicationWine window driverCompositorRequest or processwindow changeConfigure a surfaceAcknowledge and commitan appropriate bufferQueue applicable WindowsmessagesRelease buffer whenfinished
  1. Windows applicationWine window driver

    Request or process window change

  2. CompositorWine window driver

    Configure a surface

  3. Wine window driverCompositor

    Acknowledge and commit an appropriate buffer

  4. Wine window driverWindows application

    Return. Queue applicable Windows messages

  5. CompositorWine window driver

    Return. Release buffer when finished

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Windows application → Wine window driver: Request or process window change
  2. Compositor → Wine window driver: Configure a surface
  3. Wine window driver → Compositor: Acknowledge and commit an appropriate buffer
  4. Wine window driver → Windows application (return): Queue applicable Windows messages
  5. Compositor → Wine window driver (return): Release buffer when finished
Conceptual model. Illustrative ordering, not a captured compositor session.

An HWND is an opaque Windows-facing handle to a system-managed window structure; it is not a Wayland object ID, an X11 window ID, a GPU buffer, or a Linux file descriptor (Microsoft: window handles). Windows window stations and desktops organize Windows-facing objects and access; a Linux desktop compositor has its own clients, surfaces, seats and policies. Wine translates between these models. A process can have a valid Windows object while an attempted host window or display connection fails; diagnose the return path rather than assuming one handle creates all other resources.

Wine's win32u driver manager uses a table of USER driver functions. This exact excerpt from Wine 10.0 load_driver is licensed LGPL-2.1-or-later; the surrounding functions are omitted:

c
    load_display_driver();
    update_display_cache( FALSE );
    return user_driver;

The first statement attempts to load the display driver. The next refreshes cached display information, and the final statement returns the selected function table. Callers use entries in this table to reach driver operations. This does not prove a graphical connection succeeded, nor that the selected driver supports every operation. The nearby loading code checks a registry GraphicsDriver value for the relevant display configuration and has a null-driver fallback path. Selection also depends on build, configuration and available environment. Wine 10.0's Wayland driver is not a guarantee that every installation uses native Wayland or implements every desktop feature; X11 and Xwayland paths remain distinct possibilities.

The second exact excerpt is from Wine dlls/winewayland.drv/wayland_surface.c, wayland_surface_reconfigure_xdg, LGPL-2.1-or-later. It is the body of a conditional that has already checked for a processed, compatible configuration; surrounding conditions and the initial-configure alternative are omitted:

c
        surface->current = surface->processing;
        memset(&surface->processing, 0, sizeof(surface->processing));
        xdg_surface_ack_configure(surface->xdg_surface, surface->current.serial);

The first assignment adopts the processed configuration as current. The second clears the temporary processing record, avoiding reuse as though it were still pending. The final call acknowledges its serial to the compositor. The source requires a serial, a processed state and compatibility with dimensions and window state before this branch. The alternative initial-configure branch can adopt a compatible requested configuration. If no suitable current configuration exists, the function's later branch can reject the operation; acknowledging an arbitrary stale serial is not a safe substitute.

The initial-configure alternative mentioned above is the else if branch below, from wayland_surface_reconfigure_xdg, LGPL-2.1-or-later (the branches after it are omitted):

c
    /* If this is the initial configure, and we have a compatible requested
     * config, use that, in order to draw windows that don't go through the
     * message loop (e.g., some splash screens). */
    else if (!surface->current.serial && surface->requested.serial &&
             wayland_surface_config_is_compatible(&surface->requested,
                                                  width, height,
                                                  window->state))
    {
        surface->current = surface->requested;
        memset(&surface->requested, 0, sizeof(surface->requested));
        xdg_surface_ack_configure(surface->xdg_surface, surface->current.serial);

The branch needs no current serial, a requested serial and a compatible requested configuration; it then acknowledges that serial. The comment states an intent, drawing windows that never enter the message loop, which is the source's stated purpose and not a tested behavior. The excerpt does not show the later branch for an incompatible current configuration.

Driver selection and object ownership#

An application asks Windows-facing USER APIs to create and manipulate windows. Wine maintains Windows-facing state and dispatches relevant operations through its selected driver. The driver translates that state to a host protocol. On the X11 path, Wine's X11 window implementation manages X11 windows and events. On the native Wayland path, winewayland.drv manages protocol objects and a connection to a compositor. The presence of Xwayland means an X11 client can run within a Wayland desktop, but that still uses the X11-facing route from the client's perspective. Do not infer the chosen path merely from the desktop's name.

Windows and host identifiers have separate owners
Windows and host identifiers have separate ownersHWND: Windows-facing handle; Wine keys its window data by this value. → wayland_surface: Wine structure holding hwnd and current, processing and requested configurations. (stored as surface->hwnd). wl_surface proxy: Local proxy for a compositor surface, created by wayland_surface_create. → wayland_surface: Wine structure holding hwnd and current, processing and requested configurations. (created and owned by the surface). wl_surface proxy: Local proxy for a compositor surface, created by wayland_surface_create. → Shared-memory buffers: Buffers attached in the commit path; release follows the protocol. (buffers attached in the commit path). xdg_surface and toplevel: Role and configure state for a toplevel window. → wayland_surface: Wine structure holding hwnd and current, processing and requested configurations. (configure state updated for the role). HWND: Windows-facing handle; Wine keys its window data by this value. → Pointer and keyboard focus: Process-level state checked and cleared when a surface is destroyed. (compared with focused_hwnd on destroy)stored as surface->hwndcreated and owned bythe surfacebuffers attached in thecommit pathconfigure state updatedfor the rolecompared withfocused_hwnd on destroyHWNDWindows-facing handle; Winekeys its window data by thisvalue.wl_surface proxyLocal proxy for a compositorsurface, created bywayland_surface_create.xdg_surface and toplevelRole and configure state fora toplevel window.X11 WindowIdentifier on the separatewinex11.drv route; not aWayland object.wayland_surfaceWine structure holding hwndand current, processing andrequested configurations.Shared-memory buffersBuffers attached in thecommit path; release followsthe protocol.Pointer and keyboard focusProcess-level state checkedand cleared when a surfaceis destroyed.
HWNDWindows-facing handle; Wine keys its window data by this value.
wayland_surfaceWine structure holding hwnd and current, processing and requested configurations.
wl_surface proxyLocal proxy for a compositor surface, created by wayland_surface_create.
wayland_surfaceWine structure holding hwnd and current, processing and requested configurations.
wl_surface proxyLocal proxy for a compositor surface, created by wayland_surface_create.
Shared-memory buffersBuffers attached in the commit path; release follows the protocol.
xdg_surface and toplevelRole and configure state for a toplevel window.
wayland_surfaceWine structure holding hwnd and current, processing and requested configurations.
HWNDWindows-facing handle; Wine keys its window data by this value.
Pointer and keyboard focusProcess-level state checked and cleared when a surface is destroyed.
Read the text version
  • HWND: Windows-facing handle; Wine keys its window data by this value. → wayland_surface: Wine structure holding hwnd and current, processing and requested configurations. (stored as surface->hwnd)
  • wl_surface proxy: Local proxy for a compositor surface, created by wayland_surface_create. → wayland_surface: Wine structure holding hwnd and current, processing and requested configurations. (created and owned by the surface)
  • wl_surface proxy: Local proxy for a compositor surface, created by wayland_surface_create. → Shared-memory buffers: Buffers attached in the commit path; release follows the protocol. (buffers attached in the commit path)
  • xdg_surface and toplevel: Role and configure state for a toplevel window. → wayland_surface: Wine structure holding hwnd and current, processing and requested configurations. (configure state updated for the role)
  • HWND: Windows-facing handle; Wine keys its window data by this value. → Pointer and keyboard focus: Process-level state checked and cleared when a surface is destroyed. (compared with focused_hwnd on destroy)
Source map. Source map of identifiers named in Wine 10.0 winewayland.drv; the links are examples from the cited code, not every route, and the X11 route is shown separately.

The GraphicsDriver lookup in load_desktop_driver searches configuration associated with the desktop and may attempt a configured driver. load_display_driver can fall back to a null driver if desktop driver loading fails or lazy loading remains unresolved. A missing display, unavailable socket, failed driver initialization, unsupported feature or denied connection has a different cause from a Windows application never creating a window. WAYLAND_DISPLAY and DISPLAY are environment conventions used by the respective ecosystems, but setting either variable alone does not prove a functioning server, permission, build support or selection. This chapter does not change those variables or host configuration.

Wine's Wayland window code associates window data with handles and protects its tree using win_data_mutex. The removal path removes the data from that tree while locked, unlocks, destroys an associated Wayland surface if present, releases window contents and frees the data. The mutex protects the data structure; it does not make a pointer safe after its owner is freed. Other callbacks and queued work must obey their own lookup and ownership rules. A previously received numeric handle or protocol serial does not retain the object indefinitely.

A Wayland client owns local proxy objects representing server-side objects. A surface is not directly a Windows window: a toplevel role, size/configure state and one or more buffers participate in presenting content. A compositor sends configuration events asynchronously. For xdg-shell, when responding with a commit, a client must acknowledge a configure serial before that commit; if several configures arrive, it may acknowledge only the latest (xdg-shell protocol). A commit submits state; it is not proof that the frame was displayed or that the compositor has already released the attached buffer. The core protocol defines buffer release when the compositor is done using it (Wayland core protocol). Buffers must not be recycled for conflicting writes before their relevant release or synchronization condition.

Worked resize and destruction timeline#

Consider this synthetic timeline for one Windows-facing toplevel window. The application has an HWND and Wine has associated driver data and a Wayland surface. The compositor requests a new logical size of 800 by 600 and supplies configure serial 41. The driver receives and processes the configuration; the surface stores pending or processing state. A Windows message loop may receive resize-related work and arrange repainting. The exact messages, event ordering and pixel dimensions depend on driver state and scaling; the numbers here are explanatory, not captured output.

Suppose the driver's processed record contains serial 41, is marked processed and is compatible with the size and state the driver is about to commit. The quoted branch assigns that record to current, clears the processing record and acknowledges 41. A buffer of suitable dimensions can then participate in a surface commit under the applicable path. If scale is two physical pixels per logical unit in this hypothetical configuration, a straightforward full-resolution buffer might be 1600 by 1200 pixels; real fractional scaling, viewport and decoration rules require checking the actual negotiated protocols. A buffer release later permits reuse under the applicable synchronization rules. A resize request, a paint request, a commit and a displayed frame are four distinct observations.

From configure event to released buffer
  1. Configure arrives

    The compositor sends a size and serial; the driver stores them as processing or requested state.

  2. Compatibility check

    wayland_surface_config_is_compatible compares size and window state before adoption.

  3. Adopt and acknowledge

    Current is set from the stored record and xdg_surface_ack_configure sends the serial back.

  4. Commit a buffer

    A buffer of suitable size is attached and committed; the commit submits state only.

  5. Compositor releases

    The buffer is reusable only after the release condition in the Wayland protocol.

  6. Destruction intervenes

    wayland_surface_destroy clears pointer focus and constraints; late events must re-resolve their target.

Source map. Teaching model of the conditional acknowledgement path in Wine 10.0 winewayland.drv; not a captured compositor session, and a commit is not proof of display.

Now change the timeline: before a queued configure is handled, the application destroys its window. The driver removes associated data and releases its surface and contents along the relevant cleanup path. A callback arriving after that transition cannot safely dereference freed window data merely because it still has the old handle value. A correct design needs the actual event queue, proxy destruction and lookup/lifetime rules to decide whether the event is discarded or resolved to a still-live object. If a new window later receives the same numeric handle, the old event is not automatically about that new object. This counterexample is a reasoning exercise; it does not claim a specific race was observed or a Wine defect exists.

An incompatible configuration offers a second failure case. If dimensions or window state do not satisfy wayland_surface_config_is_compatible, the quoted success branch cannot be inferred. The driver may defer or reject reconfiguration instead of acknowledging that record as current. A protocol diagnostic should therefore include serial, relevant dimensions, state and object lifetime. Reporting only “resize failed” loses the information needed to distinguish an incompatible pending state from a disconnected compositor or an application that never repaints.

Input, focus, and message queues#

Wayland groups input capabilities under a seat. A compositor sends keyboard focus and key events to the appropriate client surface; applications do not have unrestricted access to every other client's input. Wine translates applicable host events into Windows-facing keyboard and pointer behavior and delivers messages through the relevant thread's Windows message queue. The seat, the protocol surface, the Windows focus window and the thread queue are different objects. A window can lose focus between a key press and later processing, so an input investigation must identify event ordering and target state rather than assuming a global keyboard stream.

Keyboard handling involves key codes, layouts, modifiers, repeat, text input and sometimes an input method editor. A physical key and a text character are different observations: a layout can map one key to different characters depending on modifiers or composition state. IME composition may have its own protocol and feature support. Converting every key event into a byte in a guessed encoding loses dead keys, non-ASCII text and composition. Whether a particular Wine 10.0 Wayland build supports an input feature depends on the implemented protocols, compositor and configuration; source and runtime evidence are needed for that exact feature.

X11 delivers events through its own connection and queue. An X server or Xwayland instance mediates windows, selection ownership, focus and input according to X11 rules and its environment. An X11 event arriving at Wine can lead to Windows messages, but there is no universal one-event-to-one-message guarantee. A window manager, compositor, application message loop and Wine driver may all contribute state. A stalled application thread that does not drain its message queue can make a visible window appear unresponsive even when the display connection is healthy. Conversely, an active message loop cannot recover a dead host connection by itself.

Scaling, clipboard, and recovery boundaries#

A Windows application can be DPI aware or rely on scaling behavior. Wine must reconcile logical Windows coordinates, monitor information and the host's surface and buffer scaling. A coordinate in logical units is not necessarily a pixel index in the backing buffer; rounding, multiple outputs and window decorations add complications. On a resize crossing monitors, a single stale scale factor can produce a mismatch between input location and rendered content. Diagnose the reported monitor, scale, surface size and buffer dimensions together instead of assuming every coordinate uses the same units.

Clipboard integration bridges Windows formats and a host selection or data-transfer protocol. Ownership and data lifetime can outlive the moment a user copies an item, and format conversion may be delayed until a recipient requests data. A missing format or inaccessible transfer is not automatically evidence of an application error; the selected driver and desktop policy matter. The exercise does not read the clipboard, record input or inspect another application's windows. The prefix and process run with host-user authority and are not a security boundary against a malicious Windows program.

On connection failure, driver creation and update operations need to unwind the resources actually acquired. Wine's Wayland surface creation has an error label that calls wayland_surface_destroy if a surface object was allocated before returning null. The destroy path also checks and clears pointer focus under its pointer mutex for an associated window. This illustrates why an early failure can require cleanup even when no usable surface is returned. It does not prove every asynchronous event or thread race is covered by one mutex. The Wayland connection source and window source are starting points for the exact failure and ownership path.

In Wine 10.0 dlls/winewayland.drv/wayland_surface.c, wayland_surface_create, LGPL-2.1-or-later (lines omitted, marked with a comment: allocation, viewport and return steps):

c
    surface->wl_surface = wl_compositor_create_surface(process_wayland.wl_compositor);
    if (!surface->wl_surface)
    {
        ERR("Failed to create wl_surface Wayland surface\n");
        goto err;
    }
    wl_surface_set_user_data(surface->wl_surface, hwnd);
    /* ... */

err:
    if (surface) wayland_surface_destroy(surface);
    return NULL;

A failed wl_surface creation jumps to err, which calls wayland_surface_destroy on the partly built surface before returning NULL. The excerpt establishes that cleanup call, not every resource the destroy function releases. The wl_surface_set_user_data line is also the source point where a Wayland proxy is tied back to its HWND.

Practice#

Use this pure state model; it opens no display, reads no input and captures no clipboard or screen. The 800 by 600 size and serial are authored example data.

sh
python3 - <<'PY'
pending = {'serial': 41, 'processed': True, 'compatible': True}
current = None
if pending['serial'] and pending['processed'] and pending['compatible']:
    current, pending = pending, None
print('acknowledged serial:', current['serial'] if current else None)
print('pending record cleared:', pending is None)
PY

The model prints serial 41 and a cleared pending record. Change the synthetic compatible value to false and it prints no acknowledged serial: this demonstrates why an event's existence alone does not satisfy the cited branch. The model is not a Wayland client and cannot validate Wine or compositor behavior. If Python is unavailable, make the same before/after table on paper.

For the source-only route, use the supplied resize and destroy timeline. List the owner and lifetime of the Windows handle, driver window data, surface configuration and buffer. Mark which observation would demonstrate receipt of a configure, acknowledgement, buffer commit and buffer release; none is interchangeable. Include the incompatible-configuration counterexample and propose what evidence would distinguish a late event from a newly created window with a reused handle. Do not start Wine or a display server for the exercise.

Knowledge check#

  1. What does the conditional acknowledgement branch require before it copies processing state to current?
  2. Why does a surface commit not prove that a buffer is immediately reusable?
  3. Why is an HWND not equivalent to a Wayland object ID or X11 window ID?
  4. What does removing driver data under a mutex fail to guarantee about a stale pointer after unlock?
  5. Why can a Wayland desktop still run Wine through an X11-facing driver?

Answers#

  1. The shown source checks for a serial, a processed configuration and compatibility with dimensions and window state. Only then does that branch adopt, clear and acknowledge the configuration. The initial configuration has a separate conditional path.
  2. The compositor can still use the attached buffer after commit. The client must honor the relevant release and synchronization rules before conflicting reuse; submission is not display or release.
  3. Each identifier belongs to a different object model and lifetime. Wine translates between them, and host object creation or connection can fail while Windows-facing state exists.
  4. The lock protects the tree mutation in its critical section; once the data is destroyed, an old pointer does not retain it. Any queued callback needs its own valid lookup and lifetime protocol.
  5. Xwayland provides an X server interface within a Wayland environment. The desktop's compositor protocol therefore does not by itself establish which Wine driver or protocol the client uses.

Checkpoint#

Submit the resize state table, buffer lifetime diagram and destroy-before-event counterexample. Pass when acknowledgement is conditional on the cited state, commit and release are distinct, and the Windows and host identifiers have separate lifetimes. Revisit the driver source and retry if the diagram treats an HWND as a compositor surface. This is not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.