Windowing
Per-window aggregate, headless variant, and the platform shell layer
Overview
WIP. The windowing subsystem is the integration boundary between platform input and the layout engine. Two pieces sit at the centre: LayoutWindow is the per-window aggregate that drives layout each frame, and shell2 is the per-OS layer that owns the native window, drains events, and presents rendered output. Every desktop backend (Windows, macOS, Linux X11, Linux Wayland, headless) embeds a LayoutWindow and pumps events through the same shared pipeline in shell2/common.
The split exists so that everything platform-independent — the layout solver, text engine, scroll/focus/cursor managers, callback dispatch, accessibility tree construction — lives in azul-layout and runs identically everywhere, while everything platform-specific — the actual HWND / NSWindow / wl_surface, IME bridge, GL/Vulkan context creation, native menu bar, screen-reader adapter — is isolated to one directory per OS.
The HeadlessWindow is the off-screen counterpart: it implements the same PlatformWindow contract as the native shells but skips WebRender, GL, and any actual window. It exists to drive E2E tests, screenshot reftests, and memory-leak scenarios on CI runners with no display server.
LayoutWindow — the per-window aggregate
LayoutWindow (defined in layout/src/window.rs) is the per-window state that the platform shells drive each frame. It owns the StyledDom after the cascade, the LayoutCache from the previous frame, every per-window manager (ScrollManager, FocusManager, SelectionManager, CursorManager, IFrameManager, GpuStateManager, GestureManager, A11yManager), and the cached rendering resources (font metrics, image refs, scrollbar geometry).
Two entry points dominate the per-frame lifecycle:
layout_and_generate_display_list— full rebuild. Runs the userLayoutCallback, recomputes theStyledDom, runs the cascade, lays out every nested DOM, registers scroll nodes, and generates aDisplayList. Fired onUpdate::RefreshDom, font-cache invalidation, and viewport breakpoint crossings.incremental_relayout— cheap path for resize. Re-runs layout against the existingStyledDomand skips the user callback. Fired fromWM_SIZE/ConfigureNotify/xdg_surface::configure/windowDidResize:.
Every per-window manager lives directly on LayoutWindow. The full table is in code-organization; the accessibility manager and per-platform IME state are documented on the per-OS pages.
HeadlessWindow — off-screen variant
HeadlessWindow (in layout/src/headless.rs, with the platform-shell glue under dll/src/desktop/shell2/headless/) is the implementation of PlatformWindow that runs without a display server. It is selected by AZ_BACKEND=headless (or the legacy AZUL_HEADLESS=1).
Layout, callbacks, timers, scroll physics, accessibility tree construction, and the debug server all behave identically to a native window — only the rendering tail is replaced. Where a native backend reaches WebRender, headless reaches cpurender:
WebRender path: DisplayList -> WrRenderApi -> Renderer (GPU) -> swapBuffers
CpuBackend path: DisplayList -> cpurender -> Pixmap (CPU) -> (no-op or PNG)
This is what powers the reftest harness, the leak-deep-dive AZ_E2E_TEST scenarios, and Python-driven E2E tests against the public C API.
The shell2 layer
shell2 is the per-OS window-and-event layer. Each platform module owns one struct that implements the PlatformWindow trait, embedding a CommonWindowState with the LayoutWindow and shared book-keeping (current/previous FullWindowState, hit-tester, render API, document/pipeline IDs, image cache, renderer resources, font cache, frame-regeneration flags).
Per-platform sub-pages:
- Common —
AzBackend, theCompositortrait,PlatformWindow,CommonWindowState, layout-regeneration helpers, GL function loader, error types, debug server,AZ_E2E_TESTrunner, and the headless event loop. - Windows —
Win32Window,WindowProc, IME via Imm32, DPI awareness,HMENUmenu bar, UIA accessibility. - macOS —
MacOSWindow,NSApplication,NSOpenGLContext,NSTextInputClientIME,CVDisplayLinkVSYNC,NSAccessibilityadapter. - Linux X11 —
X11Window, Xlib + EGL via runtime dlopen, XKB keyboard, XIM IME with GTK fallback, AT-SPI accessibility. - Linux Wayland —
WaylandWindow,xdg-shell,text-input v3IME, KDE blur,wl_subsurfacetooltips. - Linux DBus — generic libdbus-1 dlopen, GNOME global menu via
org.gtk.Menus/org.gtk.Actions. - Menus and CSD — the unified
show_menupipeline, client-side decorations, thecsd-*stylesheet. - Accessibility — the three-layer pipeline from
AccessibilityInfothroughA11yManagerto the per-OS adapter.
Cross-platform abstractions
Every backend implements the same handful of contracts in shell2/common. The PlatformWindow trait provides default implementations for the shared logic so each platform only needs to wire getters via the impl_platform_window_getters! macro and call into the trait methods at the right points. The pieces every backend reuses:
process_window_events— state-diffing betweenprevious_window_stateandcurrent_window_state, callback dispatch, and result handling. Implemented once incommon/event.rsas a default trait method.apply_system_change— applies pre- and post-callback system effects: clipboard paste, focus changes, scroll-into-view, text changesets. Backends only need to implement the platform-specific calls (e.g.,SetClipboardTexton Win32,NSPasteboardon macOS); the dispatch is shared.- IME contracts — every backend feeds composition strings into
LayoutWindow.cursor_manager.preeditand commits via the sameprocess_text_inputentry point. The platform code differs (Imm32, NSTextInputClient, XIM, text-input v3), the layout-side code is identical. - GL / Vulkan context —
common/gl_loader.rsexportsload_gl_context(get_func)which fills aGenericGlContextfrom a caller-supplied symbol resolver. Each backend supplies a closure that delegates to its native loader (wglGetProcAddress,dlsymoverOpenGL.framework,eglGetProcAddress). - Accessibility tree push —
A11yManager::update_treeproduces anaccesskit::TreeUpdatethat each platform adapter forwards to the OS bridge (accesskit_unix,accesskit_macos,accesskit_windows). Action requests come back through the same channel and are decoded bydecode_a11y_node_id+map_accesskit_action(inazul_layout::managers::a11y). - Layout regeneration —
regenerate_layoutandincremental_relayoutare free functions incommon/layout.rs, intentionally not trait methods so they can borrow disjoint fields of the platform window.
Backend selection and event loop entry
run.rs exposes one pub fn run(...) per target_os. Every variant resolves the AzBackend once via AzBackend::resolve (driven by AZ_BACKEND, WindowCreateOptions.renderer.hw_accel, and finally Auto), then dispatches:
Web(addr)— delegate tocrate::web::run_web(HTTP server, no native window).Headless— build aHeadlessWindowand enter its loop.- Anything else — call into the OS-specific window construction (
MacOSWindow::new_with_fc_cache,Win32Window::new,LinuxWindow::new_with_resources).
The OS-specific loop bodies differ in detail but share the same phases each tick: drain native events, process pending_window_creates (popup menus, dialogs, child windows), render windows that flagged frame_needs_regeneration, and block on the OS idle primitive (NSRunLoop.runMode, WaitMessage, select(2) on the X11 fd, Condvar for headless). The per-platform pages cover the loop body in detail.
Coming Up Next
- Common — Shared shell infrastructure across platforms
- Windows — Win32 messages, DirectComposition, IME
- macOS — Cocoa, AppKit, NSTextInputClient, accessibility
- Linux X11 — Xlib, EGL, XKB, XIM
- Linux Wayland — wl_surface, xdg-shell, text-input v3
- Linux DBus — DBus integration for GNOME menus
- Menus and CSD — Unified menu pipeline and client-side decorations
- Accessibility — Per-platform a11y bridges
- Events — Hit-testing, callback invocation, the Update protocol
- Rendering — From
DisplayListto pixels