Repository navigation
Background 08 gpui and wry on windows
Research note dated 2026-10-09. Read for reasoning, not current fact; later decisions in
docs/decisions.mdanddocs/architecture-decisions.mdwin.This note corrects parts of notes 01, 02a and 06. The Phase 0 spike later found one of its readings reversed on this machine: GPUI draws above a WebView2 child window, not below it (see note 09 and docs/dev/trapstoknow.md). Notes 07 and 08 correct parts of the earlier notes, and note 03 assumes Tauri.
Moved from
docs/research/08-gpui-and-wry-on-windows.md(original at af3de53) on 2026-10-16.
Date: 2026-10-09
Status: Research note 08. It checks the GPUI and wry facts that the Phase 0 spike and the first build issues depend on. It builds on 06-engine-and-ui-performance.md (sections 4 and 5, and the Phase 0 checklist), 02a-tauri-wry-facts.md and 01-shell-and-engine.md, and does not repeat them. Source code was read from shallow clones of zed-industries/zed (main, 2026-10-09), longbridge/gpui-kit (main, 2026-10-09) and tauri-apps/wry at tag wry-v0.57.0. Nothing was built or run, so every runtime behaviour below is read from code or docs, not observed.
Terms. A child HWND is an operating-system window placed inside another window. Windowed hosting is WebView2's default mode, where it draws into its own child HWND. Visual hosting is the mode where WebView2 draws into a DirectComposition visual that the app stacks itself (the CompositionController API). DWM is the Windows desktop compositor. A snapshot crate is a copy of Zed's code that a third party publishes to crates.io under another name.
Yes, with three corrections to what the earlier notes assumed.
-
GPUI is not on crates.io in a usable form.
gpui0.2.2 (2025-10-22) is still the latest release under that name. Apps outside Zed depend on Longbridge's weekly snapshot (gpui-pre0.3.8, 2026-10-05) throughgpui-kit/gpui-component0.7.1, or on a git dependency on Zed. Longbridge's repo is now calledgpui-kit;gpui-componentis one crate in it. The kit has since moved to a second Longbridge fork,gpui-fast. -
The "borderless owned popup" plan needs raw Win32 work. GPUI's
WindowKind::PopUpon Windows creates a top-most tool window with no owner, so it floats above other applications too.WindowBackgroundAppearance::Blurreduses the oldSetWindowCompositionAttributeAcrylic, notDWMSBT_TRANSIENTWINDOW. Syrinx gets the popup's HWND throughraw-window-handleand sets ownership, the backdrop and activation itself. Both are a fewwindows-crate calls, but neither has been shown to work in GPUI. -
wry covers the tab basics but not the browser-grade events. wry 0.57 handles new-window requests (with the opener, so
window.openerworks), permissions, a shared environment and memory level. It does not expose accelerator keys, fullscreen, process failure, suspend or crash-report settings. Syrinx reaches those throughWebViewExtWindows(controller(),environment(),webview()) and thewebview2-comcrate, and builds its own environment so it can set crash reporting.
The existing gpui-webview crate does not help on Windows: its README says the WebView covers GPUI overlays there. Nothing found contradicts the architecture, so there is no no-go. The riskiest unknowns are the popup's focus handoff with the WebView and Acrylic on a GPUI popup. Both are Phase 0 spike items.
-
gpui: latest 0.2.2, published 2025-10-22, Apache-2.0, repositoryzed-industries/zed. Releases 0.2.0 to 0.2.2 all came in October 2025; nothing since. The crate has 344,070 downloads. -
gpui-pre: "Zed's GPU-accelerated UI framework (gpui-pre snapshot of zed@279fe07)", 0.3.8, published 2026-10-05. It has been published weekly (0.3.5 on 2026-09-14, 0.3.6 on 09-21, 0.3.7 on 09-28).gpui-pre-platform,-web,-macrosand-reqwest-clientfollow the same numbers. These are Longbridge's snapshot crates of Zed's own code. -
gpui-fast0.1.6 (2026-10-09), repositorylongbridge/gpui-fast: "GPUI extracted from the Zed monorepo as a standalone workspace, with the layout engine reworked". Apache-2.0.
Note 06 says "used via snapshot crates"; that is confirmed, and 06's "a year old" release date is correct.
-
gpui-kitis the real name. The repositorygithub.com/longbridge/gpui-kitwaslongbridge/gpui-component. Its README calls it "GPUI Kit" and says "gpui-kitpins the matching GPUI release and re-exports GPUI, base, component, and assets, so a Rust application lists a single dependency". The crates aregpui-kit0.7.1,gpui-base,gpui-component0.7.1 (211,395 downloads) andgpui-wry/gpui-webview(see 2.2). Note 06's "gpui-kit" is therefore right as the project name, andgpui-componentis the styled-components crate inside it. -
Licence: crates are Apache-2.0. The repository root has
LICENSE-APACHEand a separateLICENSE-DOCS.md; GitHub's licence detector reports "Other". -
Version pinning: the workspace
Cargo.tomlpins exactly:gpui = { package = "gpui-pre", version = "=0.3.8" }, with a comment that a caret requirement once moved applications onto a snapshot thatgpui-componentdid not compile with (issue #3156), and that "any snapshot may change GPUI's API". A CI script fails when a pin is not exact. It also listsgpui-fast0.1.3 as the optional alternative backend. - Release rhythm: gpui-kit 0.6.2 (2026-09-18), 0.6.4, 0.6.6 (09-21), 0.7.0 (09-28), 0.7.1 (10-05). It is weekly, and one version (0.6.5) was yanked.
-
The other route is a git dependency on
zed-industries/zed, which is what the Longbridge issue examples used earlier (gpui = { git = ... }). Zed's crates have no semver promise.
- Zed shipped on Windows with a stable release on 2025-10-15, called "a fully supported platform" with a full-time Windows team; it renders with DirectX 11 and DirectWrite. The Windows backend is now its own crate,
gpui_windows, in Zed's repository. - The crate has
accesskitandaccesskit_windowsdependencies, and the window code callsadapter.update_window_focus_stateon activation.
- Zed PR #56065 "gpui: Accesskit support" was closed on 2026-05-27, and
gpuiandgpui_windowsboth depend onaccesskittoday. This agrees with note 06 ("since May 2026"). -
gpui-kit's README claims "AccessKit roles, names, states, relationships, and actions are built into the interaction layer and covered by tests". That is a vendor claim. No screen-reader test of a real app was found (low).
- GPUI's
Windowimplementsraw_window_handle::HasWindowHandleandHasDisplayHandle(crates/gpui/src/window.rs, line 7572). On Windows the platform window returns aWin32WindowHandlebuilt from itshwnd(gpui_windows/src/window.rs, line 595). - So
wry::WebViewBuilder::build_as_child(&*window)compiles against GPUI's window with no patches. This is exactly whatgpui-webviewdoes on Windows (crates/webview/src/lib.rs, line 131). - Version note: wry 0.57 depends on
raw-window-handle0.6, the same major version GPUI uses. The Longbridge crate uses a fork,lb-wry0.53.3, instead of upstream wry. Syrinx should use upstream wry 0.57 directly and write its own thin glue (see the recommendation below).
-
gpui-wrywas renamedgpui-webview; the lastgpui-wryis 0.7.1 (2026-10-05, 5,140 downloads, Apache-2.0). The README says it is "still a experimental with limited features". - Its platform table says that on Windows (WebView2) GPUI overlays above the page are "Not yet; the WebView covers the overlays". It advises "use the webview in a separate window or a Popup layer when overlapping GPUI content is required". That is the same approach Syrinx chose.
- Longbridge's open PR #2626 (opened 2026-07-30) says the macOS overlay path is validated, and that "Windows CompositionController and Linux hosting are documented follow-ups". A later comment says a Windows checkpoint (commit
4e560a95) "creates a GPUI native-surface portal, passes its IDCompositionVisual to the wry CompositionController", with "Pointer/focus/IME forwarding and device-loss rebinding" incomplete and Windows validation still needed. It depends on a Zed PR (#61945, "Render deferred overlays above native child views"), which was closed without merging on 2026-09-27. So visual hosting inside GPUI is being attempted by someone else but is not a finished fallback. - Zed itself has no WebView: PR #54433 (a wry-backed
WebViewelement) was closed unmerged on 2026-04-23.
-
Z-order. wry creates a container child HWND (
WS_CHILD | WS_CLIPCHILDREN, window classWRY_WEBVIEW) and moves it toHWND_TOP. Windowed WebView2 therefore draws over everything GPUI paints in the same window. Note 06 already records this; the Longbridge README confirms it for GPUI. -
Resize. For child webviews wry does not subclass the parent window (the subclass only attaches when
!is_child,webview2/mod.rsline 605). Syrinx must callWebView::set_boundsitself whenever the page card changes size. Bounds take logical units, which wry converts with the parent's DPI (util::hwnd_dpi). -
Focus. Issue longbridge/gpui-kit#1787 (macOS, closed) shows the failure mode: after the WebView loads, keyboard input goes to the page while GPUI still shows a caret. A maintainer called it "the known limitation of the Wry use in GPUI". On Windows, GPUI's
handle_activate_msgraises AccessKit focus events but there is no code that tells WebView2 to give focus back; Syrinx must callWebView::focus()andWebView::focus_parent()itself. This is untested here (low). -
No Windows bug reports. A search of
longbridge/gpui-kitissues for Windows plus webview found only crashes in the demo ("RefCell already borrowed", closed) and the overlay PR above. No report of resize or DPI bugs was found. Absence of reports is weak evidence (low).
gpui_windows/src/window.rs, lines 492 to 514:
-
WindowKind::PopUpcreates the window withWS_EX_TOOLWINDOW | WS_EX_TOPMOSTand style 0. It is top-most for the whole desktop, not owned by or tied to the main window, and it has no taskbar button. GPUI's own doc comment says "use sparingly". - Syrinx can hide the non-client area: when
titlebarisNoneorappears_transparent,hide_title_baris true andWM_NCCALCSIZEremoves the frame. -
WindowKind::Dialogtakes the active window as parent but disables that parent (modal), which is wrong for a palette over a live page. -
WindowKind::AnchoredPopupreturnsPopupNotSupportedErroron Windows ("Native popups are not implemented on Windows yet"). -
WindowOptions { focus: false }shows the window withSW_SHOWNOACTIVATE, which suits the hover-peek sidebar. There is noWS_EX_NOACTIVATEoption. - Syrinx gets the popup's HWND from
raw-window-handleas in 2.1, then callsSetWindowLongPtrW(GWLP_HWNDPARENT, main_hwnd)to make it owned (an owned top-most window still stays above its owner) and can dropWS_EX_TOPMOSTif ownership alone gives the stacking it wants. Whether GPUI tolerates that change after creation is unverified (low).
-
WindowBackgroundAppearancehasOpaque,Transparent,Blurred,MicaBackdropandMicaAltBackdrop(crates/gpui/src/platform.rs, line 2766). - On Windows
MicaBackdropcallsDwmSetWindowAttribute(DWMWA_SYSTEMBACKDROP_TYPE, 2)(main window) andMicaAltBackdropuses 4 (tabbed window); both are skipped below build 22621.Blurreddoes not use the system backdrop: it calls the undocumentedSetWindowCompositionAttributewith accent state 4 (the older Acrylic-blur-behind effect) fromuser32.dll(window.rs, lines 926 to 940 and 1636 to 1680). - GPUI has no
DWMSBT_TRANSIENTWINDOW(value 3) option. Syrinx callsDwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &3, 4)on the popup's HWND itself, after creating it withWindowBackgroundAppearance::Transparent. Microsoft documents value 3 asDWMSBT_TRANSIENTWINDOW, "Acrylic" for transient windows (cited in note 06). - GPUI never calls
DwmExtendFrameIntoClientArea. Zed's Mica works without it, so GPUI's composition swap chain evidently is enough, but whether Acrylic shows under a GPUI popup's alpha swap chain is something only the spike can confirm. - Windows 11 build 26300 is above the 22621 minimum.
- To the popup: show it with
focus: true; GPUI answersWM_MOUSEACTIVATEwithMA_ACTIVATE(events.rs, line 96), so clicks activate it. - Back to the page: close or hide the popup, then call wry's
WebView::focus(), which isICoreWebView2Controller::MoveFocus(PROGRAMMATIC)(webview2/mod.rs, line 1583). If Windows first gives focus to the GPUI main window,WebView::focus_parent()(aSetFocuson the parent) exists too. Syrinx should activate the main HWND, then callfocus(). - The sequence "palette opens over a focused page, Esc closes, typing resumes in the page" is already in 06's spike checklist; the code path above is what to test.
-
wry does not expose
AcceleratorKeyPressed. A search of the wry 0.57.0 source forAcceleratorKeyfinds nothing. The only related setting iswith_browser_accelerator_keys(bool), which maps toICoreWebView2Settings3::SetAreBrowserAcceleratorKeysEnabledand turns WebView2's built-in browser keys (F5, Ctrl+P and so on) on or off. wry issue #451 ("CustomMenuItem accelerators don't trigger on_menu_event on Windows") is still open (updated 2025-12-17). -
Syrinx must register the handler itself:
WebViewExtWindows::controller()returns theICoreWebView2Controller, andwebview2-com0.39 providesAcceleratorKeyPressedEventHandlerforadd_AcceleratorKeyPressed. - Which keys reach the host. Per Microsoft: the event fires "when an accelerator key or key combo is pressed or released while the WebView is focused". A key counts as an accelerator if Ctrl or Alt is held, or if the key does not map to a character. Shift is never an accelerator and Escape always is. Plain typing never reaches the host; Ctrl+K, Ctrl+T, Ctrl+W, Ctrl+L, F-keys and Esc do.
-
Constraints. In windowed mode the handler runs synchronously and blocks the browser process until it returns or calls
Handled(true); outgoing cross-process COM calls fail withRPC_E_CANTCALLOUT_ININPUTSYNCCALL. So the handler should only setHandledand post a message to Syrinx's own event loop. It must ignore auto-repeat usingPhysicalKeyStatus.WasKeyDown. Microsoft's sample does exactly this.
Everything here is for wry 0.57.0 and webview2-com 0.39.1 (2026-03-11) unless stated.
| Need | In wry? | Detail |
|---|---|---|
| One shared environment | Yes, by passing it in |
WebViewBuilderExtWindows::with_environment(ICoreWebView2Environment). Without it each build runs CreateCoreWebView2EnvironmentWithOptions again (create_environment, line 289). WebContext::new(data_directory) only holds the user-data folder path. Its docs say webviews with different environment options must use different data directories. Syrinx should create one environment once and pass it to every tab. |
| Profiles | Yes |
with_profile_name; the docs say webviews with different profile names in one environment have isolated cookies and storage. |
Popups become tabs, keep window.opener
|
Yes | `with_new_window_req_handler( |
| HTML5 fullscreen | No | wry has no ContainsFullScreenElementChanged handler. Syrinx adds it via webview() and resizes or hides its chrome (add_ContainsFullScreenElementChanged exists on ICoreWebView2). |
TrySuspend / Resume
|
No | Syrinx casts webview() to ICoreWebView2_3. Microsoft: IsVisible must be false first (so call WebView::set_visible(false)), else it fails with ERROR_INVALID_STATE. The view resumes automatically when visible. Navigate and some other calls resume it. |
MemoryUsageTargetLevel |
Yes |
WebView::set_memory_usage_level(MemoryUsageLevel::Low / Normal) casts to ICoreWebView2_19. Microsoft: pick either suspend/resume or the memory level, not both; TrySuspend sets it to low and Resume sets it to normal; scripts keep running at low. |
ProcessFailed |
No | Syrinx adds add_ProcessFailed through webview(). For a failed browser process an ICoreWebView2Environment5::BrowserProcessExited event also fires. |
| Crash-report upload | No |
IsCustomCrashReportingEnabled (ICoreWebView2EnvironmentOptions3, WebView2 1.0.1518.46 or later): setting it to TRUE means "Windows won't send crash data to Microsoft endpoint"; the default FALSE "will respect OS consent". wry builds its options internally and never sets it, so Syrinx builds its own environment with webview2-com's CoreWebView2EnvironmentOptions and passes it via with_environment. |
| Permissions | Yes |
with_permission_handler(...) maps PermissionRequested to a PermissionKind enum covering microphone, camera, geolocation, notifications, clipboard read, local fonts, sensors, MIDI, automatic downloads, file system, autoplay and window management. Anything else maps to Other. |
| Downloads | Yes | Download start and completed handlers (DownloadStarting is wired). |
- Not by itself.
AddScriptToExecuteOnDocumentCreatedis called only for scripts the app passes withwith_initialization_script, and for one IPC line,window.ipc = { postMessage }, which is added only whenwith_ipc_handleris set (webview2/mod.rs, lines 508 and 949). A page webview built with neither gets no script. -
inject_initialization_scripts.rsis described in its header as "used by the Android backend", not Windows. - Custom protocols add a
WebResourceRequestedfilter only when a protocol is registered (line 1028). -
Defaults to override. With no
with_additional_browser_args, wry passes--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection(line 301). That switches off Microsoft Defender SmartScreen for every tab. A browser should set its own arguments. The environment also sets the UI language from the user's locale. - wry hides the status bar (
SetIsStatusBarEnabled(false)) and creates the container HWND withWS_CHILD | WS_CLIPCHILDREN.
No no-go. The items below change the work, not the choice.
-
PopUpis desktop top-most and unowned (3.1). Left as is, the palette would float over other apps when Syrinx loses focus. This is the largest correction to 06's popup plan. -
Blurredis the old Acrylic accent, notDWMSBT_TRANSIENTWINDOW(3.2). The spike should compare both on the popup. -
The kit's GPUI base is moving.
gpui-kit0.7.1'sgpui-webviewforces thegpui-fastbackend, andgpui-preis "not supported" there. Syrinx should choose one base (gpui-presnapshot orgpui-fast) in the first build issue and pin it with=. - Exact pins are mandatory (1.2). A caret requirement already broke the kit once.
- wry's default browser arguments switch off SmartScreen (5.1).
- The popup-as-tab path needs the same profile and environment (section 5).
- Visual hosting is not ready to adopt. wry PR #1762 (DirectComposition hosting for WebView2) is still open (updated 2026-07-22), the Zed overlay PR it depends on is closed unmerged, and Longbridge's Windows version is incomplete. Note 06 already says to adopt it only if popups fail; add that the fallback itself is a research-grade project, so a failed popup spike is a real risk to the schedule.
- Accessibility of the page itself comes from WebView2, but GPUI's AccessKit is untested on a real screen reader (1.4).
- Build the spike on
gpui-kit0.7.1's pinnedgpui-pre0.3.8 (or a git dependency on one Zed commit), with upstream wry 0.57 andwebview2-com0.39. Write Syrinx's own thin tab-view glue instead of depending ongpui-webview, which wraps a fork and gives nothing on Windows overlays. - Make the popup spike answer, in order: (a) does
WindowKind::PopUpplusGWLP_HWNDPARENTgive a popup that stays above the page and not above other apps; (b) doesDwmSetWindowAttribute(DWMSBT_TRANSIENTWINDOW)blur the page; (c) does Esc return focus throughMoveFocus; (d) does the hover-peek popup withfocus: falseavoid stealing focus from the page. - Add
AcceleratorKeyPressed,ProcessFailedandContainsFullScreenElementChangedhandlers, and the crash-report setting, to the tab layer from the start, throughwebview2-com.
- Does a GPUI popup with a transparent swap chain show
DWMSBT_TRANSIENTWINDOWAcrylic withoutDwmExtendFrameIntoClientArea? - Does changing a GPUI popup's owner after creation upset GPUI's message handling?
- Does GPUI's AccessKit adapter work with NVDA on a Windows 11 build 26300 machine?
- Which base should Syrinx pin:
gpui-preorgpui-fast?
- crates.io API: gpui, gpui-pre, gpui-pre-platform, gpui-fast, gpui-kit, gpui-component, gpui-wry, lb-wry, wry, webview2-com
- Longbridge: gpui-kit repository (
Cargo.toml,README.md,crates/webview/README.md,crates/webview/src/lib.rs), gpui-fast, PR #2626, issue #1787 - Zed: gpui platform.rs, window.rs, gpui_windows window.rs, gpui_windows events.rs, AccessKit PR #56065, PR #61945, PR #54433, Zed for Windows
- wry 0.57.0: webview2/mod.rs, lib.rs, web_context.rs, issue #451, PR #1762
- Microsoft Learn (WebView2 Win32 reference): ICoreWebView2Controller, ICoreWebView2, ICoreWebView2NewWindowRequestedEventArgs, ICoreWebView2_3, ICoreWebView2_19, ICoreWebView2EnvironmentOptions3, DWM_SYSTEMBACKDROP_TYPE
Using the code
Background research
- 01 Shell and engine
- 02 Browser v1 scope
- 02a Tauri and wry facts
- 02b Default browser, import, signing
- 03 Core architecture
- 04 Forge and Notes
- 05 Fledge API
- 06 Engine and UI performance
- 07 Google sign-in and DRM
- 08 GPUI and wry on Windows
- 09 Phase 0 verdict
Ideas
In the repository