Discussion #13979
Dropped paths and text once again honor bracketed paste mode. IME,
dictation, emoji picker, and character viewer commits remain typed
input.
sendText calls ghostty_surface_text, which applies the clipboard paste
pipeline and bracketed paste framing when enabled. Separating the paths
at the drag-and-drop caller preserves the input-method behavior
introduced by #13817.
A Kitty clipboard protocol (OSC 5522) write transaction targeting
`loc=primary` replied `type=write:status=DONE` in the macOS app even
though macOS has no primary selection and the data was silently
discarded.
The spec requires ENOSYS when the requested location is not
available on the system, which the read path already answers correctly:
https://sw.kovidgoyal.net/kitty/clipboard/
Discussion #13979
Dropped paths and text once again honor bracketed paste mode.
IME, dictation, emoji picker, and character viewer commits remain typed input.
sendText calls ghostty_surface_text, which applies the clipboard paste
pipeline and bracketed paste framing when enabled. Separating the
paths at the drag-and-drop caller preserves the input-method behavior
introduced by #13817.
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.
Translate printable physical keybindings through the current macOS
keyboard layout before assigning menu key equivalents. Previously these
bindings could not be represented because SwiftUI shortcuts are
character-based, so actions such as `super+backquote` had no native menu
shortcut.
Keep native keycodes as dispatch identity so translated display
characters do not change physical semantics or precedence over Unicode
bindings. Refresh shortcuts when the input source changes, and prevent
AppKit from transforming equivalents that are already localized.
**AI Usage:** The approach was suggested by GPT 5.6 Sol, but I wrote
most of the code and understand it all.
Translate synthetic physical-key events with characters(byApplyingModifiers:).
This preserves current-layout and Command-table behavior while avoiding a
duplicate direct UCKeyTranslate implementation in Swift.
Text Input Sources APIs are not thread-safe, but shortcut translation could
be called outside a declared main-actor context.
Mark keyboard layout and shortcut conversion as main-actor isolated, update
their tests, and dispatch key-sequence UI notifications to the main queue
before translating their shortcuts.
Separate menu shortcut presentation from lookup identity. Store either a
normalized key equivalent or a physical keycode in a private hashable enum,
allowing Swift to synthesize equality and hashing instead of maintaining
parallel optional-key logic.
Assign display characters directly from KeyboardShortcut and remove unused
NSMenuItem and SwiftUI conversion helpers.
Translate printable physical keybindings through the current macOS
keyboard layout before assigning menu key equivalents. Previously these
bindings could not be represented because SwiftUI shortcuts are
character-based, so actions such as super+backquote had no native menu
shortcut.
Keep native keycodes as dispatch identity so translated display characters
do not change physical semantics or precedence over Unicode bindings.
Refresh shortcuts when the input source changes, and prevent AppKit from
transforming equivalents that are already localized.
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.
Partially addresses #13796. Extends #13222.
Previously, `insertText` commits without marked text were delivered via
`sendText`, which applies paste semantics and wraps the text in
bracketed
paste when the program enables it. macOS dictation and other input
methods often commit without marked text, so programs treated dictated
text as a paste: opencode collapsed it into a `"[Pasted ~N lines]"` chip
and Neovim applied paste-mode handling.
`insertText` is only invoked by input methods (IME, dictation, emoji
picker, character viewer); real paste operations use a separate path.
Every non-empty commit is now sent as a key event — the same path
already used for preedit commits since #13222 — so input method text
always arrives as typed input.
Typing is unaffected (the accumulator path returns earlier) and Cmd+V
pastes are unaffected. `committedPreeditTextAction` is renamed to
`committedTextAction` since it no longer only handles preedit commits.
Testing:
- 311 macOS unit tests pass.
- Manually verified on macOS 26: dictation into Opencode and Neovim
arrives inline with no paste handling; emoji picker inserts inline;
Chinese IME composition unchanged; dictation in Neovim normal mode now
behaves as keystrokes, matching Terminal.app.
Notes:
- Dictated "new line" now matches Terminal.app behavior (no newline
with typed-text semantics). The previous behavior came from the paste
path preserving the newline; a follow-up could deliver it as an
Enter keypress if desired.
AI usage: drafted with OMO + OpenCode + DeepSeek V4 Pro assistance;
reviewed, edited, and manually tested by the author.
Previously, insertText commits without marked text were delivered via
sendText, which applies paste semantics and wraps the text in bracketed
paste when the program enables it. macOS dictation and other input
methods often commit without marked text, so programs treated dictated
text as a paste and applied paste-specific handling.
insertText is only invoked by input methods (IME, dictation, emoji
picker, character viewer); real paste operations use a separate path.
Send every non-empty commit through the key event path so programs
interpret input method text as typed input. Typing is unaffected (the
accumulator path returns earlier) and Cmd+V pastes are unaffected.
The helper is renamed from committedPreeditTextAction to
committedTextAction since it no longer only handles preedit commits.
Swift explicitly [marked UnsafeMutablePointer as non sendable](0568dbf903). Moving from `@unchecked @retroactive` to `nonisolated(unsafe)` is safe for us as per the previous comments
#13319#13748
Normalize command-line file arguments as file URLs internally while
keeping the AppKit and FileManager string boundaries unchanged.
This handles relative paths, URL-sensitive characters, and trailing
directory separators consistently when matching duplicate open-file
events.
Fixes#13319
AppKit treats existing positional arguments as documents, causing paths
passed to a child command after -e to open an extra terminal surface.
We now process args ourselves during openFile callbacks to ignore file
paths after `-e`. There isn't a way to avoid this I can find because
AppKit processes argc/argv from the main entrypoint and that can't be
overridden.
Fixes#13319
AppKit treats existing positional arguments as documents, causing paths
passed to a child command after -e to open an extra terminal surface.
We now process args ourselves during openFile callbacks to ignore
file paths after `-e`. There isn't a way to avoid this I can find
because AppKit processes argc/argv from the main entrypoint and that
can't be overridden.
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).
To implement this, I decided to reorient the whole clipboard
confirmation thing around state on SurfaceView (which simplifies memory
management) and using Combine on BaseTerminalController to get notified.
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).
`needleSelection` was introduced in #12712 to select all texts when
syncing pasteboard, the crash happens most on macOS 15 in
`readPasteboardNeedle`. It seems that `objectWillChange` fires
differently there, and it's hard to reproduce on macOS 26/27. I think
guaranteeing from ourside is enough, I believe SwiftUI already as its
own when updating the binding.
**Confirmed with a simple example on macOS 15, it seems a SwiftUI
issue. So I changed the minimal macOS version for text selection to
macOS 26. I don't see an elegant way to fix it.**
<img width="1352" height="849" alt="image"
src="https://github.com/user-attachments/assets/1dfef3f5-ceaa-41dd-bb91-c23dbc5e4ad3"
/>
```swift
struct ContentView: View {
@State private var text = ""
@State private var selection: TextSelection?
var body: some View {
TextField("Search", text: $text, selection: $selection)
}
}
```