feat(windows): scaffold WinUI 3 app shell consuming libghostty#156
Merged
feat(windows): scaffold WinUI 3 app shell consuming libghostty#156
Conversation
Adds windows/Ghostty/, a WinUI 3 unpackaged desktop app that hosts a single libghostty surface via the DX12 composition path (SwapChainPanel, null HWND). Mirrors the macos/ layout: native shell in this repo, consuming the ghostty.dll produced by zig build -Dapp-runtime=none. What's in place: - Ghostty.sln + Ghostty.csproj: net9.0-windows, x64/ARM64, unpackaged, copies ghostty.dll from zig-out/lib into a native/ subdirectory to avoid collision with the managed Ghostty.dll on case-insensitive filesystems. NativeLibrary.SetDllImportResolver loads it at startup. - Directory.Build.props: redirects AppxMSBuildToolsPath to VS Build Tools so 'dotnet build' from the CLI can find the PRI task DLL (see WindowsAppSDK#3997). - global.json: pins .NET 9 SDK (10 is installed too, breaks the WinUI 3 build targets). - app.manifest: PerMonitorV2 DPI, longPathAware, UTF-8 code page. - Interop/NativeMethods.cs: P/Invoke covering init, config lifecycle, app lifecycle, surface lifecycle, size/scale/focus, key/text/mouse. Incremental subset of ghostty.h sorted in header order for easy drift detection. Runtime config callbacks held in fields so the GC does not free them while libghostty holds function pointers. - Interop/ISwapChainPanelNative.cs: COM interop. QueryInterface on the WinUI 3 SwapChainPanel returns the ISwapChainPanelNative v-table pointer, which is what libghostty's DX12 renderer calls SetSwapChain on (slot 3) - passing the raw IUnknown crashes inside the renderer. - App.xaml, MainWindow.xaml with Mica backdrop (default title bar for this first pass; custom title bar is a follow-up PR). - Controls/TerminalControl.xaml(.cs): SwapChainPanel + input routing. Fills working_directory/command/initial_input with an empty C string (not null) because Zig would otherwise crash in surface_new. Resize is debounced on a 30ms DispatcherQueueTimer because the DX12 renderer recreates the swap chain on every set_size call, which can't keep up with WinUI 3's per-pixel drag events. CharacterReceived filters C0 control chars so Ctrl+key combinations don't double up (mirrors the Win32 example fix in 8dde86d). just recipes: - just build-win: dotnet build the shell alone. - just run-win: zig build the DLL, dotnet build the shell, launch it. First-boot status (captured for the follow-up stack): - Opens a Mica window, creates the surface, spawns cmd.exe, DX12 renders, title updates, resize no longer crashes. - Known issues: focus flaps between the control and the panel, terminal doesn't pixel-perfect fill the window, VirtualKey is passed as the raw Keycode instead of a translated GhosttyKey. Each becomes its own small stacked PR. Not included yet (intentional): - Tabs, splits, custom title bar, settings UI, clipboard, bell, shell integration, dispatcher for ghostty_action callbacks, full VirtualKey -> GhosttyKey mapping, ~70 of the 88 functions in ghostty.h.
- Release ISwapChainPanelNative* after ghostty_surface_new returns. The DX12 device only uses it synchronously (SetSwapChain), so holding our AddRef was a leak per open/close cycle. - Split the aliased empty-UTF8 buffer into three independent allocations for working_directory / command / initial_input. - OnWakeup: capture app handle + DispatcherQueue locally before enqueue and re-verify on UI thread to avoid a race with Unloaded. - OnAction: return false so the core falls back to its own defaults instead of silently swallowing every action. - SendKey: pass the native Win32 scancode (KeyStatus.ScanCode) as the keycode, which is what embedded.zig's keycodes table matches against on Windows. VirtualKey was wrong. - Drop the now-redundant C0 filter in OnCharacterReceived; the embedded apprt handles key+text combining at comptime. - CurrentMods: distinguish L/R shift, ctrl, alt (AltGr), win; set the *Right flags; also report Caps and Num. - Resize timer: subscribe Tick once instead of -=/+= per event. - App.xaml.cs: prefer AppContext.BaseDirectory in the DllImport resolver so single-file publish and Native AOT still find native/ghostty.dll. - Delete the empty GhosttyKey stub enum; scancode path does not need a translation table on the C# side. Signed-off-by: Alessandro De Blasis <alex@deblasis.net>
This was referenced Apr 6, 2026
- Gate Mica backdrop on MicaController.IsSupported so Win10 hosts get the default backdrop instead of a transparent black window. - Stop and detach the resize debounce timer in OnUnloaded so a pending tick cannot fire after teardown and the timer does not pin the control. - Drop CompositionScale multiplication from OnPointerMoved: embedded.zig cursorPosCallback runs the input through cursorPosToPixels, so feeding it pixel coords double-scales on high DPI displays. - Pin GhosttyTarget layout explicitly (Size=16, FieldOffset 0/8) so the ABI cannot drift if the union ever gains a wider variant.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First pass at a native Windows shell for Ghostty, mirroring how macos/ consumes libghostty. Adds windows/Ghostty/ as a WinUI 3 unpackaged desktop app that hosts a single libghostty surface via the DX12 composition path (SwapChainPanel, null HWND).
What's in place
just recipes
First-boot status
Opens a Mica window, creates the surface, spawns cmd.exe, DX12 renders, title updates, resize no longer crashes.
Known issues to address in follow-up stacked PRs:
Not included yet (intentional)
Tabs, splits, custom title bar, settings UI, clipboard, bell, shell integration, dispatcher for ghostty_action callbacks, full VirtualKey -> GhosttyKey mapping, and about 70 of the 88 functions in ghostty.h. Each unlocks its own small PR.