1942 Commits

Author SHA1 Message Date
Mitchell Hashimoto
f815f84594 macos: fix quick terminal restoring stale size after display reconnect (#13250)
"Why is my quick terminal not taking up the entire top of my docked Mac
screen after I reconnect?" Boy howdy are you in the right PR.

It turns out that the quick terminal caches its last-closed window frame
per display so it can restore the user's size when reopened. The cache
entry was considered valid whenever the current screen was the same size
*or larger* than when the frame was saved ("persist when screens grow").
This has led to a pattern that was simply maddening. To wit: that rule
breaks across display changes.

When an external display is disconnected and later reconnected at a
different resolution (common after traveling with a laptop, do not even
get me started on projectors) the same display can come back larger than
when the frame was cached. The stale frame is still treated as valid and
restored, so the quick terminal no longer fills the screen (it appears
at a partial width/height). Because the cache is persisted, restarting
Ghostty does not clear it, and the user is slowly driven mad. Welcome to
madness; we have snacks.

This PR addresses this by treating a cached frame as valid when the
screen geometry matches exactly (both backing scale factor and frame
size). On any mismatch we drop the entry and fall back to the configured
quick-terminal-size. Manual resizes are still remembered across toggles
within a stable display configuration.

Fixes the regression reported in #12348.

AI disclaimer: I used AI for this. Of course I used AI for this, my code
is terrible on a good day. Specifically, Claude Code, as well as a
custom harness that has the curious tendency to write commit messages
containing conspiracy theories about the code because I am history's
greatest monster.

Fight me!
2026-07-08 10:49:39 -07:00
Lukas
91f66da245 macos: route IME preedit commits through key events (#13222) 2026-07-08 07:06:44 +02:00
qappell
751a60df61 macos: route IME preedit commits through key events 2026-07-07 13:00:39 -05:00
Lukas
6e267d3363 macOS: use the getOpinionatedStringContents same as paste 2026-07-06 09:04:24 +02:00
Mitchell Hashimoto
b213a72c03 macOS: only read file urls for new-terminal services (#13169)
macOS is already guarding this in Services settings, but guarding what
we actually need anyway
2026-07-05 13:50:34 -07:00
Lukas
10565995b9 macOS: only read file urls for new-terminal services
macOS is already guarding this system, but guarding what we actually need anyway
2026-07-03 13:24:55 +02:00
Lukas
49806fc4cc macOS: read string contents per pasteboard item in order
Pasteboards mixing file URLs with other items will now be pasted as joined string.
2026-07-03 12:53:34 +02:00
Claude Fable 5
acd09c0a6c macos: add tests for NSPasteboard.getOpinionatedStringContents 2026-07-03 12:53:34 +02:00
Corey Quinn
0274e7ad84 macos: fix quick terminal restoring stale size after display reconnect
The quick terminal caches its last-closed window frame per display so it
can restore the user's size when reopened. The cache entry was considered
valid whenever the current screen was the same size *or larger* than when
the frame was saved ("persist when screens grow"). This has led to a pattern
that was simply maddening. To wit:

That rule breaks across display changes. When an external display is
disconnected and later reconnected at a different resolution (common
after traveling with a laptop) the same display can come back larger
than when the frame was cached. The stale frame is still treated as valid
and restored, so the quick terminal no longer fills the screen (it appears
at a partial width/height). Because the cache is persisted, restarting
Ghostty does not clear it, and the user is slowly driven mad.

Only treat a cached frame as valid when the screen geometry matches
exactly (both backing scale factor and frame size). On any mismatch we
drop the entry and fall back to the configured quick-terminal-size. Manual
resizes are still remembered across toggles within a stable display
configuration.

Fixes the regression reported in #12348.
2026-06-29 15:31:07 -07:00
Mitchell Hashimoto
f52f8aab95 macos: avoid notification publisher retain cycle
Turns out combine's `publisher(for:,object:)` retains the object!
We verified this with a test script shown below. Fix this with a 
manual filter. Found by @mustafa0x.

```
import Combine
import Foundation

final class Token {
    deinit { print("Token deinitialized") }
}

weak var weakToken: Token?
var publisher: NotificationCenter.Publisher?

// Create scope that will free token.
do {
    let token = Token()
    weakToken = token
    publisher = NotificationCenter.default.publisher(
        for: Notification.Name("TestNotification"),
        object: token
    )
}

print("Retained:", weakToken != nil)
publisher = nil
print("Released:", weakToken == nil)
```
2026-06-25 11:21:21 -07:00
Lukas
632aa67560 macOS: fix tabs frame on macOS 27 beta 2 2026-06-22 21:59:14 +02:00
Mitchell Hashimoto
c04e2b64d9 macOS: get latest TerminalEntity's kind, title, pwd and screenshot (#13007)
This fixes these being wrong after creating a terminal window for the
first time.

Found them when trying [App Intent
Testing](https://developer.apple.com/documentation/AppIntentsTesting/testing-your-app-intents-code),
and confirmed them with macOS 26 as well (which is expected)

<img width="2560" height="546" alt="Xnip2026-06-13_20-48-50"
src="https://github.com/user-attachments/assets/7b13217d-66f5-48e2-ad69-9a489c22ad82"
/>

## AI Disclosure

Claude drafted the initial version, i manually refined and tested them
myself.
2026-06-18 13:33:34 -07:00
Sahil Mishra
8e8a8dc113 added async fix in order to resolve first second tab issue 2026-06-15 20:51:35 -04:00
Sahil Mishra
c3ceb55f5f added comment explaining fix 2026-06-15 10:12:44 -04:00
Sahil Mishra
749d45490a fix swiftlint 2026-06-15 10:04:54 -04:00
Sahil Mishra
4668a371c1 overrode function syncAppearance in order to update the tab bar as well 2026-06-14 15:30:08 -04:00
Lukas
e2832ae1fe macOS: get latest TerminalEntity's kind, title & pwd
This fixes these being wrong after creating a terminal window for the first time.
2026-06-13 20:56:34 +02:00
Mitchell Hashimoto
52368cbcff core: send selection_changed notification (#12902)
The core had no signal to the apprt when the active selection changed,
so a consumer (e.g. a screen reader) kept reading a stale selection
until some unrelated query refreshed it.

This change adds a payload-less selection_changed action that's fired on
a selection state transition. The apprt reads the current selection
through the normal read path.

This consolidates selection state changes so the notification fires
consistently: all sites route through setSelection rather than calling
screen.select directly, including the mouse paths that previously
bypassed it for clipboard timing.

The new setSelectionAndCopy extends setSelection with the additional
'copy_on_select' behavior.

On macOS, this posts .ghosttySelectionDidChange, which is debounced
before posting a NSAccessibility .selectedTextChanged notification.

GTK has no consumer yet and no-ops the action.

See: #9932
2026-06-04 11:03:59 -07:00
Jon Parise
c4e1ab8883 core: send selection_changed notification
The core had no signal to the apprt when the active selection changed,
so a consumer (e.g. a screen reader) kept reading a stale selection
until some unrelated query refreshed it.

This change adds a payload-less selection_changed action that's fired on
a selection state transition. The apprt reads the current selection
through the normal read path.

This consolidates selection state changes so the notification fires
consistently: all sites route through setSelection rather than calling
screen.select directly, including the mouse paths that previously
bypassed it for clipboard timing.

The new setSelectionAndCopy extends setSelection with the additional
'copy_on_select' behavior.

On macOS, this posts .ghosttySelectionDidChange, which is debounced
before posting a NSAccessibility .selectedTextChanged notification.

GTK has no consumer yet and no-ops the action.
2026-06-02 19:37:49 -04:00
YuWiz
ef68e96400 macos: fix GHOSTTY_QUICK_TERMINAL not set for quick terminal splits 2026-06-02 16:48:36 +08:00
Claude Opus 4.7
eb5c1c7220 fix(macos): mark Swift os.Logger interpolations as public 2026-05-31 16:35:11 +02:00
Jon Parise
e32d7abe6e macos: fix swiftlint opening_brace issue 2026-05-31 10:00:24 -04:00
Jon Parise
33adb58bee macos: remove unneeded initializers
These will be automatically synthesized (they only do memberwise
initialization) and do not need to be manually defined.
2026-05-31 09:54:41 -04:00
Mitchell Hashimoto
3103ae8838 macos: avoid duplicate appearance sync on tab focus (#12828)
Close #12825

Skip the initial emissions from the focused surface appearance
publishers after a tab focus change. The focused surface is already
synced immediately, so the initial Combine values only repeat the same
titlebar and background updates. Subsequent derived config and OSC
background changes still resync the window appearance.



https://github.com/user-attachments/assets/f229fb95-4b4c-4040-85ac-0acfcc54ca82



Assigned to Codex GPT 5.5(medium)
PS: Sry for I don't write zig and let AI write this.
2026-05-27 07:52:42 -07:00
Tunglies
5368adcd29 macos: avoid duplicate appearance sync on tab focus
Close #12825

Skip the initial emissions from the focused surface appearance publishers after a tab focus change. The focused surface is already synced immediately, so the initial Combine values only repeat the same titlebar and background updates. Subsequent derived config and OSC background changes still resync the window appearance.
2026-05-27 19:43:41 +08:00
Adam Bouker
57d202066d macOS: clear stale OSC 11 background cache on config change
SurfaceView caches the background color set by OSC 11 in
backgroundColor. TerminalWindow.preferredBackgroundColor consults
that cache before falling back to derivedConfig.backgroundColor,
so once OSC 11 has fired the cached value masks any later config
change. After a light/dark theme auto-switch this leaves the
window chrome on the previous theme's color until the application
next emits OSC 11.

In ghosttyConfigDidChange, after updating derivedConfig, drop the
cache when it no longer matches the new config-derived background.
A subsequent ghosttyColorDidChange repopulates it as before, so
within-config OSC 11 behavior is unchanged.
2026-05-26 14:11:45 -05:00
minorcell
da541bea63 fix stray brace from conflict resolution 2026-05-23 16:33:33 +08:00
minorcell
7a346dd8d4 macOS: fix search bar Enter key blocking IME composition
Use onSubmit for the plain Enter → next-match behavior, which respects
IME composition state. Keep onKeyPress only for Shift+Enter (previous
match), returning .ignored for plain Enter so the IME can process it.
2026-05-23 16:09:48 +08:00
Mitchell Hashimoto
3e3705b932 macOS: fix surface focus/render state after dragging in to to another window/tab (#12338)
Fixes 2 bugs

1. After dragging a non-focused surface from window A to window B
**quickly without making B the key window**, the focused surface in
window A is not receiving `keyDown` events.


https://github.com/user-attachments/assets/a8861c0a-9300-470d-bf7e-0f32a9ab2cd1

2. #12343 After dragging a surface from tab A to tab B within the same
window, the dragged surface is not rendering input correctly.
> The reason the thread is stuck is because the surface's occlusion
state is set to invisible after target tab's activate while dragging,
since the dragged surface is still in previous tree before dropping, and
after dropping the occlusion state of this surface is not updated to
visible, which causing the surface is accepting input but not rendering.



https://github.com/user-attachments/assets/d67f5dba-8609-4f67-a956-921982faf796
2026-05-22 09:05:11 -07:00
Mitchell Hashimoto
b78174a68f macOS: update window appearance for About and ConfigurationErrors (#12601)
<img width="1224" height="696" alt="Xnip2026-05-06_19-13-31"
src="https://github.com/user-attachments/assets/ab090dc0-7c06-4a01-8e7c-5d48ca6ccca3"
/>
2026-05-22 08:58:26 -07:00
Mitchell Hashimoto
7e24f0e0bc macOS: use find pasteboard for search needle (#12712)
Fixes the issue described in #12516.

### What
- Inject an `OSPasteboard` into `SearchState`
- Add `OSPasteboard` extension to normalize working with strings between
UIPasteboard/NSPasteboard
- Add `BackportSelectionTextField` which supports text selection for
MacOS 15/iOS 18 and up.
- Read from the pasteboard when the overlay opens and when the app
becomes active
- Write to the pasteboard when the search needle changes
- Annotate `SearchState` as MainActor. `NSPasteboard` isn't thread safe,
and since `SearchState` is already accessed from the main thread,
MainActor enforces our writes be thread safe
- Add SearchState unit tests

### Why
Consistent with other macOS apps, the Find bar's search needle should
persist when re-opened and should sync to the Find bar in other apps.
For example, see Xcode, Notes, Terminal, and Safari.


https://github.com/user-attachments/assets/b6a55a4a-a52c-45bc-ac38-c9df452c11cb
2026-05-22 08:57:45 -07:00
Mitchell Hashimoto
afe4819920 macOS: Re-enable global keybinds after event tap disable events (#12714)
While testing https://github.com/ghostty-org/ghostty/pull/9857, I
encountered the behavior mentioned below. It's pretty frustrating to
encounter, so I've been actually compiling this fix into my test builds
for last month or so, and the issue has not come back. I exclusively use
the QuickTerminal, so my workflow depends on global keybinds working
reliably.

Issue: https://github.com/ghostty-org/ghostty/issues/12294

The solution includes listening to two events that are fired when a tap
is disabled:
- tapDisabledByTimeout
- tapDisabledByUserInput

When these are fired, we re-enable the tap.

Apple's Docs:
https://developer.apple.com/documentation/coregraphics/cgeventtype?language=swift

Related Discussions:
- https://github.com/ghostty-org/ghostty/discussions/11819
- https://github.com/ghostty-org/ghostty/discussions/12091
2026-05-22 08:56:16 -07:00
Lukas
00a989774e macOS: add review windows when quitting
Inspired by Terminal.app
2026-05-19 20:01:32 +02:00
Lukas
8f9b86afa8 macOS: add confirmCloseAsync to return the actual response 2026-05-19 20:00:10 +02:00
Lukas
7f5c233492 macOS: add windowCanBeClosedWithoutConfirmation without any side effects 2026-05-19 19:56:09 +02:00
Lukas
3ac7562791 macOS: set error when there is no directory to open with 2026-05-19 09:58:44 +02:00
Lukas
fdf84ef7ce macOS: check the resource the URL refers to.
Fixes #12727. [`NSURL.hasDirectoryPath` doesn't do this](https://developer.apple.com/documentation/foundation/nsurl/hasdirectorypath).

We don't need to check this in NewTerminalIntent since AppIntent already appends `/` to the directory.
2026-05-19 09:58:44 +02:00
Nolin McFarland
bf716a0c39 feat: add extension to normalize OSPasteboard string interface 2026-05-18 10:12:26 -04:00
Nolin McFarland
ed52160612 feat: support BackportSelectionTextField on iOS 18 2026-05-17 12:33:56 -04:00
Nolin McFarland
69cab3d808 feat: select needle when reading from pasteboard 2026-05-17 11:26:32 -04:00
Nolin McFarland
8fa42c6ec0 feat: add search state unit tests 2026-05-16 20:05:11 -04:00
Nolin McFarland
59eece9a8e feat: use find pasteboard to store search needle 2026-05-16 19:59:20 -04:00
Lukas
b6c6f7630a macos: opacity-toggle setting persists between tabs in a window and to a newly created window (#11583) 2026-05-17 00:45:24 +02:00
Lukas
0226bcf034 macOS: update window appearance for About and ConfigurationErrors 2026-05-12 09:20:08 +02:00
Lukas
2c6dd59406 macOS: fix render_thread "stuck" after dragging surface to another tab within the same window
The reason the thread is stuck is because the surface's occlusion state is set to invisible after target tab's activate while dragging, since the dragged surface is still in previous tree before dropping, and after dropping the occlusion state of this surface is not updated to visible, which causing the surface is accepting input but not rendering.
2026-05-12 09:18:52 +02:00
Lukas
366c34831a macOS: fix first responder after dragging a non-focused surface
This fixes a bug: after dragging a non-focused surface from window A to window B **quickly without making B the key window**, the focused surface in window A is not receiving `keyDown` events.
2026-05-12 09:18:51 +02:00
Jon Parise
2b48045731 macos: simplify workingDirectory setter
This is a minor improvement to the computed property's `set` logic: we
can just use `.map {}` to unify the two optional paths.
2026-05-09 14:37:43 -04:00
Lukas
607152ec6d macOS: normalize working directory paths with FilePath
This fixes for nuShell when opening Ghostty via Finder service and Shortcuts, also makes path parsing more robust in AppleScript.
2026-05-09 12:47:46 +02:00
Akinori Musha
d60a16c146 macos: avoid replaying keys that commit preedit
Refs #10460
Related: #12518

When an input method commits all or part of marked text during keyDown,
AppKit returns the committed text through insertText. Treat that as
text committed by the input method instead of replaying the original key
event to the terminal.

Previously this path only handled arrow-key commits specially. A
control-key shortcut that commits preedit text could still be encoded as
the original control input after composition, such as ctrl+j becoming LF.

Send committed preedit text as a text-only event for any key that causes
the commit. Only replay arrow navigation keys that the existing Korean
IME handling expects, and keep plain left-arrow suppressed because AppKit
already leaves the caret in place.

AI usage: OpenAI Codex helped investigate, implement, test, and refine
this change. I reviewed and tested the resulting code.
2026-05-01 23:18:42 +09:00
Mitchell Hashimoto
4dcb09ada0 macos: suppress control-char input while composing (#12518)
macos: suppress control-char input while composing

When AppKit delivers a single C0 control character during marked-text
composition, Ghostty should treat it as input consumed by the composing
state instead of forwarding it to the terminal.

This prevents control-key IME actions, such as Japanese input shortcuts
like ctrl+h/j/m/n, from leaking into the terminal while composition is
still active. Printable text and non-composing control input continue
through the normal key path.

Refs #10460
Related: #2628, #4539
Vouched in #12169

Testing:
- xcodebuild test -scheme Ghostty -destination platform=macOS
-only-testing:GhosttyTests/SurfaceViewAppKitTests
- Manually tested Japanese IME control-key shortcuts on macOS

AI usage:
- OpenAI Codex helped investigate, implement, test, and refine this
change. I reviewed and tested the resulting code.
2026-04-30 06:48:58 -07:00