From 8e464db04924bc0f79c7a86c6561a9e79cfdd09f Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Sun, 2 Jul 2023 19:59:41 +0200 Subject: [PATCH] Toggle fullscreen on super/ctrl+return, only macOS for now This fixes or at least is the first step towards #171: - it adds `cmd/super + return` as the default keybinding to toggle fullscreen for currently focused window. - it adds a keybinding handler to the embedded apprt and then changes the macOS app to handle the keybinding by toggling currently focused window. --- include/ghostty.h | 2 ++ macos/Sources/ContentView.swift | 12 ++++++++++++ macos/Sources/Ghostty/AppState.swift | 12 +++++++++++- macos/Sources/Ghostty/Package.swift | 3 +++ src/Surface.zig | 6 ++++++ src/apprt/embedded.zig | 12 ++++++++++++ src/config.zig | 7 +++++++ src/input/Binding.zig | 3 +++ 8 files changed, 56 insertions(+), 1 deletion(-) diff --git a/include/ghostty.h b/include/ghostty.h index 70428c89f..43d478ab1 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -239,6 +239,7 @@ typedef void (*ghostty_runtime_new_split_cb)(void *, ghostty_split_direction_e); 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 *); typedef struct { void *userdata; @@ -251,6 +252,7 @@ typedef struct { ghostty_runtime_close_surface_cb close_surface_cb; ghostty_runtime_focus_split_cb focus_split_cb; ghostty_runtime_goto_tab_cb goto_tab_cb; + ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb; } ghostty_runtime_config_s; //------------------------------------------------------------------- diff --git a/macos/Sources/ContentView.swift b/macos/Sources/ContentView.swift index d53951d47..e3161f599 100644 --- a/macos/Sources/ContentView.swift +++ b/macos/Sources/ContentView.swift @@ -28,6 +28,7 @@ struct ContentView: View { case .ready: let center = NotificationCenter.default let gotoTab = center.publisher(for: Ghostty.Notification.ghosttyGotoTab) + let toggleFullscreen = center.publisher(for: Ghostty.Notification.ghosttyToggleFullscreen) let confirmQuitting = Binding(get: { self.appDelegate.confirmQuit && (self.window?.isKeyWindow ?? false) @@ -39,6 +40,7 @@ struct ContentView: View { .ghosttyApp(ghostty.app!) .background(WindowAccessor(window: $window)) .onReceive(gotoTab) { onGotoTab(notification: $0) } + .onReceive(toggleFullscreen) { onToggleFullscreen(notification: $0) } .confirmationDialog( "Quit Ghostty?", isPresented: confirmQuitting) { @@ -84,4 +86,14 @@ struct ContentView: View { let targetWindow = tabbedWindows[adjustedIndex] targetWindow.makeKeyAndOrderFront(nil) } + + private func onToggleFullscreen(notification: SwiftUI.Notification) { + // Just like in `onGotoTab`, we might receive this multiple times. But + // it's fine, because `toggleFullscreen` should only apply to the + // currently focused window. + guard let window = self.window else { return } + guard window.isKeyWindow else { return } + + window.toggleFullScreen(nil) + } } diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 6a8e3ec5c..0d25c8c37 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -62,7 +62,8 @@ extension Ghostty { new_split_cb: { userdata, direction in AppState.newSplit(userdata, direction: direction) }, 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) } + goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) }, + toggle_fullscreen_cb: { userdata in AppState.toggleFullscreen(userdata) } ) // Create the ghostty app. @@ -217,6 +218,15 @@ extension Ghostty { surfaceView.title = titleStr } } + + static func toggleFullscreen(_ userdata: UnsafeMutableRawPointer?) { + guard let surface = self.surfaceUserdata(from: userdata) else { return } + NotificationCenter.default.post( + name: Notification.ghosttyToggleFullscreen, + object: surface, + userInfo: [:] + ) + } /// Returns the GhosttyState from the given userdata value. static private func appState(fromSurface userdata: UnsafeMutableRawPointer?) -> AppState? { diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index e3c9c5a1f..6045d6dbb 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -78,6 +78,9 @@ extension Ghostty.Notification { /// Goto tab. Has tab index in the userinfo. static let ghosttyGotoTab = Notification.Name("com.mitchellh.ghostty.gotoTab") static let GotoTabKey = ghosttyGotoTab.rawValue + + /// Toggle fullscreen of current window + static let ghosttyToggleFullscreen = Notification.Name("com.mitchellh.ghostty.toggleFullscreen") } // Make the input enum hashable. diff --git a/src/Surface.zig b/src/Surface.zig index a54d09db2..8257f4946 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1135,6 +1135,12 @@ pub fn keyCallback( } else log.warn("runtime doesn't implement gotoSplit", .{}); }, + .toggle_fullscreen => { + if (@hasDecl(apprt.Surface, "toggleFullscreen")) { + self.rt_surface.toggleFullscreen(); + } else log.warn("runtime doesn't implement toggleFullscreen", .{}); + }, + .close_surface => self.close(), .close_window => { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 7289b2401..f5a8f899a 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -64,6 +64,9 @@ pub const App = struct { /// Goto tab goto_tab: ?*const fn (SurfaceUD, usize) callconv(.C) void = null, + + /// Toggle fullscreen for current window. + toggle_fullscreen: ?*const fn (SurfaceUD) callconv(.C) void = null, }; core_app: *CoreApp, @@ -371,6 +374,15 @@ pub const Surface = struct { func(self.opts.userdata, n); } + pub fn toggleFullscreen(self: *Surface) void { + const func = self.app.opts.toggle_fullscreen orelse { + log.info("runtime embedder does not toggle_fullscreen", .{}); + return; + }; + + func(self.opts.userdata); + } + /// The cursor position from the host directly is in screen coordinates but /// all our interface works in pixels. fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos { diff --git a/src/config.zig b/src/config.zig index a5de9afca..c1c9f1f72 100644 --- a/src/config.zig +++ b/src/config.zig @@ -441,6 +441,13 @@ pub const Config = struct { } } + // Toggle fullscreen + try result.keybind.set.put( + alloc, + .{ .key = .enter, .mods = ctrlOrSuper(.{}) }, + .{ .toggle_fullscreen = {} }, + ); + // Mac-specific keyboard bindings. if (comptime builtin.target.isDarwin()) { try result.keybind.set.put( diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 9e4341f56..2cb9c346e 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -207,6 +207,9 @@ pub const Action = union(enum) { /// Close the window, regardless of how many tabs or splits there may be. close_window: void, + /// Toggle fullscreen mode of window. + toggle_fullscreen: void, + /// Quit ghostty quit: void,