mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
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:
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -50,21 +53,76 @@ class FullScreenHandler {
|
|||||||
// Furthermore, it's much easier to figure out which screen the dock is on if the menubar
|
// 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!
|
// has not yet been hidden, so the order matters here!
|
||||||
if (shouldHideDock(screen: screen)) {
|
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
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
func leaveFullscreen(window: NSWindow) {
|
||||||
guard let previousFrame = previousContentFrame else { return }
|
guard let previousFrame = previousContentFrame else { return }
|
||||||
|
|
||||||
@ -74,6 +132,11 @@ class FullScreenHandler {
|
|||||||
// Restore previous presentation options
|
// Restore previous presentation options
|
||||||
NSApp.presentationOptions = []
|
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
|
// Restore frame
|
||||||
window.setFrame(window.frameRect(forContentRect: previousFrame), display: true)
|
window.setFrame(window.frameRect(forContentRect: previousFrame), display: true)
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user