macos: trigger fullscreenDidChange on any fullscreen event

Fixes #2840
Related to #2842

This builds on #2842 by missing a key situation: when native fullscreen
is toggled using the menu bar items it doesn't go through our
`FullscreenStyle` machinery so we don't trigger fullscreen change
events.

This commit makes it so that our FullscreenStyle always listens for
native fullscreen change (even in non-native modes) to fire a fullscreen
did change event. This way we can always rely on the event to be fired
when fullscreen changes no matter what.
This commit is contained in:
Mitchell Hashimoto
2024-12-01 11:31:55 -08:00
parent 3c637a2777
commit f384fd038b
4 changed files with 69 additions and 21 deletions

View File

@ -57,6 +57,7 @@ class QuickTerminalController: BaseTerminalController {
// MARK: NSWindowController // MARK: NSWindowController
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad()
guard let window = self.window else { return } guard let window = self.window else { return }
// The controller is the window delegate so we can detect events such as // The controller is the window delegate so we can detect events such as

View File

@ -404,7 +404,21 @@ class BaseTerminalController: NSWindowController,
} }
} }
//MARK: - NSWindowDelegate // MARK: NSWindowController
override func windowDidLoad() {
guard let window else { return }
// We always initialize our fullscreen style to native if we can because
// initialization sets up some state (i.e. observers). If its set already
// somehow we don't do this.
if fullscreenStyle == nil {
fullscreenStyle = NativeFullscreen(window)
fullscreenStyle?.delegate = self
}
}
// MARK: NSWindowDelegate
// This is called when performClose is called on a window (NOT when close() // This is called when performClose is called on a window (NOT when close()
// is called directly). performClose is called primarily when UI elements such // is called directly). performClose is called primarily when UI elements such

View File

@ -275,6 +275,7 @@ class TerminalController: BaseTerminalController {
} }
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad()
guard let window = window as? TerminalWindow else { return } guard let window = window as? TerminalWindow else { return }
// I copy this because we may change the source in the future but also because // I copy this because we may change the source in the future but also because

View File

@ -45,20 +45,53 @@ extension FullscreenDelegate {
func fullscreenDidChange() {} func fullscreenDidChange() {}
} }
/// The base class for fullscreen implementations, cannot be used as a FullscreenStyle on its own.
class FullscreenBase {
let window: NSWindow
weak var delegate: FullscreenDelegate?
required init?(_ window: NSWindow) {
self.window = window
// We want to trigger delegate methods on window native fullscreen
// changes (didEnterFullScreenNotification, etc.) no matter what our
// fullscreen style is.
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(didEnterFullScreenNotification),
name: NSWindow.didEnterFullScreenNotification,
object: window)
center.addObserver(
self,
selector: #selector(didExitFullScreenNotification),
name: NSWindow.didExitFullScreenNotification,
object: window)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@objc private func didEnterFullScreenNotification(_ notification: Notification) {
delegate?.fullscreenDidChange()
}
@objc private func didExitFullScreenNotification(_ notification: Notification) {
delegate?.fullscreenDidChange()
}
}
/// macOS native fullscreen. This is the typical behavior you get by pressing the green fullscreen /// macOS native fullscreen. This is the typical behavior you get by pressing the green fullscreen
/// button on regular titlebars. /// button on regular titlebars.
class NativeFullscreen: FullscreenStyle { class NativeFullscreen: FullscreenBase, FullscreenStyle {
private let window: NSWindow
weak var delegate: FullscreenDelegate?
var isFullscreen: Bool { window.styleMask.contains(.fullScreen) } var isFullscreen: Bool { window.styleMask.contains(.fullScreen) }
var supportsTabs: Bool { true } var supportsTabs: Bool { true }
required init?(_ window: NSWindow) { required init?(_ window: NSWindow) {
// TODO: There are many requirements for native fullscreen we should // TODO: There are many requirements for native fullscreen we should
// check here such as the stylemask. // check here such as the stylemask.
super.init(window)
self.window = window
} }
func enter() { func enter() {
@ -72,8 +105,9 @@ class NativeFullscreen: FullscreenStyle {
// Enter fullscreen // Enter fullscreen
window.toggleFullScreen(self) window.toggleFullScreen(self)
// Notify the delegate // Note: we don't call our delegate here because the base class
delegate?.fullscreenDidChange() // will always trigger the delegate on native fullscreen notifications
// and we don't want to double notify.
} }
func exit() { func exit() {
@ -84,14 +118,13 @@ class NativeFullscreen: FullscreenStyle {
window.toggleFullScreen(nil) window.toggleFullScreen(nil)
// Notify the delegate // Note: we don't call our delegate here because the base class
delegate?.fullscreenDidChange() // will always trigger the delegate on native fullscreen notifications
// and we don't want to double notify.
} }
} }
class NonNativeFullscreen: FullscreenStyle { class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
weak var delegate: FullscreenDelegate?
// Non-native fullscreen never supports tabs because tabs require // Non-native fullscreen never supports tabs because tabs require
// the "titled" style and we don't have it for non-native fullscreen. // the "titled" style and we don't have it for non-native fullscreen.
var supportsTabs: Bool { false } var supportsTabs: Bool { false }
@ -110,13 +143,8 @@ class NonNativeFullscreen: FullscreenStyle {
var hideMenu: Bool = true var hideMenu: Bool = true
} }
private let window: NSWindow
private var savedState: SavedState? private var savedState: SavedState?
required init?(_ window: NSWindow) {
self.window = window
}
func enter() { func enter() {
// If we are in fullscreen we don't do it again. // If we are in fullscreen we don't do it again.
guard !isFullscreen else { return } guard !isFullscreen else { return }
@ -187,8 +215,12 @@ class NonNativeFullscreen: FullscreenStyle {
guard isFullscreen else { return } guard isFullscreen else { return }
guard let savedState else { return } guard let savedState else { return }
// Remove all our notifications // Remove all our notifications. We remove them one by one because
NotificationCenter.default.removeObserver(self) // we don't want to remove the observers that our superclass sets.
let center = NotificationCenter.default
center.removeObserver(self, name: NSWindow.didBecomeMainNotification, object: window)
center.removeObserver(self, name: NSWindow.didResignMainNotification, object: window)
center.removeObserver(self, name: NSWindow.didChangeScreenNotification, object: window)
// Unhide our elements // Unhide our elements
if savedState.dock { if savedState.dock {