Found another regression when investigating #14107 after the last fix.
This regression appears on macOS 15 and 26 as well: **New window by
Shortcuts.app or service menu while a window is visible would create a
tab**.
It appears that for `new-window` triggered by Shortcuts/Service, a small
delay is needed to avoid automatic tabbing. It's either removing
`NSWindow.userTabbingPreference == .always` completely or adding another
"delay" for cascading. The latter should be better.
Also fixes another cascading for `macos-titlebar-style = hidden`
previously missed.
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.
Programs can now write the system clipboard through the Kitty
clipboard protocol in the macOS app. This also does all the hard work
plumbing through core termio/apprt so GTK should be an easy follow.
This functionality lets clients copy arbitrary representations (images,
HTML, etc.) into the clipboard. Writes honor `clipboard-write`: allow
applies silently, deny answers EPERM up front before any data is used,
and ask shows the standard confirmation prompt.
Store the Comparable ObjectIdentifier directly instead of wrapping the
only sort key type in AnySortKey.
The expected deterministic ordering of equal terminal command titles is
also now verified by a unit test.
Fixes#10077
Clipboard read confirmations would immediately show a sheet which
grabbed focus. This could be used for a bunch of dumb reasons, including
DoS attacks. But, it also caused focus/sheet loops for programs that did
OSC52 on focus changes (which was seen via some Neovim configs!).
Now, if a surface is unfocused, we bell the surface and show the confirmation
request on next focus. If the surface is not focused or another request
comes in, we cancel the prior one.
This also fixes some memory management issues around clipboard requests
that were likely small leaks (didn't verify the old bug, but verified
the new code, and eyeballed the old).
windowDidLoad undoes macOS automatic window tabbing by inspecting
window.tabGroup. Accessing tabGroup on a fresh window materializes
AppKit's tab group machinery, which takes ~15-20ms and is on the
critical path of every window creation, including the first window at
app launch.
AppKit only auto-tabs a fresh window when the system tabbing
preference is "always": the tab bar "+" button goes through
newWindowForTab which we intercept and route through our own tab
logic, so it never auto-tabs. Guard the check on
NSWindow.userTabbingPreference == .always so everyone else skips the
tab group materialization entirely.
Measured on macOS (Apple Silicon) during app launch via the startup
timeline instrumentation:
windowDidLoad tab group check: 17.8ms -> ~0ms
main() -> window visible: median ~173ms -> ~165ms (n=7)
Measured on macOS (Apple Silicon) during app launch, via a startup
timeline instrumented across the Swift app and libghostty:
config apply, errors step: 35.5ms -> 0.1ms
main() -> first frame rendered: ~126ms -> ~93ms
main() -> window visible: ~193ms -> ~173ms
For new windows to get their appearance synced, we need to call
`syncAppearance` after `super.showWindow(sender)`. All previous calls to
`syncAppearance` on `TerminalWindow` will be ignored because the window
needs to have `isVisible` set to `true`.
This regression was introduced by:
5368adcd29
It added `.dropFirst()` to the `focusedSurface` appearance publishers in
`TerminalController.swift` which removes the initial call of the
subscription.
Fixes https://github.com/ghostty-org/ghostty/issues/13324
(landed on the same fix as @rasitakyol found here:
https://github.com/ghostty-org/ghostty/pull/13341)
Matches are sorted in the following order:
leadingColor > title > subtitle > description.
Ranking is lexicographic on (colorScore, textScore)
<img height="300" alt="image"
src="https://github.com/user-attachments/assets/1ec99e67-537e-4fc6-b595-d7eec8cbf31d"
/>
### AI Disclosure
Claude reviewed and added unit tests, also did some refactoring of my
original implementation.
Fixes #/13074
Overlapping clipboard confirmations now defer denial until the next
main queue turn rather than completing inside the confirmation callback.
This prevents the native request state from being invalidated while its
callback is still active, avoiding the OSC 52 crash reported in #13074.
The deferred closure retains the originating surface view and completes
the ignored request with empty data, preserving the existing deny
behavior.
Fixes#13444
A close while AppKit temporarily cleared/changed a surface's window
would leak the surface in the controller's pslit tree. This retained
surface kept a bunch of resources around, particularly large IOSurfaces.
This seems to only be reproducible under scripted load: rapid terminal
creation/destruction so that destruction happens just while there is a
nil window on a surface view.
Track surface ownership in a weak controller map updated alongside the
split tree, with validated fallbacks for existing attachment state.
Resolve scripted and App Intent operations through that ownership, and
route non-confirming root closes directly through the immediate tab or
window close path so teardown always reaches the renderer.
Fixes#13444
A close while AppKit temporarily cleared/changed a surface's window would
leak the surface in the controller's pslit tree. This retained surface kept
a bunch of resources around, particularly large IOSurfaces.
This seems to only be reproducible under scripted load: rapid terminal
creation/destruction so that destruction happens just while there is a nil
window on a surface view.
Track surface ownership in a weak controller map updated alongside the split
tree, with validated fallbacks for existing attachment state. Resolve
scripted and App Intent operations through that ownership, and route
non-confirming root closes directly through the immediate tab or window close
path so teardown always reaches the renderer.
#13611
Route new-tab window presentation through an Objective-C exception catcher.
AppKit can raise an NSInternalInconsistencyException while selecting a
new tab in native fullscreen.
Catch the presentation exception, report it through the existing error
logging path, and leave Ghostty running when AppKit’s fullscreen window
stack is inconsistent.
This was pretty hard to reproduce but I was able to reproduce it about
1/3rd of the time via AppleScript automation...