mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-04 08:56:32 +00:00
Merge pull request #599 from mitchellh/win-size
Add `window-width`, `window-height` configurations for initial window size
This commit is contained in:
@@ -326,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;
|
||||||
@@ -345,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;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
@@ -4,6 +4,9 @@ class PrimaryWindowController: NSWindowController, NSWindowDelegate {
|
|||||||
// This is used to programmatically control tabs.
|
// This is used to programmatically control tabs.
|
||||||
weak var windowManager: PrimaryWindowManager?
|
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
|
// This is required for the "+" button to show up in the tab bar to add a
|
||||||
// new tab.
|
// new tab.
|
||||||
override func newWindowForTab(_ sender: Any?) {
|
override func newWindowForTab(_ sender: Any?) {
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22154" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22154"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
|
@@ -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 }
|
||||||
|
@@ -98,6 +98,10 @@ extension Ghostty.Notification {
|
|||||||
|
|
||||||
/// Notification sent to toggle split maximize/unmaximize.
|
/// Notification sent to toggle split maximize/unmaximize.
|
||||||
static let didToggleSplitZoom = Notification.Name("com.mitchellh.ghostty.didToggleSplitZoom")
|
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.
|
// Make the input enum hashable.
|
||||||
|
@@ -191,7 +191,11 @@ extension Ghostty {
|
|||||||
// changed with escape codes. This is public because the callbacks go
|
// changed with escape codes. This is public because the callbacks go
|
||||||
// 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
|
||||||
|
|
||||||
@@ -407,6 +411,40 @@ 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())
|
||||||
|
|
||||||
|
// Try to set the initial window size if we have one
|
||||||
|
setInitialWindowSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the initial window size requested by the Ghostty config.
|
||||||
|
///
|
||||||
|
/// This only works under certain conditions:
|
||||||
|
/// - The window must be "uninitialized"
|
||||||
|
/// - The window must have no tabs
|
||||||
|
/// - Ghostty must have requested an initial size
|
||||||
|
///
|
||||||
|
private func setInitialWindowSize() {
|
||||||
|
guard let initialSize = initialSize else { return }
|
||||||
|
|
||||||
|
// If we have tabs, then do not change the window size
|
||||||
|
guard let window = self.window else { return }
|
||||||
|
guard let windowControllerRaw = window.windowController else { return }
|
||||||
|
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;
|
||||||
|
@@ -447,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,
|
||||||
|
@@ -325,6 +325,16 @@ pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
|
|||||||
return self.size;
|
return self.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
|
||||||
|
// Note: this doesn't properly take into account the window decorations.
|
||||||
|
// I'm not currently sure how to do that.
|
||||||
|
c.gtk_window_set_default_size(
|
||||||
|
@ptrCast(self.window.window),
|
||||||
|
@intCast(width),
|
||||||
|
@intCast(height),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
pub fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = min;
|
_ = min;
|
||||||
|
@@ -86,6 +86,7 @@ export fn ghostty_config_get(
|
|||||||
key_str: [*]const u8,
|
key_str: [*]const u8,
|
||||||
len: usize,
|
len: usize,
|
||||||
) bool {
|
) bool {
|
||||||
|
@setEvalBranchQuota(10_000);
|
||||||
const key = std.meta.stringToEnum(Key, key_str[0..len]) orelse return false;
|
const key = std.meta.stringToEnum(Key, key_str[0..len]) orelse return false;
|
||||||
return c_get.get(self, key, ptr);
|
return c_get.get(self, key, ptr);
|
||||||
}
|
}
|
||||||
|
@@ -300,6 +300,31 @@ keybind: Keybinds = .{},
|
|||||||
/// This is currently only supported on macOS.
|
/// This is currently only supported on macOS.
|
||||||
@"window-theme": WindowTheme = .system,
|
@"window-theme": WindowTheme = .system,
|
||||||
|
|
||||||
|
/// The initial window size. This size is in terminal grid cells by default.
|
||||||
|
///
|
||||||
|
/// We don't currently support specifying a size in pixels but a future
|
||||||
|
/// change can enable that. If this isn't specified, the app runtime will
|
||||||
|
/// determine some default size.
|
||||||
|
///
|
||||||
|
/// Note that the window manager may put limits on the size or override
|
||||||
|
/// the size. For example, a tiling window manager may force the window
|
||||||
|
/// to be a certain size to fit within the grid. There is nothing Ghostty
|
||||||
|
/// will do about this, but it will make an effort.
|
||||||
|
///
|
||||||
|
/// This will not affect new tabs, splits, or other nested terminal
|
||||||
|
/// elements. This only affects the initial window size of any new window.
|
||||||
|
/// Changing this value will not affect the size of the window after
|
||||||
|
/// it has been created. This is only used for the initial size.
|
||||||
|
///
|
||||||
|
/// BUG: On Linux with GTK, the calculated window size will not properly
|
||||||
|
/// take into account window decorations. As a result, the grid dimensions
|
||||||
|
/// will not exactly match this configuration. If window decorations are
|
||||||
|
/// disabled (see window-decorations), then this will work as expected.
|
||||||
|
///
|
||||||
|
/// Windows smaller than 10 wide by 4 high are not allowed.
|
||||||
|
@"window-height": u32 = 0,
|
||||||
|
@"window-width": u32 = 0,
|
||||||
|
|
||||||
/// Whether to allow programs running in the terminal to read/write to
|
/// Whether to allow programs running in the terminal to read/write to
|
||||||
/// the system clipboard (OSC 52, for googling). The default is to
|
/// the system clipboard (OSC 52, for googling). The default is to
|
||||||
/// disallow clipboard reading but allow writing.
|
/// disallow clipboard reading but allow writing.
|
||||||
@@ -1007,6 +1032,10 @@ pub fn finalize(self: *Config) !void {
|
|||||||
|
|
||||||
// Clamp our split opacity
|
// Clamp our split opacity
|
||||||
self.@"unfocused-split-opacity" = @min(1.0, @max(0.15, self.@"unfocused-split-opacity"));
|
self.@"unfocused-split-opacity" = @min(1.0, @max(0.15, self.@"unfocused-split-opacity"));
|
||||||
|
|
||||||
|
// Minimmum window size
|
||||||
|
if (self.@"window-width" > 0) self.@"window-width" = @max(10, self.@"window-width");
|
||||||
|
if (self.@"window-height" > 0) self.@"window-height" = @max(4, self.@"window-height");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a shallow copy of this config. This will share all the memory
|
/// Create a shallow copy of this config. This will share all the memory
|
||||||
|
Reference in New Issue
Block a user