Windows with `macos-titlebar-style = hidden` create new windows when the
new tab binding is pressed. This behavior has existed for a long time.
However, these windows did not cascade, meaning they'd appear overlapped
directly on top of the previous window, which is kind of nasty.
This commit changes it so that new windows created via new tab from a
hidden titlebar window will cascade.
Fixes#7546
SwiftUI uses type and structure to identify views, which can lead
to issues with tree like structures where the shape and type is the same
but the content changes. This was causing #7546.
To fix this, we need to add explicit identity to the split tree view
so that SwiftUI can differentiate when it needs to redraw the view.
We don't want to blindly add Hashable to SplitTree because we don't want
to take into account all the fields. Instead, we add an explicit
"structural identity" to the SplitTreeView that can be used by SwiftUI.
When closing a window that contains multiple tabs, the undo operation
now properly restores all tabs as a single tabbed window rather than
just restoring the active tab.
The implementation:
- Collects undo states from all windows in the tab group before closing
- Sorts them by their original tab index to preserve order
- Clears tab group references to avoid referencing garbage collected objects
- Restores all windows and re-adds them as tabs to the first window
- Tracks and restores which tab was focused (or focuses the last tab if none were)
AI prompts that generated this commit are below.
Each separate prompt is separated by a blank line, so this session was
made up with many prompts in a back-and-forth conversation.
> We need to update the undo/redo implementation in
> @macos/Sources/Features/Terminal/TerminalController.swift `closeWindowImmediately`
> to handle the case that multiple windows in a tab group are closed all at once,
> and to restore them as a tabbed window. To do this, I think we should collect
> all the `undoStates`, sort them by `tabIndex` (null at the end), and then on j
> restore, restore them one at a time but add them back to the same tabGroup. We
> can't use the tab group in the `undoState` because it will be garbage collected
> by then. To be sure, we should just set it to nil.
I should note at this point that the feature already worked, but the
code quality and organization wasn't up to my standards. If someone
using AI were just trying to make something work, they might be done at
this point.
I do think this is the biggest gap I worry about with AI-assisted
development: bridging between the "it works" stage at a junior quality
and the "it works and is maintainable" stage at a senior quality. I
suspect this will be a balance of LLMs getting better but also senior
code reviewers remaining highly involved in the process.
> Let's extract all the work you just did into a dedicated private method
> called `registerUndoForCloseWindow`
Manual: made some tweaks to comments, moved some lines around, didn’t change
any logic.
> I think we can pull the tabIndex directly from the undoState instead of
> storing it in a tuple.
> Instead of `var undoStates`, I think we can create a `let undoStates` and
> build and filter and sort them all in a chain of functional mappings.
> Okay, looking at your logic for restoration, the `var firstController` and
> conditionals are littly messy. Can you make your own pass at cleaning those
> up and I'll review and provide more specific guidance after.
> Excellent. Perfect. The last thing we're missing is restoring the proper
> focused window of the tab group. We should store that and make sure the
> proper window is made key. If no windows were key, then we should make the
> last one key.
> Excellent. Any more cleanups or comments you'd recommend in the places you
> changed?
Notes on the last one: it gave me a bunch of suggestions, I rejected most but
did accept some.
> Can you write me a commit message summarizing the changes?
It wrote me a part of the commit message you're reading now, but I
always manually tweak the commit message and add my own flair.
This fixes a regression from the new split work last week, but it was
also probably an issue before that in a slightly different way.
With the new split work, the quick terminal was becoming unusable when
the final surface explicitly `exit`-ed, because AppKit/SwiftUI would
resize the window to a very small size and you couldn't see the new
terminal on the next toggle.
Prior to this, I think the quick terminal would've reverted to its
original size but I'm not sure (even if the user resized it manually).
This commit saves the size of the quick terminal at the point all
surfaces are exited and restores it when the quick terminal is shown the
next time with a new initial surface.
I've only recently been using programs that use user notifications heavily
and this commit addresses a number of annoyances I've encountered.
1. Notifications dispatched while the source terminal surface is
focused are now only shown for a short time (3 seconds hardcoded)
and then automatically dismiss.
2. Notifications are dismissed when the target surface becomes focused
from an unfocused state. This dismissal happens immediately (no
delay).
3. Notifications are dismissed when the application exits.
4. This fixes a bug where notification callbacks were modifying view
state, but the notification center doesn't guarantee that the
callback is called on the main thread. We now ensure that
the callback is always called on the main thread.
Took another look through #7504 after the merge and realized that the
logic behind the `hasWindowButtons` property wasn't quite sound. It
would return `false` if either _at least one_ button were missing in the
`standardWindowButton(.theButton) == nil` sense, or if _all_ buttons
were hidden in the `isHidden` sense.
With this PR, the logic is rectified: `false` if _all_ buttons are
missing or hidden in any sense, otherwise `true`.
In practice, I suppose Ghostty won't ever instantiate a `TerminalWindow`
where `standardWindowButton(.theButton) == nil`, but might as well get
it right and sleep better at night.
Fixes regression from #7523
I messed two things up around spatial navigation in the split tree
that this commit fixes:
1. The distance in the spatial tree only used a single dimension
that we were navigating. This commit now uses 2D euclidean
distance from the top-left corners of nodes. This handles the case
where the nodes are directly above or below each other better.
2. The spatial slots include split containers because they are layout
elements. But we should only navigate to leaf nodes. This was
causing the wrong navigatin to happen in some scenarios.
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!
Conversion of #7497 to a PR. This implements a feature requested in
#7331: an option to hide the default window buttons on macOS for a
cleaner aesthetic.
~~Builds on #7502 as it requires the same change to avoid the main
toolbar title showing on top of the tab bar.~~ EDIT: rebased on main now
that #7502 was merged.
I aligned the scope of the new option with `macos-titlebar-style`, since
they both customize titlebar elements. This means it has the same edge
case quirks: For example, if you change the setting, reload the config,
and then open a new tab, the appearance of the current window will
depend on which tab is in the foreground. I did it this way because
`macos-titlebar-style` provided an easy template for which derived
configs and functions to modify. Let me know if you want me to try
adjusting this so that a change in the setting also takes effect for
current windows/tabs, which I _think_ should be possible.
Screenshots:
* `macos-titlebar-style = transparent` (default)


