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.
This commit is contained in:
Mitchell Hashimoto
2025-02-28 09:57:18 -08:00
parent a1437e5579
commit b0f1f19da0
3 changed files with 87 additions and 40 deletions

View File

@ -44,6 +44,13 @@ const log = std.log.scoped(.surface);
// The renderer implementation to use. // The renderer implementation to use.
const Renderer = renderer.Renderer; 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 /// Allocator
alloc: Allocator, alloc: Allocator,
@ -252,6 +259,8 @@ const DerivedConfig = struct {
window_padding_left: u32, window_padding_left: u32,
window_padding_right: u32, window_padding_right: u32,
window_padding_balance: bool, window_padding_balance: bool,
window_height: u32,
window_width: u32,
title: ?[:0]const u8, title: ?[:0]const u8,
title_report: bool, title_report: bool,
links: []Link, links: []Link,
@ -313,6 +322,8 @@ const DerivedConfig = struct {
.window_padding_left = config.@"window-padding-x".top_left, .window_padding_left = config.@"window-padding-x".top_left,
.window_padding_right = config.@"window-padding-x".bottom_right, .window_padding_right = config.@"window-padding-x".bottom_right,
.window_padding_balance = config.@"window-padding-balance", .window_padding_balance = config.@"window-padding-balance",
.window_height = config.@"window-height",
.window_width = config.@"window-width",
.title = config.title, .title = config.title,
.title_report = config.@"title-report", .title_report = config.@"title-report",
.links = links, .links = links,
@ -419,9 +430,6 @@ pub fn init(
font_size, 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. // Build our size struct which has all the sizes we need.
const size: renderer.Size = size: { const size: renderer.Size = size: {
var size: renderer.Size = .{ var size: renderer.Size = .{
@ -577,12 +585,6 @@ pub fn init(
.{ .width = size.cell.width, .height = size.cell.height }, .{ .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( _ = try rt_app.performAction(
.{ .surface = self }, .{ .surface = self },
.size_limit, .size_limit,
@ -629,34 +631,11 @@ pub fn init(
// Note: it is important to do this after the renderer is setup above. // Note: it is important to do this after the renderer is setup above.
// This allows the apprt to fully initialize the surface before we // This allows the apprt to fully initialize the surface before we
// start messing with the window. // start messing with the window.
if (config.@"window-height" > 0 and config.@"window-width" > 0) init: { self.recomputeInitialSize() catch |err| {
const scale = rt_surface.getContentScale() catch break :init; // We don't treat this as a fatal error because not setting
const height = @max(config.@"window-height", min_window_height_cells) * cell_size.height; // an initial size shouldn't stop our terminal from working.
const width = @max(config.@"window-width", min_window_width_cells) * cell_size.width; log.warn("unable to set initial window size: {}", .{err});
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});
};
}
if (config.title) |title| { if (config.title) |title| {
_ = try rt_app.performAction( _ = 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. /// Returns true if the terminal has a selection.
pub fn hasSelection(self: *const Surface) bool { pub fn hasSelection(self: *const Surface) bool {
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
@ -1479,6 +1505,13 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
// Notify the terminal // Notify the terminal
self.io.queueMessage(.{ .resize = self.size }, .unlocked); 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 // Notify the window
_ = try self.rt_app.performAction( _ = try self.rt_app.performAction(
.{ .surface = self }, .{ .surface = self },

View File

@ -140,9 +140,16 @@ pub const Action = union(Key) {
/// Sets a size limit (in pixels) for the target terminal. /// Sets a size limit (in pixels) for the target terminal.
size_limit: SizeLimit, size_limit: SizeLimit,
/// Specifies the initial size of the target terminal. This will be /// Specifies the initial size of the target terminal.
/// sent only during the initialization of a surface. If it is received ///
/// after the surface is initialized it should be ignored. /// 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, initial_size: InitialSize,
/// The cell size has changed to the given dimensions in pixels. /// The cell size has changed to the given dimensions in pixels.

View File

@ -897,6 +897,13 @@ pub fn getSize(self: *const Surface) !apprt.SurfaceSize {
} }
pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void { 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 we are within a split, do not set the size.
if (self.container.split() != null) return; if (self.container.split() != null) return;