Merge pull request #599 from mitchellh/win-size

Add `window-width`, `window-height` configurations for initial window size
This commit is contained in:
Mitchell Hashimoto
2023-09-30 21:49:40 -07:00
committed by GitHub
12 changed files with 148 additions and 4 deletions

View File

@ -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;
//-------------------------------------------------------------------

View File

@ -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?) {

View File

@ -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>

View File

@ -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 }

View File

@ -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.

View File

@ -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 {

View File

@ -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 {

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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);
}

View File

@ -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