This makes it so `zig build run` can take arguments such as
`--config-default-files=false` or any other configuration. Previously,
it only accepted commands such as `+version`.
Incidentally, this also makes it so that the app in general can now take
configuration arguments via the CLI if it is launched as a new instance
via `open`. For example:
open -n Ghostty.app --args --config-default-files=false
This previously didn't work. This is kind of cool.
To make this work, the libghostty C API was modified so that
initialization requires the CLI args, and there is a new C API to try to
execute an action if it was set.
This fixes an issue where pressing the red close button in a window or
the "x" button on a tab couldn't differentiate and would always close
the tab or close the window (depending on tab counts).
It seems like in both cases, AppKit triggers the `windowShouldClose`
delegate method on the controller, but for the close window case it
triggers this on ALL the windows in the group, not just the one
that was clicked.
I implemented a kind of silly coordinator that debounces
`windowShouldClose` calls over 100ms and uses that to differentiate
between the two cases.
Resolves#7591
This moves our CI to build macOS on Sequoia (macOS 15) with Xcode 26,
including the new macOS 26 beta SDK.
Importantly, this will make our builds on macOS 26 use the new styling.
I've added a new job that ensures we can continue to build with Xcode 16 and
the macOS 15 SDK, as well, although I think that might come to an end
when we switch over to an IconComposer-based icon. I'll verify then. For
now, we continue to support both.
I've also removed our `hasLiquidGlass` check, since this will now always
be true for macOS 26 builds.
This is recommended for macOS Tahoe and all standard menu items now have
associated images. This makes our app look more polished and native for
macOS Tahoe.
For icon choice, I tried to copy other native macOS apps as much as
possible, mostly from Xcode. It looks like a lot of apps aren't updated
yet. I'm absolutely open to suggestions for better icons but I think
these are a good starting point.
One menu change is I moved "reset font size" above "increase font size"
which better matches other apps (e.g. Terminal.app).
This is a major rework of how we represent, handle, and render splits in
the macOS app.
This new PR moves the split structure into a dedicated, generic
(non-Ghostty-specific) value-type called `SplitTree<V>`. All logic
associated with splits (new split, close split, move split, etc.) is now
handled by notifications on `BaseTerminalController`. The view hierarchy
is still SwiftUI but it has no logic associated with it anymore and
purely renders a static tree of splits.
Previously, the split hierarchy was owned by AppKit in a type called
`SplitNode` (a recursive class that contained the tree structure). All
logic around creating, zooming, etc. splits was handled by notification
listeners directly within the SwiftUI hierarchy. SwiftUI managed a
significant amount of state and we heavily used bindings, publishers,
and more. The reasoning for this is mostly historical: splits date back
to when Ghostty tried to go all-in on SwiftUI. Since then, we've taken a
more balanced approach of SwiftUI for views and AppKit for data and
business logic, and this has proven a lot more maintainable.
## Spatial Navigation
Previously, focus moving was handled by traversing the tree structure.
This led to some awkward behaviors. See:
https://github.com/ghostty-org/ghostty/issues/524#issuecomment-2668396095
In this PR, we now handle focus moving spatially. This means that move
"left" means moving to the visually left split (from the top-left
corner, a future improvement would be to do it from the cursor
position).
Concretely, given the following split structure:
```
+----------+-----+
| | b |
| | |
| a +-----+
| | |
| | |
| | |
| | |
|----------| d |
| c | |
| | |
+----------+-----+
```
Moving "right" from `c` now moves to `d`. Previously, it would go to
`b`. On Linux, it still goes to `b`.
## Value Types
One of the major architectural shifts is moving **purely to immutable
value types.** Whenever a split property changes such as a new split,
the ratio between splits, zoomed state, etc. we _create an entirely new
`SplitTree` value_ and replace it along the entire view hierarchy. This
is in some ways wasteful, but split hierarchies are relatively small
(even the largest I've seen in practical use are dozens of splits, which
is small for a computer). And using value types lets us get rid of a ton
of change notification soup around the SwiftUI hierarchy. We can rely on
reference counting to properly clean up our closed views.
> [!NOTE]
>
> As an aside, I think value types are going to make it a lot easier in
the future to implement features like "undo close." We can just keep a
trailing list of surface tree states and just restore them. This PR
doesn't do anything like that, but it's now possible.
## SwiftUI Simplicity
Our SwiftUI view hierarchy is dramatically simplified. See the
difference in `TerminalSplitTreeView` (new) vs `TerminalSplit` (old).
There's so much less logic in our new views (almost none!). All of it is
in the AppKit layer which is just way nicer.
## AI Notes
This PR was heavily written by AI. I reviewed every line of code that
was rewritten, and I did manually rewrite at every step of the way in
minor ways. But it was very much written in concert. Each commit usually
started as an AI agent writing the whole commit, then nudging to get
cleaned up in the right way.
One thing I found in this task was that until the last commit, I kept
the entire previous implementation around and compiling. The agent
having access to a previous working version of code during a refactor
made the code it produced as follow up in the new architecture
significantly better, despite the new architecture having major
fundamental differences in how it works!
Fixes#6999
It appears that at some point one of the operations causes focus to move
away for non-native fullscreen. We previously relied on the delegate
method to restore this but a better approach appears to handle this
directly in the fullscreen implementations. This fixes the linked issue.
I still think long term all the `Ghostty.moveFocus` stuff is a code
smell and we should be auditing all that code to see if we can
eliminate it. But this is a step in the right direction, and removes one
of those uses.
Fixes#7114
Supercedes #7271
This fixes a crash that could occur with non-native fullscreen and
`fullscreen = true` set at once.
The "windowNumber" can be `<= 0` if the window "doesn't have a
window device." I don't fully know all the scenarios this is true but it
is true when the window is not visible, at least.
This replaces the use of our custom `Ghostty.KeyEquivalent` with
the SwiftUI `KeyboardShortcut` type. This is a more standard way to
represent keyboard shortcuts and lets us more tightly integrate with
SwiftUI/AppKit when necessary over our custom type.
Note that not all Ghostty triggers can be represented as
KeyboardShortcut values because macOS itself does not support
binding keys such as function keys (e.g. F1-F12) to KeyboardShortcuts.
This isn't an issue since all input also passes through a lower level
libghostty API which can handle all key events, we just can't show these
keyboard shortcuts on things like the menu bar. This was already true
before this commit.
This is a bug I noticed in the following scenario:
1. Open Ghostty
2. Fullscreen normal terminal window (native fullscreen)
3. Open quick terminal
4. Move spaces, QT follows
5. Fullscreen the quick terminal
The result was that the menu bar would not disappear since our app is
not frontmost but we set the fullscreen frame such that we expected it.
Finishes #378
Supercedes #4159
This adds a new enum value for `macos-non-native-fullscreen`:
`padded-notch`. This value will add padding to the top of the window to
account for the notch on applicable devices while still hiding the
menu.
This value is preferred over "visible-menu" by some people because for
screens without a notch, the window will take up the full height.
The plan in the future is that we may color the padded area when a notch
is present. In this commit it appears as transparent.
Related to #5361
The fix in 5361 wasn't sufficient since it only applied if our app was
in the foreground. Our quick terminal is a non-activating NSPanel to
allow it to work on any space (fullscreen included). This means that
Ghostty doesn't become the active app when the quick terminal is shown
and another app is in the foreground.
To work around this, we now hide the dock globally when the quick
terminal is shown AND the dock is in a conflicting position. We restore
this state when the quick terminal is hidden, loses focus, or Ghostty is
quit.
Fixes#5328
The dock sits above the level of the quick terminal, and the quick
terminal frame typical includes the dock. Hence, if the dock is visible
and the quick terminal would conflict with it, then part of the terminal
is obscured.
This commit makes the dock autohide if the quick terminal would conflict
with it. The autohide is disabled when the quick terminal is closed.
We can't set our window level above the dock, as this would prevent
things such as input methods from rendering properly in the quick
terminal window.
iTerm2 (the only other macOS terminal I know of that supports a dropdown
mode) frames the terminal around the dock. I think this looks less
aesthetically pleasing and I prefer autohiding the dock instead.
We can introduce a setting to change this behavior if desired later.
Additionally, this commit introduces a mechanism to safely set
app-global presentation options from multiple sources without stepping
on each other.