Skip to content

Cross-Library Synthesis: How Fifteen Toolkits Integrate with the Window System

The capstone of the window-system-integration survey. It distils the fifteen deep-dives — Winit, SDL3, GLFW, sokol_app.h, Qt 6, GTK 4, Flutter Engine, Chromium Ozone, Avalonia, .NET MAUI, Uno Platform, Slint, wxWidgets, JUCE, and Smithay SCTK + libdecor — and the shared concepts vocabulary into a single head-to-head comparison along the ten dimensions every deep-dive analyses: window lifecycle, the event loop, input (incl. IME), Wayland specifics, DPI/scaling, multi-window/popups, threading, clipboard/DnD, escape hatches, and history/regrets.

The goal is not to crown a winner — these systems occupy different niches, from a 3-D-app shim (sokol) to a browser's platform layer (Chromium Ozone) — but to surface where the field is unanimous (the consensus standards a new toolkit should simply adopt), where it forks (the genuine architectural decisions, with the strongest argument on each side drawn from the evidence), and what gap a from-scratch framework faces today (the delta table that bridges into recommendations).

NOTE

Scope. This is the synthesis leaf of the survey. It assumes the shared mechanics — client-vs-server decorations, scancode/keysym/virtual-key, logical vs physical coordinates, the scale factor, IME pre-edit, override-redirect vs xdg_popup grab, the Win32 modal resize loop, raw vs accelerated pointer, no-buffer-no-window, frame-callback vsync, and readiness vs completion in the loop — as defined in concepts, and cross-links rather than re-derives them. Each per-subject deep-dive is the source of truth for its own claims.

Last reviewed: June 8, 2026


Part 1 — The systems at a glance

One row per surveyed system. Loop ownership is the readiness-vs-completion loop-ownership fork (poll = app-owned; callback = library/OS-owned; hybrid = both). Native coord unit is the logical-vs-physical design choice. Wayland decoration is the CSD/SSD strategy. Escape hatch is the raw-handle / native passthrough shape (dimension 9).

LibraryLanguageCategoryLoop ownershipNative coord unitWayland decorationThreadingEscape hatch
WinitRustwindowing librarycallback (own loop)physicalown-drawn Adwaita (sctk-adwaita)main-thread; with_any_threadraw-window-handle + per-platform getters
SDL3Cwindowing + media libraryhybrid (poll / callbacks)mixed (per-platform)SSD-first → libdecor CSDmain-thread (AppKit/UIKit)properties bag + message-pump hooks
GLFWCminimal windowing librarypoll (app-owned)mixed (window vs fb size)libdecor → SSD → own-drawn fallbackmain-threadtyped glfwGet*Window accessors
sokol_app.hC99single-header app shimcallback (own loop)mixed (physical fb)N/A — no Wayland backend (X11 only)callbacks one threadtype-erased void* getters
Qt 6 (QPA)C++full GUI framework (QPA layer)hybrid (Qt owns QEventLoop)logicalown-drawn CSD plugins (predates libdecor)GUI = main threadQNativeInterface + native event filter
GTK 4 / GDKC (GObject)full framework (ref. WL client)callback/hybrid (GLib source)logicalown-drawn CSD; only KDE SSD protocolsingle main threadnative handle getters + wl_display exposure
Flutter EngineC/C++engine / embedder frameworkcallback/hybrid (embedder)mixed (logical + ratio)N/A — delegates to GTK 3 / GDKplatform = embedder main threadnative handle getters + WindowProcDelegate
Chromium OzoneC++browser platform-abstractionhybrid (fd → MessagePump)mixed (DIP + pixel)SSD-first → own Views-drawn CSDUI thread; GPU off-threadgfx::AcceleratedWidget + mix-in extension interfaces
AvaloniaC# (.NET)from-scratch Skia UI frameworkhybrid (managed Dispatcher)logical (DIPs)N/A — no native Wayland (X11/XWayland)UI thread; render off-threadTryGetPlatformHandle + WndProcHookCallback
.NET MAUIC# (.NET)native-control-wrapping frameworkcallback (owns no loop)logical (DIPs)N/A — no Linux desktop backendmain-thread onlyhandler.PlatformView (literal native window)
Uno PlatformC# (.NET)WinUI-over-Skia frameworkhybrid (per-platform)logical (view pixels)N/A — no native Wayland (XWayland)dispatcher thread; render offNativeWindow handle getters + per-window WndProc
SlintRustreactive UI on a backend abstr.hybrid (winit / calloop KMS)logicalown-drawn CSD via winit (sctk-adwaita)loop thread; main on macOSwith_winit_window + raw-window-handle
wxWidgetsC++native-widget-wrapping frameworkcallback/delegated (native)logical (DIPs)N/A — delegated to GTKmain-thread onlyGetHandle() + MSWWindowProc + wxNativeWindow
JUCEC++audio-focused GUI frameworkhybrid (own / host-as-plugin)mixed (logical Component)N/A — no Wayland backend (X11 only)single message threadgetNativeHandle() void* + FD registration
Smithay SCTK + libdecorRust + CWayland client toolkit + CSD helperpoll (caller-owned)logical (integer only)three-tier: SSD → FallbackFrame → libdecorno main-thread ruleraw proxy getters; caller bridges raw-window-handle

