All notable changes to the Ghostty Windows fork. Each release line includes the underlying upstream commit when known.
Upstream sync (1,248 commits from ghostty-org/ghostty main, through
20abdb50a) plus new Win32 actions and a stability pass focused on GPU,
thread, and sleep/resume failure modes.
- Files copied in Explorer can be pasted into the terminal as space-separated, quoted paths. File drops and clipboard paste share the same hardened path conversion.
- Win32 support for editing the configuration, exporting terminal I/O, setting surface/tab/window title overrides, moving tabs, and moving live tab trees into new windows.
- Automatic WGL context recovery after a GPU driver reset, driver update, or context loss during sleep/resume. Terminal and background images are re-uploaded from CPU-side state after recovery.
- Windows virtual-memory discard support for upstream scrollback compression, allowing inactive pages to release physical memory.
- The shared core now follows upstream through
20abdb50aand requires Zig 0.16.0 for development and release builds. - Clipboard completion uses the upstream MIME-aware API, including support for the new clipboard and terminal snapshot modes.
- Portable packaging now fails fast on missing resources, stages files within the repository, and verifies the bundled resource sentinel.
- Renderer, terminal I/O, and search mailbox writes are bounded so a wedged GPU call cannot freeze every Ghostty window. Closing a wedged surface also uses bounded joins and preserves state still reachable by detached threads.
- Windows read threads exit cleanly on broken pipes and other ConPTY shutdown errors instead of reaching undefined behavior in release builds.
- Surfaces repaint after system resume, and renderer occlusion updates are deduplicated so resize activity cannot fill the renderer mailbox.
- Toggling from transparent to opaque repaints immediately instead of waiting for a later focus or expose event.
- The Win32 integration harness only cleans up its dedicated
ghostty-testexecutable and validates owned window handles, preventing tests from closing a developer's running Ghostty session.
Upstream sync (264 commits from ghostty-org/ghostty main, through
e1d31deaa) plus a macOS-parity feature batch driven by a full audit of the
Windows apprt against the macOS app, hardened by an adversarial review pass.
- CJK IME input works again. The message loop's
TranslateMessageskip (a dead-key fix) also swallowedVK_PROCESSKEY, so the IME never received its keys and Chinese/Japanese/Korean composition never started. IME-claimed keys are now exempt from the skip. - Clipboard security guards were silently bypassed. Paste-protection and
OSC 52 read/write authorization now actually prompt:
clipboardRequestcompletes withconfirmed=falsefirst and shows a confirmation dialog onUnsafePaste/UnauthorizedPaste;setClipboardhonors the confirm flag. The dialog no longer holds the system clipboard open, and the surface is re-resolved by id after the modal loop (a child exiting mid-dialog could otherwise use freed memory). - AltGr layouts (German
@,[, …) no longer encode as C0/CSIu control sequences: the synthesized Ctrl+Alt is stripped from the encoded mods when AltGr produced printable text. - Whole-window close (title-bar X, Alt+F4,
close_window) now shows one aggregate confirmation when any tab still has a running process. window-position-x/yis honored only when both are set (a partial config previously created the window off-screen).- libghostty-vt: selection gesture click-repeat timing was broken on Windows (raw nanoseconds stored as QPC ticks); double/triple-click detection via the C API now works. Regression test added.
- Right-click context menu no longer leaves the core's right-button state stuck pressed; minimized windows actually stop rendering; IME preedit is cleared on focus loss and survives commit+recompose messages; search-bar DPI changes no longer leave a deleted font on the match-count label.
- Inline IME preedit: the composition renders underlined at the cursor (the default floating composition box is suppressed); candidate window follows the cursor.
- Taskbar progress for OSC 9;4 progress reports via
ITaskbarList3(remove/set/error/indeterminate/pause, gated onprogress-style). - Surface right-click context menu (Copy / Paste / Select All / Split Right / Split Down / Reset Terminal).
- Command-finished notifications honoring
notify-on-command-finish,-after, and-action(bell and/or desktop notification with duration and exit code). - Search bar match counter ("3/17") driven by
search_total/search_selected. - Desktop notifications are click-to-focus: clicking the balloon raises and focuses the originating tab.
window-themehonored for the title bar (dark / light / system / auto), with the caption tint reserved for the luminance-derived themes.- Global hotkeys generalized to every
global:-flagged keybind, re-registered on config reload. background-blur: acrylic-style DWM blur behind translucent windows.- Resize overlay (columns × rows) honoring
resize-overlay,-position, and-duration. - Hovered-URL preview bubble at the bottom-left of the surface.
- User-defined
command-palette-entrycommands in the command palette. - X1/X2 (back/forward) mouse buttons delivered to the terminal.
window-decoration = nonecreates a borderless (still resizable) window.- Renderer occlusion wired to tab switches, split zoom, and window minimize/restore — hidden surfaces stop rebuilding frames.
- A warning notification when the renderer reports an unhealthy state.
Upstream sync (63 commits from ghostty-org/ghostty main, through
e90b7c9fa) plus a batch of Win32 input/UX fixes that surfaced during
parity testing against the macOS app.
- Themed scrollbar replaces the native white
WS_VSCROLL. Painted in the terminal's foreground/background colors via a layered popup overlay (UpdateLayeredWindowwith per-pixel alpha) composited above the OpenGL surface. Honors the OS "Always show scrollbars" accessibility setting: overlay (auto-hide on idle) by default, always-visible when the OS prefers it; mode switches live without a Ghostty restart. Mouse drag, hover, and page-click work; clicks fall through to the terminal when the scrollbar is hidden.
- Alt-modified keybindings (
Alt+-,Alt+\,Alt+letter, …) no longer ring the system bell on press.TranslateMessagesynthesizes aWM_SYSCHARafter everyWM_SYSKEYDOWNwith a printable character, and forwardingWM_SYSCHARtoDefWindowProcwas treating it as an unmatched menu accelerator and triggeringMessageBeep. The keystroke itself is dispatched inWM_SYSKEYDOWN, so the newWM_SYSCHARhandler simply consumes the message. - JIS keyboard layout no longer drifts to a US-like mapping inside
applications that use Win32 Input Mode (notably Claude Code's prompt
via ConPTY):
@becoming`,[becoming],]becoming\, etc. The trigger wasToUnicodereturning -1 for a dead key and ghostty leaving the buffered dead character in the kernel's per-thread keyboard state. SubsequentToUnicodecalls then composed against that residue, producing wrong characters until ghostty exited — the damage even survived aclaude -crestart because the state lives in ghostty's process, not Claude's. The fix drains the dead-key state after detecting it (mirroring wezterm'sKeyboardLayoutInfo::clear_key_state) and surfaces the dead character itself as theUcfield so the application still sees the keystroke. BothToUnicodecall sites now also skip modifier-only VKs (Shift, Ctrl, Alt, Win, lock keys), which never produce a character on their own and are another way to perturb the per-thread state. - Ghostty no longer freezes after extended use under heavy ConPTY output
(notably while a long Claude Code task streamed through the terminal).
The UI thread was deadlocking inside
oleacc!CAccessible::QueryInterface -> SleepConditionVariableSRW: every cross-pane focus transition caused oleacc to destroy the outgoing surface's MSAA AccWrap synchronously insideDefWindowProc, the destructor re-entered our WindowProc viaSetFocus->user32!ImeSystemHandler->oleacc!CreateClient, and the resulting COM marshaling waited for a reply that the same UI thread needed to pump. Each split being its ownWS_CHILDHWND (vs. wezterm/rio's single-HWND model) is what wedged the round-trip. BothsurfaceWndProcandwindowWndProcnow return0fromWM_GETOBJECTforOBJID_CLIENT, opting out of the default oleacc proxy entirely. We don't expose terminal-cell-level accessibility today, so the only thing this disables is the generic window-frame proxy a screen reader would otherwise see; once a proper IAccessible / UI Automation provider lands, it can be returned here viaLresultFromObjectinstead. - Dead-key composition now works correctly on pt-BR ABNT2 and other
layouts with dead keys (
~+a→ã,´+e→é,^+o→ô). The root cause wasTranslateMessagebeing called beforeDispatchMessagefor terminal surface windows: its internalToUnicodeExmutated the thread's per-queue dead-key state beforehandleKeyEvent's ownToUnicodecall, so dead keys composed with themselves instead of the following character. The fix skipsTranslateMessagefor surface windows (discriminated by class atom);ToUnicodeinhandleKeyEventis now the sole consumer of the dead-key state. The same race in the Win32 Input Mode path (used by PowerShell + PSReadLine) is fixed by the same change:~+ain a PowerShell prompt now correctly producesã. Edit controls (search bar, command palette, tab rename) are unaffected and still receiveTranslateMessage. shift+5,ctrl+u,ctrl+d,ctrl+f, arrow keys, andenterno longer drop their effective character when typed into applications that use Win32 Input Mode (notably Claude Code's prompt via ConPTY): on a JIS layoutshift+5was arriving as5instead of%. The trigger was Claude enabling kitty-keyboard / modifyOtherKeys on top of Win32 Input Mode, after whichcore_surface.keyCallbackwould encode the keystroke through the legacy/kitty path and return.consumed— the apprt then suppressed its own\x1b[Vk;Sc;Uc;Kd;Cs;Rc_sequence, so the receiving app only ever saw the core encoding (a kitty-kbd CSI forshift+5that Claude rendered as5). Win32 Input Mode is the apprt's exclusive wire format, soSurface.encodeKeynow short-circuits whenever DEC mode 9001 is active. Bindings still fire (binding lookup runs beforeencodeKey); modifier release tracking still works.toggle_background_opacitystate now propagates to newly created windows. Toggling a window to opaque and then opening a new window vianew_windowno longer drops the new window back to the configuredbackground-opacity. Mirrors macOS behavior from upstream#11583.- Global keybindings now fire from inside the inline tab rename / search
bar / command palette Edit controls. Previously the Edit ate
Ctrl-modified keystrokes so e.g.
Ctrl+Shift+Pwhile renaming did nothing; now those bubble up to the surface (the popup is dismissed first, the action then runs).Ctrl+A/C/V/X/Y/Zwithout Shift still stay with the Edit as text-editing shortcuts. - Inline tab rename now commits on focus loss (clicking on the
terminal, Alt+Tab, etc.) instead of silently reverting — matches
Win32 inline-edit convention used by Explorer file rename and the
Edge tab rename.
Enterstill commits explicitly,Esccancels. - Inline tab rename Edit displays the real tab title cleanly. It was
showing trailing box glyphs because the Edit was given a pointer to
the raw 256-
u16buffer andCreateWindowExWread uninitialized memory past the title looking for a NUL terminator. - Clicking on the terminal now actually takes keyboard focus.
WS_CHILDsurface windows don't auto-focus the way top-level windows do, so without an explicitSetFocus(hwnd)onWM_LBUTTONDOWN/WM_RBUTTONDOWN/WM_MBUTTONDOWNa sibling popup edit (rename, palette, search) kept focus and the click did nothing visible. - Command palette and search bar now auto-dismiss when their Edit
loses focus — clicking elsewhere in the terminal, switching to
another app via Alt+Tab. Matches popup UX in VS Code (command
palette) and Edge/Chrome (find bar).
Escstill dismisses explicitly. Clicking a palette item still executes it normally; the popup doesn't take focus on click, so the Edit retains focus through item selection.
- Horizontal scroll support via WM_MOUSEHWHEEL.
- File drag-and-drop onto a terminal pastes the file path(s), quoting if whitespace is present.
present_terminalaction now restores+focuses the window.mouse_visibilityaction toggles the cursor via SetCursor(NULL).command_finishedflashes the taskbar button on non-zero exit when the window is unfocused.float_windowaction sets/clears WS_EX_TOPMOST.toggle_visibilityhides/shows all top-level windows.size_limitaction enforces minimum/maximum window dimensions via WM_GETMINMAXINFO.color_change(OSC 10/11) updates the class background brush so the resize-flash matches.ring_bellnow also flashes the taskbar when the window is unfocused.- Confirm-close dialog when programmatically closing a tab while a shell command is running.
- Built-in
ghostty.icois used for the window icon and tray notifications (was the generic Windows app icon). - New windows cascade 30px from the most recently created window.
- Build option
-Dwindows-console=truekeeps stderr visible in release builds for debugging. - Title-bar color tracks the configured background on Windows 11 22H2+ (DWMWA_CAPTION_COLOR), and dark/light chrome is chosen by background luminance instead of being hardcoded dark.
- Title-bar chrome refreshes on config reload.
- Clicking the update-available balloon opens the GitHub releases page in the user's default browser.
- Update-check requests are rate-limited to once per hour via a
persisted timestamp at
%LOCALAPPDATA%/ghostty/update_check_at. - VS_VERSION_INFO resource is filled in (Explorer → Properties → Details shows real values; signtool tooling has version metadata to attach).
- Application manifest declares UTF-8 active codepage, longPathAware, and supportedOS GUIDs for Windows 7 through 11.
test_window_size_confignow hard-fails (was non-blocking) if the configured window width didn't take effect.test_resizeis a real test (was permanently SKIPPED).ghostty_test.shpre-kills leftover processes and retries the exe copy once, eliminating intermittent EBUSY in batch runs.
- Update-check version source is now
build_config.version(set by the win-v git tag at build time) instead of a hardcoded constant that drifted every release. - Build accepts
win-vX.Y.Ztags as a separate version namespace from upstream'svX.Y.Z(which still must matchbuild.zig.zon). - Manifest declares UTF-8 active codepage, longPathAware, and supportedOS for Windows 7-11.
- All Win32 bindings use
callconv(.winapi)instead ofcallconv(.c). Identical ABI on x64 today; correct WNDPROC type, future-proof for x86. - DirectWrite locale fallback chain widened: en-US, en-us, en-GB, en before index 0.
- DragDropFiles, FlashWindowEx, ShowCursor, GetSystemMetrics, SetClassLongPtrW, SIF_TRACKPOS bindings added.
- DrawTextW binding moved from
gdi32touser32(correct DLL).
- C1:
cleanupAllSurfaceswas deinit'ing a local copy of each SplitTree, leaving stale arena pointers; now deinits in place and resets the slot to.empty. - C2: A
closingflag is now set before posting WM_CLOSE for the last tab; queued mouse/keyboard events are dropped while it's set so they can't allocate into a window about to be freed. - C6: Packaging now includes
share/terminfo/ghostty.terminfo(the resourcesDir sentinel on Windows). Without it, fresh ZIP installs silently failed to load themes. - C7: Update-check version is now passed via heap pointer through WPARAM/LPARAM instead of a static buffer with no fence.
- DirectWrite no longer panics when the factory or system font collection is unavailable (Wine, restricted SKUs); discovery returns empty and callers fall back to bundled JetBrains Mono.
- ToUnicode scancode mask was 0x1FF; including bit 24 (extended-key flag) produced wrong translations for AltGr layouts.
- Palette
scroll_offset @intCastcould panic when the popup was too small to render any items. - IME
ImmGetCompositionStringWbyte length now uses@divTruncwith parity check instead of@divExact(which would panic on odd input). high_surrogatebuffer is cleared on focus loss and IME composition start so a stray surrogate can't pair with the next character key.selectTab(_)clamps the discriminant non-negative before@intCast.tab_rectsis zero-initialized so input handlers reading it before the first WM_PAINT see a no-match instead of stack garbage.moveTabTonow cancels any in-progress tab rename.paintTabBarbrush failures no longer skip the geometry update at the bottom of the loop (was piling subsequent tabs on top of the failed one).QuickTerminal.onWindowDestroyedunconditionally KillTimers the animation timer.- Search/palette popup fonts are rebuilt on DPI change.
- Multi-button mouse capture: SetCapture/ReleaseCapture only run on the 0→nonzero and nonzero→0 transitions of a button-mask.
show_child_exitedMessageBoxW now actually formats the exit code into the body text instead of building it and discarding.- Desktop and update notifications use distinct (uID, timer-id) pairs so one's auto-cleanup doesn't NIM_DELETE the other's icon.
paintPalettefont and bg brush are cached for the popup lifetime instead of allocated per repaint.- Process HANDLE in
Command.zigis nowCloseHandled on deinit. - Several upstream features that had been dropped during past merges
(R1-R9 in the audit) were restored:
GHOSTTY_SURFACE_IDenv var, theprocessLinksrenderer-state mutex,semantic_prompt_boundaryin link detection,middle-click-actionconfig option, defaultmove_tabkeybinding, DECBKM mode 67, macOS-only scroll magnitude flooring,freetype_windowsbackend variant, theGHOSTTY_ENUM_TYPEDmacro andGHOSTTY_MODE_REPORT_MAX_VALUEsentinel.
Initial public release.