Skip to content

Merge upstream 2026 10 29 - #11

Merged
adetxt merged 105 commits into
mainfrom
merge-upstream-2026-10-29
Oct 29, 2026
Merged

adetxt merged 105 commits into
mainfrom
merge-upstream-2026-10-29

Conversation

@adetxt

@adetxt adetxt commented Oct 29, 2026

Copy link
Copy Markdown
Owner

No description provided.

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/
…(#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.
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 #14805

AI disclosure: Claude Code was used in the preparation of this PR.
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/ghostty#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: #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: #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: #14833
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes a regression of #1297 on GTK.

**Issue**
Splits can be dragged or resized down to 0px, which just looks like a
thick divider. The 10px minimum from #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/ghostty#14844 (comment))
from @jcollie.

Denounce: @ifmael

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
jcollie and others added 27 commits October 27, 2026 13:14
Fix hexadecimal letter conversion in CommaSplitter's Unicode escape
validation. Letters a-f and A-F were interpreted as 0-5 instead of
10-15, allowing oversized escapes such as \u{aaaaaa} to pass validation.

Add regression tests for lowercase and uppercase escapes.

Validation:
- Standalone tests: 27 passed, 3 Windows tests skipped.
- Formatting: passed.

AI assistance: Used OpenAI Codex for diagnosis, explanations, code/test
suggestions, validation, and PR wording.
These have been broken since Zig 0.17, I think snap since before that.

The snap moves to core26 (Ubuntu 26.04), which ships new enough GTK,
libadwaita, and harfbuzz. It also uses the system gtk4-layer-shell now
that Ubuntu packages it, and redirects `-lbzip2` to `libbz2.so` with a
one-line linker script, the same way the flatpak already does.
Add the VT420 horizontal scrolling sequences:

- DECIC (CSI Pn ' }) inserts columns at the cursor in every row of
  the scrolling region.
- DECDC (CSI Pn ' ~) deletes columns at the cursor in every row of
  the scrolling region.
- DECBI (ESC 6) moves the cursor left, or at the left margin scrolls
  the region right.
- DECFI (ESC 9) moves the cursor right, or at the right margin
  scrolls the region left, losing the column at the left margin.

DECIC and DECDC do nothing when the cursor is outside the scrolling
region. The new stream actions are appended to the C ABI action list
so that existing values are unchanged.

This fixes 20 esctest failures (DECICTests, DECDCTests, DECBITests,
and DECFITests now pass in full).
`CodepointIterator` could panic on incomplete UTF-8, such as a lone 0xC2
byte, when slicing beyond the available input.

Check the remaining byte count before slicing and return `InvalidString`
for truncated sequences. Test incomplete two-, three-, and four-byte
sequences in the iterator and `SelectionWordChars.parseCLI`.

Validation:
- `zig build test -Dtest-filter=SelectionWordChars
-Dtest-filter=codepointIterator -Demit-macos-app=false`: 91 tests
passed.
- Standalone string helper tests: 13 passed.
- Formatting and whitespace checks passed.

AI disclosure: OpenAI Codex assisted with the code, tests, and PR
description.
Signed-Off-By: Paal Øye-Strømme <paal.o.eye@gmail.com>
goto() indexes the node and spatial slot arrays with `from` unchecked.
The only caller (GTK SplitTree.goto) never reaches it with an empty
tree, so make the precondition explicit like remove() does instead of
silently returning null.
Just a small update that I did to catch a bit of missing API that I
wanted to expose in `Context`, nothing that affects Ghostty, however.

Had to skip 0.13.1 because of an issue with my release tooling.
:man_facepalming:
Add the VT420 horizontal scrolling sequences:

- DECIC (CSI Pn ' }) inserts columns at the cursor in every row of the
scrolling region.
- DECDC (CSI Pn ' ~) deletes columns at the cursor in every row of the
scrolling region.
- DECBI (ESC 6) moves the cursor left, or at the left margin scrolls the
region right.
- DECFI (ESC 9) moves the cursor right, or at the right margin scrolls
the region left, losing the column at the left margin.

DECIC and DECDC do nothing when the cursor is outside the scrolling
region. The new stream actions are appended to the C ABI action list so
that existing values are unchanged.

This fixes 20 esctest failures (DECICTests, DECDCTests, DECBITests, and
DECFITests now pass in full).

AI disclosure: Claude Code was used in the preparation of this PR.
`SplitTree.goto()` indexes `self.nodes` / the spatial slots with `from`
without checking it. On an empty tree, `.previous_wrapped`,
`.next_wrapped` and `.spatial` hit `index out of bounds: index 0, len
0`; `.previous` and `.next` just return null.

This is not reachable today: the only caller, the GTK
`SplitTree.goto()`, gets `from` from `getActiveSurfaceHandle()`, which
returns null for an empty tree, so it bails out first. So this is not a
bug fix, only the contract made explicit, the same way `remove()`
asserts `at.idx() < self.nodes.len`:

- doc comment: `from` must be a valid handle, so the tree must not be
empty
- `assert(from.idx() < self.nodes.len)` at the top of `goto()`

I first sent a `return null` guard with a test and overstated the
impact; this replaces it. Can close it if you'd rather not have the
assert.

Tested: `zig build test -Dtest-filter=SplitTree`, `zig fmt --check`.

AI disclosure: written with LM-based assistants - Antigravity for the
first version, Opus for the slightly improved rework. I reviewed the
changeset of both.
Write map entries directly to the output writer instead of using a
256-byte buffer. Add a regression test for a 300-byte value.
#14908

Replace both deprecated std.fmt.allocPrint calls with alloc.print.

AI assistance: Codex updated both calls and ran the targeted tests.
Write map entries directly to the output writer instead of using a
256-byte buffer. Add a regression test for a 300-byte value.

<img width="654" height="77" alt="Screenshot 2026-10-29 at 2 50 41 AM"
src="https://github.com/user-attachments/assets/82ba4f78-2137-4bc5-bd6f-2f2afe6cd96c"
/>
The nixpkgs update for Zig 0.17 removed gnome-photos, so evaluating
any GNOME VM check fails with "'gnome-photos' has been removed".
Excluding a package that no longer exists is unnecessary.
The pinned GNOME starts org.gnome.Shell@user, so the --unsafe-mode
override on org.gnome.Shell@wayland never applies. Without it,
org.gnome.Shell.Eval returns nothing and basic-window-check-gnome
times out waiting for the focused window.
…for tests (#14911)

Test on main takes over an hour and I'm tired of it. Almost all of that
is one chain: every expensive job waits on `test` (~13 min), and then
the arm64 `build-nix` job runs six `nix build`s serially (~50 min),
rebuilding the Zig deps derivation every commit because its name embeds
the git revision so that forces a rebuild.

This breaks that chain up. By commit:

- **nix: drop the revision from the zig deps derivation name.** The deps
derivation now matches `.#deps`, so it only rebuilds when
`build.zig.zon` changes and Cachix actually hits. This was costing ~8
minutes of arm64 time per run.
- **ci: gate expensive jobs on a quick build.** A new `build-gate` job
(just `zig build`, ~2 min) replaces `needs: test`. Cheap jobs that don't
need a compile gate now only wait on `skip`. `test` runs in parallel
with everything else.
- **ci: split build-nix into one job per target and architecture.** 12
parallel jobs instead of 2 long serial ones. `required` checks them by
result like everything else.
- **ci: build the source tarball without waiting on its tests.**
`build-dist` now runs `zig build dist`, so the Debian, snap, and flatpak
jobs get the tarball in a few minutes instead of ~13. The full
`distcheck` (unchanged) runs as `test-dist` on an md runner in parallel.
- **ci: run the full test suite once across the GTK and SIMD legs.** The
x11/wayland options don't change anything the unit tests cover, and
`-Dsimd=true` is the default, so we were running the same full suite
four extra times. The GTK legs still all build. This saves about 46
runner-minutes per run. Also pins `nix-installer-action` to v23 instead
of `@main`.

Some tradeoffs worth knowing:

- If `test` fails, the expensive jobs still run, since they're only
gated on a compile now. Failing PRs cost more compute; passing PRs get
much faster feedback. I think that's fine.
- The first run after this merges will be a bit slow for Nix since all
12 jobs build the deps derivation cold. After that it comes from Cachix.

I plan to continue looking for improvements after this.

**AI disclosure:** I used Fable to measure the existing CI runs and
propose plans to break it down. I massaged those plans pretty
significantly. I used Opus to write YAML because fuck writing YAML. I
read it all.
Triggered by [discussion
comment](ghostty-org/ghostty#14914 (comment))
from @jcollie.

Vouch: @clinta

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The GNOME VM checks fail on main, unnoticed because CI doesn't build
them: `gnome-photos` was removed from nixpkgs in 3110fb3, breaking
evaluation, and the `--unsafe-mode` override targets
`org.gnome.Shell@wayland` while GNOME now starts `org.gnome.Shell@user`,
so `Shell.Eval` returns nothing. Dropping the exclude and retargeting
the override makes `basic-window-check-gnome` and
`bell-leak-check-gnome` pass again.

**AI assistance:** I used Claude Code (Claude Opus 5.5) to find and
verify this. I reviewed the change.
@adetxt adetxt self-assigned this Oct 29, 2026
@adetxt
adetxt merged commit 35fa535 into main Oct 29, 2026
@adetxt
adetxt deleted the merge-upstream-2026-10-29 branch October 29, 2026 07:55
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.