Two structural observations fall straight out of the table and frame the rest of this document:

  1. The "Wayland decoration" column is mostly empty. Of fifteen, only six speak Wayland natively (Winit, SDL3, GLFW, Qt 6, GTK 4, Chromium Ozone, Slint via winit, Smithay); the rest either have no Linux backend (MAUI), run under XWayland as an X11 client (Avalonia, Uno, JUCE, sokol), or delegate to GTK (Flutter, wxWidgets). Native Wayland is the single biggest gap in the field — and the headline finding for a new toolkit (see §Dimension 4 and §Part 5).
  2. Loop ownership and native coordinate unit are the two axes that genuinely fork. Every other dimension trends toward a consensus (Part 3); these two are real decisions with strong arguments on both sides (Part 4).

Part 2 — Per-dimension comparison

Each subsection cuts one of the ten analysis-spine dimensions across all fifteen subjects.

Dimension 1 — Window creation & lifecycle

The fork is synchronous map (X11/Win32) vs the asynchronous no-buffer-no-window handshake (Wayland). On X11/Win32, CreateWindowEx+ShowWindow / XMapWindow puts the window on screen immediately; on Wayland a wl_surface is invisible until a buffer is committed and the compositor has sent the initial xdg_surface.configure.

LibraryCreate-window model
Winitconstructor blocks in roundtrip+blocking_dispatch until is_configured()
SDL3Wayland_ShowWindow spins on libdecor_dispatch until the first configure
GLFWxdg_toplevel created only if visible/monitor; commit+roundtrip before first buffer
Smithay SCTKdraws the first frame inside WindowHandler::configure, never on creation
Chromium Ozonesurface unmapped until buffer; set_window_geometry silently dropped without one
Slintdestroys the window on hide (winit won't create an invisible Wayland window)
Qt 6QWaylandWindow::isExposed() false until configure; expose fires only after it
GTK 4gdk_toplevel_present() single async entry; freeze/thaw on first configure
Flutterlifecycle delegated to GDK; engine itself owns no window
Avalonia, Uno, JUCE, sokolsynchronous X11 map (no Wayland to negotiate)
MAUI, wxWidgetsdelegated to the native/host stack (WinUI, GTK, UIKit)

The recurring lesson, recorded most sharply in SDL3's bug tail (KDE bug 448856): retrofitting a synchronous "create then show" API onto the async handshake is fragile — model the lifecycle as explicit state from the start. The same rule produces the universal Wayland no-op: clients cannot set their own toplevel position (SDL3, GLFW, GTK 4, Chromium Ozone, Smithay all record SetWindowPosition as a silent no-op), because the compositor owns placement.

Dimension 2 — The event loop

Three loop-ownership models, mapped to readiness vs completion in the loop:

ModelWho drivesSubjects
Poll (app-owned)app calls PollEvents / dispatches the loopGLFW, default SDL3, Smithay SCTK
Callback (library/OS-owned)control inverted; the OS/library calls the appWinit (ApplicationHandler), sokol, MAUI, opt-in SDL3
Hybrid (own loop over native source)toolkit owns a dispatcher wrapping the native pumpQt 6, GTK 4, Avalonia, Uno, JUCE, Flutter, Chromium Ozone, wxWidgets, Slint

On Linux the loop is, underneath, a readiness reactor over the display socket fd. Winit wraps the Wayland socket as a calloop WaylandSource and exposes AsFd so embedders can poll it; Smithay integrates via calloop-wayland-source; Chromium Ozone plugs the fd into base::MessagePump and runs a dedicated fd-watch thread to dodge the libwayland prepare_read single-reader deadlock when a GPU/EGL also reads it; GTK 4 integrates as a GLib GSource (and inverts on macOS so CFRunLoop drives GLib via a select thread).

Two pervasive event-loop hazards recur:

  • The Win32 modal resize/move loop freezes redraws during a titlebar drag. The survey-wide fix is identical: SetTimer on WM_ENTERSIZEMOVE, drive a frame from WM_TIMER (SDL3, GTK 4, sokol, Uno). Winit and sokol add the dummy-WM_MOUSEMOVE trick to cancel the ~500 ms first-click pause. Qt 6, JUCE, wxWidgets track an in-size-move flag and accept the loop is mitigated, not eliminated.
  • Nested native loops on macOS. wxWidgets and Avalonia document at length that [NSApp run] may only be the outermost loop; nested loops must use nextEventMatchingMask:. Flutter adds a custom CFRunLoopMode (FlutterRunLoopMode) to common+private modes to survive modal run loops — the cleanest recorded fix for a guest-on-host-loop design.

Dimension 3 — Input (keyboard, pointer, IME)

Keyboard input splits the scancode / keysym / virtual-key identities. On Linux the consensus translator is xkbcommon; the cautionary cases are the toolkits that ship their own table and inherit layout bugs.

ConcernConsensus / fork
Keymap state machinexkbcommon everywhere on Linux (Winit, SDL3, GTK 4, Chromium Ozone, Smithay); own table = bug (Avalonia's X11KeyTransform, JUCE's XLookupString-only, wxWidgets hardcoded us)
Wayland keycode offsetuniversal +8 before every xkb call (wl_keyboard delivers raw evdev)
Key repeat (Wayland)client-synthesised (timer per held key) everywhere — the protocol only sends rate/delay
High-res scrolluniversal axis_value120 / WHEEL_DELTA=120 accumulator (1/120-detent) (SDL3, Smithay, GLFW, Qt 6, GTK 4)
Raw/relative pointerseparate event source (zwp_relative_pointer_v1 / WM_INPUT); GTK 4 binds none (not a game toolkit)

IME (pre-edit/composition) is the most uneven dimension and produces two field-wide findings:

PlatformModern protocolWhat the field actually does
Waylandzwp_text_input_v3SDL3, Winit, Qt 6 bind it; GDK does not (GTK 4 does IME above GDK); Smithay ships no consumer
Win32Text Services Frameworknobody uses TSF — SDL3, Winit, Qt 6, GTK 4, JUCE, Avalonia, Uno all use legacy IMM32
macOSNSTextInputClientSDL3, Winit, JUCE implement it; GLFW mispositions the candidate window
X11XIM (or D-Bus IBus/Fcitx)Winit/SDL3 use XIM; Uno uses D-Bus IBus/Fcitx; GLFW, JUCE, sokol omit pre-edit entirely

Finding 1: the universal IMM32-over-TSF choice on Windows — not one toolkit uses the modern TSF. Finding 2: IME is frequently absentGLFW (issue #41 open since 2013), JUCE's X11, sokol, Smithay, and MAUI all punt it, confirming that pre-edit is table-stakes-but-tedious and a reusable windowing layer should own the consumer rather than push it upward (as GTK 4 does to GDK).

Dimension 4 — Wayland specifics

The dimension that most sharply separates the field. Only six subjects are native Wayland clients; the rest are absences-as-findings.

Wayland postureSubjects
Native Wayland clientWinit, SDL3, GLFW, Qt 6, GTK 4, Chromium Ozone, Smithay SCTK (+ Slint via winit)
Delegates to GTK/GDKFlutter (GTK 3), wxWidgets (GTK 3)
XWayland only (X11 client)Avalonia, Uno, JUCE, sokol
No Linux desktop backend at allMAUI

Even among native clients, the protocol coverage is patchy and itself a finding:

  • Decorations: GTK 4 binds neither libdecor nor zxdg-decoration-v1 (only the KDE protocol), so on wlroots it always falls back to own CSD. Chromium Ozone and SDL3 prefer SSD then fall back (Ozone to own Views-drawn CSD, SDL3 to libdecor) because GNOME/Mutter advertises no xdg-decoration at all.
  • Fractional scale: Smithay SCTK implements none (integer only) — the cautionary tale; GTK 4/Qt 6/GLFW bind wp_fractional_scale_v1+viewporter.
  • Pointer constraints / relative motion: GTK 4 binds none — no FPS-style input.
  • layer-shell / idle-inhibit: unbound by GTK 4 and upstream Qt 6 6.8.

Smithay SCTK + libdecor is the catalog authority for this dimension: it is the only Wayland-only subject, and its three-tier decoration story (compositor SSD → FallbackFrame → libdecor dlopen plugin) is the reference model.

Dimension 5 — DPI & scaling

The native-coordinate fork (dimension covered in §Part 4) plays out here as where the scale comes from and two hazards. See the scale factor.

PlatformScale source (consensus)
Waylandwp_fractional_scale_v1 (preferred scale as integer 1/120ths) + wp_viewporter
Win32Per-Monitor-V2 awareness; WM_DPICHANGED (scale = dpi/96)
macOSNSWindow.backingScaleFactor (integer 1×/2× only)
X11no per-surface protocol — scrape global Xft.dpi or guess (the universal weak spot)
  • Created-at-wrong-scale: Winit's ScaleFactorChanged ships a surface_size_writer; Slint dispatches ScaleFactorChanged on creation before first paint — the two cleanest cures. GTK 4/wxWidgets correct after realization on the first configure.
  • Mixed-DPI migration: Win32 re-fires WM_DPICHANGED and Wayland re-fires preferred_scale; X11 single-global-DPI cannot — so every XWayland/X11-only toolkit (Avalonia (#13450), Uno, JUCE integer-only + dconf shell-out, sokol high-DPI TODO) inherits blurry mixed-DPI.

Dimension 6 — Multi-window & popups

Two independent capabilities: multiple top-levels, and transient override-redirect / xdg_popup grab surfaces (menus, tooltips, dropdowns).

CapabilitySubjects
Multi-window first-classmost frameworks (Qt 6, GTK 4, Avalonia, Uno (5.2), Flutter (settling), wxWidgets, JUCE)
Single window onlysokol (static _sapp precludes it architecturally)
No cross-platform popup grabWinit (only X11 _NET_WM_WINDOW_TYPE hints — punts entirely)
Real OS popups (X11 override-redirect)SDL3, Avalonia (ManagedPopupPositioner), Qt 6 (BypassWindowManagerHint)
Real OS popups (xdg_popup grab)SDL3, Smithay (Popup/XdgPositioner), libdecor window-menu grab
In-canvas popups (no OS sub-surface)Uno, Slint (winit backend) — sidestep the fork, lose compositor dismiss

The cross-platform consequence the survey keeps hitting: a popup abstraction cannot be a simple "place this window at (x, y)" call, because that maps onto neither the X11 override-redirect model nor the Wayland parent-relative xdg_positioner model cleanly. Winit punts it to consumers; the framework toolkits either draw in-canvas or carry both native paths.

Dimension 7 — Threading

The most unanimous dimension in the survey: GUI = main thread, forced by macOS AppKit.

Threading postureSubjects
Main-thread-only for windows/eventsSDL3, GLFW, Qt 6, GTK 4, Avalonia, MAUI, wxWidgets, JUCE, Flutter, Chromium Ozone (UI thread)
Main-thread on macOS, relaxed elsewhereWinit (MainThreadMarker panics off-main; with_any_thread on Linux/Win), Slint
No main-thread ruleSmithay SCTK (Wayland has no such constraint; events on the dispatch thread)

The constraint is driven by one platform — every deep-dive that explains why it is main-thread-only (JUCE, Qt 6, wxWidgets, Avalonia) names AppKit. Rendering is commonly off-thread (Avalonia, Uno, Chromium Ozone's sandboxed GPU process); cross-thread work marshals back via a dispatcher/CallAfter/MessageManagerLock. The only escapee is Smithay, because it never touches AppKit.

Dimension 8 — Clipboard & drag-and-drop

The consensus is that clipboard is a negotiated, MIME/atom-typed, chunked transfer, and that doing it well (large transfers, rich formats, DnD) is where toolkits cut corners.

BehaviourField finding
X11 large transfers (INCR)done right by Qt 6, GTK 4; deliberately skipped by JUCE (~1 MB cap, "a pain in the *ss"), sokol (detected, not implemented), Uno (read-only)
Async MIME-stream clipboardGTK 4 (with delayed rendering) is the model
Wayland MIME mappingwxWidgets needs an X11-atom→Wayland-MIME table (wxGTKGetAltWaylandFormat)
Text-only clipboardSlint, Flutter (Linux text/plain only) — DnD/rich formats punted
No portable drag-sourceSDL3 (drop-target only); DnD is plugin territory for Flutter

DnD is consistently the weakest corner: no surveyed minimal/windowing library offers a portable drag source, and frameworks that have it inherit it from the wrapped native toolkit.

Dimension 9 — Escape hatches

Universal: every toolkit exposes the native handle. The fork is how typed it is, and whether raw native-event passthrough exists. The consensus modern shape is raw-window-handle-style typed handoff for GPU interop.

Escape-hatch shapeSubjects
Typed raw-window-handle (rwh 0.6)Winit, Slint (caller bridges in Smithay)
Typed per-platform accessorsGLFW (glfwGet*Window), Qt 6 (QNativeInterface), Avalonia (IMacOSTopLevelPlatformHandle)
ABI-stable property/struct lookupSDL3 (properties bag), sokol (sapp_get_swapchain value struct)
Type-erased void*sokol, JUCE ("no guarantees what you'll get back")
Literal native window objectMAUI (handler.PlatformView), Flutter (HWND/NSView/GtkWidget)
Raw message-pump passthroughSDL3 (SetWindowsMessageHook/SetX11EventHook), Qt 6 (native event filter), Avalonia/Uno/wxWidgets/JUCE (WndProc); Winit (Win32 msg_hook); GLFW has none
Capability probing (not a fat interface)Avalonia (TryGetFeature<T>), Qt 6 (hasCapability), Slint (WindowAdapter trait), Chromium Ozone (mix-in extension interfaces)

The strongest recorded pattern is naming the leak: Chromium Ozone's X11Extension and Avalonia's [PrivateApi]/TryGetFeature<T> document exactly where the abstraction is incomplete instead of pretending completeness — the opposite of JUCE's un-typed void*.

Dimension 10 — History, redesigns & known regrets

The field's collective scar tissue, and the most direct input to a new design:

LibraryDefining redesign / standing regret
WinitEvent Loop 2.0 (iterator → callback, 2018-20); 0.30 ApplicationHandler; recurring renames
SDL3main-callbacks model; properties bag replaces typed getters; gestures removed (no replacement)
GLFWWayland 3.2 (2016), libdecor 3.4 (2024); IME still omitted (issue #41 since 2013)
sokolwindow/swapchain decoupled (2025); MTKView → CAMetalLayer+CADisplayLink (2026); Wayland open since 2020
Qt 6QPA "Lighthouse" (2012); high-DPI always-on in 6.0; QPA contract kept \internal
GTK 4GdkWindow removed → one GdkSurface per toplevel (4.0); Wayland-first protocol expansion
FlutterGLFW prototype → GObject GTK 3; multi-view (2022-23); multi-window (2024-25, settling); GTK 3 pin
Chromium Ozone"Interfaces not ifdefs" (2013-16); dedicated Wayland fd thread (2021); legacy X11 removed (2021)
AvaloniaDispatcher rewrite (2023, WPF model); platform interfaces marked [PrivateApi] (v11); Wayland open since 2017 (#1243)
MAUIXamarin.Forms renderers → handler/PropertyMapper (2022); Linux desktop repeatedly declined
Unofrom-scratch X11/Win32 hosts replace GTK+3 (5.2, 2024, −200 MB); multi-window (5.2)
Slintwinit 0.30 forced ApplicationHandler + lazy WinitWindowOrNone (1.7); winit default everywhere (1.16)
wxWidgetsGTK1→2→3 migrations; GTK4 still not shipping (no --with-gtk=4); own wl_display for pointer-warp
JUCEDirect2D backend (JUCE 8, 2024); X11-only on Linux (Wayland gap since 2019, #549)
Smithayrewritten onto wayland-rs 0.30+ handler-trait model (~2020); delegate_dispatch2! collapse (2025-26)

The two regrets that every verdict converges on: (a) deferring native Wayland inherits XWayland's ceiling (no fractional scale, no frame-callback vsync, no xdg_popup grabs) — voiced by Avalonia (8-year delay), JUCE, Uno, sokol, MAUI; and (b) under-specifying the windowing contract (pushing title/decoration/popups/fullscreen onto embedders) yields "silently unsupported per platform" — voiced by Flutter and MAUI.


Part 3 — The consensus standards

Where the field is unanimous, a new toolkit should simply adopt the standard and move on. The survey establishes these:

  1. xkbcommon for keymaps on Linux. Every native Linux backend uses xkbcommon for scancode→keysym translation; the toolkits that ship their own table (Avalonia, JUCE, wxWidgets) are explicitly recorded as inheriting layout bugs. Corollary standards: the +8 evdev→xkb keycode offset and client-side key-repeat synthesis on Wayland.
  2. GUI on the main thread. Forced by macOS AppKit and adopted uniformly (§Dimension 7); rendering goes off-thread, cross-thread work marshals back through a dispatcher.
  3. raw-window-handle-style typed handle handoff for windowing↔rendering decoupling. Winit pioneered drawing nothing and handing a typed handle to the GPU layer; it is now the reference shape (Slint, and the pattern JUCE's void* is critiqued against).
  4. The axis_value120 / WHEEL_DELTA=120 high-resolution scroll accumulator — the 1/120-detent convention shared verbatim between Wayland and Win32, accumulated per frame (SDL3, Smithay, GLFW, Qt 6, GTK 4).
  5. Per-Monitor-V2 DPI awareness + WM_DPICHANGED (scale = dpi/96) on Win32, and wp_fractional_scale_v1 + viewporter on Wayland, as the scale sources.
  6. "SSD is only a hint." Per the xdg-decoration protocol there is no reliable way to force or forbid server-side decorations, so every serious toolkit must carry a CSD path (§Dimension 4) — the lesson GNOME forced on the ecosystem.
  7. Model the Wayland window lifecycle as explicit async state — the no-buffer-no-window handshake (commit-without-buffer → wait for configure → ack → attach buffer) cannot be hidden behind a synchronous "create then show" without fragility.
  8. The Win32 SetTimer-driven live-resize workaround for the modal resize loop — identical in spirit across every Win32 backend.

These are not decisions; they are table stakes.


Part 4 — The architectural trade-offs (where the field forks)

Four genuine forks, each presented as a decision with the strongest argument for each side drawn from the evidence.

Fork A — Poll vs callback event loop

SideStrongest argument (from the evidence)
Poll (app-owned)The smallest honest API; the app stays in control and can add its own fd sources. GLFW's PollEvents/WaitEvents/PostEmptyEvent is "the smallest honest event-loop API."
Callback (library-owned)The only model that survives Web/iOS/macOS, where the OS owns the loop. Winit abandoned its poll_events() iterator for exactly this reason; SDL3 added opt-in SDL_MAIN_USE_CALLBACKS to run where the OS owns the loop without #ifdefs.

Resolution the evidence points to: the hybrid (SDL3) — a dead-simple app-owned pump as the default plus an opt-in inverted callback model — is the design most verdicts praise, because the callback model is mandatory on some platforms but the poll model is the more ergonomic default everywhere else. The deeper requirement, voiced by GLFW and sokol's verdicts and the readiness concept, is an external-fd integration point so an async runtime and the windowing loop can share the process's blocking wait — Winit's calloop/AsFd story is the reference; its absence in GLFW/SDL3 is the recorded gap.

Fork B — Logical vs physical native coordinates

SideStrongest argument (from the evidence)
Physical (device pixels)No hidden conversion; the app sees exactly what the GPU rasterizes and converts itself. Winit's PhysicalSize/PhysicalPosition are the API-native unit; its surface_size_writer lets the app rewrite the post-rescale physical size in-callback.
Logical (DIPs)The app lays out resolution-independently; physical pixels appear only at the OS seam. The majority of frameworks (Qt 6, GTK 4, Avalonia, JUCE, Slint, MAUI) choose this, converting at the QPA-style seam (QHighDpiScaling).
Mixed (per-platform unit)Honest to each OS but inconsistent. SDL3 uses each platform's native unit and exposes scale rather than hiding it (distinct pixel-density / content-scale / display-scale with guaranteed change events); GLFW splits window-size vs framebuffer-size.

Resolution the evidence points to: for an application framework, logical is the majority and the ergonomic default — but the universal corollary, learned the hard way across the survey, is never size the render target off the logical window size; size it off the physical/framebuffer size (or the pixel-size-changed event), or you render blurry. The cleanest cure for the created-at-wrong-scale transient is Slint's "learn the scale before first paint."

Fork C — Wayland decorations: libdecor vs own-drawn CSD vs punt

SideStrongest argument (from the evidence)
libdecor (delegate CSD)No per-toolkit decoration maintenance; native-looking frames via the desktop's plugin. Chromium Ozone's verdict: "Don't reimplement CSD by hand if you can integrate libdecor — Chromium's own-drawn path is a maintenance tax." SDL3, GLFW, Smithay take this route.
Own-drawn CSDConsistent look, no C dependency, full control. Winit (sctk-adwaita, no libdecor C dep), Qt 6 (predates libdecor), GTK 4, Chromium Ozone draw their own — but Qt 6's own verdict concedes its plugin approach "now diverges from the desktop."
Punt (delegate to GTK / X11-only)Zero Wayland decoration code at all. Flutter/wxWidgets delegate to GTK; Avalonia/Uno/JUCE/sokol are X11-only and inherit the WM's frame. The cost: XWayland's ceiling (§Dimension 10 regret (a)).

Resolution the evidence points to: carry a CSD path regardless (SSD is only a hint), and prefer libdecor over hand-rolled CSD unless you have a strong reason for a bespoke frame — multiple verdicts (Chromium Ozone, Qt 6) treat own-drawn CSD as a tax that drifts from the desktop. Smithay + libdecor's three-tier model (SSD → minimal FallbackFrame → libdecor) is the reference fallback chain.

Fork D — Own the windowing layer vs delegate to native

SideStrongest argument (from the evidence)
Own the pixels (draw everything, OS window is just a surface)Full control of frame pacing, raw pointer, inline pre-edit, Wayland popups, custom chrome — none of which a wrapper can add. Avalonia, Flutter, Uno, Slint, and winit-based stacks fill exactly this niche; MAUI's own verdict says a wrapper "cannot get there."
Delegate to native (wrap the platform toolkit/controls)Native fidelity, accessibility, and IME "for free"; far less code. wxWidgets/MAUI wrap native controls; Flutter/wxWidgets delegate Linux to GTK to reuse its mature Wayland/IME/clipboard stack.

The cost of delegating, recorded repeatedly: you are perpetually a step behind on the display server (wxWidgets is "perpetually a step behind on Wayland and must hack around its own dependency"), you inherit every native quirk, and capabilities silently no-op where the wrapped stack lacks them (MAUI's size/position/decorations work on only two of four platforms). The cost of owning: you must build Wayland, IME, frame pacing, and popup grabs yourself — which is precisely the gap analysed next.

Resolution the evidence points to: for a toolkit that wants modern frame pacing, raw pointer, inline pre-edit, Wayland-native popups, and custom chrome on every OS, owning the windowing layer is the only path — but it must own the CSD layer, the popup/grab model, and the frame-clock from day one, not defer them (the Avalonia/winit stance the verdicts endorse).


Part 5 — The delta table

Where a brand-new from-scratch windowing framework stands today against the field's consensus, per dimension — the gap analysis that bridges into recommendations. "Field consensus" is the standard from Part 3 and the resolution from Part 4; "Gap for a new framework" is the work that consensus implies a green-field toolkit must do.

DimensionField consensus / best-in-classGap for a new framework (build this)Reference to steal from
Window lifecycleAsync no-buffer-no-window state on Wayland; sync map on X11/Win32Model lifecycle as explicit async state from day one; one async present() entryGTK 4 gdk_toplevel_present, Smithay
Event loopHybrid poll+callback; external-fd integration; Win32 SetTimer live-resizeA pollable-fd loop (calloop-style) that an async runtime can driveWinit calloop/AsFd, SDL3 hybrid
Input — keymapsxkbcommon + +8 offset + client-side repeatUse xkbcommon (don't ship your own table); synthesise Wayland key repeatChromium Ozone, Smithay
Input — IMESeparate pre-edit event stream; own the consumer; IMM32 on Win32, text_input_v3 on WLOwn zwp_text_input_v3 + NSTextInputClient + IMM32 (TSF is the open opportunity)SDL3, Winit
Wayland specificsNative client; SSD-is-a-hint + CSD path; fractional scale; popupsBuild a native Wayland client (the field's biggest gap) — libdecor CSD, fractional scaleSmithay + libdecor (authority)
DPI / scalingLogical native unit; learn scale before first paint; re-fire on migrationProtocol-driven scale (not heuristic Xft.dpi); size render target off physical sizeSlint, Winit surface_size_writer
Multi-window / popupsFirst-class window handles; both override-redirect and xdg_popup popup pathsA popup primitive that maps onto both servers (not "place at (x,y)"); avoid in-canvas-onlySDL3, Smithay XdgPositioner
ThreadingGUI = main thread (AppKit-forced); render off-thread; marshal backAdopt main-thread rule; provide a dispatcher for cross-thread marshallingAvalonia two-layer Dispatcher
Clipboard / DnDAsync MIME-typed transfer; INCR chunking; portable drag source (rare)Do INCR/async clipboard right; provide a real drag-source (the field's weakest corner)GTK 4 async MIME-stream clipboard
Escape hatchesTyped raw-window-handle handoff; capability probing; name the leaksTyped handle accessor + TryGetFeature-style probe; raw message-pump hookWinit rwh, Avalonia TryGetFeature
Frame pacing (cross-cutting)Per-platform vsync source folded into one frame clock; CADisplayLink planOne frame-clock abstraction over wl_surface.frame/CVDisplayLinkCADisplayLink/DXGI vblankGTK 4 GdkFrameClock, Slint FrameThrottle

The single highest-leverage finding for a new framework: native Wayland is the field's biggest collective gap (only ~6 of 15 even attempt it, and the universal regret is having deferred it), so on Linux a green-field toolkit's strongest differentiator is to be Wayland-first — treating X11 as the legacy path, owning the CSD/popup/frame-clock layers itself, and using Smithay + libdecor as the implementation authority. This is a Linux-backend stance within an otherwise cross-platform design (Win32 and AppKit are first-class, not afterthoughts); the concrete, milestoned plan for that build-out is recommendations.


Sources