macos: add visible-menu non-native-fullscreen option

This commit is contained in:
Will Pragnell
2023-09-01 20:17:30 -07:00
parent 7408971d6d
commit 86122624e0
8 changed files with 63 additions and 27 deletions

View File

@ -70,6 +70,12 @@ typedef enum {
GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN, GHOSTTY_MOUSE_MOMENTUM_MAY_BEGIN,
} ghostty_input_mouse_momentum_e; } 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 // 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 // afaik doesn't let us reliably define packed structs so we build it up
// from scratch. // 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_close_surface_cb)(void *, bool);
typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direction_e); 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_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 { typedef struct {
void *userdata; void *userdata;

View File

@ -146,7 +146,7 @@ struct PrimaryView: View {
// Check whether we use non-native fullscreen // Check whether we use non-native fullscreen
guard let useNonNativeFullscreenAny = notification.userInfo?[Ghostty.Notification.NonNativeFullscreenKey] else { return } 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) self.fullScreen.toggleFullscreen(window: window, nonNativeFullscreen: useNonNativeFullscreen)
// After toggling fullscreen we need to focus the terminal again. // 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) }, close_surface_cb: { userdata, processAlive in AppState.closeSurface(userdata, processAlive: processAlive) },
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) }, focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) }, 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. // Create the ghostty app.
@ -276,14 +276,13 @@ extension Ghostty {
} }
} }
static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, useNonNativeFullscreen: Bool) { static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
// togo: use non-native fullscreen
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post( NotificationCenter.default.post(
name: Notification.ghosttyToggleFullscreen, name: Notification.ghosttyToggleFullscreen,
object: surface, object: surface,
userInfo: [ userInfo: [
Notification.NonNativeFullscreenKey: useNonNativeFullscreen, Notification.NonNativeFullscreenKey: nonNativeFullscreen,
] ]
) )
} }

View File

