Skip to content

WSI supported-feature comparison

This is the implementation ledger for sparkles:wsi and a source-pinned comparison with nine third-party designs. It answers a narrower question than the fifteen-library research synthesis: what can an application obtain through each subject's window-system-facing API across the four Sparkles desktop targets?

Last reviewed: August 21, 2026

IMPORTANT

An internal backend implementation is not automatically a public capability. Qt QPA is explicitly marked internal; raylib and OpenTK may delegate a platform; and raw-window-handle intentionally creates no window or event. Those are architectural facts, not quality rankings.

Every capability-bearing sparkles:wsi commit updates the first column and cites its test evidence. planned is a contract only, partial is implemented with a named semantic/platform gap, and verified means the evidence lanes in SPEC §11 are complete.

Legend

MarkMeaning
verified public capability across the subject's claimed desktop platforms
public but semantically/platform incomplete for the F row
delegated to another WSI/backend rather than owned by the subject
internalimplemented by a private/internal platform abstraction, not a supported standalone API
absent
N/Aintentionally outside the subject's scope

Pinned revisions

The source trees follow the repository research convention: existing language buckets under $REPOS/<lang> are reused; no flat $REPOS clones are introduced.

SubjectRevision reviewedLocal evidence rootPositioning
sparkles:wsithis branchlibs/wsi, this spec treenative WSI implementation in progress
GLFW 31e59848 (after 3.5.1)$REPOS/c/glfwfocused window/input library
raylibdbc56a8 (6.0)$REPOS/c/raylibgame framework; desktop WSI delegated to GLFW by default
SDL3b53f1b0$REPOS/c/sdlbroad public platform abstraction
SFML 3d8b93d1 (after 3.1.0)$REPOS/cpp/sfmlmultimedia window library; no Wayland backend
Qt QPAQt Base f18845a, Wayland 9638a7d$REPOS/cpp/qtbase, $REPOS/cpp/qtwaylandinternal Qt platform abstraction
Gioc035a61 (after 0.10.2)$REPOS/go/gioimmediate-mode app/window integration
winit81b2729$REPOS/rust/winitcallback-first window/event library
raw-window-handle1b85948 (after 0.6.2)$REPOS/rust/raw-window-handletyped borrowed/owned handles only
OpenTK 55.0-pre.16 (bf55b84)$REPOS/csharp/opentk, tag objectprerelease PAL2; native Win32/X11/macOS plus SDL component backend

F01F17 matrix

The row definitions are normative in feature requirements. For third parties, means the public surface substantially covers the row on the desktop backends the library itself claims; the platform table immediately below makes missing Sparkles targets visible.

IDsparkles:wsiGLFW 3raylibSDL3SFML 3Qt QPAGiowinitraw-window-handleOpenTK 5
F01 first pixel/initpartialinternalN/A
F02 resizepartialinternalN/A
F03 modal-loop survivalplannedinternalN/A
F04 frame pacingplannedinternalN/A
F05 loop wake/external sourcespartialinternalN/A
F06 keyboard/keymappartialinternalN/A
F07 IME/text inputpartialinternalN/A
F08 DPI/runtime rescaleplannedinternalN/A
F09 outputs/hotplugplannedinternalN/A
F10 pointer capture/rawplannedinternalN/A
F11 scroll fidelityplannedinternalN/A
F12 cursorsplannedinternalN/A
F13 decorationsplannedinternalN/A
F14 state/vetoable closeplannedinternalN/A
F15 grabbed popupplannedinternalN/A
F16 clipboard + DnDplannedinternalN/A
F17 threading/reentrancypartialinternalN/A

Reading the dense rows

  • GLFW commits text but has no public pre-edit model; clipboard is text and file-drop is receive-only; frame pacing remains application/graphics-API work.
  • raylib's desktop window semantics mostly inherit GLFW, while its public event surface deliberately simplifies scroll, display and window-state detail.
  • SDL3 has the broadest public C surface in this set. F05 remains partial because it has wake/user events and hooks but no portable public external-fd integration; F16 lacks a general public drag-source peer to its drop target.
  • SFML implements X11, Win32 and Cocoa directly but no Wayland client. Rows that would otherwise be complete therefore remain partial for Sparkles' four-desktop target.
  • Qt implements most rows, often deeply, but QPA is a private plugin contract. Public applications consume QWindow/QtGui rather than QPA as a standalone WSI library.
  • Gio integrates editor/IME state and frame scheduling unusually well; grabbed native popup windows and a full bidirectional data-transfer surface are not its abstraction.
  • winit provides strong window/input/lifecycle coverage but intentionally omits clipboard and native popup abstractions; file hover/drop is receive-side only.
  • raw-window-handle's N/A cells are correct success: it standardizes handle handoff without claiming lifecycle or events.
  • OpenTK 5 PAL2 is prerelease. Its component model is broad, but Wayland currently comes through its SDL component rather than the native X11/Win32/macOS implementations.

