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
|
self.position = position
|
||||||
super.init(ghostty, baseConfig: base, surfaceTree: tree)
|
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) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) is not supported for this view")
|
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
|
// MARK: NSWindowController
|
||||||
|
|
||||||
override func windowDidLoad() {
|
override func windowDidLoad() {
|
||||||
@ -199,6 +213,11 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
// We always animate out to whatever screen the window is actually on.
|
// We always animate out to whatever screen the window is actually on.
|
||||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
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
|
// If we have a previously active application, restore focus to it. We
|
||||||
// do this BEFORE the animation below because when the animation completes
|
// do this BEFORE the animation below because when the animation completes
|
||||||
// macOS will bring forward another window.
|
// macOS will bring forward another window.
|
||||||
@ -239,4 +258,19 @@ class QuickTerminalController: BaseTerminalController {
|
|||||||
alert.alertStyle = .warning
|
alert.alertStyle = .warning
|
||||||
alert.beginSheetModal(for: window)
|
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
|
/// view the TerminalView SwiftUI view must be used and this class is the view model and
|
||||||
/// delegate.
|
/// 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):
|
/// Notably, things this class does NOT implement (not exhaustive):
|
||||||
///
|
///
|
||||||
/// - Tabbing, because there are many ways to get tabbed behavior in macOS and we
|
/// - Tabbing, because there are many ways to get tabbed behavior in macOS and we
|
||||||
/// don't want to be opinionated about it.
|
/// don't want to be opinionated about it.
|
||||||
/// - Fullscreen
|
|
||||||
/// - Window restoration or save state
|
/// - Window restoration or save state
|
||||||
/// - Window visual styles (such as titlebar colors)
|
/// - Window visual styles (such as titlebar colors)
|
||||||
///
|
///
|
||||||
@ -25,7 +29,8 @@ class BaseTerminalController: NSWindowController,
|
|||||||
NSWindowDelegate,
|
NSWindowDelegate,
|
||||||
TerminalViewDelegate,
|
TerminalViewDelegate,
|
||||||
TerminalViewModel,
|
TerminalViewModel,
|
||||||
ClipboardConfirmationViewDelegate
|
ClipboardConfirmationViewDelegate,
|
||||||
|
FullscreenDelegate
|
||||||
{
|
{
|
||||||
/// The app instance that this terminal view will represent.
|
/// The app instance that this terminal view will represent.
|
||||||
let ghostty: Ghostty.App
|
let ghostty: Ghostty.App
|
||||||
@ -46,6 +51,9 @@ class BaseTerminalController: NSWindowController,
|
|||||||
/// The clipboard confirmation window, if shown.
|
/// The clipboard confirmation window, if shown.
|
||||||
private var clipboardConfirmation: ClipboardConfirmationController? = nil
|
private var clipboardConfirmation: ClipboardConfirmationController? = nil
|
||||||
|
|
||||||
|
/// Fullscreen state management.
|
||||||
|
private(set) var fullscreenStyle: FullscreenStyle?
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) is not supported for this view")
|
fatalError("init(coder:) is not supported for this view")
|
||||||
}
|
}
|
||||||
@ -123,6 +131,63 @@ class BaseTerminalController: NSWindowController,
|
|||||||
|
|
||||||
func zoomStateDidChange(to: Bool) {}
|
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
|
// MARK: Clipboard Confirmation
|
||||||
|
|
||||||
@objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) {
|
@objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) {
|
||||||
|
@ -4,14 +4,9 @@ import SwiftUI
|
|||||||
import GhosttyKit
|
import GhosttyKit
|
||||||
|
|
||||||
/// A classic, tabbed terminal experience.
|
/// A classic, tabbed terminal experience.
|
||||||
class TerminalController: BaseTerminalController,
|
class TerminalController: BaseTerminalController {
|
||||||
FullscreenDelegate
|
|
||||||
{
|
|
||||||
override var windowNibName: NSNib.Name? { "Terminal" }
|
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 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
|
/// this controller registers a listener for ALL frame change notifications and this lets us bail
|
||||||
/// early if we don't care.
|
/// 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
|
//MARK: - NSWindowController
|
||||||
|
|
||||||
override func windowWillLoad() {
|
override func windowWillLoad() {
|
||||||
@ -584,7 +522,6 @@ class TerminalController: BaseTerminalController,
|
|||||||
targetWindow.makeKeyAndOrderFront(nil)
|
targetWindow.makeKeyAndOrderFront(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
|
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
|
||||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||||
guard target == self.focusedSurface 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 is a singleton; only one instance can exist at a time.
|
||||||
/// - It does not support tabs.
|
/// - It does not support tabs.
|
||||||
/// - It does not support fullscreen.
|
|
||||||
/// - It will not be restored when the application is restarted
|
/// - It will not be restored when the application is restarted
|
||||||
/// (for systems that support window restoration).
|
/// (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
|
/// See the various configurations for the quick terminal in the
|
||||||
/// configuration file to customize its behavior.
|
/// configuration file to customize its behavior.
|
||||||
|
Reference in New Issue
Block a user