mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #2341 from ghostty-org/push-noxxuypymuoz
macos: quick terminal supports fullscreen
This commit is contained in:
@ -25,12 +25,26 @@ class QuickTerminalController: BaseTerminalController {
|
||||
) {
|
||||
self.position = position
|
||||
super.init(ghostty, baseConfig: base, surfaceTree: tree)
|
||||
|
||||
// Setup our notifications for behaviors
|
||||
let center = NotificationCenter.default
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(onToggleFullscreen),
|
||||
name: Ghostty.Notification.ghosttyToggleFullscreen,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported for this view")
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Remove all of our notificationcenter subscriptions
|
||||
let center = NotificationCenter.default
|
||||
center.removeObserver(self)
|
||||
}
|
||||
|
||||
// MARK: NSWindowController
|
||||
|
||||
override func windowDidLoad() {
|
||||
@ -199,6 +213,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
// We always animate out to whatever screen the window is actually on.
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
// If we are in fullscreen, then we exit fullscreen.
|
||||
if let fullscreenStyle, fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
}
|
||||
|
||||
// If we have a previously active application, restore focus to it. We
|
||||
// do this BEFORE the animation below because when the animation completes
|
||||
// macOS will bring forward another window.
|
||||
@ -239,4 +258,19 @@ class QuickTerminalController: BaseTerminalController {
|
||||
alert.alertStyle = .warning
|
||||
alert.beginSheetModal(for: window)
|
||||
}
|
||||
|
||||
@IBAction func toggleGhosttyFullScreen(_ sender: Any) {
|
||||
guard let surface = focusedSurface?.surface else { return }
|
||||
ghostty.toggleFullscreen(surface: surface)
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard target == self.focusedSurface else { return }
|
||||
|
||||
// We ignore the requested mode and always use non-native for the quick terminal
|
||||
toggleFullscreen(mode: .nonNative)
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,15 @@ import GhosttyKit
|
||||
/// view the TerminalView SwiftUI view must be used and this class is the view model and
|
||||
/// delegate.
|
||||
///
|
||||
/// Special considerations to implement:
|
||||
///
|
||||
/// - Fullscreen: you must manually listen for the right notification and implement the
|
||||
/// callback that calls toggleFullscreen on this base class.
|
||||
///
|
||||
/// Notably, things this class does NOT implement (not exhaustive):
|
||||
///
|
||||
/// - Tabbing, because there are many ways to get tabbed behavior in macOS and we
|
||||
/// don't want to be opinionated about it.
|
||||
/// - Fullscreen
|
||||
/// - Window restoration or save state
|
||||
/// - Window visual styles (such as titlebar colors)
|
||||
///
|
||||
@ -25,7 +29,8 @@ class BaseTerminalController: NSWindowController,
|
||||
NSWindowDelegate,
|
||||
TerminalViewDelegate,
|
||||
TerminalViewModel,
|
||||
ClipboardConfirmationViewDelegate
|
||||
ClipboardConfirmationViewDelegate,
|
||||
FullscreenDelegate
|
||||
{
|
||||
/// The app instance that this terminal view will represent.
|
||||
let ghostty: Ghostty.App
|
||||
@ -46,6 +51,9 @@ class BaseTerminalController: NSWindowController,
|
||||
/// The clipboard confirmation window, if shown.
|
||||
private var clipboardConfirmation: ClipboardConfirmationController? = nil
|
||||
|
||||
/// Fullscreen state management.
|
||||
private(set) var fullscreenStyle: FullscreenStyle?
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) is not supported for this view")
|
||||
}
|
||||
@ -123,6 +131,63 @@ class BaseTerminalController: NSWindowController,
|
||||
|
||||
func zoomStateDidChange(to: Bool) {}
|
||||
|
||||
// MARK: Fullscreen
|
||||
|
||||
/// Toggle fullscreen for the given mode.
|
||||
func toggleFullscreen(mode: FullscreenMode) {
|
||||
// We need a window to fullscreen
|
||||
guard let window = self.window else { return }
|
||||
|
||||
// If we have a previous fullscreen style initialized, we want to check if
|
||||
// our mode changed. If it changed and we're in fullscreen, we exit so we can
|
||||
// toggle it next time. If it changed and we're not in fullscreen we can just
|
||||
// switch the handler.
|
||||
var newStyle = mode.style(for: window)
|
||||
newStyle?.delegate = self
|
||||
old: if let oldStyle = self.fullscreenStyle {
|
||||
// If we're not fullscreen, we can nil it out so we get the new style
|
||||
if !oldStyle.isFullscreen {
|
||||
self.fullscreenStyle = newStyle
|
||||
break old
|
||||
}
|
||||
|
||||
assert(oldStyle.isFullscreen)
|
||||
|
||||
// We consider our mode changed if the types change (obvious) but
|
||||
// also if its nil (not obvious) because nil means that the style has
|
||||
// likely changed but we don't support it.
|
||||
if newStyle == nil || type(of: newStyle) != type(of: oldStyle) {
|
||||
// Our mode changed. Exit fullscreen (since we're toggling anyways)
|
||||
// and then unset the style so that we replace it next time.
|
||||
oldStyle.exit()
|
||||
self.fullscreenStyle = nil
|
||||
|
||||
// We're done
|
||||
return
|
||||
}
|
||||
|
||||
// Style is the same.
|
||||
} else {
|
||||
// We have no previous style
|
||||
self.fullscreenStyle = newStyle
|
||||
}
|
||||
guard let fullscreenStyle else { return }
|
||||
|
||||
if fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
} else {
|
||||
fullscreenStyle.enter()
|
||||
}
|
||||
}
|
||||
|
||||
func fullscreenDidChange() {
|
||||
// For some reason focus can get lost when we change fullscreen. Regardless of
|
||||
// mode above we just move it back.
|
||||
if let focusedSurface {
|
||||
Ghostty.moveFocus(to: focusedSurface)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Clipboard Confirmation
|
||||
|
||||
@objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) {
|
||||
|
@ -4,14 +4,9 @@ import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
/// A classic, tabbed terminal experience.
|
||||
class TerminalController: BaseTerminalController,
|
||||
FullscreenDelegate
|
||||
{
|
||||
class TerminalController: BaseTerminalController {
|
||||
override var windowNibName: NSNib.Name? { "Terminal" }
|
||||
|
||||
/// Fullscreen state management.
|
||||
private(set) var fullscreenStyle: FullscreenStyle?
|
||||
|
||||
/// This is set to true when we care about frame changes. This is a small optimization since
|
||||
/// this controller registers a listener for ALL frame change notifications and this lets us bail
|
||||
/// early if we don't care.
|
||||
@ -200,63 +195,6 @@ class TerminalController: BaseTerminalController,
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Fullscreen
|
||||
|
||||
/// Toggle fullscreen for the given mode.
|
||||
func toggleFullscreen(mode: FullscreenMode) {
|
||||
// We need a window to fullscreen
|
||||
guard let window = self.window else { return }
|
||||
|
||||
// If we have a previous fullscreen style initialized, we want to check if
|
||||
// our mode changed. If it changed and we're in fullscreen, we exit so we can
|
||||
// toggle it next time. If it changed and we're not in fullscreen we can just
|
||||
// switch the handler.
|
||||
var newStyle = mode.style(for: window)
|
||||
newStyle?.delegate = self
|
||||
old: if let oldStyle = self.fullscreenStyle {
|
||||
// If we're not fullscreen, we can nil it out so we get the new style
|
||||
if !oldStyle.isFullscreen {
|
||||
self.fullscreenStyle = newStyle
|
||||
break old
|
||||
}
|
||||
|
||||
assert(oldStyle.isFullscreen)
|
||||
|
||||
// We consider our mode changed if the types change (obvious) but
|
||||
// also if its nil (not obvious) because nil means that the style has
|
||||
// likely changed but we don't support it.
|
||||
if newStyle == nil || type(of: newStyle) != type(of: oldStyle) {
|
||||
// Our mode changed. Exit fullscreen (since we're toggling anyways)
|
||||
// and then unset the style so that we replace it next time.
|
||||
oldStyle.exit()
|
||||
self.fullscreenStyle = nil
|
||||
|
||||
// We're done
|
||||
return
|
||||
}
|
||||
|
||||
// Style is the same.
|
||||
} else {
|
||||
// We have no previous style
|
||||
self.fullscreenStyle = newStyle
|
||||
}
|
||||
guard let fullscreenStyle else { return }
|
||||
|
||||
if fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
} else {
|
||||
fullscreenStyle.enter()
|
||||
}
|
||||
}
|
||||
|
||||
func fullscreenDidChange() {
|
||||
// For some reason focus can get lost when we change fullscreen. Regardless of
|
||||
// mode above we just move it back.
|
||||
if let focusedSurface {
|
||||
Ghostty.moveFocus(to: focusedSurface)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - NSWindowController
|
||||
|
||||
override func windowWillLoad() {
|
||||
@ -584,7 +522,6 @@ class TerminalController: BaseTerminalController,
|
||||
targetWindow.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
|
||||
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard target == self.focusedSurface else { return }
|
||||
|
@ -376,9 +376,12 @@ pub const Action = union(enum) {
|
||||
///
|
||||
/// - It is a singleton; only one instance can exist at a time.
|
||||
/// - It does not support tabs.
|
||||
/// - It does not support fullscreen.
|
||||
/// - It will not be restored when the application is restarted
|
||||
/// (for systems that support window restoration).
|
||||
/// - It supports fullscreen, but fullscreen will always be a non-native
|
||||
/// fullscreen (macos-non-native-fullscreen = true). This only applies
|
||||
/// to the quick terminal window. This is a requirement due to how
|
||||
/// the quick terminal is rendered.
|
||||
///
|
||||
/// See the various configurations for the quick terminal in the
|
||||
/// configuration file to customize its behavior.
|
||||
|
Reference in New Issue
Block a user