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
| Mark | Meaning |
|---|---|
✓ | 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 |
internal | implemented by a private/internal platform abstraction, not a supported standalone API |
— | absent |
N/A | intentionally 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.
| Subject | Revision reviewed | Local evidence root | Positioning |
|---|---|---|---|
sparkles:wsi | this branch | libs/wsi, this spec tree | native WSI implementation in progress |
| GLFW 3 | 1e59848 (after 3.5.1) | $REPOS/c/glfw | focused window/input library |
| raylib | dbc56a8 (6.0) | $REPOS/c/raylib | game framework; desktop WSI delegated to GLFW by default |
| SDL3 | b53f1b0 | $REPOS/c/sdl | broad public platform abstraction |
| SFML 3 | d8b93d1 (after 3.1.0) | $REPOS/cpp/sfml | multimedia window library; no Wayland backend |
| Qt QPA | Qt Base f18845a, Wayland 9638a7d | $REPOS/cpp/qtbase, $REPOS/cpp/qtwayland | internal Qt platform abstraction |
| Gio | c035a61 (after 0.10.2) | $REPOS/go/gio | immediate-mode app/window integration |
| winit | 81b2729 | $REPOS/rust/winit | callback-first window/event library |
| raw-window-handle | 1b85948 (after 0.6.2) | $REPOS/rust/raw-window-handle | typed borrowed/owned handles only |
| OpenTK 5 | 5.0-pre.16 (bf55b84) | $REPOS/csharp/opentk, tag object | prerelease PAL2; native Win32/X11/macOS plus SDL component backend |
F01–F17 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.
| ID | sparkles:wsi | GLFW 3 | raylib | SDL3 | SFML 3 | Qt QPA | Gio | winit | raw-window-handle | OpenTK 5 |
|---|---|---|---|---|---|---|---|---|---|---|
| F01 first pixel/init | partial | ✓ | ◇ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F02 resize | partial | ✓ | ◇ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F03 modal-loop survival | planned | ◐ | ◇ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F04 frame pacing | planned | ◐ | ◐ | ◐ | ◐ | internal | ✓ | ◐ | N/A | ◐ |
| F05 loop wake/external sources | partial | ◐ | — | ◐ | — | internal | ◐ | ◐ | N/A | ✓ |
| F06 keyboard/keymap | partial | ✓ | ✓ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F07 IME/text input | partial | ◐ | ◐ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F08 DPI/runtime rescale | planned | ◐ | ◐ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F09 outputs/hotplug | planned | ✓ | ◐ | ✓ | ◐ | internal | ◐ | ✓ | N/A | ✓ |
| F10 pointer capture/raw | partial | ✓ | ✓ | ✓ | ◐ | internal | ◐ | ✓ | N/A | ◐ |
| F11 scroll fidelity | planned | ◐ | ◐ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F12 cursors | planned | ✓ | ◐ | ✓ | ✓ | internal | ✓ | ✓ | N/A | ✓ |
| F13 decorations | planned | ✓ | ◇ | ✓ | ◐ | internal | ✓ | ✓ | N/A | ◐ |
| F14 state/vetoable close | planned | ✓ | ◐ | ✓ | ✓ | internal | ✓ | ✓ | N/A | ✓ |
| F15 grabbed popup | planned | — | — | ✓ | — | internal | — | — | N/A | — |
| F16 clipboard + DnD | planned | ◐ | ◐ | ◐ | ◐ | internal | ◐ | ◐ | N/A | ◐ |
| F17 threading/reentrancy | partial | ✓ | ◇ | ✓ | ◐ | internal | ✓ | ✓ | N/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.
F05remains partial because it has wake/user events and hooks but no portable public external-fd integration;F16lacks 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/Acells 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
| Subject | Wayland | X11 | Win32 | AppKit | Cross-platform fallback/delegation |
|---|---|---|---|---|---|
sparkles:wsi | native partial | native partial | native partial | native partial | SDL only as explicit separate package |
| GLFW 3 | native | native | native | native | optional Wayland libdecor decoration helper |
| raylib | delegated GLFW | delegated GLFW | delegated GLFW | delegated GLFW | GLFW default; other ports exist outside this desktop row |
| SDL3 | native | native | native | native | runtime video-driver selection |
| SFML 3 | — | native | native | native | none |
| Qt QPA | plugin | plugin | plugin | plugin | internal plugin selection |
| Gio | native | native | native | native | platform files selected by Go build constraints |
| winit | native | native | native | native | backend chosen by target/runtime |
| raw-window-handle | handle variants | handle variants | handle variants | handle variants | no implementation |
| OpenTK 5 | SDL ◇ | native + SDL | native + SDL | native + SDL | PAL2 component selection, prerelease |
Architectural comparison
| Subject | Loop ownership | Public coordinate default | Typed renderer handle | Threading rule | Dependency posture |
|---|---|---|---|---|---|
sparkles:wsi | Event Horizon only; native sources attach | logical plus mandatory physical surface metrics | closed backend sum | UI/main thread | OS APIs only in native core |
| GLFW 3 | application polls/waits | screen coordinates plus explicit framebuffer pixels | per-platform native getters | main thread required on macOS; documented restrictions | X11/Wayland client libraries; optional libdecor |
| raylib | application frame loop | physical-ish pixels through simplified API | public native handle helper is backend-dependent | delegated/backend rule | GLFW by default plus graphics/audio stack |
| SDL3 | poll/wait or main callbacks | platform-native logical policy plus pixel queries | property bag/native properties | main-thread API annotations | owns platform backends; optional helpers |
| SFML 3 | application poll/wait | pixels | getNativeHandle, type varies by platform | window thread restrictions by platform | native X11/Win32/Cocoa, no Wayland |
| Qt QPA | Qt dispatcher owns loop | device-independent Qt coordinates | private QPlatform* objects and native interfaces | GUI thread | Qt plugin stack |
| Gio | callback/event channel around platform loop | device-independent units with metric | renderer selected inside app driver, not generic public raw handle | platform main thread plus Go event loop | direct platform code |
| winit | callback-first ApplicationHandler | physical-native with explicit logical conversion | raw-window-handle traits | main thread by default/required on AppKit | direct backends plus Rust ecosystem helpers |
| raw-window-handle | N/A | N/A | its entire public purpose | handle safety/lifetime contract only | zero WSI implementation |
| OpenTK 5 | component event queue/poll/wakeup | pixel-oriented component values | typed WindowHandle plus Vulkan component | component/backend-specific | native PAL2 plus selectable SDL |
Handle handoff specifically
| Subject | Shape | Lifetime signal | Cross-backend misuse resistance |
|---|---|---|---|
sparkles:wsi | closed display/window sum with exact variants | ready through destruction start | compile-/match-time variant |
| GLFW 3 | separate native-access functions per platform | documented window lifetime | function/platform selected by caller |
| raylib | backend-dependent GetWindowHandle-style value | window lifetime | weak; generic pointer/value |
| SDL3 | typed property keys over an SDL window | window/property lifetime | runtime property lookup |
| SFML 3 | platform typedef WindowHandle | window lifetime | build-target type |
| Qt QPA | internal platform-native interfaces | QObject/platform object lifetime | runtime plugin/native-interface type |
| Gio | renderer integration is internal to app driver | frame/window driver lifetime | not a general public handoff |
| winit | HasWindowHandle/HasDisplayHandle | borrowed handle tied to owner | strong typed variants |
| raw-window-handle | borrowed/owned display and window enums | explicit borrow/ownership traits | strong typed variants |
| OpenTK 5 | WindowHandle discriminated platform handle | component window lifetime | typed 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.
- GLFW:
glfw3.h,wl_window.c, and the Sparkles GLFW deep-dive. - raylib:
raylib.h,rcore.c, andrcore_desktop_glfw.c. - SDL3:
SDL_video.h,SDL_events.h, and the Sparkles SDL3 deep-dive. - SFML 3:
WindowBase.hpp,Event.hpp, and the direct X11, Win32, and Cocoa implementations. - Qt QPA:
qplatformwindow.h,qwindow.h, theqwindowswindow.cppplugin, and the Sparkles Qt QPA deep-dive. - Gio:
app.go,os.go,ime.go, and the native Wayland, X11, Windows, and macOS drivers. - winit:
event.rs,window.rs, and the Sparkles winit deep-dive. - raw-window-handle:
lib.rs,borrowed.rs, andowned.rs. - OpenTK 5: PAL2 README,
IWindowComponent,WindowHandle, and the native Windows, X11, macOS, and SDL components.
Sparkles evidence log
| Date | Revision | Matrix changes | Evidence |
|---|---|---|---|
| 2026-08-20 | 5c17ddf21 | initialized all F01–F17 cells as planned; no implementation credit claimed | normative spec plus pinned source audit |
| 2026-08-20 | M1 shared-input preparation | no F cell changed; consolidated geometry, cell conversion, and pointer-shape imports before the WSI package consumes them | dub test :input, :ui-sdl3, :ui-raylib |
| 2026-08-20 | M3 core working tree | no native F cell changed; added unit-explicit values, generation ids, lossless events, typed handles/errors, pure backend selection, and a bounded non-reentrant recording lifecycle | dub test :wsi (11 tests); checked build; win32-ldc2 core compile |
| 2026-08-21 | M1 bounded UTF conversion | no F cell changed; added shared allocation-free UTF-8/UTF-16 conversion for the future Win32 IMM32 and AppKit adapters, including safe NUL-terminated variants | dub test :base (461 tests; 6 UTF-16 conversion cases) |
| 2026-08-21 | M2–M4 Win32 lifecycle, input + host wait | F01, F02, F05, F06, F07, and F17 → partial; added native HWND lifecycle, typed handle, metrics, queued key/text/IMM32 events, and the Event Horizon User32/IOCP hosted wait | cross-compile plus Wine/Xvfb HWND/resize/close, scan-code + UTF-16 input, IMM32 pre-edit/commit, IOCP timer/foreign-waker smoke |
| 2026-08-21 | M2/M3 AppKit lifecycle + host wait | no 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 wait | arm64 macOS NSWindow first draw/resize/close, main-thread rejection, kqueue timer/foreign-waker smoke |
| 2026-08-21 | M2/M3 X11 lifecycle + foreign fd | no 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 path | Xvfb XCB create/expose/resize/close/destroy, wrong-thread rejection, timer/foreign-waker smoke; cancelAndWait unit |
| 2026-08-21 | M2/M3 Wayland lifecycle + foreign fd | no F cell state changed; completed four-platform partial F01, F02, F05, and F17 coverage with asynchronous registry discovery, immediate configure acknowledgement and OpPollAdd readiness | no-compositor gate plus headless Weston configure/maximize/destroy, wrong-thread, timer/foreign-waker and cancellation smoke |
| 2026-08-21 | M3/M6 native Vulkan surface bridge | WSI7 and INT2 → partial; added typed Wayland/XCB/Win32 surface plans, target-native Vulkan dispatch, present-device selection and a Wayland native-I/O borrow around ICD display access | dub test :vulkan, :vulkan-wsi, :ui-sdl3; headless Weston handle → surface → present-device → re-arm; Win32 cross/Wine ABI plan |
| 2026-08-21 | M6 shared Vulkan presentation resources | no 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 SDL | dub test :vulkan-wsi (18), :ui-sdl3 (24); existing SDL Vulkan triangle builds unchanged and --help runs |
| 2026-08-21 | M6 native Wayland Vulkan triangle | no 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 access | native 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-21 | M6 X11/Win32 runtime surface probes | no 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 ran | Xvfb 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-21 | M6 scripted Wayland presentation gate | no 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 session | Weston 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-21 | M4 X11 keyboard slice | no 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 open | Xvfb 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-21 | M4 Wayland keyboard slice | no 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 X11 | full-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-21 | M4 AppKit keyboard slice | no 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 state | arm64 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-21 | conformance suite consolidation | no 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 drivers | recording 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-21 | vulkan-wsi probe consolidation | no 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 RAII | Xvfb 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-21 | M4 logical keys on all four backends | no 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 named | the 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-21 | M4 pointer and scroll slice | F11 → partial; 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 flag | conformance 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-21 | M4 scale and outputs slice | F08/F09 → partial; 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 factor | kiosk-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-21 | M4 cursor slice | F12 → partial; 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 error | cursor 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-21 | M4 drag-capture slice | F10 → partial; 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 coordinates | X11 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-22 | M4 Wayland key-repeat slice | no 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 detach | shared 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-22 | M4 Wayland text-input slice | extended 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 event | pure 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-22 | M4 X11 XIM slice | extended 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 $XMODIFIERS | end-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-22 | M4 AppKit NSTextInputClient slice | completed 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 segments | the 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 |
| 2026-08-22 | M3 modal-loop survival slice | F03 -> partial on the two platforms whose native loops steal the thread — Win32: a USER timer armed on WM_ENTERSIZEMOVE/WM_ENTERMENULOOP drives one non-blocking Event Horizon drain per WM_TIMER inside the native loop; AppKit: the common-modes kqueue source drives the drain itself (re-arming its one-shot callback) whenever it fires outside this host's own wait; hosts learn their loop by presence (noteHostLoop); Wayland/X11 need nothing — no native loop takes their thread | non-vacuous conformance property: an Event Horizon timer armed before the phase must fire during it and is the only thing that ends it, with enterModalPhase blocking throughout — Wine 15/3 with TrackPopupMenu's real menu loop cancelled by the timer's completion (SC_MOVE bails under Wine and the property caught it, as it caught the never-wired-loop hole); AppKit 15/3 with runModalForWindow stopped by the completion (plus two more catches: kqueue hands armed ops to the kernel only at the next wait, so the property steps once between arming and the phase; stopModal from a source callout needs a posted wake event); X11 16/2, weston 9/9, kiosk 13/5 skip honestly |
| 2026-08-23 | UAT echo + interactive move/resize | F13 -> first-sliver partial - startInteractiveMove/startInteractiveResize (Wayland: xdg_toplevel move/resize on the last pointer-button serial) with the shared ResizeEdge vocabulary and the resizeEdgeAt border-zone helper; wsi-input-echo is the interactive UAT program (backend auto-select, every event echoed, shm-painted grip borders on Wayland, cursor cycling, maximize toggle), and the native triangle gains border-drag resize + alt-drag move - a bare GNOME toplevel was otherwise unmovable and unresizable | resizeEdgeAt model test in dub test :wsi (21, ldc2+dmd); echo runs verified under Xvfb (X11 arm) and headless Weston (the Wayland arm maps its shm buffer, OutputEnteredEvent observed); the scripted lavapipe presentation gate re-run green with the triangle's new pointer handling; live GNOME move/resize/IME passes are exactly what the echo exists to let a human run |
| 2026-08-23 | event-queue coalescing (UAT fix) | no feature cell changed; the bounded queue now coalesces superseded observations - pushCoalesced scans the trailing run and replaces the same-kind same-window metrics/moved/expose entry before appending, so a resize-drag flood (hundreds of alternating metrics+expose per dispatch) can no longer overflow the 128-slot queue into a sticky capacity error; input/text/focus/lifecycle events anchor ordering and drained sequences stay strictly increasing; all four native emits route through it | found by live GNOME UAT with wsi-input-echo (assert at ~event 900 of a fast drag); flood unittest drives 200 alternating events through an 8-slot queue (holds 2, order anchored across a key event); dub test :wsi 23 ldc2+dmd; X11/headless-Weston/kiosk lanes and the Wine lane (15/3 + IMM32) re-run green through the coalescing path |
| 2026-09-05 | native Windows CI leg | no feature cell changed; the Win32 column gains native evidence - the test matrix has a windows-latest entry (CircleCI: test-windows) that runs dub test over 19 sub-packages through ci/test-windows.sh and then the Win32 conformance driver on a real User32/IOCP; the driver builds through its own dub recipe on both lanes, so Wine and native compile one thing; the IMM32 addendum now requires the result commit on every host and reports the preedit phase only where an IME UI shows one (a started composition must still end) | dub test :wsi 17/17 natively on -t 1 and -t 0 (the six Wayland/X11-module tests version out); the Win32 driver natively: 15 checked / 3 skipped, identical to Wine, UTF-16 WM_CHAR commit ok, IMM32 CPS_COMPLETE commits the result string natively but shows no preedit phase (the runner's US layout has no IME UI; ImmIsIME answers true on both hosts, so the preedit evidence stays with Wine); base, math, input, the runner, build-primitives, code-instrumentation, diff, dsv, fuzzy, dql, versions, twoslash-protocol, ui, tui and http pass natively too (base's source_uri tests were the one Windows compile break: a POSIX-root assert); F08 scale and modal loops under a real DWM stay owed to a non-96-DPI host - this runner is a 96-DPI desktop |
| 2026-09-06 | M4 explicit capture slice (X11) | F10 stays partial and gains its distinct-capability half on X11 - PointerCaptureMode {none, capture, confine} behind setPointerCapture and raw relative motion behind setRelativePointer; X11 implements capture and confinement as one active pointer grab (owner_events keeps in-window delivery, confine_to = the window) and raw motion as XInput 2 RawMotion selected on the root for every device (unaccelerated valuators 0/1 -> RelativePointerEvent(raw)), both unwound on destroy; a server without XI2 answers typed unsupported | three conformance properties, each present-by-introspection and accepting typed unsupported: capture requires motion reported with outside coordinates and no button held, confinement requires motion aimed outside to report from inside, relative requires a non-zero raw delta; the outside hook moves to two outside points and never positions inside first, so without a grab nothing arrives and the drive times out (revert-the-fix checked: with the grab disabled the capture property fails) - X11 19/2 under Xvfb, where XTEST relative motion had to replace core WarpPointer, which raises no raw event; the other lanes skip honestly (+3 each) until their backends land |
| 2026-09-06 | M4 explicit capture slice (Wayland) | F10 Wayland column - vendored pointer-constraints-unstable-v1 and relative-pointer-unstable-v1; confine is confine_pointer over the whole surface with a persistent lifetime, raw motion is the seat's zwp_relative_pointer_v1 whose unaccelerated deltas reach the focused window that asked (RelativePointerEvent(raw)), both dying with the surface and with the seat's pointer; explicit capture is typed unsupported because the compositor owns every grab | kiosk 14/7 - the relative property passes live through Weston (the injector's warps of Weston's X11 window become compositor-computed deltas; with no relative-pointer object created the property fails), capture and confinement skip honestly (a fullscreen kiosk surface has no outside to aim at) and a kiosk addendum proves what remains provable: the compositor accepts the constraint and its release with no protocol error, and capture is refused with the typed kind; headless Weston 9/12 unchanged; dub test :wsi 23 under ldc2 and dmd |
| 2026-09-06 | M4 explicit capture slice (Win32) | F10 Win32 column - explicit capture is SetCapture held past the buttons (the implicit press-to-release capture no longer drops it), confine is ClipCursor over the client area in screen coordinates, re-clipped on WM_MOVE/WM_SIZE and lifted while focus is elsewhere, raw motion is Raw Input for the mouse usage targeted at the window (WM_INPUT -> RAWMOUSE relative deltas -> RelativePointerEvent(raw)); WM_CAPTURECHANGED and destruction unwind all three | Wine 18/3 - all three properties pass on User32's own routing: the driver moves the real cursor (SetCursorPos beyond the client area, two points, never inside first) because a posted WM_MOUSEMOVE can show neither capture routing nor clip clamping, and injects SendInput(MOUSEEVENTF_MOVE) for Raw Input, which a cursor warp never reaches; revert-the-fix checked (with SetCapture disabled the capture property fails); the native Windows leg greps the same 18/3 |
| 2026-09-06 | M4 explicit capture slice (AppKit) | F10 AppKit column completes the vocabulary's backend set - relative motion is the delta pair every mouse event carries, reported as RelativePointerEvent(raw = false) (device motion after the system's acceleration) while a window asked for it; capture and confine are typed unsupported (a drag already routes to the mouseDown view, and macOS has no pointer confinement) rather than pretended; the echo gains g/r toggles for the two switches | AppKit 18/3 on arm64 macOS 26.6 - the relative property passes on a Core Graphics event (a posted NSEvent carries no deltas: the driver sets the delta fields on a CGEvent, names the window under the pointer, and converts it with eventWithCGEvent:), and the two capture properties run against an unreachable outside hook, so the typed refusal is what they check; all five lanes green at the slice's end (X11 19/2, kiosk 14/7 + confine addendum, headless Weston 9/12, Wine 18/3, AppKit 18/3) |
| 2026-09-10 | X11 raw motion: one event per device motion (UAT fix) | no feature cell changed; F10's X11 raw motion now keeps only the slave-originated raw event (deviceid == sourceid, XI 2.1 also copies it under the master's id) from a device whose x/y valuators are relative (asked once per device id via XIQueryDevice, forgotten on hierarchy/device-changed events): an absolute device carries positions in its raw values, not deltas, and Xwayland exposes an absolute xwayland-pointer beside its xwayland-relative-pointer, so every physical move had echoed two or three RelativePointerEvents on GNOME | found by UAT with wsi-input-echo --backend x11 under GNOME/Xwayland; X11 lane addendum sends one XTEST relative motion and requires exactly one RelativePointerEvent with the device's own delta (with the filter disabled Xvfb delivers the master's copy too and the addendum fails); 19/2 + addendum; the echo now pretty-prints every event kind; documented Xwayland's grab limits (no motion over non-X surfaces reaches a grab, the confine-time warp is dropped unless the pointer is already inside) |
| 2026-09-17 | event delivery under load (ED2, ED5) | no feature cell changed; the bounded queue stops being able to kill the loop - a full queue drops the arriving event and counts it instead of setting a sticky capacity error, and the next drain reports the loss as an EventsDropped event delivered last, carrying the last dropped sequence so the gap names what went missing; coalescing now merges in place, keeping the queued event's sequence and slot, which is cheaper than remove-and-append and is what makes that notice position the only one compatible with strictly increasing drain order | the UAT failure this came from: wsi-input-echo --backend x11 under GNOME/Xwayland with confinement and relative motion on died after roughly 74k events with "XCB dispatch/re-arm failed"; an X11 lane addendum reproduces the original failure end to end (400 XTEST motions in one burst become 804 events, of which 675 are dropped and reported while the loop keeps running; with the old behaviour restored the same addendum dies on accessed the value of an error IoResult, the shape the UAT crash took), and two unit tests pin the queue (a 4-slot queue flooded with 10 non-mergeable events reports 6 dropped and delivers 5 with the notice last, order increasing; a merged event keeps sequence 1 while carrying the newest observation); dub test :wsi 25 under ldc2 and dmd, X11 19/2 + addendum, kiosk 14/7 + addendum, headless Weston 9/12; specified as SPEC.md §4.5 ED1-ED10 |
| 2026-10-14 | event text in a pool (ED8, ED5 latch) | no feature cell changed; a queued event no longer carries its text - committed text, pre-edit and MIME type live in a TextPool (sparkles.base.pool) owned by the queue and are named by an 8-byte TextId, readable through withText during the drain that delivers them and copied out with retain; WindowEvent falls from 600 to 88 bytes and holds no pointer, so assigning it is @safe and the queue's @trusted copy is gone; a text-pool miss closes admission until the drop notice is delivered, so drained order stays increasing; window ids became pool handles on every backend (0-based index) | WindowEvent.sizeof 88 measured by probe (CompositionEvent 576 to 64 B); per backend the 128 slots shrink from 75 KiB to 11 KiB beside a 64 KiB text arena, so the memory is about the same but text is no longer silently dropped at 256/512 bytes (the bound is 65,535 per text, truncated and flagged); unit tests pin the latch (sequence 2 refused for text space, 3 refused by the latch, drained 1 then the notice), the spare in-flight text slot, a throwing sink releasing its text, and stale reads after the drain - removing the latch or the spare slot fails its test; dub test :wsi 32 under ldc2 and dmd, :dql wsi schema 3, :vulkan-wsi 19, :ui-sdl3 24; X11 19/2 with both addenda and the XIM commit read through withText; Win32 under Wine 18/3 with the IMM32 composition and commit; headless Weston 9/12; the kiosk lane passes conformance 14/7 but its confine addendum fails because Weston segfaults, identically on the commit before this series; AppKit on macOS 18/3 with the marked-text round trip, after spelling the smoke's ~-concatenated @selector strings as single literals (the frontend scans a selector argument for a NUL that a CTFE concatenation need not have, and baked the bytes after it into the selector) |