Platform ownership

SubjectWaylandX11Win32AppKitCross-platform fallback/delegation
sparkles:wsinative partialnative partialnative partialnative partialSDL only as explicit separate package
GLFW 3nativenativenativenativeoptional Wayland libdecor decoration helper
raylibdelegated GLFWdelegated GLFWdelegated GLFWdelegated GLFWGLFW default; other ports exist outside this desktop row
SDL3nativenativenativenativeruntime video-driver selection
SFML 3nativenativenativenone
Qt QPApluginpluginpluginplugininternal plugin selection
Gionativenativenativenativeplatform files selected by Go build constraints
winitnativenativenativenativebackend chosen by target/runtime
raw-window-handlehandle variantshandle variantshandle variantshandle variantsno implementation
OpenTK 5SDL native + SDLnative + SDLnative + SDLPAL2 component selection, prerelease

Architectural comparison

SubjectLoop ownershipPublic coordinate defaultTyped renderer handleThreading ruleDependency posture
sparkles:wsiEvent Horizon only; native sources attachlogical plus mandatory physical surface metricsclosed backend sumUI/main threadOS APIs only in native core
GLFW 3application polls/waitsscreen coordinates plus explicit framebuffer pixelsper-platform native gettersmain thread required on macOS; documented restrictionsX11/Wayland client libraries; optional libdecor
raylibapplication frame loopphysical-ish pixels through simplified APIpublic native handle helper is backend-dependentdelegated/backend ruleGLFW by default plus graphics/audio stack
SDL3poll/wait or main callbacksplatform-native logical policy plus pixel queriesproperty bag/native propertiesmain-thread API annotationsowns platform backends; optional helpers
SFML 3application poll/waitpixelsgetNativeHandle, type varies by platformwindow thread restrictions by platformnative X11/Win32/Cocoa, no Wayland
Qt QPAQt dispatcher owns loopdevice-independent Qt coordinatesprivate QPlatform* objects and native interfacesGUI threadQt plugin stack
Giocallback/event channel around platform loopdevice-independent units with metricrenderer selected inside app driver, not generic public raw handleplatform main thread plus Go event loopdirect platform code
winitcallback-first ApplicationHandlerphysical-native with explicit logical conversionraw-window-handle traitsmain thread by default/required on AppKitdirect backends plus Rust ecosystem helpers
raw-window-handleN/AN/Aits entire public purposehandle safety/lifetime contract onlyzero WSI implementation
OpenTK 5component event queue/poll/wakeuppixel-oriented component valuestyped WindowHandle plus Vulkan componentcomponent/backend-specificnative PAL2 plus selectable SDL

Handle handoff specifically

SubjectShapeLifetime signalCross-backend misuse resistance
sparkles:wsiclosed display/window sum with exact variantsready through destruction startcompile-/match-time variant
GLFW 3separate native-access functions per platformdocumented window lifetimefunction/platform selected by caller
raylibbackend-dependent GetWindowHandle-style valuewindow lifetimeweak; generic pointer/value
SDL3typed property keys over an SDL windowwindow/property lifetimeruntime property lookup
SFML 3platform typedef WindowHandlewindow lifetimebuild-target type
Qt QPAinternal platform-native interfacesQObject/platform object lifetimeruntime plugin/native-interface type
Giorenderer integration is internal to app driverframe/window driver lifetimenot a general public handoff
winitHasWindowHandle/HasDisplayHandleborrowed handle tied to ownerstrong typed variants
raw-window-handleborrowed/owned display and window enumsexplicit borrow/ownership traitsstrong typed variants
OpenTK 5WindowHandle discriminated platform handlecomponent window lifetimetyped enum/fields, prerelease

Primary source anchors

The matrix was read against these public contracts and backend seams; the broader behavioral rationale remains in the linked research deep-dives.

Sparkles evidence log

