From cc8e1cd936195648badd6c61fa0381a86a044a60 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 30 Sep 2023 20:47:31 -0700 Subject: [PATCH] macos: support initial window size --- include/ghostty.h | 10 +++++ .../PrimaryWindowController.swift | 3 ++ .../Features/Settings/ConfigurationErrors.xib | 4 +- macos/Sources/Ghostty/Package.swift | 4 ++ macos/Sources/Ghostty/SurfaceView.swift | 45 ++++++++++++++++++- src/apprt/embedded.zig | 25 +++++++++++ 6 files changed, 88 insertions(+), 3 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 42e83db5f..198fe08dc 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -291,6 +291,15 @@ typedef enum { GHOSTTY_BUILD_MODE_RELEASE_SMALL, } ghostty_build_mode_e; +typedef struct { + bool origin; + bool size; + uint32_t x; + uint32_t y; + uint32_t w; + uint32_t h; +} ghostty_rect_s; + // Fully defined types. This MUST be kept in sync with equivalent Zig // structs. To find the Zig struct, grep for this type name. The documentation // for all of these types is available in the Zig source. @@ -380,6 +389,7 @@ ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*); void ghostty_surface_free(ghostty_surface_t); ghostty_app_t ghostty_surface_app(ghostty_surface_t); bool ghostty_surface_transparent(ghostty_surface_t); +ghostty_rect_s ghostty_surface_window_frame(ghostty_surface_t); void ghostty_surface_refresh(ghostty_surface_t); void ghostty_surface_set_content_scale(ghostty_surface_t, double, double); void ghostty_surface_set_focus(ghostty_surface_t, bool); diff --git a/macos/Sources/Features/Primary Window/PrimaryWindowController.swift b/macos/Sources/Features/Primary Window/PrimaryWindowController.swift index 0d2389cd7..6add783e3 100644 --- a/macos/Sources/Features/Primary Window/PrimaryWindowController.swift +++ b/macos/Sources/Features/Primary Window/PrimaryWindowController.swift @@ -4,6 +4,9 @@ class PrimaryWindowController: NSWindowController, NSWindowDelegate { // This is used to programmatically control tabs. weak var windowManager: PrimaryWindowManager? + // This should be set to true once a surface has been initialized once. + var didInitializeFromSurface: Bool = false + // This is required for the "+" button to show up in the tab bar to add a // new tab. override func newWindowForTab(_ sender: Any?) { diff --git a/macos/Sources/Features/Settings/ConfigurationErrors.xib b/macos/Sources/Features/Settings/ConfigurationErrors.xib index d3f94b14d..dcd40ad21 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrors.xib +++ b/macos/Sources/Features/Settings/ConfigurationErrors.xib @@ -1,8 +1,8 @@ - + - + diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 81404fbfb..8f97a090d 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -98,6 +98,10 @@ extension Ghostty.Notification { /// Notification sent to toggle split maximize/unmaximize. static let didToggleSplitZoom = Notification.Name("com.mitchellh.ghostty.didToggleSplitZoom") + + /// Notification + static let didReceiveInitialWindowFrame = Notification.Name("com.mitchellh.ghostty.didReceiveInitialWindowFrame") + static let FrameKey = "com.mitchellh.ghostty.frame" } // Make the input enum hashable. diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index d33563ff2..ac2788796 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -71,6 +71,7 @@ extension Ghostty { // We use these notifications to determine when the window our surface is // attached to is or is not focused. let pubBecomeFocused = NotificationCenter.default.publisher(for: Notification.didBecomeFocusedSurface, object: surfaceView) + let pubInitialFrame = NotificationCenter.default.publisher(for: Notification.didReceiveInitialWindowFrame, object: surfaceView) let pubBecomeKey = NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification) let pubResign = NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification) @@ -100,6 +101,26 @@ extension Ghostty { surfaceFocus = true } } + .onReceive(pubInitialFrame) { notification in + // We never set the initial frame if we're a split + guard !isSplit else { return } + + // We need a window to set the frame + guard let surfaceWindow = surfaceView.window else { return } + guard let frameAny = notification.userInfo?[Ghostty.Notification.FrameKey] else { return } + guard let frame = frameAny as? NSRect else { return } + + // If we have tabs, then do not change the window size + guard let windowControllerRaw = surfaceWindow.windowController else { return } + guard let windowController = windowControllerRaw as? PrimaryWindowController else { return } + guard !windowController.didInitializeFromSurface else { return } + + // We have no tabs and we are not a split, so set the initial size of the window. + surfaceWindow.setFrame(frame, display: true) + + // Note that we did initialize + windowController.didInitializeFromSurface = true + } .onAppear() { // Welcome to the SwiftUI bug house of horrors. On macOS 12 (at least // 12.5.1, didn't test other versions), the order in which the view @@ -191,7 +212,7 @@ extension Ghostty { // changed with escape codes. This is public because the callbacks go // to the app level and it is set from there. @Published var title: String = "👻" - + private(set) var surface: ghostty_surface_t? var error: Error? = nil @@ -407,6 +428,28 @@ extension Ghostty { // If we have a blur, set the blur ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque()) + + // Set the window size + let rect = ghostty_surface_window_frame(surface) + if (rect.size || rect.origin) { + var frame = window.frame + if (rect.origin) { + frame.origin.x = Double(rect.x) + frame.origin.y = Double(rect.y) + } + if (rect.size) { + frame.size.width = Double(rect.w) + frame.size.height = Double(rect.h) + } + + NotificationCenter.default.post( + name: Notification.didReceiveInitialWindowFrame, + object: self, + userInfo: [ + Notification.FrameKey: frame, + ] + ) + } } override func becomeFirstResponder() -> Bool { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 36240b7f0..ecfb08012 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -757,6 +757,16 @@ pub const Surface = struct { pub const CAPI = struct { const global = &@import("../main.zig").state; + /// ghostty_rect_s + const Rect = extern struct { + origin: bool = false, + size: bool = false, + x: u32 = 0, + y: u32 = 0, + w: u32 = 0, + h: u32 = 0, + }; + /// Create a new app. export fn ghostty_app_new( opts: *const apprt.runtime.App.Options, @@ -864,6 +874,21 @@ pub const CAPI = struct { return surface.app.config.@"background-opacity" < 1.0; } + /// The desired window frame for a new window. + export fn ghostty_surface_window_frame(surface: *Surface) Rect { + const config = surface.app.config; + + // If the desired height/width isn't configured, return 0. + if (config.@"window-height" == 0 or config.@"window-width" == 0) return .{}; + + // Return the desired rect + return .{ + .size = true, + .h = @max(config.@"window-height" * surface.core_surface.cell_size.height, 480), + .w = @max(config.@"window-width" * surface.core_surface.cell_size.width, 640), + }; + } + /// Tell the surface that it needs to schedule a render export fn ghostty_surface_refresh(surface: *Surface) void { surface.refresh();