fix: calculate cell size before presenting gtk window (#10459)

Fixes #7937

Added `computeInitialSize` to GTK `Surface` and call it in GTK
`Application` before the first `present()`, so the window manager
centers the correct size on initial show.

The issue occurs because the core `Surface.recomputeInitialSize()` runs
only after the renderer is initialized. In GTK, the `GLArea` isn’t
realized until after `present()`, so the initial size arrives too late
for WM centering.

**Limitations**: when we precompute size before `present()` we do not
have access to padding, so the sizing will be very slightly off... but
since it is only off a few pixels I was unable to tell visually that it
wasn't perfectly centered.

**Other thoughts**: I was hesitant to make changes to core `Surface`
because the issue is Linux-specific, but it may make sense to extract a
helper from `recomputeInitialSize` to avoid duplicating the sizing math.

**AI Disclosure:** I used AI to explore the project, help with any
language / API questions (I've never used zig before and rarely use
gtk), and make implementation suggestions.
This commit is contained in:
Mitchell Hashimoto
2026-03-03 08:12:48 -08:00
committed by GitHub
3 changed files with 63 additions and 2 deletions

View File

@@ -46,8 +46,8 @@ const Renderer = rendererpkg.Renderer;
/// being resized to a size that is too small to be useful. These defaults
/// are chosen to match the default size of Mac's Terminal.app, but is
/// otherwise somewhat arbitrary.
const min_window_width_cells: u32 = 10;
const min_window_height_cells: u32 = 4;
pub const min_window_width_cells: u32 = 10;
pub const min_window_height_cells: u32 = 4;
/// The maximum number of key tables that can be active at any
/// given time. `activate_key_table` calls after this are ignored.

View File

@@ -2182,6 +2182,18 @@ const Action = struct {
// Create a new tab with window context (first tab in new window)
win.newTabForWindow(parent);
// Estimate the initial window size before presenting so the window
// manager can position it correctly.
if (win.getActiveSurface()) |surface| {
surface.estimateInitialSize();
if (surface.getDefaultSize()) |size| {
win.as(gtk.Window).setDefaultSize(
@intCast(size.width),
@intCast(size.height),
);
}
}
// Show the window
gtk.Window.present(win.as(gtk.Window));
}

View File

@@ -2005,6 +2005,55 @@ pub const Surface = extern struct {
self.as(gobject.Object).notifyByPspec(properties.@"default-size".impl.param_spec);
}
/// Estimate and set the initial window size from config and font metrics.
/// This can be called before the core surface exists to set up the window
/// size before presenting. This is an estimate because it does not take
/// into account any padding that may need to be added to the window.
pub fn estimateInitialSize(self: *Self) void {
const priv: *Private = self.private();
const config_obj = priv.config orelse return;
const config = config_obj.get();
// Both dimensions must be configured
if (config.@"window-height" <= 0 or config.@"window-width" <= 0) return;
const app = Application.default();
const alloc = app.allocator();
// Get content scale and compute DPI
const content_scale = self.getContentScale();
const x_dpi = content_scale.x * font.face.default_dpi;
const y_dpi = content_scale.y * font.face.default_dpi;
const font_size: font.face.DesiredSize = .{
.points = config.@"font-size",
.xdpi = @intFromFloat(x_dpi),
.ydpi = @intFromFloat(y_dpi),
};
// Get font grid for cell metrics
var derived_config = font.SharedGridSet.DerivedConfig.init(alloc, config) catch return;
defer derived_config.deinit();
const font_grid_key, const font_grid = app.core().font_grid_set.ref(
&derived_config,
font_size,
) catch return;
defer app.core().font_grid_set.deref(font_grid_key);
const cell = font_grid.cellSize();
const width = @max(CoreSurface.min_window_width_cells, config.@"window-width") * cell.width;
const height = @max(CoreSurface.min_window_height_cells, config.@"window-height") * cell.height;
const width_f32: f32 = @floatFromInt(width);
const height_f32: f32 = @floatFromInt(height);
const final_width: u32 = @intFromFloat(@ceil(width_f32 / content_scale.x));
const final_height: u32 = @intFromFloat(@ceil(height_f32 / content_scale.y));
self.setDefaultSize(.{ .width = final_width, .height = final_height });
}
/// Get the key sequence list. Full transfer.
fn getKeySequence(self: *Self) ?*ext.StringList {
const priv = self.private();