This is a follow-up to #8064, moving the activation into the async block
such that it happens after the window is created. As discussed in #8064,
this is necessary to bring only the newly created window to the front,
rather than both the previous main window and the new window.
Also made the same change for the new tab action, which also needs to
activate in case it was triggered from the dock menu or a global
keybind.
Finally, I removed the activations within AppDelegate that are redundant
now that TerminalController itself takes care of activating.
> [!NOTE]
> The change might have been intentional, and so I lack context. I
mention two ways to fix below, this PR implements the first possible
fix.
This change makes sure that the new window is focused and visible.
When commit 33d128bcff removed the
TerminalManager class and moved its functionality into
TerminalController, it accidentally removed app activation for windows
triggered by global keybinds.
How the bug works:
1. Menu actions (like File → New Window) call AppDelegate.newWindow()
which: 2. Calls TerminalController.newWindow() 3. AND explicitly calls
NSApp.activate(ignoringOtherApps: true) in the AppDelegate
4. Global keybind actions trigger ghosttyNewWindow() notification
handler which:
5. Only calls TerminalController.newWindow()
6. Does NOT call NSApp.activate(ignoringOtherApps: true)
7. While TerminalController.newWindow() does call
NSApp.activate(ignoringOtherApps: true) internally, this call happens
before the async dispatch that shows the window, so the activation
occurs but the window isn't focused when it's actually shown.
8. In the old TerminalManager.newWindow(), the activation happened
immediately before the async dispatch, ensuring proper timing for window
focus.
To see the bug in action:
- run recent Ghostty `main`
- set up a global keybind for `new_window`
- focus some other window
- trigger keybind
- notice that Ghostty doesn't come to the foreground, but when manually
switching to Ghostty you will see that the new window _was_ created
The fix would be to either move the NSApp.activate() call back into
TerminalController.newWindow(), as it was for TerminalManager, or add
the activation call to the notification handlers in AppDelegate.
This change makes sure that the new window is focused and visible.
When commit 33d128bcff removed the
TerminalManager class and moved its functionality into
TerminalController, it accidentally removed app activation for windows
triggered by global keybinds.
How the bug works:
1. Menu actions (like File → New Window) call AppDelegate.newWindow()
which:
2. Calls TerminalController.newWindow()
3. AND explicitly calls NSApp.activate(ignoringOtherApps: true) in
the AppDelegate
4. Global keybind actions trigger ghosttyNewWindow() notification
handler which:
5. Only calls TerminalController.newWindow()
6. Does NOT call NSApp.activate(ignoringOtherApps: true)
7. While TerminalController.newWindow() does call
NSApp.activate(ignoringOtherApps: true) internally, this call
happens before the async dispatch that shows the window, so the
activation occurs but the window isn't focused when it's actually
shown.
8. In the old TerminalManager.newWindow(), the activation happened
immediately before the async dispatch, ensuring proper timing for
window focus.
The fix would be to either move the NSApp.activate() call back into
TerminalController.newWindow(), as it was for TerminalManager, or add
the activation call to the notification handlers in AppDelegate.
## Summary
Implements the theme filtering hotkey as requested in #7930.
## Implementation
- Adds 'f' hotkey to cycle through filtering options: all, dark, and
light.
- Integrates with existing search functionality.
- Preserves CLI `--color` flag behavior for initial state.
- Updates help menu with the new hotkey.
**NOTE**: I noticed another PR
[#8079](https://github.com/ghostty-org/ghostty/pull/8079) opened. I
started this implementation independently prior and don't want to step
on any toes. Happy to collaborate or defer to maintainers.
Compromise solution to #7356
XKB is naughty. It's really really naughty. I don't understand why we
didn't just kill XKB with hammers during the Wayland migration and
change it for something much better. I don't understand why we're
content with what amounts to an OS-level software key remapper that
completely jumbles information about original physical key codes in
order to fake keyboard layouts, and not just let users who really want
to remap keys use some sort of evdev or udev-based mapper program.
In a sane system like macOS, the "c" key is always the "c" key, but it's
understood to produce the Unicode character "ц" when using a Russian
layout. XKB defies sanity, and just pretends that your "c" key is
actually a "ц" key instead, and so when you ask for the keybind "Ctrl+C"
it just shrugs in apathy (#7309). And so, we took matters into our own
hands and interpreted hardware keycodes ourselves.
But then, a *lot* of people have the ingrained muscle memory of swapping
Escape with Caps Lock so that it is easier to hit. We respect that. In a
sane system, they would use a remapper that actually makes the system
think you've hit the Escape key when in reality you've hit the Caps Lock
key, so in all intents and purposes to the OS and any app developer,
these two just have their wires swapped. But not on Linux. Somehow this
and the aforementioned case should be treated by the same key transform
algorithm, which is completely diabolical.
As a result, we have to settle for a compromise that truly satisfies
neither party — by allowing XKB remaps for keys that don't really change
depending on the layout.
The Linux input stack besets all hopes and aspirations.
Compromise solution to #7356
XKB is naughty. It's really really naughty. I don't understand why we
didn't just kill XKB with hammers during the Wayland migration and change
it for something much better. I don't understand why we're content with
what amounts to an OS-level software key remapper that completely jumbles
information about original physical key codes in order to fake keyboard
layouts, and not just let users who really want to remap keys use some
sort of evdev or udev-based mapper program.
In a sane system like macOS, the "c" key is always the "c" key, but it's
understood to produce the Unicode character "ц" when using a Russian
layout. XKB defies sanity, and just pretends that your "c" key is
actually a "ц" key instead, and so when you ask for the keybind "Ctrl+C"
it just shrugs in apathy (#7309). And so, we took matters into our own
hands and interpreted hardware keycodes ourselves.
But then, a *lot* of people have the ingrained muscle memory of swapping
Escape with Caps Lock so that it is easier to hit. We respect that.
In a sane system, they would use a remapper that actually makes the
system think you've hit the Escape key when in reality you've hit the
Caps Lock key, so in all intents and purposes to the OS and any app
developer, these two just have their wires swapped. But not on Linux.
Somehow this and the aforementioned case should be treated by the same
key transform algorithm, which is completely diabolical.
As a result, we have to settle for a compromise that truly satisfies
neither party — by allowing XKB remaps for keys that don't really change
depending on the layout.
The Linux input stack besets all hopes and aspirations.
This PR adds a new configuration option
`macos-dock-drop-folder-behavior` that controls whether folders dropped
onto the Ghostty dock icon open in a new tab (default) or a new window.
## Changes
### Configuration Option Added
- **Option name**: `macos-dock-drop-folder-behavior`
- **Valid values**:
- `tab` (default) - Opens folders in a new tab in the main window
- `window` - Opens folders in a new window
- **Platform**: macOS only
### Files Modified
1. **`src/config/Config.zig`**
- Added `MacOSDockDropFolderBehavior` enum with `tab` and `window`
values
- Added configuration field with default value of `.tab`
- Added documentation explaining the option
2. **`macos/Sources/Ghostty/Package.swift`**
- Added `MacOSDockDropFolderBehavior` enum to match the Zig enum
3. **`macos/Sources/Ghostty/Ghostty.Config.swift`**
- Added `macosDockDropFolderBehavior` computed property to access the
configuration value from Swift
4. **`macos/Sources/App/macOS/AppDelegate.swift`**
- Modified `application(_:openFile:)` method to check the configuration
- When a folder is dropped on the dock icon, it now respects the user's
preference
## Usage
Add to your Ghostty configuration file:
```
macos-dock-drop-folder-behavior = window
```
## Motivation
This feature is useful for users (like me!) who prefer window-based
workflows over tab-based workflows when opening folders via drag and
drop on macOS.
This adds a new configuration option that controls whether folders
dropped onto the Ghostty dock icon open in a new tab (default) or
a new window.
The option accepts two values:
- tab: Opens folders in a new tab in the main window (default)
- window: Opens folders in a new window
This is useful for users who prefer window-based workflows over
tab-based workflows when opening folders via drag and drop.
This PR aims to improve custom icons on macOS in the following ways. (I
based this PR on the discussion #3631)
### Currently
- Current Icon customizations are not persistent *(when closing the
application the icon in dock reverts back to official icon)*
- There is no officially supported way to change icon to be something
completely custom.
### After this PR
- Current icon customizations are persistent (closing the application no
longer reverts back to official icon)
- Ghostty config `macos-icon` has a new option `custom` which by default
looks for icon `~/.config/ghostty/Ghostty.icns`. It has an accompanying
new configuration `macos-custom-icon` which allows for a different path
to be specified, it does support more than just `.icns` as well.
Both changes are based on the thread with @sfsam in
https://github.com/ghostty-org/ghostty/discussions/3631#discussioncomment-12180647
Feedback is always welcome, if I have not done something up to par
please let me know and I will do my best to correct it.
NOTE: I did notice some newlines with indents which seems to be against
convention in those files so I removed the whitespace if this is not
preferred I can revert.
---
P.S. Thanks for all the work you put into making an awesome terminal!
Fix for discussion #8113
The cursor Y position value exposed to the shader uniforms was
incorrectly calculated. As per the doc in cell_text.v.glsl: In order to
get the top left of the glyph, we compute an offset based on the
bearings. The Y bearing is the distance from the bottom of the cell to
the top of the glyph, so we subtract it from the cell height to get the
y offset.
This calculation was mistakenly left out of the original code.
This will ensure that the custom shaders using
iCurrentCursor/iPreviousCursor get the correct Y coordinate representing
the top-left corner of the cursor rectangle, matching the documented
uniform behavior
This switches our macOS builds to build on Tahoe, rather than on
Sequoia.
The primary motivation is to get builds out using a new Xcode version
(our builds at the time of writing this are _still produced_ with beta
1! ONE!). Every subsequent beta has had bugs that have prevented us from
upgrading, amusingly enough. But the later betas _also_ have a bunch of
fixes I want to get in. I hope this one works...
The reason we have to use Tahoe instead of Sequoia is because on
Sequoia, builds in CI _crash xcodebuild_. This is definitely an Apple
bug but I can't reproduce it locally to create a bug report, so I'm not
sure what to do.
The SDK published on Flathub updated to Zig 0.15.1 which broke the
Flathub build in CI. So let's install it ourselves so that we can
control the version.
This contains the various changes necessary to get the full unit test
suite passing Valgrind, and configures CI to run this.
I disabled relatively few (less than 10) tests under Valgrind because
they're way too slow: all `verifyIntegrity` tests, because those run
anyways in debug and check their own memory health, a font test that
fills out font map, and the sprite render test. Everything else runs
as-is.
I found a number of issues, most were in the tests themselves. A couple
in actual code. A funny one was some undefined memory on tabstop resize
if you exceed the default number of tabstops. I don't know any real
world program that ever even did that (memory issue aside), and that
whole file hasn't been touched since 2022, so that was funny.
No memory leaks in actual code, but a number of leaks in tests. All
resolved.
I think we're still missing some reports because of the Zig bug:
https://github.com/ziglang/zig/issues/19148 so I'm gong to audit our
codebase after this and look for cases of that.
When constraints increased or decreased the size significantly, the
fractional position was getting messed up by the scale. This change
separates that out so that it applies correctly.
I noticed this when messing around with constraints, adding this
constraint to every glyph and then running with `font-family=Arial` and
`adjust-cell-width = -35%` (if you want to reproduce this)
```zig
constraint = .{
.size_horizontal = .stretch,
.align_horizontal = .center,
.pad_left = 0.1,
.pad_right = 0.1,
};
```
The padding was disproportionately affecting thin glyphs that were
stretched a lot. The problem was that the padding was being multiplied
by the scale.
This also made it so the top or right of said thin glyphs often got
clipped off by the edge of the canvas.
Anyway I fixed it.
|Before|After|
|-|-|
|<img width="1824" height="1480" alt="image"
src="https://github.com/user-attachments/assets/32779f9d-a048-4a8c-b5ea-0e8a851d5119"
/>|<img width="1824" height="1480" alt="image"
src="https://github.com/user-attachments/assets/5bf449e5-699e-4bdc-ac96-2b776f9fb7fa"
/>|
The SDK published on Flathub updated to Zig 0.15.1 which broke the
Flathub build in CI. So let's install it ourselves so that we can
control the version.
When constraints increased or decreased the size significantly, the
fractional position was getting messed up by the scale. This change
separates that out so that it applies correctly.