@ -1,4 +1,5 @@
import SwiftUI import SwiftUI
import GhosttyKit
class FullScreenHandler { class FullScreenHandler {
var previousTabGroup: NSWindowTabGroup? var previousTabGroup: NSWindowTabGroup?
@ -11,9 +12,10 @@ class FullScreenHandler {
// and then wants to toggle it off // and then wants to toggle it off
var isInNonNativeFullscreen: Bool = false 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 isInFullscreen {
if nonNativeFullscreen || isInNonNativeFullscreen { if useNonNativeFullscreen || isInNonNativeFullscreen {
leaveFullscreen(window: window) leaveFullscreen(window: window)
isInNonNativeFullscreen = false isInNonNativeFullscreen = false
} else { } else {
@ -21,8 +23,9 @@ class FullScreenHandler {
} }
isInFullscreen = false isInFullscreen = false
} else { } else {
if nonNativeFullscreen { if useNonNativeFullscreen {
enterFullscreen(window: window) let hideMenu = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_VISIBLE_MENU
enterFullscreen(window: window, hideMenu: hideMenu)
isInNonNativeFullscreen = true isInNonNativeFullscreen = true
} else { } else {
window.toggleFullScreen(nil) 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 screen = window.screen else { return }
guard let contentView = window.contentView else { return } guard let contentView = window.contentView else { return }
@ -41,7 +44,7 @@ class FullScreenHandler {
// Save previous contentViewFrame and screen // Save previous contentViewFrame and screen
previousContentFrame = window.convertToScreen(contentView.frame) 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 // 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!) // 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 // When these options are set separately, the menu bar hiding problem will only occur in
@ -52,19 +55,30 @@ class FullScreenHandler {
if (shouldHideDock(screen: screen)) { if (shouldHideDock(screen: screen)) {
NSApp.presentationOptions.insert(.autoHideDock) NSApp.presentationOptions.insert(.autoHideDock)
} }
NSApp.presentationOptions.insert(.autoHideMenuBar) if (hideMenu) {
NSApp.presentationOptions.insert(.autoHideMenuBar)
}
// This is important: it gives us the full screen, including the // This is important: it gives us the full screen, including the
// notch area on MacBooks. // notch area on MacBooks.
window.styleMask.remove(.titled) window.styleMask.remove(.titled)
// Set frame to screen size // Set frame to screen size, accounting for the menu bar if needed
window.setFrame(screen.frame, display: true) let frame = calculateFullscreenFrame(screenFrame: screen.frame, subtractMenu: !hideMenu)
window.setFrame(frame, display: true)
// Focus window // Focus window
window.makeKeyAndOrderFront(nil) window.makeKeyAndOrderFront(nil)
} }
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) { func leaveFullscreen(window: NSWindow) {
guard let previousFrame = previousContentFrame else { return } guard let previousFrame = previousContentFrame else { return }

View File

@ -137,7 +137,7 @@ const DerivedConfig = struct {
copy_on_select: configpkg.CopyOnSelect, copy_on_select: configpkg.CopyOnSelect,
confirm_close_surface: bool, confirm_close_surface: bool,
mouse_interval: u64, mouse_interval: u64,
macos_non_native_fullscreen: bool, macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
macos_option_as_alt: configpkg.OptionAsAlt, macos_option_as_alt: configpkg.OptionAsAlt,
pub fn init(alloc_gpa: Allocator, config: *const configpkg.Config) !DerivedConfig { 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 input = @import("../input.zig");
const CoreApp = @import("../App.zig"); const CoreApp = @import("../App.zig");
const CoreSurface = @import("../Surface.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); 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, goto_tab: ?*const fn (SurfaceUD, usize) callconv(.C) void = null,
/// Toggle fullscreen for current window. /// 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, core_app: *CoreApp,
@ -622,7 +623,7 @@ pub const Surface = struct {
func(self.opts.userdata, n); 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 { const func = self.app.opts.toggle_fullscreen orelse {
log.info("runtime embedder does not toggle_fullscreen", .{}); log.info("runtime embedder does not toggle_fullscreen", .{});
return; return;

View File

@ -10,7 +10,8 @@ const font = @import("../font/main.zig");
const input = @import("../input.zig"); const input = @import("../input.zig");
const CoreApp = @import("../App.zig"); const CoreApp = @import("../App.zig");
const CoreSurface = @import("../Surface.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({ pub const c = @cImport({
@cInclude("gtk/gtk.h"); @cInclude("gtk/gtk.h");
@ -518,7 +519,7 @@ const Window = struct {
} }
/// Toggle fullscreen for this window. /// 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); const is_fullscreen = c.gtk_window_is_fullscreen(self.window);
if (is_fullscreen == 0) { if (is_fullscreen == 0) {
c.gtk_window_fullscreen(self.window); c.gtk_window_fullscreen(self.window);
@ -896,7 +897,7 @@ pub const Surface = struct {
c.gtk_widget_show(alert); 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); self.window.toggleFullscreen(mac_non_native);
} }

View File

@ -278,11 +278,17 @@ pub const Config = struct {
/// The default value is "detect". /// The default value is "detect".
@"shell-integration": ShellIntegration = .detect, @"shell-integration": ShellIntegration = .detect,
/// If true, fullscreen mode on macOS will not use the native fullscreen, /// If anything other than false, fullscreen mode on macOS will not use the
/// but make the window fullscreen without animations and using a new space. /// native fullscreen, but make the window fullscreen without animations and
/// That's faster than the native fullscreen mode since it doesn't use /// using a new space. It's faster than the native fullscreen mode since it
/// animations. /// doesn't use animations.
@"macos-non-native-fullscreen": bool = false, ///
/// 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 /// If true, the Option key will be treated as Alt. This makes terminal
/// sequences expecting Alt to work properly, but will break Unicode /// 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. /// Valid values for macos-option-as-alt.
pub const OptionAsAlt = enum { pub const OptionAsAlt = enum {
false, false,