Skip to content

Merge upstream Ghostty 1.4.0 and audit Windows parity - #18

Merged
shiweis merged 80 commits into
mainfrom
sync/upstream-1.4.0
Oct 29, 2026
Merged

shiweis merged 80 commits into
mainfrom
sync/upstream-1.4.0

Conversation

@shiweis

@shiweis shiweis commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Integrate upstream Ghostty v1.4.0 (83fb95624, 73 commits after our previous sync) while preserving the Win32 runtime, WGL/ConPTY adaptations, cache helpers, and disabled upstream publishing workflows.

Fix Windows clipboard ownership and bounded reads, close-dialog surface lifetime, application APC cancellation, PowerShell OSC sanitization and cursor features, native OpenSSH CRLF parsing/options, and hidden-window/split-zoom visibility. Harden portable packaging with complete resource and executable/PE-version verification.

The shared core is aligned; full native feature parity is not achieved. README/ROADMAP record the confirmed clipboard, inspector, split movement, navigation, state-indicator, bell, inactive-pane, and PowerShell integration gaps separately from platform-specific roadmap commitments. Hold win-v1.4.0 tagging and publication until full parity is implemented and verified.

Validation:

  • Windows debug and stripped fast builds passed; Ghostty Dev refreshed without closing existing terminals.
  • Full Windows unit executable: 4,099 passed, 71 skipped, zero failed, staged locally with fixtures.
  • Focused native libghostty-vt: 2,068 passed, 30 skipped.
  • PowerShell 5.1 and 7: 20 shell-integration assertions each; package and development-install checks passed.
  • No-global-input resize, tab overview, reply-feedback and mouse-reporting cases passed. Visibility regression: 18 assertions passed under each PowerShell engine; old binary reproduced the bug.
  • Six packaging regressions, tracked Zig formatting, Bash syntax, and Git whitespace checks passed.

This PR merges the upstream baseline and audited fixes; it does not authorize a 1.4 release or claim UI parity.

thomasfedb and others added 30 commits October 22, 2026 01:39
Also clamp keyboard split resizes to the split size, which fixed an
assertion failure when resize_split moved past the edge of a split.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Zig 0.17 port replaced the absolute path to the executable in the
desktop file, D-Bus services and systemd user service with a bare
`ghostty`, since the install prefix is no longer known while the build
is configured. systemd resolves a bare ExecStart= name against a fixed
search path rather than PATH: /usr/bin and /usr/local/bin on most
systems, and only systemd's own directory on NixOS. So the service
failed to start for any install outside those directories, including
every Nix one.

-Dinstalled-exe-path gives the absolute path to write into those files.
Without it they still name the executable alone, which is right for
packages installing to /usr. The Nix package passes its output path, and
the Flatpak manifests pass /app/bin/ghostty, which is what they produced
before the port.
Run `systemd-analyze verify` on the systemd user service, and check that
every Exec= and TryExec= in the D-Bus service and desktop file is an
absolute path to an executable, so a build that stops passing
-Dinstalled-exe-path fails instead of shipping a service systemd can't
start.

The verifier starts a user manager, which needs a writable runtime
directory that the sandbox doesn't have, so it gets one under $TMPDIR.
While a chunked Kitty graphics load was in progress, every transmit
or animation frame command was treated as its next chunk, regardless
of its transmission medium. A file (`t=f`), temporary file (`t=t`), or
shared memory (`t=s`) command arriving mid-load had its encoded path
appended to the partial image as pixel data.

This diverged from Kitty. The spec only defines chunking for direct
data, and continuation chunks must carry only the `m` and `q` keys, so
a command with another medium can never be a continuation. Kitty
abandons the in-progress load and handles the command as a new
transmission, for both image and animation frame loads. A client that
interrupts a load this way now gets the image or frame it asked for
instead of a corrupted or failed one.

`execute` now destroys the in-progress load before dispatching any
non-direct transmit or frame command, so the command is handled as a
fresh transmission, including its own `q` setting.
ECMA-48 says a control string that is cancelled or aborted is thrown
away. xterm only dispatches a control string on ST, and Kitty only
ends an APC on ST or BEL, so neither runs the command.

https://ecma-international.org/publications-and-standards/standards/ecma-48/
…hostty-org#14811)

While a chunked Kitty graphics load was in progress, every transmit or
animation frame command was treated as its next chunk, regardless of its
transmission medium. A file (`t=f`), temporary file (`t=t`), or shared
memory (`t=s`) command arriving mid-load had its encoded path appended
to the partial image as pixel data.

