Merge pull request #380 from mitchellh/goonz/non-native-fullscreen-menu-options

macos: add hidden-menu/visible-menu non-native-fullscreen opts
This commit is contained in:
Mitchell Hashimoto
2023-09-02 07:51:44 -07:00
committed by GitHub
8 changed files with 113 additions and 28 deletions

View File

@ -70,6 +70,12 @@ typedef enum {
GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN,
} ghostty_input_mouse_momentum_e;
typedef enum {
GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE,
GHOSTTY_NON_NATIVE_FULLSCREEN_TRUE,
GHOSTTY_NON_NATIVE_FULLSCREEN_VISIBLE_MENU,
} ghostty_non_native_fullscreen_e;
// This is a packed struct (see src/input/mouse.zig) but the C standard
// afaik doesn't let us reliably define packed structs so we build it up
// from scratch.
@ -257,7 +263,7 @@ typedef void (*ghostty_runtime_new_window_cb)(void *, ghostty_surface_config_s);
typedef void (*ghostty_runtime_close_surface_cb)(void *, bool);
typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direction_e);
typedef void (*ghostty_runtime_goto_tab_cb)(void *, int32_t);
typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, bool);
typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, ghostty_non_native_fullscreen_e);
typedef struct {
void *userdata;

View File

@ -146,7 +146,7 @@ struct PrimaryView: View {
// Check whether we use non-native fullscreen
guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return }
guard let useNonNativeFullscreen = useNonNativeFullscreenAny as? Bool else { return }
guard let useNonNativeFullscreen = useNonNativeFullscreenAny as? ghostty_non_native_fullscreen_e else { return }
self.fullScreen.toggleFullscreen(window: window, nonNativeFullscreen: useNonNativeFullscreen)
// After toggling fullscreen we need to focus the terminal again.

View File

@ -74,7 +74,7 @@ extension Ghostty {
close_surface_cb: { userdata, processAlive in AppState.closeSurface(userdata, processAlive: processAlive) },
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) },
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, useNonNativeFullscreen: nonNativeFullscreen) }
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) }
)
// Create the ghostty app.
@ -276,14 +276,13 @@ extension Ghostty {
}
}
static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, useNonNativeFullscreen: Bool) {
// togo: use non-native fullscreen
static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post(
name: Notification.ghosttyToggleFullscreen,
object: surface,
userInfo: [
Notification.NonNativeFullscreenKey: useNonNativeFullscreen,
Notification.NonNativeFullscreenKey: nonNativeFullscreen,
]
)
}

View File

