mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
initial window size needs to take into account window chrome
This commit is contained in:
@ -291,15 +291,6 @@ typedef enum {
|
|||||||
GHOSTTY_BUILD_MODE_RELEASE_SMALL,
|
GHOSTTY_BUILD_MODE_RELEASE_SMALL,
|
||||||
} ghostty_build_mode_e;
|
} 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
|
// 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
|
// structs. To find the Zig struct, grep for this type name. The documentation
|
||||||
// for all of these types is available in the Zig source.
|
// for all of these types is available in the Zig source.
|
||||||
@ -335,6 +326,7 @@ typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direc
|
|||||||
typedef void (*ghostty_runtime_toggle_split_zoom_cb)(void *);
|
typedef void (*ghostty_runtime_toggle_split_zoom_cb)(void *);
|
||||||
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 *, ghostty_non_native_fullscreen_e);
|
typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, ghostty_non_native_fullscreen_e);
|
||||||
|
typedef void (*ghostty_runtime_set_initial_window_size_cb)(void *, uint32_t, uint32_t);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *userdata;
|
void *userdata;
|
||||||
@ -354,6 +346,7 @@ typedef struct {
|
|||||||
ghostty_runtime_toggle_split_zoom_cb toggle_split_zoom_cb;
|
ghostty_runtime_toggle_split_zoom_cb toggle_split_zoom_cb;
|
||||||
ghostty_runtime_goto_tab_cb goto_tab_cb;
|
ghostty_runtime_goto_tab_cb goto_tab_cb;
|
||||||
ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb;
|
ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb;
|
||||||
|
ghostty_runtime_set_initial_window_size_cb set_initial_window_size_cb;
|
||||||
} ghostty_runtime_config_s;
|
} ghostty_runtime_config_s;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
@ -389,7 +382,6 @@ ghostty_surface_t ghostty_surface_new(ghostty_app_t, ghostty_surface_config_s*);
|
|||||||
void ghostty_surface_free(ghostty_surface_t);
|
void ghostty_surface_free(ghostty_surface_t);
|
||||||
ghostty_app_t ghostty_surface_app(ghostty_surface_t);
|
ghostty_app_t ghostty_surface_app(ghostty_surface_t);
|
||||||
bool ghostty_surface_transparent(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_refresh(ghostty_surface_t);
|
||||||
void ghostty_surface_set_content_scale(ghostty_surface_t, double, double);
|
void ghostty_surface_set_content_scale(ghostty_surface_t, double, double);
|
||||||
void ghostty_surface_set_focus(ghostty_surface_t, bool);
|
void ghostty_surface_set_focus(ghostty_surface_t, bool);
|
||||||
|
@ -125,7 +125,8 @@ extension Ghostty {
|
|||||||
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
|
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
|
||||||
toggle_split_zoom_cb: { userdata in AppState.toggleSplitZoom(userdata) },
|
toggle_split_zoom_cb: { userdata in AppState.toggleSplitZoom(userdata) },
|
||||||
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, nonNativeFullscreen: nonNativeFullscreen) }
|
toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) },
|
||||||
|
set_initial_window_size_cb: { userdata, width, height in AppState.setInitialWindowSize(userdata, width: width, height: height) }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the ghostty app.
|
// Create the ghostty app.
|
||||||
@ -413,6 +414,12 @@ extension Ghostty {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func setInitialWindowSize(_ userdata: UnsafeMutableRawPointer?, width: UInt32, height: UInt32) {
|
||||||
|
// We need a window to set the frame
|
||||||
|
guard let surfaceView = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
surfaceView.initialSize = NSMakeSize(Double(width), Double(height))
|
||||||
|
}
|
||||||
|
|
||||||
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
@ -71,7 +71,6 @@ extension Ghostty {
|
|||||||
// We use these notifications to determine when the window our surface is
|
// We use these notifications to determine when the window our surface is
|
||||||
// attached to is or is not focused.
|
// attached to is or is not focused.
|
||||||
let pubBecomeFocused = NotificationCenter.default.publisher(for: Notification.didBecomeFocusedSurface, object: surfaceView)
|
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 pubBecomeKey = NotificationCenter.default.publisher(for: NSWindow.didBecomeKeyNotification)
|
||||||
let pubResign = NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification)
|
let pubResign = NotificationCenter.default.publisher(for: NSWindow.didResignKeyNotification)
|
||||||
|
|
||||||
@ -101,26 +100,6 @@ extension Ghostty {
|
|||||||
surfaceFocus = true
|
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() {
|
.onAppear() {
|
||||||
// Welcome to the SwiftUI bug house of horrors. On macOS 12 (at least
|
// 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
|
// 12.5.1, didn't test other versions), the order in which the view
|
||||||
@ -213,6 +192,10 @@ extension Ghostty {
|
|||||||
// to the app level and it is set from there.
|
// to the app level and it is set from there.
|
||||||
@Published var title: String = "👻"
|
@Published var title: String = "👻"
|
||||||
|
|
||||||
|
// An initial size to request for a window. This will only affect
|
||||||
|
// then the view is moved to a new window.
|
||||||
|
var initialSize: NSSize? = nil
|
||||||
|
|
||||||
private(set) var surface: ghostty_surface_t?
|
private(set) var surface: ghostty_surface_t?
|
||||||
var error: Error? = nil
|
var error: Error? = nil
|
||||||
|
|
||||||
@ -429,27 +412,39 @@ extension Ghostty {
|
|||||||
// If we have a blur, set the blur
|
// If we have a blur, set the blur
|
||||||
ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque())
|
ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque())
|
||||||
|
|
||||||
// Set the window size
|
// Try to set the initial window size if we have one
|
||||||
let rect = ghostty_surface_window_frame(surface)
|
setInitialWindowSize()
|
||||||
if (rect.size || rect.origin) {
|
}
|
||||||
var frame = window.frame
|
|
||||||
if (rect.origin) {
|
/// Sets the initial window size requested by the Ghostty config.
|
||||||
frame.origin.x = Double(rect.x)
|
///
|
||||||
frame.origin.y = Double(rect.y)
|
/// This only works under certain conditions:
|
||||||
}
|
/// - The window must be "uninitialized"
|
||||||
if (rect.size) {
|
/// - The window must have no tabs
|
||||||
frame.size.width = Double(rect.w)
|
/// - Ghostty must have requested an initial size
|
||||||
frame.size.height = Double(rect.h)
|
///
|
||||||
}
|
private func setInitialWindowSize() {
|
||||||
|
guard let initialSize = initialSize else { return }
|
||||||
NotificationCenter.default.post(
|
|
||||||
name: Notification.didReceiveInitialWindowFrame,
|
// If we have tabs, then do not change the window size
|
||||||
object: self,
|
guard let window = self.window else { return }
|
||||||
userInfo: [
|
guard let windowControllerRaw = window.windowController else { return }
|
||||||
Notification.FrameKey: frame,
|
guard let windowController = windowControllerRaw as? PrimaryWindowController else { return }
|
||||||
]
|
guard !windowController.didInitializeFromSurface else { return }
|
||||||
)
|
|
||||||
}
|
// Setup our frame. We need to first subtract the views frame so that we can
|
||||||
|
// just get the chrome frame so that we only affect the surface view size.
|
||||||
|
var frame = window.frame
|
||||||
|
frame.size.width -= self.frame.size.width
|
||||||
|
frame.size.height -= self.frame.size.height
|
||||||
|
frame.size.width += initialSize.width
|
||||||
|
frame.size.height += initialSize.height
|
||||||
|
|
||||||
|
// We have no tabs and we are not a split, so set the initial size of the window.
|
||||||
|
window.setFrame(frame, display: true)
|
||||||
|
|
||||||
|
// Note that we did initialize
|
||||||
|
windowController.didInitializeFromSurface = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func becomeFirstResponder() -> Bool {
|
override func becomeFirstResponder() -> Bool {
|
||||||
|
@ -471,6 +471,36 @@ pub fn init(
|
|||||||
.{&self.io_thread},
|
.{&self.io_thread},
|
||||||
);
|
);
|
||||||
self.io_thr.setName("io") catch {};
|
self.io_thr.setName("io") catch {};
|
||||||
|
|
||||||
|
// Determine our initial window size if configured. We need to do this
|
||||||
|
// quite late in the process because our height/width are in grid dimensions,
|
||||||
|
// so we need to know our cell sizes first.
|
||||||
|
//
|
||||||
|
// Note: it is important to do this after the renderer is setup above.
|
||||||
|
// This allows the apprt to fully initialize the surface before we
|
||||||
|
// start messing with the window.
|
||||||
|
if (config.@"window-height" > 0 and config.@"window-width" > 0) init: {
|
||||||
|
const scale = rt_surface.getContentScale() catch break :init;
|
||||||
|
const height = @max(config.@"window-height" * cell_size.height, 480);
|
||||||
|
const width = @max(config.@"window-width" * cell_size.width, 640);
|
||||||
|
const width_f32: f32 = @floatFromInt(width);
|
||||||
|
const height_f32: f32 = @floatFromInt(height);
|
||||||
|
|
||||||
|
// The final values are affected by content scale and we need to
|
||||||
|
// account for the padding so we get the exact correct grid size.
|
||||||
|
const final_width: u32 =
|
||||||
|
@as(u32, @intFromFloat(@ceil(width_f32 / scale.x))) +
|
||||||
|
padding.left +
|
||||||
|
padding.right;
|
||||||
|
const final_height: u32 =
|
||||||
|
@as(u32, @intFromFloat(@ceil(height_f32 / scale.y))) +
|
||||||
|
padding.top +
|
||||||
|
padding.bottom;
|
||||||
|
|
||||||
|
rt_surface.setInitialWindowSize(final_width, final_height) catch |err| {
|
||||||
|
log.warn("unable to set initial window size: {s}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Surface) void {
|
pub fn deinit(self: *Surface) void {
|
||||||
|
@ -87,6 +87,10 @@ pub const App = struct {
|
|||||||
|
|
||||||
/// Toggle fullscreen for current window.
|
/// Toggle fullscreen for current window.
|
||||||
toggle_fullscreen: ?*const fn (SurfaceUD, configpkg.NonNativeFullscreen) callconv(.C) void = null,
|
toggle_fullscreen: ?*const fn (SurfaceUD, configpkg.NonNativeFullscreen) callconv(.C) void = null,
|
||||||
|
|
||||||
|
/// Set the initial window size. It is up to the user of libghostty to
|
||||||
|
/// determine if it is the initial window and set this appropriately.
|
||||||
|
set_initial_window_size: ?*const fn (SurfaceUD, u32, u32) callconv(.C) void = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Special values for the goto_tab callback.
|
/// Special values for the goto_tab callback.
|
||||||
@ -734,6 +738,15 @@ pub const Surface = struct {
|
|||||||
func(self.opts.userdata, options);
|
func(self.opts.userdata, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
|
||||||
|
const func = self.app.opts.set_initial_window_size orelse {
|
||||||
|
log.info("runtime embedder does not set_initial_window_size", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
func(self.opts.userdata, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
|
fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options {
|
||||||
const font_size: u16 = font_size: {
|
const font_size: u16 = font_size: {
|
||||||
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
|
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
|
||||||
@ -757,16 +770,6 @@ pub const Surface = struct {
|
|||||||
pub const CAPI = struct {
|
pub const CAPI = struct {
|
||||||
const global = &@import("../main.zig").state;
|
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.
|
/// Create a new app.
|
||||||
export fn ghostty_app_new(
|
export fn ghostty_app_new(
|
||||||
opts: *const apprt.runtime.App.Options,
|
opts: *const apprt.runtime.App.Options,
|
||||||
@ -874,21 +877,6 @@ pub const CAPI = struct {
|
|||||||
return surface.app.config.@"background-opacity" < 1.0;
|
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
|
/// Tell the surface that it needs to schedule a render
|
||||||
export fn ghostty_surface_refresh(surface: *Surface) void {
|
export fn ghostty_surface_refresh(surface: *Surface) void {
|
||||||
surface.refresh();
|
surface.refresh();
|
||||||
|
@ -376,15 +376,6 @@ pub const Surface = struct {
|
|||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
errdefer self.core_surface.deinit();
|
errdefer self.core_surface.deinit();
|
||||||
|
|
||||||
// If we have a desired window size, we can now calculate the size
|
|
||||||
// because we have the cell size.
|
|
||||||
if (config.@"window-height" > 0 or config.@"window-width" > 0) {
|
|
||||||
self.window.setSize(.{
|
|
||||||
.height = @max(config.@"window-height" * self.core_surface.cell_size.height, 480),
|
|
||||||
.width = @max(config.@"window-width" * self.core_surface.cell_size.width, 640),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Surface) void {
|
pub fn deinit(self: *Surface) void {
|
||||||
@ -456,6 +447,13 @@ pub const Surface = struct {
|
|||||||
self.app.app.alloc.destroy(self);
|
self.app.app.alloc.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the initial window size. This is called exactly once at
|
||||||
|
/// surface initialization time. This may be called before "self"
|
||||||
|
/// is fully initialized.
|
||||||
|
pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
|
||||||
|
self.window.setSize(.{ .width = width, .height = height });
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the size limits of the window.
|
/// Set the size limits of the window.
|
||||||
/// Note: this interface is not good, we should redo it if we plan
|
/// Note: this interface is not good, we should redo it if we plan
|
||||||
/// to use this more. i.e. you can't set max width but no max height,
|
/// to use this more. i.e. you can't set max width but no max height,
|
||||||
|
Reference in New Issue
Block a user