This diverged from Kitty. The spec only defines chunking for direct
data, and continuation chunks must carry only the `m` and `q` keys, so a
command with another medium can never be a continuation. Kitty abandons
the in-progress load and handles the command as a new transmission, for
both image and animation frame loads. A client that interrupts a load
this way now gets the image or frame it asked for instead of a corrupted
or failed one.

`execute` now destroys the in-progress load before dispatching any
non-direct transmit or frame command, so the command is handled as a
fresh transmission, including its own `q` setting.
ECMA-48 says a control string that is cancelled or aborted is thrown
away. xterm only dispatches a control string on ST, and Kitty only ends
an APC on ST or BEL, so neither runs the command.


https://ecma-international.org/publications-and-standards/standards/ecma-48/
A stream continuation that ended after an 8-bit C1 introducer had
aborted an APC could not be exported. For example, `ESC _ data 0x9B`
ends the APC and starts a CSI, but the tracker only treats ESC as the
start of a new sequence, so it kept the aborted APC. Replaying that
would end the APC a second time, and validation rejected it.

Export now starts at the last C1 introducer that moved the parser into
a new sequence and writes it in its 7-bit ESC form.
…-org#14806)

The Zig 0.17 port replaced the absolute path to the executable in the
desktop file, D-Bus services and systemd user service with a bare
`ghostty`, since the install prefix is no longer known while the build
is configured. systemd resolves a bare ExecStart= name against a fixed
search path rather than PATH: /usr/bin and /usr/local/bin on most
systems, and only systemd's own directory on NixOS. So the service
failed to start for any install outside those directories, including
every Nix one.
    
-Dinstalled-exe-path gives the absolute path to write into those files.
Without it they still name the executable alone, which is right for
packages installing to /usr. The Nix package passes its output path, and
the Flatpak manifests pass /app/bin/ghostty, which is what they produced
before the port.

Fixes ghostty-org#14805

AI disclosure: Claude Code was used in the preparation of this PR.
…tty-org#14814)

A stream continuation that ended after an 8-bit C1 introducer had
aborted an APC could not be exported. For example, `ESC _ data 0x9B`
ends the APC and starts a CSI, but the tracker only treats ESC as the
start of a new sequence, so it kept the aborted APC. Replaying that
would end the APC a second time, and validation rejected it.

Export now starts at the last C1 introducer that moved the parser into a
new sequence and writes it in its 7-bit ESC form.
…`ghostty_osc_free`

