From b0f1f19da087b02ded1e780785960ce5cab6c430 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 28 Feb 2025 09:57:18 -0800 Subject: [PATCH] apprt initial_size is sent whenever the grid size changes As noted in the comments, this is so that apprt's can always know what the default size of a window would be so they can utilize this for "return to default size" actions. The initial size shouldn't be treated as a "resize" event and was already documented as such. Prior to this commit the docs already noted that the initial size may be sent multiple times but only the first time during initialization should be used as a resize. Therefore, this shouldn't impact prior behavior. I've verified this with the apprts. --- src/Surface.zig | 107 +++++++++++++++++++++++++------------- src/apprt/action.zig | 13 +++-- src/apprt/gtk/Surface.zig | 7 +++ 3 files changed, 87 insertions(+), 40 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 98c344927..7e6aa2d0d 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -44,6 +44,13 @@ const log = std.log.scoped(.surface); // The renderer implementation to use. const Renderer = renderer.Renderer; +/// Minimum window size in cells. This is used to prevent the window from +/// 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; + /// Allocator alloc: Allocator, @@ -252,6 +259,8 @@ const DerivedConfig = struct { window_padding_left: u32, window_padding_right: u32, window_padding_balance: bool, + window_height: u32, + window_width: u32, title: ?[:0]const u8, title_report: bool, links: []Link, @@ -313,6 +322,8 @@ const DerivedConfig = struct { .window_padding_left = config.@"window-padding-x".top_left, .window_padding_right = config.@"window-padding-x".bottom_right, .window_padding_balance = config.@"window-padding-balance", + .window_height = config.@"window-height", + .window_width = config.@"window-width", .title = config.title, .title_report = config.@"title-report", .links = links, @@ -419,9 +430,6 @@ pub fn init( font_size, ); - // Pre-calculate our initial cell size ourselves. - const cell_size = font_grid.cellSize(); - // Build our size struct which has all the sizes we need. const size: renderer.Size = size: { var size: renderer.Size = .{ @@ -577,12 +585,6 @@ pub fn init( .{ .width = size.cell.width, .height = size.cell.height }, ); - // Set a minimum size that is cols=10 h=4. This matches Mac's Terminal.app - // but is otherwise somewhat arbitrary. - - const min_window_width_cells: u32 = 10; - const min_window_height_cells: u32 = 4; - _ = try rt_app.performAction( .{ .surface = self }, .size_limit, @@ -629,34 +631,11 @@ pub fn init( // 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", min_window_height_cells) * cell_size.height; - const width = @max(config.@"window-width", min_window_width_cells) * cell_size.width; - 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))) + - size.padding.left + - size.padding.right; - const final_height: u32 = - @as(u32, @intFromFloat(@ceil(height_f32 / scale.y))) + - size.padding.top + - size.padding.bottom; - - _ = rt_app.performAction( - .{ .surface = self }, - .initial_size, - .{ .width = final_width, .height = final_height }, - ) catch |err| { - // We don't treat this as a fatal error because not setting - // an initial size shouldn't stop our terminal from working. - log.warn("unable to set initial window size: {s}", .{err}); - }; - } + self.recomputeInitialSize() catch |err| { + // We don't treat this as a fatal error because not setting + // an initial size shouldn't stop our terminal from working. + log.warn("unable to set initial window size: {}", .{err}); + }; if (config.title) |title| { _ = try rt_app.performAction( @@ -1220,6 +1199,53 @@ pub fn updateConfig( ); } +const InitialSizeError = error{ + ContentScaleUnavailable, + AppActionFailed, +}; + +/// Recalculate the initial size of the window based on the +/// configuration and invoke the apprt `initial_size` action if +/// necessary. +fn recomputeInitialSize( + self: *Surface, +) InitialSizeError!void { + // Both width and height must be set for this to work, as + // documented on the config options. + if (self.config.window_height <= 0 or + self.config.window_width <= 0) return; + + const scale = self.rt_surface.getContentScale() catch + return error.ContentScaleUnavailable; + const height = @max( + self.config.window_height, + min_window_height_cells, + ) * self.size.cell.height; + const width = @max( + self.config.window_width, + min_window_width_cells, + ) * self.size.cell.width; + 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))) + + self.size.padding.left + + self.size.padding.right; + const final_height: u32 = + @as(u32, @intFromFloat(@ceil(height_f32 / scale.y))) + + self.size.padding.top + + self.size.padding.bottom; + + _ = self.rt_app.performAction( + .{ .surface = self }, + .initial_size, + .{ .width = final_width, .height = final_height }, + ) catch return error.AppActionFailed; +} + /// Returns true if the terminal has a selection. pub fn hasSelection(self: *const Surface) bool { self.renderer_state.mutex.lock(); @@ -1479,6 +1505,13 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void { // Notify the terminal self.io.queueMessage(.{ .resize = self.size }, .unlocked); + // Update our terminal default size if necessary. + self.recomputeInitialSize() catch |err| { + // We don't treat this as a fatal error because not setting + // an initial size shouldn't stop our terminal from working. + log.warn("unable to recompute initial window size: {}", .{err}); + }; + // Notify the window _ = try self.rt_app.performAction( .{ .surface = self }, diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 20b86707e..02a8a0a81 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -140,9 +140,16 @@ pub const Action = union(Key) { /// Sets a size limit (in pixels) for the target terminal. size_limit: SizeLimit, - /// Specifies the initial size of the target terminal. This will be - /// sent only during the initialization of a surface. If it is received - /// after the surface is initialized it should be ignored. + /// Specifies the initial size of the target terminal. + /// + /// This may be sent once during the initialization of a surface + /// (as part of the init call) to indicate the initial size requested + /// for the window if it is not maximized or fullscreen. + /// + /// This may also be sent at any time after the surface is initialized + /// to note the new "default size" of the window. This should in general + /// be ignored, but may be useful if the apprt wants to support + /// a "return to default size" action. initial_size: InitialSize, /// The cell size has changed to the given dimensions in pixels. diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index dc8b11d31..34bf0f069 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -897,6 +897,13 @@ pub fn getSize(self: *const Surface) !apprt.SurfaceSize { } pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void { + // If we've already become realized once then we ignore this + // request. The apprt initial_size action should only modify + // the physical size of the window during initialization. + // Subsequent actions are only informative in case we want to + // implement a "return to default size" action later. + if (self.realized) return; + // If we are within a split, do not set the size. if (self.container.split() != null) return;