Before this change, ghostty frequently logs the following warning, even though a `gtk-xft-dpi` value of `-1` is valid and indicates default scaling.
```
warning(gtk_ghostty_surface): gtk-xft-dpi has invalid value (-1), using default
```
From [the gtk docs](https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html):
> The font resolution, in 1024 * dots/inch.
>
> -1 to use the default value.
A follow on for #14035, we can now fix a long standing
effiency/performance bug now that we park the display link while idle.
This actually could cause "animated" un-focused windows to use more GPU
than their focused counterparts. (AI Agent interfaces seem to love
animation).
6ae1784f4
Unfocused surfaces stopped the CVDisplayLink and encoded a GPU frame on
every PTY wakeup. A burst of close writes became that many Metal submits
instead of one vsync.
Keep the link running while the surface is visible and dirty or
animating, whether or not it is focused. Idle surfaces still park.
Update the macOS Sparkle dependency from 2.9.0 to 2.9.4.
This keeps the Swift package resolution and all tag/tip release workflow
downloads aligned on the same version. Sparkle 2.9.2 included fixes for
GHSA-g3hp-f6mg-559v and GHSA-hg88-v3cw-3qrh; 2.9.4 is the current stable
release.
Validation:
- verified the 2.9.4 release contains
`Sparkle-for-Swift-Package-Manager.zip`
- verified the lockfile revision matches the 2.9.4 tag
- `jq empty` on `Package.resolved`
- `git diff --check`
I could not run Xcode package resolution locally because the active
developer directory is Command Line Tools rather than a full Xcode
installation.
The Zig 0.16.0 update dropped the negation from the std default
(!strip_debug_info), disabling stack traces in every unstripped build.
AI disclosure: Claude Fable was used to diagnose the problem and find
the fix. Commit message was written by me.
Claude-Session: https://claude.ai/code/session_01QfzQME46DQXwMWa43bQaa3
Fixes#14037 where dragging the surface from a tab with just a single
surface to another tab causes a crash.
The cause of the crash is a stale pointer to the property binding
created in `Surface.bindIsSplit`. When the surface is moved,
`SplitTree.moveSplit` first updates the two split tree data structures
of the source/target tab and then calls `bindIsSplit` to bind the
`is-split` property of the moved surface to the `SplitTree` widget in
the target tab. When `bindIsSplit` is called, the `SplitTree` widget in
the source tab has already been destroyed (because the source tab is now
empty) which causes the old binding to be freed automatically and the
pointer `Surface.is_split_binding` becomes stale. `bindIsSplit` then
tries to run `is_split_binding.unbind()` which causes the crash.
When you create a binding with `bindProperty`, the binding itself owns
the initially created reference and it gets freed when the source or
target object of the binding is finalized. To prevent this, we now
create an extra reference to the binding object so that the Surface
widget owns it and is responsible for freeing it. The binding can still
get severed automatically, but the binding object itself will not be
destroyed. This is the solution mentioned in the [GObject
docs](https://docs.gtk.org/gobject/method.Object.bind_property.html).
Alternatively, using a WeakRef for the pointer would have also worked.
Updated the binding in `SurfaceScrolledWindow` to use the same pattern.
That one was probably fine, because the binding should only be created
once, but it doesn't hurt to be safe.
I reproduced the crash on KDE, on Hyprland I just got a glib critical
error message about the invalid pointer. That probably has to do with
what exactly happens to the freed memory, or maybe differing versions.
#### AI Disclosure
Code and comments were written by myself, used GPT5.6 in researching
gobject binding lifecycles.
The Zig 0.16.0 update dropped the negation from the std default
(!strip_debug_info), disabling stack traces in every unstripped build.
AI disclosure: Claude Fable was used to diagnose the problem and find the
fix. Commit message was written by me.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfzQME46DQXwMWa43bQaa3
6ae1784f4
Unfocused surfaces stopped the CVDisplayLink and encoded a GPU
frame on every PTY wakeup. A burst of close writes became that many
Metal submits instead of one vsync.
Keep the link running while the surface is visible and dirty or
animating, whether or not it is focused. Idle surfaces still park.
Focus continues to gate cursor blink, custom-shader animation, and
QoS.
Previously, the property binding created in `Surface.bindIsSplit` would
get freed automatically when the source object (the SplitTree widget)
got finalized. A subsequent call to `bindIsSplit` could then cause a
crash by using the stale pointer to the binding. This bug could e.g. be
triggered by dragging the surface from a single-surface tab to another
tab.
We now create an extra reference to the binding object so that Surface
essentially owns the binding and is responsible for freeing it.
Updated the binding in `SurfaceScrolledWindow` to use the same pattern.
That one was probably fine, because the binding is only created once,
but let's be safe.
Erasing a wrapped wide character at the start of a row (`ECH` or `DCH`)
also clears the spacer head it left at the end of the previous row, but
that row was never marked dirty. With both rows visible, an incremental
render kept the stale spacer head on screen until something unrelated
redrew that row.
The clearing happens in the row-start branch of `splitCellBoundary`.
`clearCells` doesn't do dirty tracking, and both callers only mark the
cursor row, so mark the previous row at the point it's mutated.
The added dirty assertions fail without the fix.
## AI Disclaimer
Claude did the heavy lifting - identifying the root cause, generating
code and description. I reviewed and iterated on it to move around and
tweak tests, comments and reduce verboseness. Verified the end user
visible behaviour improvement with a script that coloured the wide
character, which made the stale rendering visible until a switch to the
alt screen and back cleared it it.
Erasing a wrapped wide character at the start of a row (ECH or DCH)
also clears the spacer head it left at the end of the previous row,
but that row was never marked dirty. With both rows visible, an
incremental render kept the stale spacer head on screen until
something unrelated redrew that row.
The clearing happens in the row-start branch of splitCellBoundary.
clearCells doesn't do dirty tracking, and both callers only mark
the cursor row, so mark the previous row at the point it's mutated.
The added dirty assertions fail without the fix.
Discussion #14048
Directory URLs no longer export a trailing slash through PWD, which
keeps zsh's %1~ prompt expansion from resolving to an empty string.
A shared URL helper removes trailing separators while preserving the
filesystem root and percent-decoding behavior. Tests cover normal,
repeated, encoded, and root paths.
Discussion #14048
Directory URLs no longer export a trailing slash through PWD, which
keeps zsh's %1~ prompt expansion from resolving to an empty string.
A shared URL helper removes trailing separators while preserving the
filesystem root and percent-decoding behavior. Tests cover normal,
repeated, encoded, and root paths.
`send key` only works for control keys like `enter` currently; this adds
(fixes) the support for other keys listed as available. Found by
@paaloeye in #13180
The core of this fix is relying on `UCKeyTranslate` to get the
corresponding character and code point from a key code using
`KeyboardLayout.character(for:modifiers:)`.
ScriptKeyEventCommand now respects `macos-option-as-alt`, and attach
`text`, `unshifted_codepoint` and `consumed_mods` under the same
condition as a manual input events like in `performKeyEquivalent` and
`localEventKeyDown`.
## AI Disclosure
Claude did the heavy lifting, I reviewed and rephrased some of the
comments it generated. And ofc reviewed and tested myself.
Use a bounded 512-byte stack fallback for complete-snapshot record
scratch. Small records avoid heap growth while larger records continue
through the heap allocator.
This usually means 2 fewer allocations per encode, resulting in a small
performance improvement in a local benchmark:
| Workload | `main` median | Branch median | Change |
|---|---:|---:|---:|
| Empty, 50k encodes | 135.06 ms | 130.76 ms | **3.2% faster** |
| 1 MiB ASCII, 500 encodes | 280.75 ms | 281.25 ms | **0.2% slower**,
within noise |
| 1 MiB styled, 200 encodes | 1,023.02 ms | 996.32 ms | **2.6% faster**
|
Bumps
[hustcer/milestone-action](https://github.com/hustcer/milestone-action)
from 3.1 to 3.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/hustcer/milestone-action/releases">hustcer/milestone-action's
releases</a>.</em></p>
<blockquote>
<h2>v3.2</h2>
<h2>[3.2] - 2026-08-25</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Harden action inputs and make GraphQL file lookup path-independent
(<a
href="https://redirect.github.com/hustcer/milestone-action/issues/168">#168</a>)</li>
<li>Look up milestones by title across all states with pagination (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/170">#170</a>)</li>
<li>Guard GITHUB_OUTPUT, surface GraphQL errors and tighten is-int (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/172">#172</a>)</li>
<li>Require title for create action and sync stale docs (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/174">#174</a>)</li>
<li>Send milestone fields as raw strings and harden action inputs (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/176">#176</a>)</li>
</ul>
<h3>Deps</h3>
<ul>
<li>Upgrade hustcer/setup-nu to v3.25 & Nu to 0.113.1</li>
<li>Upgrade hustcer/setup-nu to v3.27 and Nu to 0.115</li>
<li>Upgrade Nu to 0.115.1</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/hustcer/milestone-action/blob/main/CHANGELOG.md">hustcer/milestone-action's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<h2>[3.2] - 2026-08-25</h2>
<h3>Bug Fixes</h3>
<ul>
<li>Harden action inputs and make GraphQL file lookup path-independent
(<a
href="https://redirect.github.com/hustcer/milestone-action/issues/168">#168</a>)</li>
<li>Look up milestones by title across all states with pagination (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/170">#170</a>)</li>
<li>Guard GITHUB_OUTPUT, surface GraphQL errors and tighten is-int (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/172">#172</a>)</li>
<li>Require title for create action and sync stale docs (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/174">#174</a>)</li>
<li>Send milestone fields as raw strings and harden action inputs (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/176">#176</a>)</li>
</ul>
<h3>Deps</h3>
<ul>
<li>Upgrade hustcer/setup-nu to v3.25 & Nu to 0.113.1</li>
<li>Upgrade hustcer/setup-nu to v3.27 and Nu to 0.115</li>
<li>Upgrade Nu to 0.115.1</li>
</ul>
<h2>[3.1] - 2026-01-23</h2>
<h3>Documentation</h3>
<ul>
<li>Update milestone-action version in README (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/162">#162</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>Break before sleep when milestone found</li>
</ul>
<h3>Miscellaneous Tasks</h3>
<ul>
<li>Update README.md (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/166">#166</a>)</li>
</ul>
<h3>Deps</h3>
<ul>
<li>Update Nu to 0.109.1</li>
<li>Update Nushell to 0.110.0 (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/167">#167</a>)</li>
<li>Upgrade hustcer/setup-nu to v3.22</li>
</ul>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<h2>[3.0] - 2025-10-26</h2>
<p>This release introduces changes that may impact some users. If the
action fails due to insufficient permissions, please add the
<code>issues: write</code> and <code>pull-requests: write</code>
permissions to your workflow. Additionally, the API for binding
milestones has been modified. Due to these changes, the major version
has been incremented to 3.</p>
<h3>Bug Fixes</h3>
<ul>
<li>Try to fix GitHub Projects (classic) deprecation warning by using
REST API instead of GraphQL (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/157">#157</a>)</li>
<li>Fix "Resource not accessible by integration" error for
issue milestone binding by adding <code>issues: write</code>
permission</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2f38355153"><code>2f38355</code></a>
Bump to v3.2</li>
<li><a
href="90d6122217"><code>90d6122</code></a>
deps: Upgrade Nu to 0.115.1</li>
<li><a
href="e587063f37"><code>e587063</code></a>
fix: Send milestone fields as raw strings and harden action inputs (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/176">#176</a>)</li>
<li><a
href="cc2b756fd4"><code>cc2b756</code></a>
fix: Require title for create action and sync stale docs (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/174">#174</a>)</li>
<li><a
href="f8cedae0c1"><code>f8cedae</code></a>
fix: Guard GITHUB_OUTPUT, surface GraphQL errors and tighten is-int (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/172">#172</a>)</li>
<li><a
href="b57a2e26d7"><code>b57a2e2</code></a>
fix: Look up milestones by title across all states with pagination (<a
href="https://redirect.github.com/hustcer/milestone-action/issues/170">#170</a>)</li>
<li><a
href="3ba8f40c95"><code>3ba8f40</code></a>
fix: Harden action inputs and make GraphQL file lookup path-independent
(<a
href="https://redirect.github.com/hustcer/milestone-action/issues/168">#168</a>)</li>
<li><a
href="1e6a3fcc55"><code>1e6a3fc</code></a>
deps: Upgrade hustcer/setup-nu to v3.27 and Nu to 0.115</li>
<li><a
href="9131595492"><code>9131595</code></a>
deps: Upgrade hustcer/setup-nu to v3.25 & Nu to 0.113.1</li>
<li>See full diff in <a
href="ebed8d5daa...2f38355153">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
#14033
Pause the CVDisplayLink when there isn't any real work to do.
Start the link after updateFrame rebuilds cells, keep it running while
cell changes or animations remain pending, and resync it from the
no-redraw path to sleep it again.
I also did some benchmark to measure the cost of starting/stopping the
display link since this includes a lot more of that and I found that a
continuously running link used 29 to 35 us of CPU per callback, while
starting and stopping it for every frame used 103 to 121 us. So, in some
pathological case this can be worse, but its still microseconds, and in
the normal case this helps Ghostty sleep a lot more.
#14033
Pause the CVDisplayLink when there isn't any real work to do.
Start the link after updateFrame rebuilds cells, keep it running while
cell changes or animations remain pending, and resync it from the
no-redraw path to sleep it again.
I also did some benchmark to measure the cost of starting/stopping the
display link since this includes a lot more of that and I found that
a continuously running link used 29 to 35 us of CPU per callback, while
starting and stopping it for every frame used 103 to 121 us. So, in some
pathological case this can be worse, but its still microseconds, and in
the normal case this helps Ghostty sleep a lot more.
Co-authored-by: Jesse Miller <jmiller@jmiller.com>
Drop UTF-8 decoded C1 controls entirely. This matches xterm's default
behavior which is our standard policy (but note it diverges from libvte
which executes them). There isn't really any standard I could find
around this.
The ground state UTF-8 fast paths (both the scalar decoder and the
batched SIMD path) previously treated decoded codepoints C1 control
codepoints as normal UTF-8 text and routed them to print.
Drop UTF-8 decoded C1 controls entirely. This matches xterm's default
behavior which is our standard policy (but note it diverges from libvte
which executes them). There isn't really any standard I could find
around this.
The ground state UTF-8 fast paths (both the scalar decoder and the
batched SIMD path) previously treated decoded codepoints C1 control
codepoints as normal UTF-8 text and routed them to print.
Fixes#14021
The ground state UTF-8 fast paths only classified 0x00-0x0F plus 0x1B
(escape) as C0 controls. The remaining C0 bytes (0x10-0x1A, 0x1C-0x1F)
were decoded as ordinary codepoints and routed to print as if they were
text.
This resulted in incorrect grids but also very weird font fallback, e.g.
U+0014 would find CJK fonts.
This commit fixes this by routing every ground state C0 byte except ESC
to execute as it should be.
Fixes#14021
The ground state UTF-8 fast paths only classified 0x00-0x0F plus 0x1B (escape)
as C0 controls. The remaining C0 bytes (0x10-0x1A, 0x1C-0x1F) were decoded
as ordinary codepoints and routed to print as if they were text.
This resulted in incorrect grids but also very weird font fallback, e.g.
U+0014 would find CJK fonts.
This commit fixes this by routing every ground state C0 byte except ESC to
execute as it should be.