* `macos-titlebar-style = tabs`


First, remove the always-inlined openTerminalFromPasteboard code and
combine it with openTerminal. Now that we're doing a bit of work inside
openTerminal, there's little better to having an intermediate, inlined
function.
Second, combine some type-casting operations (saving a .map() call).
Lastly, adjust some variable names because a generic `objs` or `urls`
was a little ambiguous now that we're all in one function scope.
First, remove the always-inlined openTerminalFromPasteboard code and
combine it with openTerminal. Now that we're doing a bit of work inside
openTerminal, there's little better to having an intermediate, inlined
function.
Second, combine some type-casting operations (saving a .map() call).
Lastly, adjust some variable names because a generic `objs` or `urls`
was a little ambiguous now that we're all in one function scope.
This fixes a small memory leak I found where the `SplitNode.Leaf` was
not being deinitialized properly when closing a split. It would get
deinitialized the next time a split was made or the window was closed,
so the leak wasn't big. The surface view underneath the split was also
properly deinitialized because we forced it, so again, the leak was
quite small.
But conceptually this is a big problem, because when we change the
surface tree we expect the deinit chain to propagate properly through
the whole thing, _including_ to the SurfaceView.
This fixes that by removing the `id(node)` call. I don't find this to be
necessary anymore. I don't know when that happened but we've changed
quite a lot in our split system since it was introduced. I'm also not
100% sure why the `id(node)` was causing a strong reference to begin
with... which bothers me a bit.
AI note: While I manually hunted this down, I started up Claude Code and
Codex in separate tabs to also hunt for the memory leak. They both
failed to find it and offered solutions that didn't work.
The default keybinds for showing the GTK inspector (`ctrl+shift+i` and
`ctrl+shift+d`) don't work reliably in Ghostty due to the way Ghostty
handles input. You can show the GTK inspector by setting the environment
variable `GTK_DEBUG` to `interactive` before starting Ghostty but that's
not always convenient.
This adds a keybind action that will show the GTK inspector. Due to
API limitations toggling the GTK inspector using the keybind action is
impractical because GTK does not provide a convenient API to determine
if the GTK inspector is already showing. Thus we limit ourselves to
strictly showing the GTK inspector. To close the GTK inspector the user
must click the close button on the GTK inspector window. If the GTK
inspector window is already visible but is hidden, calling the keybind
action will not bring the GTK inspector window to the front.
Fixes#7286
Previously, when using the "New Ghostty Window/Tab Here" macOS service
on a file, the new terminal window/tab would incorrectly open in the
user's home directory. This was because the service handler only
expected directory paths.
This commit updates the service handler to check if the provided path is
a file. If it is, the handler now uses the file's parent
directory as the working directory for the new Ghostty window or tab,
aligning with user expectations. If the path is a directory, it's used
directly as before.
Fixes#7337
AppKit encodes functional keys as PUA codepoints. We don't want to send
that down as valid text encoding for a key event because KKP uses that
in particular to change the encoding with associated text.
I think there may be a more specific solution to this by only doing this
within the KKP encoding part of KeyEncoder but that was filled with edge
cases and I didn't want to risk breaking anything else.
Fixes#7236
Supersedes #7249
This removes all of our `focusedValue`-based tracking of the surface
title and moves it completely to the window controller. The window
controller now sets up event listeners (via Combine) when the focused
surface changes and updates the window title accordingly.
There is some complicated logic here to handle when we lose focus to
something other than a surface. In this case, we want our title to be
the last focused surface so long as it exists.
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.
#7173
(1) The command palette no longer has any selection by default.
If and when we introduce most recently used commands, defaulting to that
would make sense. A selection only appears when the arrow keys are used
or the user starts typing.
(2) The selection with arrow keys now wraps, so if you press "down" on
the last option, it will wrap to the first option, and if you press "up"
on the first option, it will wrap to the last option. This matches both
VSCode and Zed.
Removes the withAnimation closure which caused flashing when scrolling
up or down with arrow keys. Also removes the center anchor to behave
more like other command palletes (e.g., Zed, Raycast).
This introduces a command palette (inspired by @pluiedev's work in
#5681, but not using it as a base) for macOS.
The command palette is available in the `View` menu and also bindable
via `toggle_command_palette`, default binding is `cmd+shift+p` to match
VSCode.
The commands in the command palette must map to a _bindable_ action,
though they may not have an associated keybinding. This means that any
new binding actions we add in the future can be represented here and
also makes it easy in the future to add configuration to add new custom
entries to the command palette. For this initial PR, the available
commands are hardcoded (`src/input/commands.zig`).
I've noticed in other programs (VSCode, Zed), the command palette
contains pretty much _all available actions_ even if they're basically
useless in the context of a command palette. For example, Zed has the
"toggle command palette" action in the command palette and it... does
nothing (it probably should hide the palette). I followed @pluiedev's
lead and made this subjective in this PR but I wonder if we should
actually force all binding actions to be available.
There are various other improvements I'd like to make but omitted from
this PR for the sake of limiting scope:
* Instead of an entry with no matches doing nothing, we can allow users
to manually input _any_ configurable binding.
* Localization, since macOS doesn't have any yet. But for Linux when we
port this we probably have to change our strings extraction.
## Demo
https://github.com/user-attachments/assets/a2155cfb-d86b-4c1a-82b5-74ba927e4d69
Resolves#7108
This PR adds visual notification badges to the Ghostty dock icon when
bell events are triggered while the application is in the background.
This complements the existing dock bounce notification, making it easier
for users to notice when a terminal needs attention.
https://github.com/user-attachments/assets/b54c881f-fea8-4085-8614-432d9e5847b9
Fixes a regression where `C-S-c` stopped working properly in both legacy
and Kitty modes (although the Kitty mode side only affected alternates
and not the key itself so it probably worked fine in most programs).
The issue is that `charactersIgnoringModifiers` changes behavior if
`control` is pressed, so it doesn't really ignore all modifiers. We have
to use `characters(byApplyingModifiers:)` to get the proper unshifted
codepoint when `control` is pressed.
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.
This PR should have no user impact. This is just some cleanup for future
work.
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.
Fixes a regression where `C-S-c` stopped working properly in both legacy
and Kitty modes (although the Kitty mode side only affected alternates
and not the key itself so it probably worked fine in most programs).
The issue is that `charactersIgnoringModifiers` changes behavior if
`control` is pressed, so it doesn't really ignore all modifiers. We have
to use `characters(byApplyingModifiers:)` to get the proper unshifted codepoint
when `control` is pressed.
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.
Fixes#7131
Regression from #7121
Our consumed mods should not include "alt" if `macos-option-as-alt` is
set. To do this, we need to calculate our consumed mods based on the
actual translation event mods (if available, only available during
keyDown).
This is a large refactor of the keyboard input handling code in
libghostty and macOS. Previously, libghostty did a lot of things that
felt out of scope or was repeated work due to lacking context. For
example, libghostty would do full key translation from key event to
character (including unshifted translation) as well as managing dead key
states and setting the proper preedit text.
This is all information the apprt can and should have on its own.
NSEvent on macOS already provides us with all of this information,
there's no need to redo the work. The reason we did in the first place
is mostly historical: libghostty powered our initial macOS port years
ago when we didn't have an AppKit runtime yet.
This cruft has already practically been the source of numerous issues, e.g.
#5558, but many other hacks along the way, too.
This commit pushes all preedit (e.g. dead key) handling and key translation
including unshifted keys up into the caller of libghostty.
Besides code cleanup, a practical benefit of this is that key event
handling on macOS is now about 10x faster on average. That's because
we're avoiding repeated key translations as well as other unnecessary
work. This should have a meaningful impact on input latency but I didn't
measure the full end-to-end latency.
A scarier part of this commit is that key handling is not well tested
since its a GUI component. I suspect we'll have some fallout for certain
keyboard layouts or input methods, but I did my best to run through
everything I could think of.
Fixes#7099
This adds basic bell features to macOS to conceptually match the GTK
implementation. When a bell is triggered, macOS will do the following:
1. Bounce the dock icon once, if the app isn't already in focus.
2. Add a bell emoji (🔔) to the title of the surface that triggered
the bell. This emoji will be removed after the surface is focused
or a keyboard event if the surface is already focused. This
behavior matches iTerm2.
This doesn't add an icon badge because macOS's dockTitle.badgeLabel API
wasn't doing anything for me and I wasn't able to fully figure out
why...
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.
Fixes#7071
When the mouse is being actively dragged, AppKit continues to emit
mouseDragged events which will update our position appropriately. The
mouseExit event we were sending sends a synthetic (-1, -1) position
which was causing a scroll up.
Fixes#7000
Related to #6909, the same mechanism, but it turns out some control+keys
are also handled in this same way (namely control+esc leads to "cancel"
by default, which is not what we want).
Fixes#2595
This fixes an issue where a left mouse click on a terminal while not
focused would subsequently be encoded to the pty as a mouse event. This
is atypical for macOS applications in general and wasn't something we
wanted to do.
We do, however, want to ensure our terminal gains focus when clicked
without focus. Specifically, a split. This matches iTerm2 behavior and
is rather nice. We had this behavior before but our logic to make this
work before caused the issue this commit is fixing.
I also tested this with command+click which is a common macOS shortcut
to emit a mouse event without raising the focus of the target window. In
this case, we will properly focus the split but will not encode the
mouse event to the pty. I think we actually do a _better job_ here tha
iTerm2 (but, subjective) because we do encode the pty event properly if
the split is focused whereas iTerm2 never does.
Fixes a regression from #6909
See #6887
In certain scenarios, the last command key state would linger around (I
could only see this happen with global keybinds for unknown reasons
yet). This state is only meant to have an effect within the cycle of a
single keybind and only so we can ensure an event reaches keyDown so it should
be reset if keyDown is ever sent (since, by definition at that point,
keyDown has been reached).
I'm still not happy that this is necessary and I suspect there is a
better root cause to resolve, but I'd rather get this fix in now and
figure out the root cause later.
Fixes#5522
This commit re-dispatches command inputs that are unhandled by our macOS
app so they can be encoded to the pty and handled by the core libghostty
key callback system.
We've had a special case `cmd+period` handling in Ghostty for a very
long time (since well into the private beta). `cmd+period` by default
binds to "cancel" in macOS, so it doesn't encode to the pty. We don't
handle "cancel" in any meaningful way in Ghostty, so we special-cased it
to encode properly to the pty.
However, as shown in #5522, if the user rebinds `cmd+period` at the
system level to some other operation, then this is ignored and we encode
it still. This isn't desirable, we just want to work around not caring
about "cancel."
The callback path that AppKit takes for key events is a bit convoluted.
For command keys, it first calls `performKeyEquivalent`. If this returns
false (we want to continue standard processing), then it calls EITHER
`keyDown` or `doCommand(by:)`. It calls the latter if there is a
standard system command that matches the key event. For `cmd+period` by
default, this is "cancel." Unfortunately, from `doCommand` we can't say
"oops, we don't want to handle this, please continue processing." Its
too late.
So, this commit stores the last command key event from
`performKeyEquivalent` and if we reach `doCommand` for it without having
called `keyDown`, we re-dispatch the event and send it to keyDown.
I'm honestly pretty sus about this whole logic but it is scoped to only
command-keys and I couldn't trigger any adverse behavior in my testing.
It also definitely fixed#5522 as far as I could reproduce it before.
Fixes#5934 for macOS
If a `title` config is set, this change sets the title immediately on
windowDidLoad to ensure that the window appears with the correct title
right away.
If there is any reason to set another title, the `set_title` apprt
action will come on another event loop tick (due to our usage of
notifications) but that's okay since that's already how it works. This
is just to say that setting this here won't break any shell integration
or anything.
Related to #6035
This implements the keybind/action portion of #5974 so that this can
have a binding and so that other apprts can respond to this and
implement it this way.
Fixed: [2475](https://github.com/ghostty-org/ghostty/issues/2475)
The problem actually existed because of the responder chain, as
previously pointed out in the report (thanks to @mitchellh).
When we first click on Toggle Terminal Inspector:
* the responder chain goes to _toggleTerminalInspector_
(_SurfaceView_AppKit_ implementation).
When we click the second toggleTerminalInspector:
* it tries to find the next responder, but the one available is
_TerminalController_. (if we remove this method from there, the bug will
reproduce even without quick mode)
**Problem**:
TerminalController not available during quick terminal, so there's no
way to toggle inspector
**Solution**:
We add toggleTerminalInspector to the _QuickTerminalController_:
selector, as we did with other similar methods.
## Root Cause
The issue has two aspects:
1. The window creation process didn't explicitly force focus on the new
window after showing it.
2. More fundamentally, we were relying on `NSApp.isActive` to determine
whether to activate the application, which is problematic because:
- When creating a window from quick terminal, the application is already
"active" but this active state is owned by the quick terminal
- The [`NSApp.isActive`
check](4cfe5522db/macos/Sources/Features/Terminal/TerminalManager.swift (L100))
doesn't accurately reflect our intent - creating a new window is an
explicit user action that should always result in that window gaining
focus
## Solution
Removing the `NSApp.isActive` check.
```swift
// Before
if !NSApp.isActive {
NSApp.activate(ignoringOtherApps: true)
}
// After
NSApp.activate(ignoringOtherApps: true)
```
Fixes#5688
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.
Fixes#5552
This makes the mentioned actions performable. This isn't perfect, but it
does so in a way that resolves the user issue in #5552. This commit
returns an action is NOT performed if it doesn't have splits or tabs
(respectiely for the actions), but also reports its ALWAYS performed if
it does.
This latter logic isn't accurate: we should only return performable if
it was actually done. So for example, goto_split:top should do nothing
if we're already at the top. But, we report it as performed today.
This is good enough to resolve the issue and fix the core problem faced
for 1.1.0.
Fixes#5448
We previously removed the ctrl modifier for text commit (IME-style)
to workaround a libghostty quirk (as noted in the comment in the diff).
But this broke other keyboard layouts.
This commit attempts to clean this up slightly -- but not completely --
by removing that hack, and only modifying the ctrl behavior for the
UCKeyTranslate call.
Long term, I plan to remove UCKeyTranslate completely, as noted in the
todo comment already written just below this diff.
This fixes the aforementioned issue and hopefully doesn't regress any
other behavior. I tested the following:
1. Dvorak Ctrl characters
2. Ergo-L Ctrl characters
3. US standard Ctrl characters
4. Japanese IME input Ctrl input to modify IME state
This is just a fun change to add a bunch of alternate icons. We don't
want to add too many since this increases the final bundle size but we
also want to have some fun. :)
Fixes#5690
When we hide the app and then show it again, the previously key window
is lost. This is because we are not using unhide and are manually
doing it (and we're not using unhide for good reasons commented in the
source already).
Modify our hidden state to include what the key window was (as a weak
ref) and restore it when we show the app again.
This fixes a regression from #5472. The fullscreen check must check if
the app is active otherwise the guard statement fails and we can't bring
the macOS app back from the background.
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.
Fixes#4631
This introduces a mechanism by which parsed config fields can be renamed
to maintain backwards compatibility. This already has a use case --
implemented in this commit -- for `background-blur-radius` to be renamed
to `background-blur`.
The remapping is comptime-known which lets us do some comptime
validation. The remap check isn't done unless no fields match which
means for well-formed config files, there's no overhead.
For future improvements:
- We should update our config help generator to note renamed fields.
- We could offer automatic migration of config files be rewriting them.
- We can enrich the value type with more metadata to help with
config gen or other tooling.
The native window drag region is driven ultimately by the window's
`contentLayoutRect`, so we can just override it in `TerminalWindow`
to return a rect the size of the full window, disabling the gesture
without causing any side effects by altering the responder chain.
ghostty#5000 changed the window level from `.popupMenu` to `.floating`
to improve IME support. However, this introduced a side effect which
render the Quick Terminal (QT) below the macOS menu bar, whereas
previously it would cover it.
When positioned on `right` and `left`, the top of the QT becomes
partially hidden. This PR adjust the size of the QT to ensure it remains
fully visible and stays below the menu bar.
Allowing the alert to be automatically closed after the completion handler finishes doesn't seem to play well when the completion handler closes the window on which the alert is attached
This commit is quite large because it's fairly interconnected and can't
be split up in a logical way. The main part of this commit is that alpha
blending is now always done in the Display P3 color space, and depending
on the configured `window-colorspace` colors will be converted from sRGB
or assumed to already be Display P3 colors. In addition, a config option
`text-blending` has been added which allows the user to configure linear
blending (AKA "gamma correction"). Linear alpha blending also applies to
images and makes custom shaders receive linear colors rather than sRGB.
In addition, an experimental option has been added which corrects linear
blending's tendency to make dark text look too thin and bright text look
too thick. Essentially it's a correction curve on the alpha channel that
depends on the luminance of the glyph being drawn.
Fixes#4999
We need to set the level to popUpMenu so that we can move the window
offscreen and animate it over the main menu, but we must reset it back
to floating after the animation is complete so that other higher-level
windows can be shown on top of it such as IME windows.
Two major changes:
1. Hiding uses `NSApp.hide` which hides all windows, preserves tabs, and
yields focus to the next app.
2. Unhiding manually tracks and brings forward only the windows we hid.
Proper focus should be retained.
Fixes#4799
This PR attempts to reduce the flash caused by the ghost emoji in the
title bar when opening new windows.
## Changes:
- Initialize `SurfaceView.title` with empty string instead of ghost
emoji
- Simplify title computation logic in `TerminalView`
- Adding a 500ms fallback timer for "👻"
- Canceling timer if title is set
## Current Status:
While these changes reduce the initial ghost emoji flash, there's still
a brief moment where a folder emoji appears alone in the title bar when
opening a new window. This suggests there might be a race condition or
timing issue with how the title is being set and updated.
https://github.com/user-attachments/assets/3688c9f3-1727-4379-b04d-0bd6ac105728
Would appreciate feedback on the remaining flash issue and suggestions
for further improvements.
Fixes:
https://github.com/ghostty-org/ghostty/issues/4634#issuecomment-2573469532
This commit fixes two issues:
1. `libghostty` must not override ctrl+key inputs if we are in a preedit
state. This allows thigs like `ctrl+h` to work properly in an IME.
2. On macOS, when an IME commits text, we strip the control modifier
from the key event we send to libghostty. This is a bit of a hack but
this avoids triggering special ctrl+key handling.
Fixes: https://github.com/ghostty-org/ghostty/issues/4634#issuecomment-2573469532
This commit fixes two issues:
1. `libghostty` must not override ctrl+key inputs if we are in a preedit
state. This allows thigs like `ctrl+h` to work properly in an IME.
2. On macOS, when an IME commits text, we strip the control modifier
from the key event we send to libghostty. This is a bit of a hack but
this avoids triggering special ctrl+key handling.
Fixes#4801
Our size calculation before improperly used a screens frame instead of
its visibleFrame. Additionally, we didn't properly account for origin
needing to move in order to fit the window on the screen.
Apparently, setting a frame height to high crashes AppKit. The width
gets clamped by AppKit but the height does not. Fun!
Adds the missing Bluetooth permission to Ghostty's application playlist
manifest file, as it was missing. Tweaks have also been made to existing
permission descriptions to be more readable. Should fix an abrupt crash
in Fastfetch and any other apps that may request Bluetooth permissions.