DateRevisionMatrix changesEvidence
2026-08-205c17ddf21initialized all F01F17 cells as planned; no implementation credit claimednormative spec plus pinned source audit
2026-08-20M1 shared-input preparationno F cell changed; consolidated geometry, cell conversion, and pointer-shape imports before the WSI package consumes themdub test :input, :ui-sdl3, :ui-raylib
2026-08-20M3 core working treeno native F cell changed; added unit-explicit values, generation ids, lossless events, typed handles/errors, pure backend selection, and a bounded non-reentrant recording lifecycledub test :wsi (11 tests); checked build; win32-ldc2 core compile
2026-08-21M1 bounded UTF conversionno F cell changed; added shared allocation-free UTF-8/UTF-16 conversion for the future Win32 IMM32 and AppKit adapters, including safe NUL-terminated variantsdub test :base (461 tests; 6 UTF-16 conversion cases)
2026-08-21M2–M4 Win32 lifecycle, input + host waitF01, F02, F05, F06, F07, and F17partial; added native HWND lifecycle, typed handle, metrics, queued key/text/IMM32 events, and the Event Horizon User32/IOCP hosted waitcross-compile plus Wine/Xvfb HWND/resize/close, scan-code + UTF-16 input, IMM32 pre-edit/commit, IOCP timer/foreign-waker smoke
2026-08-21M2/M3 AppKit lifecycle + host waitno F cell state changed; extended partial F01, F02, F05, and F17 coverage with native NSWindow lifecycle, typed handles, point/pixel metrics, and the Event Horizon CFRunLoop/kqueue hosted waitarm64 macOS NSWindow first draw/resize/close, main-thread rejection, kqueue timer/foreign-waker smoke
2026-08-21M2/M3 X11 lifecycle + foreign fdno F cell state changed; extended partial F01, F02, F05, and F17 coverage with native XCB lifecycle, typed handles, ordered metrics/close events, and the Event Horizon OpPollAdd pathXvfb XCB create/expose/resize/close/destroy, wrong-thread rejection, timer/foreign-waker smoke; cancelAndWait unit
2026-08-21M2/M3 Wayland lifecycle + foreign fdno F cell state changed; completed four-platform partial F01, F02, F05, and F17 coverage with asynchronous registry discovery, immediate configure acknowledgement and OpPollAdd readinessno-compositor gate plus headless Weston configure/maximize/destroy, wrong-thread, timer/foreign-waker and cancellation smoke
2026-08-21M3/M6 native Vulkan surface bridgeWSI7 and INT2partial; added typed Wayland/XCB/Win32 surface plans, target-native Vulkan dispatch, present-device selection and a Wayland native-I/O borrow around ICD display accessdub test :vulkan, :vulkan-wsi, :ui-sdl3; headless Weston handle → surface → present-device → re-arm; Win32 cross/Wine ABI plan
2026-08-21M6 shared Vulkan presentation resourcesno feature cell changed; extended partial WSI7/INT2 with one backend-neutral CommandPool, FrameSync, Swapchain, acquire/resize decisions and deferred-retirement implementation consumed by native WSI and SDLdub test :vulkan-wsi (18), :ui-sdl3 (24); existing SDL Vulkan triangle builds unchanged and --help runs
2026-08-21M6 native Wayland Vulkan triangleno feature cell state changed; extended partial WSI7/INT2 with the first native frame loop — configure/frame/expose-paced presents, swapchain rebuild on compositor resize, and the native-I/O borrow widened to cover acquire and present ICD display accessnative Mutter 150-frame run with --resize-stress under VK_LAYER_VALIDATE_SYNC=1; no-compositor SKIP and --help gates scripted; dub test :vulkan-wsi (19), :ui-sdl3 (24); one shared renderer builds both hosts
2026-08-21M6 X11/Win32 runtime surface probesno feature cell state changed; extended partial WSI7/INT2 with live-ICD runtime probes on the two remaining Linux/Windows backends — typed XCB and HWND handles create a real surface, select a present-capable device, and the integrated wait dispatches after the driver ranXvfb XCB probe (lavapipe fallback when hardware ICDs cannot present) and winevulkan/Wine Win32 probe, both scripted with honest SKIP gates the lanes refuse
2026-08-21M6 scripted Wayland presentation gateno feature cell state changed; the native triangle reports the SDL handoff comparison counters (framesOver50ms/framesOver100ms, reaps, maxDispatchMs, slow-dispatch traces) and the presentation gate no longer needs a live desktop sessionWeston X11-backend-in-Xvfb lane with the client pinned to lavapipe: 150 frames with --resize-stress under VK_LAYER_VALIDATE_SYNC=1, 4 compositor resizes, 5 swapchains, 0 out-of-date, 0 frames over 50 ms; lane scripted in verify-native-wayland-triangle.sh
2026-08-21M4 X11 keyboard sliceno F06 state change; extended partial F06 coverage with the X11 column — evdev-keycode physical identity, left/right/numpad location, core-state modifiers, and press/repeat/release with XKB detectable auto-repeat requested at openXvfb XTEST shift-chorded press/release round trip through the integrated loop; pure location/modifier/held-key model tests in dub test :wsi (14); logical keys stay deferred to the xkbcommon slice
2026-08-21M4 Wayland keyboard sliceno F06 state change; extended partial F06 coverage with the Wayland column — wl_seat binding with capability arrival/departure, keymap-fd consumption, keyboard focus from enter/leave, and press/release through one shared evdev-location/xkb-real-modifier policy also adopted by X11full-stack lane: XTEST chord into Xvfb → Weston X11 backend → wl_keyboard → queued KeyboardEvent with left-shift location and chorded modifier state; the smoke maps a real shm buffer under the native-I/O borrow; dub test :wsi (15)
2026-08-21M4 AppKit keyboard sliceno F06 state change; completed partial F06 physical coverage on all four backends — the content view takes first responder, keyDown/keyUp queue Carbon virtual-keycode identity/location/repeat, and modifier keys translate from flagsChanged flag statearm64 macOS 26.6 smoke posts a synthetic left-shift chord through the real responder chain and drains it back as KeyboardEvents; pure location/modifier model tests in dub test :wsi (14 on macOS)
2026-08-21conformance suite consolidationno feature cell changed; every cross-backend behavior assertion (ordered sequences, ready metrics, expose/frame opportunities, wrong-thread rejection, typed handles, resize reporting, chorded keys, single-wait timer/waker progress, close semantics, generation-safe destruction) moved into one sparkles.wsi.conformance suite; platform smokes became thin driversrecording fake passes the value contract inside dub test :wsi (16); X11 10/0, Wayland 8/2 headless and 9/1 chord lane, Win32 10/0 plus IMM32 addendum under Wine, AppKit 10/0 on arm64 macOS 26.6 — all against identical assertions
2026-08-21vulkan-wsi probe consolidationno feature cell changed; the three native surface probes now share one sparkles.vulkan_wsi.conformance check — window → typed handles → present-capable context on the live ICD → single-wait progress — with the native-I/O borrow applied by introspection wherever the backend exposes it; VulkanContext teardown is RAIIXvfb XCB probe, headless-Weston Wayland probe, winevulkan/Wine Win32 probe, the scripted presentation gate, dub test :vulkan-wsi (19) and :ui-sdl3 (24) all green against the shared check
2026-08-21M4 logical keys on all four backendsno F06 state change; extended partial F06 with layout-derived unshifted LogicalKey identity everywhere — xkbcommon behind Wayland (keymap fd compiled under the listener) and X11 (core device map, state from the core mask), MAPVK_VK_TO_CHAR on Win32, charactersIgnoringModifiers on AppKit with private-range function keys as namedthe shared conformance chord now asserts the unshifted spelling on every backend: X11 10/0 and both Wayland lanes under Xvfb/Weston, Win32 under Wine, AppKit on arm64 macOS 26.6; keysym classifier model test in dub test :wsi (17)
2026-08-21M4 pointer and scroll sliceF11partial; extended partial WSI5 with pointer button/motion/enter/leave and scroll values on all four backends under one sign convention — Wayland frame-accumulated axes with source/discrete, X11 legacy wheel buttons, Win32 TrackMouseEvent-paired enter/leave plus wheel, AppKit hit-tested clicks (acceptsFirstMouse) with precise deltas and the inverted flagconformance click property (exact position on X11/Win32) and scroll property on X11 12/0 Xvfb, Wayland 10/2 kiosk-Weston lane, Win32 12/0 Wine, AppKit 11/1 arm64 macOS; injection via XTEST buttons, SendMessageW, posted NSEvents, and the gated external injector
2026-08-21M4 scale and outputs sliceF08/F09partial; Wayland windows derive scale from entered outputs (max policy), request the matching buffer scale, and report one atomic logical/physical/scale transition, with OutputEnteredEvent tracking the active output set; a conformance expectedScale property waits for the environment-declared factorkiosk-Weston lane at --scale=2: metrics reach scale 2 with physical = logical × 2 while chord/click/scroll still pass (11/2); Wine phase documents the per-monitor 96-DPI limitation honestly (12/1, scale skipped) — native Windows CI owes that column
2026-08-21M4 cursor sliceF12partial; the seven shared CSS-named PointerShape shapes plus per-window visibility on all four backends — vendored cursor-shape-v1 server-side shapes on Wayland, core cursor-font glyphs on X11, stock IDC_* on WM_SETCURSOR for Win32, stock NSCursor on AppKit — with unsupported reported as a typed errorcursor conformance property on every lane: X11 13/1 Xvfb, Wayland 9/5 headless and 12/2 kiosk, Win32 13/1 Wine with GetCursor proving the applied IDC_IBEAM, AppKit 12/2 with currentCursor is IBeamCursor on arm64 macOS 26.6
2026-08-21M4 drag-capture sliceF10partial; a drag leaving the window keeps reporting — Win32 gains implicit SetCapture from first press to last release (unwound on WM_CAPTURECHANGED), X11 relies on the automatic core grab, AppKit on window drag routing — and the conformance drag property requires the release to arrive with outside coordinatesX11 14/1 (release at 700,500 beyond a 640x480 window; the lane Xvfb screen grew past the window for the property to be expressible), Win32 14/1 with GetCapture proving engagement and release under Wine, AppKit 13/2 with the outside release routed to the mouseDown view; Wayland skips (kiosk fullscreen has no outside; the compositor owns its implicit grab)
2026-08-22M4 Wayland key-repeat sliceno F06 state change; closed the Wayland repeat gap in partial F06 — the backend synthesizes KeyAction.repeat from the compositor's wl_keyboard.repeat_info schedule (delay then steady interval, rate 0 disables, most recent repeating key wins, release/leave/capability-loss stop it) with at most one Event Horizon timer op in flight and a cancelAndWait reap on detachshared hold-a-repeating-key conformance property: X11 15/1 Xvfb (server detectable auto-repeat via a held XTEST key), kiosk-Weston lane 13/3 where Weston filters the X server's repeat so every observed repeat is this client's synthesis (signal-file hold choreography with the external injector); Win32 14/2 and AppKit 13/3 skip honestly (posted events do not auto-repeat); schedule model test in dub test :wsi (18)
2026-08-22M4 Wayland text-input sliceextended partial F07 with the Wayland column — vendored text-input-unstable-v3, the per-seat text input auto-enabled on the compositor's enter (IMM32's always-on role), double-buffered batches applied atomically on done (commit before pre-edit, fields reset per cycle), byte-offset cursor/selection mapped into the shared composition vocabulary with one underline segment, and an active composition ended by exactly one empty event; surrounding-text deletion is stashed but awaits a vocabulary eventpure batch/mapping model tests in dub test :wsi (20, ldc2+dmd); all Linux lanes re-run green (X11 15/1, headless Weston 9/7, kiosk 13/3 — the code is inert there because Weston implements only text-input-v1, which is why the live-IME runtime proof is recorded as owed to a text-input-v3 compositor rather than claimed)
2026-08-22M4 X11 XIM sliceextended partial F07 with the X11 column — xcb-imdkit's XIM client rides the backend's single XCB connection (xcb_xim_filter_event inside the ordinary pump: the hosted wait stays the only wait, closing M2's open question), per-window input contexts created serially and focus-followed, every key forwarded to a live IM first (commit → owned validated UTF-8 TextCommittedEvent; unconsumed → bounced back through the unchanged delivery path), inert without a server on $XMODIFIERSend-to-end lane: an in-tree test XIM server (xcb-imdkit server API, @im=test) commits "xim" for one keycode and bounces all others — X11 16/1 under Xvfb with chord/hold/click/scroll/drag all passing THROUGH the IM roundtrip; other lanes skip honestly (headless Weston 9/8, kiosk 13/4, Wine 14/3 + IMM32 addendum); dub test :wsi 20 under ldc2+dmd
2026-08-22M4 AppKit NSTextInputClient slicecompleted partial F07's column set — the content view adopts NSTextInputClient (registered via class_addProtocol: D's extern (Objective-C) has no adoption syntax and inputContext is nil without conformance), keyDown routes through the input context after the physical key, insertText commits validated UTF-8 (NSString/NSAttributedString discriminated by respondsToSelector:) and ends marked text with one empty event, setMarkedText maps UTF-16-unit selection to byte offsets (astral scalars count two) with underline + selected segmentsthe shared IME-commit property runs live on AppKit — a posted "x" returns as TextCommittedEvent through the real NSTextInputContext — and an AppKit addendum drives the protocol exactly as the context does (kana marked text with selected middle, replacing commit, single composition-end, unmark idempotence): 14/3 + marked-text round trip on arm64 macOS 26.6; UTF-16-unit mapping model-tested; found: a required protocol method (validAttributesForMarkedText) is called during context creation itself