@ -1,4 +1,5 @@
import SwiftUI
import GhosttyKit
class FullScreenHandler {
var previousTabGroup: NSWindowTabGroup?
@ -11,9 +12,10 @@ class FullScreenHandler {
// and then wants to toggle it off
var isInNonNativeFullscreen: Bool = false
func toggleFullscreen(window: NSWindow, nonNativeFullscreen: Bool) {
func toggleFullscreen(window: NSWindow, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
let useNonNativeFullscreen = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE
if isInFullscreen {
if nonNativeFullscreen || isInNonNativeFullscreen {
if useNonNativeFullscreen || isInNonNativeFullscreen {
leaveFullscreen(window: window)
isInNonNativeFullscreen = false
} else {
@ -21,8 +23,9 @@ class FullScreenHandler {
}
isInFullscreen = false
} else {
if nonNativeFullscreen {
enterFullscreen(window: window)
if useNonNativeFullscreen {
let hideMenu = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_VISIBLE_MENU
enterFullscreen(window: window, hideMenu: hideMenu)
isInNonNativeFullscreen = true
} else {
window.toggleFullScreen(nil)
@ -31,7 +34,7 @@ class FullScreenHandler {
}
}
func enterFullscreen(window: NSWindow) {
func enterFullscreen(window: NSWindow, hideMenu: Bool) {
guard let screen = window.screen else { return }
guard let contentView = window.contentView else { return }
@ -41,7 +44,7 @@ class FullScreenHandler {
// Save previous contentViewFrame and screen
previousContentFrame = window.convertToScreen(contentView.frame)
// Change presentation style to hide menu bar and dock
// 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
@ -50,21 +53,76 @@ class FullScreenHandler {
// 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)) {
NSApp.presentationOptions.insert(.autoHideDock)
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.unHideMenu),
name: NSWindow.didResignMainNotification,
object: window)
}
NSApp.presentationOptions.insert(.autoHideMenuBar)
// This is important: it gives us the full screen, including the
// notch area on MacBooks.
window.styleMask.remove(.titled)
// Set frame to screen size
window.setFrame(screen.frame, display: true)
// Set frame to screen size, accounting for the menu bar if needed
let frame = calculateFullscreenFrame(screenFrame: screen.frame, subtractMenu: !hideMenu)
window.setFrame(frame, display: true)
// Focus window
window.makeKeyAndOrderFront(nil)
}
@objc func hideMenu() {
NSApp.presentationOptions.insert(.autoHideMenuBar)
}
@objc func unHideMenu() {
NSApp.presentationOptions.remove(.autoHideMenuBar)
}
@objc func hideDock() {
NSApp.presentationOptions.insert(.autoHideDock)
}
@objc func unHideDock() {
NSApp.presentationOptions.remove(.autoHideDock)
}
func calculateFullscreenFrame(screenFrame: NSRect, subtractMenu: Bool)->NSRect {
if (subtractMenu) {
let menuHeight = NSApp.mainMenu?.menuBarHeight ?? 0
return NSMakeRect(screenFrame.minX, screenFrame.minY, screenFrame.width, screenFrame.height - menuHeight)
}
return screenFrame
}
func leaveFullscreen(window: NSWindow) {
guard let previousFrame = previousContentFrame else { return }
@ -74,6 +132,11 @@ class FullScreenHandler {
// 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)

View File

@ -137,7 +137,7 @@ const DerivedConfig = struct {
copy_on_select: configpkg.CopyOnSelect,
confirm_close_surface: bool,
mouse_interval: u64,
macos_non_native_fullscreen: bool,
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
macos_option_as_alt: configpkg.OptionAsAlt,
pub fn init(alloc_gpa: Allocator, config: *const configpkg.Config) !DerivedConfig {

View File

@ -13,7 +13,8 @@ const apprt = @import("../apprt.zig");
const input = @import("../input.zig");
const CoreApp = @import("../App.zig");
const CoreSurface = @import("../Surface.zig");
const Config = @import("../config.zig").Config;
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
const log = std.log.scoped(.embedded_window);
@ -75,7 +76,7 @@ pub const App = struct {
goto_tab: ?*const fn (SurfaceUD, usize) callconv(.C) void = null,
/// Toggle fullscreen for current window.
toggle_fullscreen: ?*const fn (SurfaceUD, bool) callconv(.C) void = null,
toggle_fullscreen: ?*const fn (SurfaceUD, configpkg.NonNativeFullscreen) callconv(.C) void = null,
};
core_app: *CoreApp,
@ -622,7 +623,7 @@ pub const Surface = struct {
func(self.opts.userdata, n);
}
pub fn toggleFullscreen(self: *Surface, nonNativeFullscreen: bool) void {
pub fn toggleFullscreen(self: *Surface, nonNativeFullscreen: configpkg.NonNativeFullscreen) void {
const func = self.app.opts.toggle_fullscreen orelse {
log.info("runtime embedder does not toggle_fullscreen", .{});
return;

View File

@ -10,7 +10,8 @@ const font = @import("../font/main.zig");
const input = @import("../input.zig");
const CoreApp = @import("../App.zig");
const CoreSurface = @import("../Surface.zig");
const Config = @import("../config.zig").Config;
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
pub const c = @cImport({
@cInclude("gtk/gtk.h");
@ -518,7 +519,7 @@ const Window = struct {
}
/// Toggle fullscreen for this window.
fn toggleFullscreen(self: *Window, _: bool) void {
fn toggleFullscreen(self: *Window, _: configpkg.NonNativeFullscreen) void {
const is_fullscreen = c.gtk_window_is_fullscreen(self.window);
if (is_fullscreen == 0) {
c.gtk_window_fullscreen(self.window);
@ -896,7 +897,7 @@ pub const Surface = struct {
c.gtk_widget_show(alert);
}
pub fn toggleFullscreen(self: *Surface, mac_non_native: bool) void {
pub fn toggleFullscreen(self: *Surface, mac_non_native: configpkg.NonNativeFullscreen) void {
self.window.toggleFullscreen(mac_non_native);
}

View File

@ -278,11 +278,17 @@ pub const Config = struct {
/// The default value is "detect".
@"shell-integration": ShellIntegration = .detect,
/// If true, fullscreen mode on macOS will not use the native fullscreen,
/// but make the window fullscreen without animations and using a new space.
/// That's faster than the native fullscreen mode since it doesn't use
/// animations.
@"macos-non-native-fullscreen": bool = false,
/// If anything other than false, fullscreen mode on macOS will not use the
/// native fullscreen, but make the window fullscreen without animations and
/// using a new space. It's faster than the native fullscreen mode since it
/// doesn't use animations.
///
/// Allowable values are:
///
/// * "visible-menu" - Use non-native macOS fullscreen, keep the menu bar visible
/// * "true" - Use non-native macOS fullscreen, hide the menu bar
/// * "false" - Use native macOS fullscreeen
@"macos-non-native-fullscreen": NonNativeFullscreen = .false,
/// If true, the Option key will be treated as Alt. This makes terminal
/// sequences expecting Alt to work properly, but will break Unicode
@ -1075,6 +1081,15 @@ fn equal(comptime T: type, old: T, new: T) bool {
}
}
/// Valid values for macos-non-native-fullscreen
/// c_int because it needs to be extern compatible
/// If this is changed, you must also update ghostty.h
pub const NonNativeFullscreen = enum(c_int) {
false,
true,
@"visible-menu",
};
/// Valid values for macos-option-as-alt.
pub const OptionAsAlt = enum {
false,