Some control key combinations must be handled specially by Ghostty to
prevent undesirable behavior at the OS level. For now, this includes
only Ctrl-/, which makes a "beep" sound when processed by AppKit. It is
unclear why this beep occurs and no answer was found after extensive
searching.
This solution is inspired by iTerm2, which also handles certain control
key combinations (including C-/) manually before passing them on to
Cocoa/AppKit.
Fixes#1018Fixes#1020
This disables the "visibleAtLaunch" configuration in the xib and
manually shows the window when it loads. This lets us carefully control
what happens particularly when a window is full screen (native) and part
of Mission Control.
Previously, the behavior depended on the Settings.app "Prefer tabs
when opening documents" setting, but we didn't handle every behavior
correctly (see #1018 and #1020). I couldn't find a way to robustly
handle all cases because there are no published macOS APIs for
interacting with Mission Control...
Plus, terminals aren't really "documents" so it did confuse at least one
user that Ghostty would follow this configuration. We just incidently
did because we use native tabbing.
This PR takes full control into our own hands. Our behavior is now:
- If a new window is created from a native fullscreen window, the
new window is created into native fullscreen.
- If a new tab is created from a native fullscreen window, the
tab is added to the existing window and does not create a new space.
- If a window or tab is created from a non-fullscreen window, the
existing behaviors remain.
Prior to this commit, the shortcut shown on the tab would not be updated
until a focus change occurred. There is no event for this so the way we
do it is by listening for the tab view frame to change, comparing the
window state, and then updating labels.
Related to #642Fixes#910
See #642 for why we want to ignore the "always" userTabbingPreference.
To do that, we'd set tabbingMode to "disallow" because we manually (in
code) handled it all.
Unfortunately, setting the tabbingMode to "disallow" introduce #910. I
still believe this is a macOS bug at heart, so I'm going to submit an
Apple Feedback item for it. However, I've found a workaround which I
also feel is the better solution, implemented here.
Instead of setting tabbingMode to "disallow" I now detect if we're in
the scenario where the user has their system tabbing preference set to
"always". In that case, we detect if the new window has been
automatically put into a tab group by macOS, and if so we remove it.
This all happens in the `windowDidLoad` controller callback. At this
phase, our Ghostty-managed windows should NEVER be in a tab group,
because "new tab" adds them to a tab _after_. So we can be certain that
if we're already in a tab group it was from the macOS system setting.
This happens to fix#910.
The return value of takeUnretainedValue() is non-optional, so this
function was never _actually_ returning an optional value, so the guard
clauses sprinkled throughout were unnecessary.
This enables shifted alt-prefixed keys, such as `shift+alt+.` on
US standard becoming `M->`. To do this, we needed to fix a few bugs:
(1) translation mods should strip alt even if other mods are set
(2) AppKit translation event needs to construct new characters with
the translation mods.
(3) Alt-prefix handling in KeyEncoder needs to allow ASCII utf8
translations even for macOS.
Fixes#872
In #867 we fixed macos-option-as-alt, but unfortunately AppKit ALSO does
some translation so some behaviors were not working correctly.
Specifically, when you had macos-option-as-alt set, option+e would
properly send `esc+e` to the pty but it would ALSO set the dead key
state for "`" since AppKit was still translating the option key.
This commit introduces a function to strip alt when necessary from the
translation modifiers used at the AppKit layer, preventing this
behavior.
This fixes#786 by adding a check to the callback that unhides the menu
bar to only unhide the menu bar if focus was lost to another Ghostty
window.
That's the desired behaviour: when focus is lost to another app's
window, we want the non-native-fullscreen window to stay unchanged in
background, but when changing focus to another Ghostty window, we want
to unhide the menu bar.
Why did this problem even show up?
It started to show up with the introduction of the Xib-file based
approach, in 3018377.
Before that, in 27ddc90, for example, the app would receive the same
notifications, but the `.autoHideMenuBar` didn't have an effect on the
window. Only after adding the Xib-file did it start to have an effect.
So I figured there's two ways we could fix it:
1. Figure out why the `.autoHideMenuBar` now works with Xib-files and
suppress it, or
2. Encode in the code the behaviour that we actually want: we only want
to show the menu bar when focus shifts to another one of Ghostty's
windows, but we want to leave it untouched when focus is lost to
another app's window.
I went with (2) but I think (1) is also valid and happy to close PR if
that's what we want to do.
Instead of making a separate enum that must be translated from the
ClipboardRequest type, simply re-use ClipboardRequest to determine the
clipboard confirmation reason.
This tells AppKit to give the tab labels higher priority than the tab
titles. When the tabs become small, this causes the tab title text to be
truncated instead of dropping the tab label.
Fixes: https://github.com/mitchellh/ghostty/issues/843
This is a small cleanup change. This provides the Ghostty.AppState
object to any view that needs it within the TerminalSplit view hierarchy
without needing to explicitly define EnvironmentKeys.
This commit does two things: adds a weak reference to the parent
container of each SplitNode.Container and SplitNode.Leaf and moves the
"direction" of a split out of the SplitNode enum and into the
SplitNode.Container struct as a field.
Both changes are required for supporting split resizing. A reference to
the parent in each leaf and split container is needed in order to
traverse upwards through the split tree. If the focused split is not
part of a container that is split along the direction that was requested
to be resized, then we instead try and resize the parent. If the parent
is split along the requested direction, then it is resized
appropriately; otherwise, repeat until the root of the tree is reached.
The direction is needed inside the SplitNode.Container object itself so
that the container knows whether or not it is able to resize itself in
the direction requested by the user. Once the split direction was moved
inside of SplitNode.Container, it became redundant to also have it as
part of the SplitNode enum, so this simplifies things.
Related to #799
With tabbing mode disabled, fullscreen (native) windows when creating a
new tab creates a window in the tab bar, but ALSO puts the window into a
new "screen" on macOS. When you click the tab bar, macOS animates
between screens. Its jarring!
This commit makes it so that in fullscreen we go back to automatic
tabbing even for new windows, which produces new windows within a tab.
This is normal default behavior for macOS tabbed programs. When you are
not in fullscreen or exit fullscreen, then the tabbing mode returns to
disabled so Ghostty can manage it.