Signed-off-by: Uzair Aftab <git@uzaaft.me>
Change-Id: I4efdf6a51f568b4bc2678ef4a6aa1c106a6a6964
Triggered by [discussion
comment](ghostty-org#14822 (comment))
from @jcollie.

Vouch: @AlJohri

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Implement the kitty_read, kitty_write, and list (paste event) requests
in the GTK apprt on top of the existing core protocol support, and wire
the confirmation dialog up to Kitty requests, including session-grant
remember support.

Also fix two adjacent issues found while testing: the clipboard-write
signal now carries a "cleared" flag so image-only writes toast as
copies instead of clears, and a failed OSC 52 text read now answers
with an empty reply instead of leaving the client waiting forever.

TODO: the new remember-row strings in the confirmation dialog are
deliberately not marked for translation; wrap them in i18n._() and
regenerate the translation files once they should be translated.
Decode the first image/* representation of a Kitty clipboard request
with gdk_texture_new_from_bytes and show it in the dialog in place of
the text preview, falling back to the text preview when there is no
decodable image. The picture's paintable and the visible preview page
are bound declaratively to the clipboard-image property.
Remembering the denial set clipboard-read to deny before the request was
answered, and the OSC 52 reply is written by the same path that serves an
allowed read, which asserts reads aren't denied.
A multipart payload was reduced to one preview — the first decodable
image, else the first text, else a bare list of MIME types — so the user
authorized data they were never shown. The dialog now gives each
representation its own page, named by MIME type.
The script exited as soon as it had written the transaction, so the
terminal's status packet arrived with nothing left to read it and
printed itself on the next shell prompt. It also carries a name and a
session password now, so a run exercises the permission prompt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Erasing scrollback (ED3) moved Kitty image placements anchored in the
erased rows onto the top-left of the active area, where they became
visible again until the next screen clear. PageList.eraseRows moves
tracked pins in erased rows to the top-left of the retained rows
without marking them garbage, so the image storage never noticed the
placement had lost its row.

Remove pin-backed placements in the erased history range (and any
relative placements orphaned by that) before erasing the rows, while
their pins still identify them. The PageList pin semantics are left
unchanged since search relies on erased pins not being garbage.

Fixes: ghostty-org#14831
Erasing scrollback (ED3) moved Kitty image placements anchored in the
erased rows onto the top-left of the active area, where they became
visible again until the next screen clear. PageList.eraseRows moves
tracked pins in erased rows to the top-left of the retained rows without
marking them garbage, so the image storage never noticed the placement
had lost its row.

Remove pin-backed placements in the erased history range (and any
relative placements orphaned by that) before erasing the rows, while
their pins still identify them. The PageList pin semantics are left
unchanged since search relies on erased pins not being garbage.

Fixes: ghostty-org#14831

AI disclosure: Claude Code was used in the preparation of this PR.
StreamHandler.changeConfig queued a color_scheme_report message into the
termio mailbox. changeConfig runs on the io thread, which is the only
reader of that mailbox, so when the mailbox was full (for example, from
a burst of query replies queued by the reader thread) the io thread
blocked forever pushing to itself. Every other thread then blocked on
its next send to the same mailbox, freezing Ghostty on a config reload.

Write the report from Termio.changeConfig instead, using the same
colorSchemeReportLocked the message handler uses. The report still
follows the config update in order with other output.

Fixes: ghostty-org#14833
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes a regression of ghostty-org#1297 on GTK.

**Issue**
Splits can be dragged or resized down to 0px, which just looks like a
thick divider. The 10px minimum from ghostty-org#1323 was lost in the GTK rewrite.

**Fix**
Splits are now at least one cell plus padding in size, and the divider
can't be dragged past that. This also clamps keyboard resizes.

Tested on Fedora 44. Dragging and `resize_split` stop at one column or
line. I believe that macOS already has its own minimum and is unchanged.

First pass written with Claude Code (Claude Opus 5.5), reviewed and
reprompted by me.
Triggered by [discussion
comment](ghostty-org#14844 (comment))
from @jcollie.

Denounce: @ifmael

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
mitchellh and others added 29 commits October 26, 2026 09:53
…hostty-org#14859)

The zsh integration reported the working directory with `print`, which
expands backslash escapes in every argument, including `$PWD`. A
directory named `x\a\e]52;c;SGVsbG8=\a` ended our OSC 7 early and sent a
real OSC 52 sequence. A directory name containing a raw ESC or BEL byte
did the same thing in both zsh and bash.

zsh now uses `print -r` so backslashes in the path are printed as-is,
and writes the ESC and BEL framing bytes with `$'...'` quoting. Both zsh
and bash strip control bytes from the hostname and directory before
sending them, the same way the zsh title code already does. Fish was
already safe because it percent-encodes the path.
- expose read-write `name override` property on tab and terminal classes in Ghostty.sdef
- implement `titleOverride` accessors in `ScriptTab` and `ScriptTerminal`
- add `titleOverride` property to `SurfaceView` to manage manual terminal title overrides
- fix minor inconsistencies in comments

AppleScript API usage:

```applescript
-- Setting and clearing a tab name override via AppleScript
tell application "Ghostty"
    -- Target the selected tab of the front window
    set targetTab to selected tab of front window

    -- Set name override
    set name override of targetTab to "My Custom Tab Title"
    log "Tab title: " & (name of targetTab)
    log "Tab override: " & (name override of targetTab)
    delay 2.0 -- delay is here only for illustrative purposes

    -- Clear override
    set name override of targetTab to ""
end tell
```

AI disclosure: Antigravity CLI was used for the first draft. Iterated, minimised, reviewed, and tested by me.

Closes: ghostty-org#12048
Discussion: ghostty-org#12047
Co-Authored-By: Gemini 4 Argon (Medium)
Co-Authored-By: Gemini 3.8 Flash (Medium)
Co-Authored-By: Antigravity CLI 1.3.5
Agent-Session: cdd11bbe-928a-4a00-a085-c8f87a57966f
Signed-Off-By: Paal Øye-Strømme <paal.o.eye@gmail.com>
When closing sibling splits down to a single terminal leaf while
zoomed via AppleScript, `SplitTree` retained its zoomed node
reference because the target being removed was not the zoomed node itself.

Because the tree was no longer split but the zoomed property remained non-nil,
the window's zoom state stayed active and the reset zoom indicator remained visible.

`SplitTree` now validates that a tree can only have a zoomed node if
it actually contains splits and the zoomed node exists in root. In
addition, `SplitTree` exposes an `isZoomed` property, and the terminal
controller and window reactively update the zoom state and accessory
controls accordingly.

AI disclosure: Antigravity CLI wrote the tests initially. Iterated, reviewed, and tested by me.

Co-Authored-By: Gemini 3.8 Flash (Medium) <gemini-code-assist@google.com>
Signed-Off-By: Paal Øye-Strømme <paal.o.eye@gmail.com>
…g#14733)

- expose read-write `name override` property on tab and terminal classes
in Ghostty.sdef
- implement `titleOverride` accessors in `ScriptTab` and
`ScriptTerminal`
- add `titleOverride` property to `SurfaceView` to manage manual
terminal title overrides
- fix minor inconsistencies in comments

AppleScript API usage:

```applescript
-- Setting and clearing a tab name override via AppleScript
tell application "Ghostty"
    -- Target the selected tab of the front window
    set targetTab to selected tab of front window

    -- Set name override
    set name override of targetTab to "My Custom Tab Title"
    log "Tab title: " & (name of targetTab)
    log "Tab override: " & (name override of targetTab)
    delay 2.0 -- delay is here only for illustrative purposes

    -- Clear override
    set name override of targetTab to ""
end tell
```

AI disclosure: Antigravity CLI was used for the first draft. Iterated,
minimised, reviewed, and tested by me.

Closes: ghostty-org#12048
Discussion: ghostty-org#12047
…#14703)

When closing sibling splits down to a single terminal leaf while zoomed
via AppleScript, `SplitTree` retained its zoomed node reference because
the target being removed was not the zoomed node itself.

Because the tree was no longer split but the zoomed property remained
non-nil, the window's zoom state stayed active and the reset zoom
indicator remained visible.

`SplitTree` now validates that a tree can only have a zoomed node if it
actually contains splits and the zoomed node exists in root. In
addition, `SplitTree` exposes an `isZoomed` property, and the terminal
controller and window reactively update the zoom state and accessory
controls accordingly.

AI disclosure: Antigravity CLI wrote the tests initially. Iterated,
reviewed, and tested by me.

<details>
<summary>UX before:</summary>
<video
src="https://github.com/user-attachments/assets/263557d1-1be8-41ca-9070-85075c659afb"
width="100%"></video>
</details>

<details>
<summary>UX after:</summary>
<video
src="https://github.com/user-attachments/assets/55ba6778-0901-4722-aa51-8ea0b0c83a1c"
width="100%"></video>
</details>

Minimal reproducible example (MRE) using `AppleScript`:
```applescript
tell application id "com.mitchellh.ghostty"
    activate

    -- Get the current focused terminal
    set t1 to focused terminal of selected tab of front window

    -- Create 4 splits (2x2 grid)
    --   [ t1 | t2 ]
    --   [ t3 | t4 ]
    set t2 to split t1 direction right
    delay 0.5

    set t3 to split t1 direction down
    delay 0.5

    set t4 to split t2 direction down
    delay 0.5

    -- Zoom one of the splits (t1)
    perform action "toggle_split_zoom" on t1
    delay 0.5

    -- Close other splits
    try
        close t4
    on error
        perform action "close_surface" on t4
    end try
    delay 0.2

    try
        close t3
    on error
        perform action "close_surface" on t3
    end try
    delay 0.2

    try
        close t2
    on error
        perform action "close_surface" on t2
    end try
end tell
```
Co-authored-by: OshDubh <60195526+OshDubh@users.noreply.github.com>
This adds support for FreeBSD to libghostty-vt and validates it in CI. 

There were only a few places we really need to make compatibility fixes:

- The memory decommit/recommit stuff, which is very OS specific.
- Kitty graphics shared memory paths
- TinyIO, one method
Triggered by [discussion
comment](ghostty-org#14877 (comment))
from @jcollie.

Vouch: @rtgibbons

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Integrate upstream release 83fb956 while retaining the native Windows runtime, text-only clipboard mode gate, cache maintenance, and disabled upstream publishing workflows.
Preserve clipboard ownership and allocation bounds, resolve surfaces after modal close confirmation, discard aborted application APC commands, sanitize PowerShell OSC payloads, and adapt native OpenSSH output and terminfo arguments. Add focused regressions without desktop-global input.
Recognize cursor:blink and cursor:steady from setupFeatures and verify prompt/Enter output with actual feature strings under PowerShell 5.1 and 7 without accepting input.
Require clean annotated Windows tags for production packages, compare executable and PE versions, reject missing resources, and validate the staged portable layout. Add package regression tests and Windows CI payload checks under PowerShell 5.1 and 7.
Publish top-level show/hide transitions, keep hidden layouts occluded, and preserve zoomed-out pane visibility during restore. Add a no-global-input integration regression for mode 2033 across normal and quick-terminal window states.
Document the exact upstream release baseline, tested Windows fixes, confirmed common GUI and shell gaps, and distinct platform-specific roadmap work. Hold 1.4 tagging and publication until full feature parity is implemented and verified.
Give fixture builds and helper-launched builds the same disposable local cache even when setup-zig exports ZIG_LOCAL_CACHE_DIR. Verify the maintenance suite with an inherited CI-style cache override.
@shiweis
shiweis merged commit 3d9074d into main Oct 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.