mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03: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_goto_tab_cb)(void *, int32_t);
|
||||
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 {
|
||||
void *userdata;
|
||||
@ -345,6 +346,7 @@ typedef struct {
|
||||
ghostty_runtime_toggle_split_zoom_cb toggle_split_zoom_cb;
|
||||
ghostty_runtime_goto_tab_cb goto_tab_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;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
@ -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?) {
|
||||
|
@ -1,8 +1,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>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -125,7 +125,8 @@ extension Ghostty {
|
||||
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
|
||||
toggle_split_zoom_cb: { userdata in AppState.toggleSplitZoom(userdata) },
|
||||
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.
|
||||
@ -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) {
|
||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||
|
@ -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.
|
||||
|
@ -191,7 +191,11 @@ 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 = "👻"
|
||||
|
||||
|
||||
// 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?
|
||||
var error: Error? = nil
|
||||
|
||||
@ -407,6 +411,40 @@ extension Ghostty {
|
||||
|
||||
// If we have a blur, set the blur
|
||||
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 {
|
||||
|
@ -471,6 +471,36 @@ pub fn init(
|
||||
.{&self.io_thread},
|
||||
);
|
||||
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 {
|
||||
|
@ -87,6 +87,10 @@ pub const App = struct {
|
||||
|
||||
/// Toggle fullscreen for current window.
|
||||
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.
|
||||
@ -734,6 +738,15 @@ pub const Surface = struct {
|
||||
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 {
|
||||
const font_size: u16 = font_size: {
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// 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,
|
||||
|
@ -325,6 +325,16 @@ pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
|
||||
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 {
|
||||
_ = self;
|
||||
_ = min;
|
||||
|
@ -86,6 +86,7 @@ export fn ghostty_config_get(
|
||||
key_str: [*]const u8,
|
||||
len: usize,
|
||||
) bool {
|
||||
@setEvalBranchQuota(10_000);
|
||||
const key = std.meta.stringToEnum(Key, key_str[0..len]) orelse return false;
|
||||
return c_get.get(self, key, ptr);
|
||||
}
|
||||
|
@ -300,6 +300,31 @@ keybind: Keybinds = .{},
|
||||
/// This is currently only supported on macOS.
|
||||
@"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
|
||||
/// the system clipboard (OSC 52, for googling). The default is to
|
||||
/// disallow clipboard reading but allow writing.
|
||||
@ -1007,6 +1032,10 @@ pub fn finalize(self: *Config) !void {
|
||||
|
||||
// Clamp our 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
|
||||
|
Reference in New Issue
Block a user