mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #2337 from ghostty-org/nonnative
macOS: fullscreen implementation improvements
This commit is contained in:
@ -42,11 +42,15 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>GhosttyCommit</key>
|
||||||
|
<string></string>
|
||||||
<key>LSEnvironment</key>
|
<key>LSEnvironment</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>GHOSTTY_MAC_APP</key>
|
<key>GHOSTTY_MAC_APP</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
<key>NSServices</key>
|
<key>NSServices</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@ -88,41 +92,7 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSAppleEventsUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use AppleScript.</string>
|
|
||||||
<key>NSCalendarsUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use your calendar.</string>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use the camera.</string>
|
|
||||||
<key>NSContactsUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use your contacts.</string>
|
|
||||||
<key>NSLocalNetworkUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to access the local network.</string>
|
|
||||||
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
|
|
||||||
<string>A program in Ghostty wants to use your location temporarily.</string>
|
|
||||||
<key>NSLocationUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use your location information.</string>
|
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use your microphone.</string>
|
|
||||||
<key>NSMotionUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to access motion data.</string>
|
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use your photo library.</string>
|
|
||||||
<key>NSRemindersUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to access your reminders.</string>
|
|
||||||
<key>NSSpeechRecognitionUsageDescription</key>
|
|
||||||
<string>A program in Ghostty wants to use speech recognition.</string>
|
|
||||||
<key>NSSystemAdministrationUsageDescription</key>
|
|
||||||
<string>A program in Ghostty requires elevated privileges.</string>
|
|
||||||
<key>SUPublicEDKey</key>
|
<key>SUPublicEDKey</key>
|
||||||
<string>wsNcGf5hirwtdXMVnYoxRIX/SqZQLMOsYlD3q3imeok=</string>
|
<string>wsNcGf5hirwtdXMVnYoxRIX/SqZQLMOsYlD3q3imeok=</string>
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string></string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string></string>
|
|
||||||
<key>GhosttyCommit</key>
|
|
||||||
<string></string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; };
|
55154BE02B33911F001622DC /* ghostty in Resources */ = {isa = PBXBuildFile; fileRef = 55154BDF2B33911F001622DC /* ghostty */; };
|
||||||
552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; };
|
552964E62B34A9B400030505 /* vim in Resources */ = {isa = PBXBuildFile; fileRef = 552964E52B34A9B400030505 /* vim */; };
|
||||||
8503D7C72A549C66006CFF3D /* FullScreenHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */; };
|
|
||||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; };
|
||||||
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||||
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
A514C8D72B54A16400493A16 /* Ghostty.Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = A514C8D52B54A16400493A16 /* Ghostty.Config.swift */; };
|
||||||
@ -22,6 +21,9 @@
|
|||||||
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */; };
|
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */; };
|
||||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; };
|
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; };
|
||||||
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */; };
|
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */; };
|
||||||
|
A52FFF592CAA4FF3000C6A5B /* Fullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */; };
|
||||||
|
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */; };
|
||||||
|
A52FFF5D2CAB4D08000C6A5B /* NSScreen+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */; };
|
||||||
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; };
|
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; };
|
||||||
A5333E1D2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; };
|
A5333E1D2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; };
|
||||||
A5333E202B5A2111008AEFF7 /* SurfaceView_UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */; };
|
A5333E202B5A2111008AEFF7 /* SurfaceView_UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */; };
|
||||||
@ -94,7 +96,6 @@
|
|||||||
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
|
3B39CAA42B33949B00DABEB8 /* GhosttyReleaseLocal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyReleaseLocal.entitlements; sourceTree = "<group>"; };
|
||||||
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; };
|
55154BDF2B33911F001622DC /* ghostty */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ghostty; path = "../zig-out/share/ghostty"; sourceTree = "<group>"; };
|
||||||
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
|
552964E52B34A9B400030505 /* vim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vim; path = "../zig-out/share/vim"; sourceTree = "<group>"; };
|
||||||
8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenHandler.swift; sourceTree = "<group>"; };
|
|
||||||
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||||
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
|
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Config.swift; sourceTree = "<group>"; };
|
||||||
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
|
A51B78462AF4B58B00F3EDB9 /* TerminalWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalWindow.swift; sourceTree = "<group>"; };
|
||||||
@ -105,6 +106,9 @@
|
|||||||
A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateDelegate.swift; sourceTree = "<group>"; };
|
A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateDelegate.swift; sourceTree = "<group>"; };
|
||||||
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = "<group>"; };
|
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = "<group>"; };
|
||||||
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalScreen.swift; sourceTree = "<group>"; };
|
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalScreen.swift; sourceTree = "<group>"; };
|
||||||
|
A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fullscreen.swift; sourceTree = "<group>"; };
|
||||||
|
A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FullscreenMode+Extension.swift"; sourceTree = "<group>"; };
|
||||||
|
A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_UIKit.swift; sourceTree = "<group>"; };
|
A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_UIKit.swift; sourceTree = "<group>"; };
|
||||||
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrossKit.swift; sourceTree = "<group>"; };
|
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrossKit.swift; sourceTree = "<group>"; };
|
||||||
A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = "<group>"; };
|
A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = "<group>"; };
|
||||||
@ -233,11 +237,12 @@
|
|||||||
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */,
|
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */,
|
||||||
A5CBD0572C9F30860017A1AE /* Cursor.swift */,
|
A5CBD0572C9F30860017A1AE /* Cursor.swift */,
|
||||||
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */,
|
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */,
|
||||||
8503D7C62A549C66006CFF3D /* FullScreenHandler.swift */,
|
A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */,
|
||||||
A59630962AEE163600D64628 /* HostingWindow.swift */,
|
A59630962AEE163600D64628 /* HostingWindow.swift */,
|
||||||
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
|
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */,
|
||||||
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */,
|
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */,
|
||||||
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */,
|
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */,
|
||||||
|
A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */,
|
||||||
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
||||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
||||||
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
||||||
@ -314,6 +319,7 @@
|
|||||||
A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */,
|
A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */,
|
||||||
A59630A12AF0415000D64628 /* Ghostty.TerminalSplit.swift */,
|
A59630A12AF0415000D64628 /* Ghostty.TerminalSplit.swift */,
|
||||||
A55685DF29A03A9F004303CE /* AppError.swift */,
|
A55685DF29A03A9F004303CE /* AppError.swift */,
|
||||||
|
A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */,
|
||||||
);
|
);
|
||||||
path = Ghostty;
|
path = Ghostty;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -573,8 +579,10 @@
|
|||||||
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */,
|
A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */,
|
||||||
A5CBD06B2CA322430017A1AE /* GlobalEventTap.swift in Sources */,
|
A5CBD06B2CA322430017A1AE /* GlobalEventTap.swift in Sources */,
|
||||||
AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */,
|
AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */,
|
||||||
|
A52FFF5D2CAB4D08000C6A5B /* NSScreen+Extension.swift in Sources */,
|
||||||
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
|
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
|
||||||
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */,
|
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */,
|
||||||
|
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */,
|
||||||
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
|
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
|
||||||
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
||||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||||
@ -597,8 +605,8 @@
|
|||||||
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */,
|
||||||
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */,
|
||||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */,
|
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */,
|
||||||
8503D7C72A549C66006CFF3D /* FullScreenHandler.swift in Sources */,
|
|
||||||
A596309E2AEE1D6C00D64628 /* TerminalView.swift in Sources */,
|
A596309E2AEE1D6C00D64628 /* TerminalView.swift in Sources */,
|
||||||
|
A52FFF592CAA4FF3000C6A5B /* Fullscreen.swift in Sources */,
|
||||||
AEF9CE242B6AD07A0017E195 /* TerminalToolbar.swift in Sources */,
|
AEF9CE242B6AD07A0017E195 /* TerminalToolbar.swift in Sources */,
|
||||||
C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */,
|
C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */,
|
||||||
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */,
|
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */,
|
||||||
@ -704,8 +712,21 @@
|
|||||||
INFOPLIST_FILE = "Ghostty-Info.plist";
|
INFOPLIST_FILE = "Ghostty-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||||
|
INFOPLIST_KEY_NSAppleEventsUsageDescription = "A program in Ghostty wants to use AppleScript.";
|
||||||
|
INFOPLIST_KEY_NSCalendarsUsageDescription = "A program in Ghostty wants to use your calendar.";
|
||||||
|
INFOPLIST_KEY_NSCameraUsageDescription = "A program in Ghostty wants to use the camera.";
|
||||||
|
INFOPLIST_KEY_NSContactsUsageDescription = "A program in Ghostty wants to use your contacts.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "A program in Ghostty wants to access the local network.";
|
||||||
|
INFOPLIST_KEY_NSLocationTemporaryUsageDescriptionDictionary = "A program in Ghostty wants to use your location temporarily.";
|
||||||
|
INFOPLIST_KEY_NSLocationUsageDescription = "A program in Ghostty wants to use your location information.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
|
INFOPLIST_KEY_NSMicrophoneUsageDescription = "A program in Ghostty wants to use your microphone.";
|
||||||
|
INFOPLIST_KEY_NSMotionUsageDescription = "A program in Ghostty wants to access motion data.";
|
||||||
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "A program in Ghostty wants to use your photo library.";
|
||||||
|
INFOPLIST_KEY_NSRemindersUsageDescription = "A program in Ghostty wants to access your reminders.";
|
||||||
|
INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "A program in Ghostty wants to use speech recognition.";
|
||||||
|
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "A program in Ghostty requires elevated privileges.";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
@ -858,8 +879,21 @@
|
|||||||
INFOPLIST_FILE = "Ghostty-Info.plist";
|
INFOPLIST_FILE = "Ghostty-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||||
|
INFOPLIST_KEY_NSAppleEventsUsageDescription = "A program in Ghostty wants to use AppleScript.";
|
||||||
|
INFOPLIST_KEY_NSCalendarsUsageDescription = "A program in Ghostty wants to use your calendar.";
|
||||||
|
INFOPLIST_KEY_NSCameraUsageDescription = "A program in Ghostty wants to use the camera.";
|
||||||
|
INFOPLIST_KEY_NSContactsUsageDescription = "A program in Ghostty wants to use your contacts.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "A program in Ghostty wants to access the local network.";
|
||||||
|
INFOPLIST_KEY_NSLocationTemporaryUsageDescriptionDictionary = "A program in Ghostty wants to use your location temporarily.";
|
||||||
|
INFOPLIST_KEY_NSLocationUsageDescription = "A program in Ghostty wants to use your location information.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
|
INFOPLIST_KEY_NSMicrophoneUsageDescription = "A program in Ghostty wants to use your microphone.";
|
||||||
|
INFOPLIST_KEY_NSMotionUsageDescription = "A program in Ghostty wants to access motion data.";
|
||||||
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "A program in Ghostty wants to use your photo library.";
|
||||||
|
INFOPLIST_KEY_NSRemindersUsageDescription = "A program in Ghostty wants to access your reminders.";
|
||||||
|
INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "A program in Ghostty wants to use speech recognition.";
|
||||||
|
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "A program in Ghostty requires elevated privileges.";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
@ -867,7 +901,7 @@
|
|||||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
MARKETING_VERSION = 0.1;
|
MARKETING_VERSION = 0.1;
|
||||||
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
|
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty.debug;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
|
||||||
@ -898,8 +932,21 @@
|
|||||||
INFOPLIST_FILE = "Ghostty-Info.plist";
|
INFOPLIST_FILE = "Ghostty-Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||||
|
INFOPLIST_KEY_NSAppleEventsUsageDescription = "A program in Ghostty wants to use AppleScript.";
|
||||||
|
INFOPLIST_KEY_NSCalendarsUsageDescription = "A program in Ghostty wants to use your calendar.";
|
||||||
|
INFOPLIST_KEY_NSCameraUsageDescription = "A program in Ghostty wants to use the camera.";
|
||||||
|
INFOPLIST_KEY_NSContactsUsageDescription = "A program in Ghostty wants to use your contacts.";
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "A program in Ghostty wants to access the local network.";
|
||||||
|
INFOPLIST_KEY_NSLocationTemporaryUsageDescriptionDictionary = "A program in Ghostty wants to use your location temporarily.";
|
||||||
|
INFOPLIST_KEY_NSLocationUsageDescription = "A program in Ghostty wants to use your location information.";
|
||||||
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
INFOPLIST_KEY_NSMainNibFile = MainMenu;
|
||||||
|
INFOPLIST_KEY_NSMicrophoneUsageDescription = "A program in Ghostty wants to use your microphone.";
|
||||||
|
INFOPLIST_KEY_NSMotionUsageDescription = "A program in Ghostty wants to access motion data.";
|
||||||
|
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "A program in Ghostty wants to use your photo library.";
|
||||||
|
INFOPLIST_KEY_NSRemindersUsageDescription = "A program in Ghostty wants to access your reminders.";
|
||||||
|
INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "A program in Ghostty wants to use speech recognition.";
|
||||||
|
INFOPLIST_KEY_NSSystemAdministrationUsageDescription = "A program in Ghostty requires elevated privileges.";
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
|
@ -4,12 +4,13 @@ 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.
|
/// Fullscreen state management.
|
||||||
let fullscreenHandler = FullScreenHandler()
|
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
|
||||||
@ -199,6 +200,63 @@ 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() {
|
||||||
@ -531,17 +589,16 @@ class TerminalController: BaseTerminalController
|
|||||||
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 }
|
||||||
|
|
||||||
// We need a window to fullscreen
|
// Get the fullscreen mode we want to toggle
|
||||||
guard let window = self.window else { return }
|
let fullscreenMode: FullscreenMode
|
||||||
|
if let any = notification.userInfo?[Ghostty.Notification.FullscreenModeKey],
|
||||||
|
let mode = any as? FullscreenMode {
|
||||||
|
fullscreenMode = mode
|
||||||
|
} else {
|
||||||
|
Ghostty.logger.warning("no fullscreen mode specified or invalid mode, doing nothing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether we use non-native fullscreen
|
toggleFullscreen(mode: fullscreenMode)
|
||||||
guard let fullscreenModeAny = notification.userInfo?[Ghostty.Notification.FullscreenModeKey] else { return }
|
|
||||||
guard let fullscreenMode = fullscreenModeAny as? ghostty_action_fullscreen_e else { return }
|
|
||||||
self.fullscreenHandler.toggleFullscreen(window: window, mode: fullscreenMode)
|
|
||||||
|
|
||||||
// For some reason focus always gets lost when we toggle fullscreen, so we set it back.
|
|
||||||
if let focusedSurface {
|
|
||||||
Ghostty.moveFocus(to: focusedSurface)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,17 +65,25 @@ class TerminalManager {
|
|||||||
let c = createWindow(withBaseConfig: base)
|
let c = createWindow(withBaseConfig: base)
|
||||||
let window = c.window!
|
let window = c.window!
|
||||||
|
|
||||||
// We want to go fullscreen if we're configured for new windows to go fullscreen
|
// If the previous focused window was native fullscreen, the new window also
|
||||||
var toggleFullScreen = ghostty.config.windowFullscreen
|
// becomes native fullscreen.
|
||||||
|
if let parent = focusedSurface?.window,
|
||||||
// If the previous focused window prior to creating this window is fullscreen,
|
parent.styleMask.contains(.fullScreen) {
|
||||||
// then this window also becomes fullscreen.
|
|
||||||
if let parent = focusedSurface?.window, parent.styleMask.contains(.fullScreen) {
|
|
||||||
toggleFullScreen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toggleFullScreen && !window.styleMask.contains(.fullScreen)) {
|
|
||||||
window.toggleFullScreen(nil)
|
window.toggleFullScreen(nil)
|
||||||
|
} else if ghostty.config.windowFullscreen {
|
||||||
|
switch (ghostty.config.windowFullscreenMode) {
|
||||||
|
case .native:
|
||||||
|
// Native has to be done immediately so that our stylemask contains
|
||||||
|
// fullscreen for the logic later in this method.
|
||||||
|
c.toggleFullscreen(mode: .native)
|
||||||
|
|
||||||
|
case .nonNative, .nonNativeVisibleMenu:
|
||||||
|
// If we're non-native then we have to do it on a later loop
|
||||||
|
// so that the content view is setup.
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
c.toggleFullscreen(mode: self.ghostty.config.windowFullscreenMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our app isn't active, we make it active. All new_window actions
|
// If our app isn't active, we make it active. All new_window actions
|
||||||
@ -114,7 +122,8 @@ class TerminalManager {
|
|||||||
// If our parent is in non-native fullscreen, then new tabs do not work.
|
// If our parent is in non-native fullscreen, then new tabs do not work.
|
||||||
// See: https://github.com/mitchellh/ghostty/issues/392
|
// See: https://github.com/mitchellh/ghostty/issues/392
|
||||||
if let controller = parent.windowController as? TerminalController,
|
if let controller = parent.windowController as? TerminalController,
|
||||||
controller.fullscreenHandler.isInNonNativeFullscreen {
|
let fullscreenStyle = controller.fullscreenStyle,
|
||||||
|
fullscreenStyle.isFullscreen && !fullscreenStyle.supportsTabs {
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = "Cannot Create New Tab"
|
alert.messageText = "Cannot Create New Tab"
|
||||||
alert.informativeText = "New tabs are unsupported while in non-native fullscreen. Exit fullscreen and try again."
|
alert.informativeText = "New tabs are unsupported while in non-native fullscreen. Exit fullscreen and try again."
|
||||||
|
20
macos/Sources/Ghostty/FullscreenMode+Extension.swift
Normal file
20
macos/Sources/Ghostty/FullscreenMode+Extension.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import GhosttyKit
|
||||||
|
|
||||||
|
extension FullscreenMode {
|
||||||
|
/// Initialize from a Ghostty fullscreen action.
|
||||||
|
static func from(ghostty: ghostty_action_fullscreen_e) -> Self? {
|
||||||
|
return switch ghostty {
|
||||||
|
case GHOSTTY_FULLSCREEN_NATIVE:
|
||||||
|
.native
|
||||||
|
|
||||||
|
case GHOSTTY_FULLSCREEN_NON_NATIVE:
|
||||||
|
.nonNative
|
||||||
|
|
||||||
|
case GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
|
||||||
|
.nonNativeVisibleMenu
|
||||||
|
|
||||||
|
default:
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -598,7 +598,7 @@ extension Ghostty {
|
|||||||
private static func toggleFullscreen(
|
private static func toggleFullscreen(
|
||||||
_ app: ghostty_app_t,
|
_ app: ghostty_app_t,
|
||||||
target: ghostty_target_s,
|
target: ghostty_target_s,
|
||||||
mode: ghostty_action_fullscreen_e) {
|
mode raw: ghostty_action_fullscreen_e) {
|
||||||
switch (target.tag) {
|
switch (target.tag) {
|
||||||
case GHOSTTY_TARGET_APP:
|
case GHOSTTY_TARGET_APP:
|
||||||
Ghostty.logger.warning("toggle fullscreen does nothing with an app target")
|
Ghostty.logger.warning("toggle fullscreen does nothing with an app target")
|
||||||
@ -607,6 +607,10 @@ extension Ghostty {
|
|||||||
case GHOSTTY_TARGET_SURFACE:
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
guard let surface = target.target.surface else { return }
|
guard let surface = target.target.surface else { return }
|
||||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
guard let mode = FullscreenMode.from(ghostty: raw) else {
|
||||||
|
Ghostty.logger.warning("unknow fullscreen mode raw=\(raw.rawValue)")
|
||||||
|
return
|
||||||
|
}
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
name: Notification.ghosttyToggleFullscreen,
|
name: Notification.ghosttyToggleFullscreen,
|
||||||
object: surfaceView,
|
object: surfaceView,
|
||||||
|
@ -219,6 +219,28 @@ extension Ghostty {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if canImport(AppKit)
|
||||||
|
var windowFullscreenMode: FullscreenMode {
|
||||||
|
let defaultValue: FullscreenMode = .native
|
||||||
|
guard let config = self.config else { return defaultValue }
|
||||||
|
var v: UnsafePointer<Int8>? = nil
|
||||||
|
let key = "macos-non-native-fullscreen"
|
||||||
|
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||||
|
guard let ptr = v else { return defaultValue }
|
||||||
|
let str = String(cString: ptr)
|
||||||
|
return switch str {
|
||||||
|
case "false":
|
||||||
|
.native
|
||||||
|
case "true":
|
||||||
|
.nonNative
|
||||||
|
case "visible-menu":
|
||||||
|
.nonNativeVisibleMenu
|
||||||
|
default:
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
var windowTitleFontFamily: String? {
|
var windowTitleFontFamily: String? {
|
||||||
guard let config = self.config else { return nil }
|
guard let config = self.config else { return nil }
|
||||||
var v: UnsafePointer<Int8>? = nil
|
var v: UnsafePointer<Int8>? = nil
|
||||||
|
@ -1,239 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
import GhosttyKit
|
|
||||||
|
|
||||||
class FullScreenHandler {
|
|
||||||
var previousTabGroup: NSWindowTabGroup?
|
|
||||||
var previousTabGroupIndex: Int?
|
|
||||||
var previousContentFrame: NSRect?
|
|
||||||
var previousStyleMask: NSWindow.StyleMask? = nil
|
|
||||||
|
|
||||||
// We keep track of whether we entered non-native fullscreen in case
|
|
||||||
// a user goes to fullscreen, changes the config to disable non-native fullscreen
|
|
||||||
// and then wants to toggle it off
|
|
||||||
var isInNonNativeFullscreen: Bool = false
|
|
||||||
var isInFullscreen: Bool = false
|
|
||||||
|
|
||||||
func toggleFullscreen(window: NSWindow, mode: ghostty_action_fullscreen_e) {
|
|
||||||
let useNonNativeFullscreen = switch (mode) {
|
|
||||||
case GHOSTTY_FULLSCREEN_NATIVE:
|
|
||||||
false
|
|
||||||
|
|
||||||
case GHOSTTY_FULLSCREEN_NON_NATIVE, GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
|
|
||||||
true
|
|
||||||
|
|
||||||
default:
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
if isInFullscreen {
|
|
||||||
if useNonNativeFullscreen || isInNonNativeFullscreen {
|
|
||||||
leaveFullscreen(window: window)
|
|
||||||
isInNonNativeFullscreen = false
|
|
||||||
} else {
|
|
||||||
// Restore titlebar separator style. See below for explanation.
|
|
||||||
window.titlebarSeparatorStyle = .automatic
|
|
||||||
window.toggleFullScreen(nil)
|
|
||||||
}
|
|
||||||
isInFullscreen = false
|
|
||||||
} else {
|
|
||||||
if useNonNativeFullscreen {
|
|
||||||
let hideMenu = mode != GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU
|
|
||||||
enterFullscreen(window: window, hideMenu: hideMenu)
|
|
||||||
isInNonNativeFullscreen = true
|
|
||||||
} else {
|
|
||||||
// The titlebar separator shows up erroneously in fullscreen if the tab bar
|
|
||||||
// is made to appear and then disappear by opening and then closing a tab.
|
|
||||||
// We get rid of the separator while in fullscreen to prevent this.
|
|
||||||
window.titlebarSeparatorStyle = .none
|
|
||||||
window.toggleFullScreen(nil)
|
|
||||||
}
|
|
||||||
isInFullscreen = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func enterFullscreen(window: NSWindow, hideMenu: Bool) {
|
|
||||||
guard let screen = window.screen else { return }
|
|
||||||
guard let contentView = window.contentView else { return }
|
|
||||||
|
|
||||||
previousTabGroup = window.tabGroup
|
|
||||||
previousTabGroupIndex = window.tabGroup?.windows.firstIndex(of: window)
|
|
||||||
|
|
||||||
// Save previous contentViewFrame and screen
|
|
||||||
previousContentFrame = window.convertToScreen(contentView.frame)
|
|
||||||
|
|
||||||
// Change presentation style to hide menu bar and dock if needed
|
|
||||||
// It's important to do this in two calls, because setting them in a single call guarantees
|
|
||||||
// that the menu bar will also be hidden on any additional displays (why? nobody knows!)
|
|
||||||
// When these options are set separately, the menu bar hiding problem will only occur in
|
|
||||||
// specific scenarios. More investigation is needed to pin these scenarios down precisely,
|
|
||||||
// but it seems to have something to do with which app had focus last.
|
|
||||||
// Furthermore, it's much easier to figure out which screen the dock is on if the menubar
|
|
||||||
// has not yet been hidden, so the order matters here!
|
|
||||||
if (shouldHideDock(screen: screen)) {
|
|
||||||
self.hideDock()
|
|
||||||
|
|
||||||
// Ensure that we always hide the dock bar for this window, but not for non fullscreen ones
|
|
||||||
NotificationCenter.default.addObserver(
|
|
||||||
self,
|
|
||||||
selector: #selector(FullScreenHandler.hideDock),
|
|
||||||
name: NSWindow.didBecomeMainNotification,
|
|
||||||
object: window)
|
|
||||||
NotificationCenter.default.addObserver(
|
|
||||||
self,
|
|
||||||
selector: #selector(FullScreenHandler.unHideDock),
|
|
||||||
name: NSWindow.didResignMainNotification,
|
|
||||||
object: window)
|
|
||||||
}
|
|
||||||
if (hideMenu) {
|
|
||||||
self.hideMenu()
|
|
||||||
|
|
||||||
// Ensure that we always hide the menu bar for this window, but not for non fullscreen ones
|
|
||||||
// This is not the best way to do this, not least because it causes the menu to stay visible
|
|
||||||
// for a brief moment before being hidden in some cases (e.g. when switching spaces).
|
|
||||||
// If we end up adding a NSWindowDelegate to PrimaryWindow, then we may be better off
|
|
||||||
// handling this there.
|
|
||||||
NotificationCenter.default.addObserver(
|
|
||||||
self,
|
|
||||||
selector: #selector(FullScreenHandler.hideMenu),
|
|
||||||
name: NSWindow.didBecomeMainNotification,
|
|
||||||
object: window)
|
|
||||||
NotificationCenter.default.addObserver(
|
|
||||||
self,
|
|
||||||
selector: #selector(FullScreenHandler.onDidResignMain),
|
|
||||||
name: NSWindow.didResignMainNotification,
|
|
||||||
object: window)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is important: it gives us the full screen, including the
|
|
||||||
// notch area on MacBooks.
|
|
||||||
self.previousStyleMask = window.styleMask
|
|
||||||
window.styleMask.remove(.titled)
|
|
||||||
|
|
||||||
// Set frame to screen size, accounting for the menu bar if needed
|
|
||||||
let frame = calculateFullscreenFrame(screen: screen, subtractMenu: !hideMenu)
|
|
||||||
window.setFrame(frame, display: true)
|
|
||||||
|
|
||||||
// Focus window
|
|
||||||
window.makeKeyAndOrderFront(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func hideMenu() {
|
|
||||||
NSApp.presentationOptions.insert(.autoHideMenuBar)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func onDidResignMain(_ notification: Notification) {
|
|
||||||
guard let resigningWindow = notification.object as? NSWindow else { return }
|
|
||||||
guard let mainWindow = NSApplication.shared.mainWindow else { return }
|
|
||||||
|
|
||||||
// We're only unhiding the menu bar, if the focus shifted within our application.
|
|
||||||
// In that case, `mainWindow` is the window of our application the focus shifted
|
|
||||||
// to.
|
|
||||||
if !resigningWindow.isEqual(mainWindow) {
|
|
||||||
NSApp.presentationOptions.remove(.autoHideMenuBar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func hideDock() {
|
|
||||||
NSApp.presentationOptions.insert(.autoHideDock)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func unHideDock() {
|
|
||||||
NSApp.presentationOptions.remove(.autoHideDock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateFullscreenFrame(screen: NSScreen, subtractMenu: Bool)->NSRect {
|
|
||||||
if (subtractMenu) {
|
|
||||||
if let menuHeight = NSApp.mainMenu?.menuBarHeight {
|
|
||||||
var padding: CGFloat = 0
|
|
||||||
|
|
||||||
// Detect the notch. If there is a safe area on top it includes the
|
|
||||||
// menu height as a safe area so we also subtract that from it.
|
|
||||||
if (screen.safeAreaInsets.top > 0) {
|
|
||||||
padding = screen.safeAreaInsets.top - menuHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NSMakeRect(
|
|
||||||
screen.frame.minX,
|
|
||||||
screen.frame.minY,
|
|
||||||
screen.frame.width,
|
|
||||||
screen.frame.height - (menuHeight + padding)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return screen.frame
|
|
||||||
}
|
|
||||||
|
|
||||||
func leaveFullscreen(window: NSWindow) {
|
|
||||||
guard let previousFrame = previousContentFrame else { return }
|
|
||||||
|
|
||||||
// Restore the style mask
|
|
||||||
window.styleMask = self.previousStyleMask!
|
|
||||||
|
|
||||||
// Restore previous presentation options
|
|
||||||
NSApp.presentationOptions = []
|
|
||||||
|
|
||||||
// Stop handling any window focus notifications
|
|
||||||
// that we use to manage menu bar visibility
|
|
||||||
NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeMainNotification, object: window)
|
|
||||||
NotificationCenter.default.removeObserver(self, name: NSWindow.didResignMainNotification, object: window)
|
|
||||||
|
|
||||||
// Restore frame
|
|
||||||
window.setFrame(window.frameRect(forContentRect: previousFrame), display: true)
|
|
||||||
|
|
||||||
// Have titlebar tabs set itself up again, since removing the titlebar when fullscreen breaks its constraints.
|
|
||||||
if let window = window as? TerminalWindow, window.titlebarTabs {
|
|
||||||
window.titlebarTabs = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the window was previously in a tab group that isn't empty now, we re-add it
|
|
||||||
if let group = previousTabGroup, let tabIndex = previousTabGroupIndex, !group.windows.isEmpty {
|
|
||||||
var tabWindow: NSWindow?
|
|
||||||
var order: NSWindow.OrderingMode = .below
|
|
||||||
|
|
||||||
// Index of the window before `window`
|
|
||||||
let tabIndexBefore = tabIndex-1
|
|
||||||
if tabIndexBefore < 0 {
|
|
||||||
// If we were the first tab, we add the window *before* (.below) the first one.
|
|
||||||
tabWindow = group.windows.first
|
|
||||||
} else if tabIndexBefore < group.windows.count {
|
|
||||||
// If we weren't the first tab in the group, we add our window after
|
|
||||||
// the tab that was before it.
|
|
||||||
tabWindow = group.windows[tabIndexBefore]
|
|
||||||
order = .above
|
|
||||||
} else {
|
|
||||||
// If index is after group, add it after last window
|
|
||||||
tabWindow = group.windows.last
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the window
|
|
||||||
tabWindow?.addTabbedWindow(window, ordered: order)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus window
|
|
||||||
window.makeKeyAndOrderFront(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only want to hide the dock if it's not already going to be hidden automatically, and if
|
|
||||||
// it's on the same display as the ghostty window that we want to make fullscreen.
|
|
||||||
func shouldHideDock(screen: NSScreen) -> Bool {
|
|
||||||
if let dockAutohide = UserDefaults.standard.persistentDomain(forName: "com.apple.dock")?["autohide"] as? Bool {
|
|
||||||
if (dockAutohide) { return false }
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is no public API to directly ask about dock visibility, so we have to figure it out
|
|
||||||
// by comparing the sizes of visibleFrame (the currently usable area of the screen) and
|
|
||||||
// frame (the full screen size). We also need to account for the menubar, any inset caused
|
|
||||||
// by the notch on macbooks, and a little extra padding to compensate for the boundary area
|
|
||||||
// which triggers showing the dock.
|
|
||||||
let frame = screen.frame
|
|
||||||
let visibleFrame = screen.visibleFrame
|
|
||||||
let menuHeight = NSApp.mainMenu?.menuBarHeight ?? 0
|
|
||||||
var notchInset = 0.0
|
|
||||||
if #available(macOS 12, *) {
|
|
||||||
notchInset = screen.safeAreaInsets.top
|
|
||||||
}
|
|
||||||
let boundaryAreaPadding = 5.0
|
|
||||||
|
|
||||||
return visibleFrame.height < (frame.height - max(menuHeight, notchInset) - boundaryAreaPadding) || visibleFrame.width < frame.width
|
|
||||||
}
|
|
||||||
}
|
|
362
macos/Sources/Helpers/Fullscreen.swift
Normal file
362
macos/Sources/Helpers/Fullscreen.swift
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
import Cocoa
|
||||||
|
import GhosttyKit
|
||||||
|
|
||||||
|
/// The fullscreen modes we support define how the fullscreen behaves.
|
||||||
|
enum FullscreenMode {
|
||||||
|
case native
|
||||||
|
case nonNative
|
||||||
|
case nonNativeVisibleMenu
|
||||||
|
|
||||||
|
/// Initializes the fullscreen style implementation for the mode. This will not toggle any
|
||||||
|
/// fullscreen properties. This may fail if the window isn't configured properly for a given
|
||||||
|
/// mode.
|
||||||
|
func style(for window: NSWindow) -> FullscreenStyle? {
|
||||||
|
switch self {
|
||||||
|
case .native:
|
||||||
|
return NativeFullscreen(window)
|
||||||
|
|
||||||
|
case .nonNative:
|
||||||
|
return NonNativeFullscreen(window)
|
||||||
|
|
||||||
|
case .nonNativeVisibleMenu:
|
||||||
|
return NonNativeFullscreenVisibleMenu(window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Protocol that must be implemented by all fullscreen styles.
|
||||||
|
protocol FullscreenStyle {
|
||||||
|
var delegate: FullscreenDelegate? { get set }
|
||||||
|
var isFullscreen: Bool { get }
|
||||||
|
var supportsTabs: Bool { get }
|
||||||
|
init?(_ window: NSWindow)
|
||||||
|
func enter()
|
||||||
|
func exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delegate that can be implemented for fullscreen implementations.
|
||||||
|
protocol FullscreenDelegate: AnyObject {
|
||||||
|
/// Called whenever the fullscreen state changed. You can call isFullscreen to see
|
||||||
|
/// the current state.
|
||||||
|
func fullscreenDidChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FullscreenDelegate {
|
||||||
|
func fullscreenDidChange() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// macOS native fullscreen. This is the typical behavior you get by pressing the green fullscreen
|
||||||
|
/// button on regular titlebars.
|
||||||
|
class NativeFullscreen: FullscreenStyle {
|
||||||
|
private let window: NSWindow
|
||||||
|
|
||||||
|
weak var delegate: FullscreenDelegate?
|
||||||
|
var isFullscreen: Bool { window.styleMask.contains(.fullScreen) }
|
||||||
|
var supportsTabs: Bool { true }
|
||||||
|
|
||||||
|
required init?(_ window: NSWindow) {
|
||||||
|
// TODO: There are many requirements for native fullscreen we should
|
||||||
|
// check here such as the stylemask.
|
||||||
|
|
||||||
|
self.window = window
|
||||||
|
}
|
||||||
|
|
||||||
|
func enter() {
|
||||||
|
guard !isFullscreen else { return }
|
||||||
|
|
||||||
|
// The titlebar separator shows up erroneously in fullscreen if the tab bar
|
||||||
|
// is made to appear and then disappear by opening and then closing a tab.
|
||||||
|
// We get rid of the separator while in fullscreen to prevent this.
|
||||||
|
window.titlebarSeparatorStyle = .none
|
||||||
|
|
||||||
|
// Enter fullscreen
|
||||||
|
window.toggleFullScreen(self)
|
||||||
|
|
||||||
|
// Notify the delegate
|
||||||
|
delegate?.fullscreenDidChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit() {
|
||||||
|
guard isFullscreen else { return }
|
||||||
|
|
||||||
|
// Restore titlebar separator style. See enter for explanation.
|
||||||
|
window.titlebarSeparatorStyle = .automatic
|
||||||
|
|
||||||
|
window.toggleFullScreen(nil)
|
||||||
|
|
||||||
|
// Notify the delegate
|
||||||
|
delegate?.fullscreenDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonNativeFullscreen: FullscreenStyle {
|
||||||
|
weak var delegate: FullscreenDelegate?
|
||||||
|
|
||||||
|
// Non-native fullscreen never supports tabs because tabs require
|
||||||
|
// the "titled" style and we don't have it for non-native fullscreen.
|
||||||
|
var supportsTabs: Bool { false }
|
||||||
|
|
||||||
|
// isFullscreen is dependent on if we have saved state currently. We
|
||||||
|
// could one day try to do fancier stuff like inspecting the window
|
||||||
|
// state but there isn't currently a need for it.
|
||||||
|
var isFullscreen: Bool { savedState != nil }
|
||||||
|
|
||||||
|
// The default properties. Subclasses can override this to change
|
||||||
|
// behavior. This shouldn't be written to (only computed) because
|
||||||
|
// it must be immutable.
|
||||||
|
var properties: Properties { Properties() }
|
||||||
|
|
||||||
|
struct Properties {
|
||||||
|
var hideMenu: Bool = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private let window: NSWindow
|
||||||
|
private var savedState: SavedState?
|
||||||
|
|
||||||
|
required init?(_ window: NSWindow) {
|
||||||
|
self.window = window
|
||||||
|
}
|
||||||
|
|
||||||
|
func enter() {
|
||||||
|
// If we are in fullscreen we don't do it again.
|
||||||
|
guard !isFullscreen else { return }
|
||||||
|
|
||||||
|
// If we are in native fullscreen, exit native fullscreen. This is counter
|
||||||
|
// intuitive but if we entered native fullscreen (through the green max button
|
||||||
|
// or an external event) and we press the fullscreen keybind, we probably
|
||||||
|
// want to EXIT fullscreen.
|
||||||
|
if window.styleMask.contains(.fullScreen) {
|
||||||
|
window.toggleFullScreen(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the screen that we're going to go fullscreen on. We use the
|
||||||
|
// screen the window is currently on.
|
||||||
|
guard let screen = window.screen else { return }
|
||||||
|
|
||||||
|
// Save the state that we need to exit again
|
||||||
|
guard let savedState = SavedState(window) else { return }
|
||||||
|
self.savedState = savedState
|
||||||
|
|
||||||
|
// We hide the dock if the window is on a screen with the dock.
|
||||||
|
if (savedState.dock) {
|
||||||
|
hideDock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the menu if requested
|
||||||
|
if (properties.hideMenu) {
|
||||||
|
hideMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When this window becomes or resigns main we need to run some logic.
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(windowDidBecomeMain),
|
||||||
|
name: NSWindow.didBecomeMainNotification,
|
||||||
|
object: window)
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(windowDidResignMain),
|
||||||
|
name: NSWindow.didResignMainNotification,
|
||||||
|
object: window)
|
||||||
|
|
||||||
|
// When we change screens we need to redo everything.
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(windowDidChangeScreen),
|
||||||
|
name: NSWindow.didChangeScreenNotification,
|
||||||
|
object: window)
|
||||||
|
|
||||||
|
// Being untitled let's our content take up the full frame.
|
||||||
|
window.styleMask.remove(.titled)
|
||||||
|
|
||||||
|
// Focus window
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
|
||||||
|
// Set frame to screen size, accounting for any elements such as the menu bar.
|
||||||
|
// We do this async so that all the style edits above (title removal, dock
|
||||||
|
// hide, menu hide, etc.) take effect. This fixes:
|
||||||
|
// https://github.com/ghostty-org/ghostty/issues/1996
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.window.setFrame(self.fullscreenFrame(screen), display: true)
|
||||||
|
self.delegate?.fullscreenDidChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit() {
|
||||||
|
guard isFullscreen else { return }
|
||||||
|
guard let savedState else { return }
|
||||||
|
|
||||||
|
// Remove all our notifications
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
|
||||||
|
// Unhide our elements
|
||||||
|
if savedState.dock {
|
||||||
|
unhideDock()
|
||||||
|
}
|
||||||
|
unhideMenu()
|
||||||
|
|
||||||
|
// Restore our saved state
|
||||||
|
window.styleMask = savedState.styleMask
|
||||||
|
window.setFrame(window.frameRect(forContentRect: savedState.contentFrame), display: true)
|
||||||
|
|
||||||
|
// This is a hack that I want to remove from this but for now, we need to
|
||||||
|
// fix up the titlebar tabs here before we do everything below.
|
||||||
|
if let window = window as? TerminalWindow,
|
||||||
|
window.titlebarTabs {
|
||||||
|
window.titlebarTabs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the window was previously in a tab group that isn't empty now,
|
||||||
|
// we re-add it. We have to do this because our process of doing non-native
|
||||||
|
// fullscreen removes the window from the tab group.
|
||||||
|
if let tabGroup = savedState.tabGroup,
|
||||||
|
let tabIndex = savedState.tabGroupIndex,
|
||||||
|
!tabGroup.windows.isEmpty {
|
||||||
|
if tabIndex == 0 {
|
||||||
|
// We were previously the first tab. Add it before ("below")
|
||||||
|
// the first window in the tab group currently.
|
||||||
|
tabGroup.windows.first!.addTabbedWindow(window, ordered: .below)
|
||||||
|
} else if tabIndex <= tabGroup.windows.count {
|
||||||
|
// We were somewhere in the middle
|
||||||
|
tabGroup.windows[tabIndex - 1].addTabbedWindow(window, ordered: .above)
|
||||||
|
} else {
|
||||||
|
// We were at the end
|
||||||
|
tabGroup.windows.last!.addTabbedWindow(window, ordered: .below)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unset our saved state, we're restored!
|
||||||
|
self.savedState = nil
|
||||||
|
|
||||||
|
// Focus window
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
|
||||||
|
// Notify the delegate
|
||||||
|
self.delegate?.fullscreenDidChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fullscreenFrame(_ screen: NSScreen) -> NSRect {
|
||||||
|
// It would make more sense to use "visibleFrame" but visibleFrame
|
||||||
|
// will omit space by our dock and isn't updated until an event
|
||||||
|
// loop tick which we don't have time for. So we use frame and
|
||||||
|
// calculate this ourselves.
|
||||||
|
var frame = screen.frame
|
||||||
|
|
||||||
|
if (!properties.hideMenu) {
|
||||||
|
// We need to subtract the menu height since we're still showing it.
|
||||||
|
frame.size.height -= NSApp.mainMenu?.menuBarHeight ?? 0
|
||||||
|
|
||||||
|
// NOTE on macOS bugs: macOS used to have a bug where menuBarHeight
|
||||||
|
// didn't account for the notch. I reported this as a radar and it
|
||||||
|
// was fixed at some point. I don't know when that was so I can't
|
||||||
|
// put an #available check, but it was in a bug fix release so I think
|
||||||
|
// if a bug is reported to Ghostty we can just advise the user to
|
||||||
|
// update.
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Window Events
|
||||||
|
|
||||||
|
@objc func windowDidChangeScreen(_ notification: Notification) {
|
||||||
|
guard isFullscreen else { return }
|
||||||
|
guard let savedState else { return }
|
||||||
|
|
||||||
|
// This should always be true due to how we register but just be sure
|
||||||
|
guard let object = notification.object as? NSWindow,
|
||||||
|
object == window else { return }
|
||||||
|
|
||||||
|
// Our screens must have changed
|
||||||
|
guard savedState.screen != window.screen else { return }
|
||||||
|
|
||||||
|
// When we change screens, we simply exit fullscreen. Changing
|
||||||
|
// screens shouldn't naturally be possible, it can only happen
|
||||||
|
// through external window managers. There's a lot of accounting
|
||||||
|
// to do to get the screen change right so instead of breaking
|
||||||
|
// we just exit out. The user can re-enter fullscreen thereafter.
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func windowDidBecomeMain(_ notification: Notification) {
|
||||||
|
guard let savedState else { return }
|
||||||
|
|
||||||
|
// This should always be true due to how we register but just be sure
|
||||||
|
guard let object = notification.object as? NSWindow,
|
||||||
|
object == window else { return }
|
||||||
|
|
||||||
|
// This is crazy but at least on macOS 15.0, you must hide the dock
|
||||||
|
// FIRST then hide the menu. If you do the opposite, it does not
|
||||||
|
// work.
|
||||||
|
|
||||||
|
if savedState.dock {
|
||||||
|
hideDock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.hideMenu) {
|
||||||
|
hideMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func windowDidResignMain(_ notification: Notification) {
|
||||||
|
guard let savedState else { return }
|
||||||
|
|
||||||
|
// This should always be true due to how we register but just be sure
|
||||||
|
guard let object = notification.object as? NSWindow,
|
||||||
|
object == window else { return }
|
||||||
|
|
||||||
|
if (properties.hideMenu) {
|
||||||
|
unhideMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
if savedState.dock {
|
||||||
|
unhideDock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Dock
|
||||||
|
|
||||||
|
private func hideDock() {
|
||||||
|
NSApp.presentationOptions.insert(.autoHideDock)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func unhideDock() {
|
||||||
|
NSApp.presentationOptions.remove(.autoHideDock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Menu
|
||||||
|
|
||||||
|
func hideMenu() {
|
||||||
|
NSApp.presentationOptions.insert(.autoHideMenuBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unhideMenu() {
|
||||||
|
NSApp.presentationOptions.remove(.autoHideMenuBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state that must be saved for non-native fullscreen to exit fullscreen.
|
||||||
|
class SavedState {
|
||||||
|
weak var screen: NSScreen?
|
||||||
|
let tabGroup: NSWindowTabGroup?
|
||||||
|
let tabGroupIndex: Int?
|
||||||
|
let contentFrame: NSRect
|
||||||
|
let styleMask: NSWindow.StyleMask
|
||||||
|
let dock: Bool
|
||||||
|
|
||||||
|
init?(_ window: NSWindow) {
|
||||||
|
guard let contentView = window.contentView else { return nil }
|
||||||
|
|
||||||
|
self.screen = window.screen
|
||||||
|
self.tabGroup = window.tabGroup
|
||||||
|
self.tabGroupIndex = window.tabGroup?.windows.firstIndex(of: window)
|
||||||
|
self.contentFrame = window.convertToScreen(contentView.frame)
|
||||||
|
self.styleMask = window.styleMask
|
||||||
|
self.dock = window.screen?.hasDock ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
|
||||||
|
override var properties: Properties { Properties(hideMenu: false) }
|
||||||
|
}
|
36
macos/Sources/Helpers/NSScreen+Extension.swift
Normal file
36
macos/Sources/Helpers/NSScreen+Extension.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import Cocoa
|
||||||
|
|
||||||
|
extension NSScreen {
|
||||||
|
// Returns true if the given screen has a visible dock. This isn't
|
||||||
|
// point-in-time visible, this is true if the dock is always visible
|
||||||
|
// AND present on this screen.
|
||||||
|
var hasDock: Bool {
|
||||||
|
// If the dock autohides then we don't have a dock ever.
|
||||||
|
if let dockAutohide = UserDefaults.standard.persistentDomain(forName: "com.apple.dock")?["autohide"] as? Bool {
|
||||||
|
if (dockAutohide) { return false }
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no public API to directly ask about dock visibility, so we have to figure it out
|
||||||
|
// by comparing the sizes of visibleFrame (the currently usable area of the screen) and
|
||||||
|
// frame (the full screen size). We also need to account for the menubar, any inset caused
|
||||||
|
// by the notch on macbooks, and a little extra padding to compensate for the boundary area
|
||||||
|
// which triggers showing the dock.
|
||||||
|
|
||||||
|
// If our visible width is less than the frame we assume its the dock.
|
||||||
|
if (visibleFrame.width < frame.width) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to see if our visible frame height is less than the full
|
||||||
|
// screen height minus the menu and notch and such.
|
||||||
|
let menuHeight = NSApp.mainMenu?.menuBarHeight ?? 0
|
||||||
|
let notchInset: CGFloat = if #available(macOS 12, *) {
|
||||||
|
safeAreaInsets.top
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
let boundaryAreaPadding = 5.0
|
||||||
|
|
||||||
|
return visibleFrame.height < (frame.height - max(menuHeight, notchInset) - boundaryAreaPadding)
|
||||||
|
}
|
||||||
|
}
|
@ -1375,8 +1375,15 @@ keybind: Keybinds = .{},
|
|||||||
/// using a new space. It's faster than the native fullscreen mode since it
|
/// using a new space. It's faster than the native fullscreen mode since it
|
||||||
/// doesn't use animations.
|
/// doesn't use animations.
|
||||||
///
|
///
|
||||||
/// Warning: tabs do not work with a non-native fullscreen window. This
|
/// Important: tabs DO NOT WORK in this mode. Non-native fullscreen removes
|
||||||
/// can be fixed but is looking for contributors to help. See issue #392.
|
/// the titlebar and macOS native tabs require the titlebar. If you use tabs,
|
||||||
|
/// you should not use this mode.
|
||||||
|
///
|
||||||
|
/// If you fullscreen a window with tabs, the currently focused tab will
|
||||||
|
/// become fullscreen while the others will remain in a separate window in
|
||||||
|
/// the background. You can switch to that window using normal window-switching
|
||||||
|
/// keybindings such as command+tilde. When you exit fullscreen, the window
|
||||||
|
/// will return to the tabbed state it was in before.
|
||||||
///
|
///
|
||||||
/// Allowable values are:
|
/// Allowable values are:
|
||||||
///
|
///
|
||||||
@ -1384,6 +1391,9 @@ keybind: Keybinds = .{},
|
|||||||
/// * `true` - Use non-native macOS fullscreen, hide the menu bar
|
/// * `true` - Use non-native macOS fullscreen, hide the menu bar
|
||||||
/// * `false` - Use native macOS fullscreen
|
/// * `false` - Use native macOS fullscreen
|
||||||
///
|
///
|
||||||
|
/// Changing this option at runtime works, but will only apply to the next
|
||||||
|
/// time the window is made fullscreen. If a window is already fullscreen,
|
||||||
|
/// it will retain the previous setting until fullscreen is exited.
|
||||||
@"macos-non-native-fullscreen": NonNativeFullscreen = .false,
|
@"macos-non-native-fullscreen": NonNativeFullscreen = .false,
|
||||||
|
|
||||||
/// The style of the macOS titlebar. Available values are: "native",
|
/// The style of the macOS titlebar. Available values are: "native",
|
||||||
|
Reference in New Issue
Block a user