apprt/gtk-ng: initial size apprt action (window-width/height) (#8115)

Simple port. I might add size limits if I get to it, but finished this
and it works so opened it up.
This commit is contained in:
Mitchell Hashimoto
2025-07-31 13:58:34 -07:00
committed by GitHub
5 changed files with 113 additions and 1 deletions

View File

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const build_config = @import("../build_config.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const apprt = @import("../apprt.zig"); const apprt = @import("../apprt.zig");
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");
@ -533,6 +534,16 @@ pub const SizeLimit = extern struct {
pub const InitialSize = extern struct { pub const InitialSize = extern struct {
width: u32, width: u32,
height: u32, height: u32,
/// Make this a valid gobject if we're in a GTK environment.
pub const getGObjectType = switch (build_config.app_runtime) {
.gtk, .@"gtk-ng" => @import("gobject").ext.defineBoxed(
InitialSize,
.{ .name = "GhosttyApprtInitialSize" },
),
.none => void,
};
}; };
pub const CellSize = extern struct { pub const CellSize = extern struct {

View File

@ -501,6 +501,8 @@ pub const Application = extern struct {
.goto_tab => return Action.gotoTab(target, value), .goto_tab => return Action.gotoTab(target, value),
.initial_size => return Action.initialSize(target, value),
.mouse_over_link => Action.mouseOverLink(target, value), .mouse_over_link => Action.mouseOverLink(target, value),
.mouse_shape => Action.mouseShape(target, value), .mouse_shape => Action.mouseShape(target, value),
.mouse_visibility => Action.mouseVisibility(target, value), .mouse_visibility => Action.mouseVisibility(target, value),
@ -548,7 +550,6 @@ pub const Application = extern struct {
.toggle_tab_overview => return Action.toggleTabOverview(target), .toggle_tab_overview => return Action.toggleTabOverview(target),
// Unimplemented but todo on gtk-ng branch // Unimplemented but todo on gtk-ng branch
.initial_size,
.size_limit, .size_limit,
.prompt_title, .prompt_title,
.toggle_command_palette, .toggle_command_palette,
@ -1346,6 +1347,23 @@ const Action = struct {
} }
} }
pub fn initialSize(
target: apprt.Target,
value: apprt.action.InitialSize,
) bool {
switch (target) {
.app => return false,
.surface => |core| {
const surface = core.rt_surface.surface;
surface.setDefaultSize(
value.width,
value.height,
);
return true;
},
}
}
pub fn mouseOverLink( pub fn mouseOverLink(
target: apprt.Target, target: apprt.Target,
value: apprt.action.MouseOverLink, value: apprt.action.MouseOverLink,

View File

@ -16,6 +16,7 @@ const renderer = @import("../../../renderer.zig");
const terminal = @import("../../../terminal/main.zig"); const terminal = @import("../../../terminal/main.zig");
const CoreSurface = @import("../../../Surface.zig"); const CoreSurface = @import("../../../Surface.zig");
const gresource = @import("../build/gresource.zig"); const gresource = @import("../build/gresource.zig");
const ext = @import("../ext.zig");
const adw_version = @import("../adw_version.zig"); const adw_version = @import("../adw_version.zig");
const gtk_key = @import("../key.zig"); const gtk_key = @import("../key.zig");
const ApprtSurface = @import("../Surface.zig"); const ApprtSurface = @import("../Surface.zig");
@ -75,6 +76,20 @@ pub const Surface = extern struct {
); );
}; };
pub const @"default-size" = struct {
pub const name = "default-size";
const impl = gobject.ext.defineProperty(
name,
Self,
?*apprt.action.InitialSize,
.{
.nick = "Default Size",
.blurb = "The default size of the window for this surface.",
.accessor = C.privateBoxedFieldAccessor("default_size"),
},
);
};
pub const @"font-size-request" = struct { pub const @"font-size-request" = struct {
pub const name = "font-size-request"; pub const name = "font-size-request";
const impl = gobject.ext.defineProperty( const impl = gobject.ext.defineProperty(
@ -333,6 +348,9 @@ pub const Surface = extern struct {
/// if `Application.transient_cgroup_base` is set. /// if `Application.transient_cgroup_base` is set.
cgroup_path: ?[]const u8 = null, cgroup_path: ?[]const u8 = null,
/// The default size for a window that embeds this surface.
default_size: ?*apprt.action.InitialSize = null,
/// The requested font size. This only applies to initialization /// The requested font size. This only applies to initialization
/// and has no effect later. /// and has no effect later.
font_size_request: ?*font.face.DesiredSize = null, font_size_request: ?*font.face.DesiredSize = null,
@ -1182,6 +1200,10 @@ pub const Surface = extern struct {
glib.free(@constCast(@ptrCast(v))); glib.free(@constCast(@ptrCast(v)));
priv.mouse_hover_url = null; priv.mouse_hover_url = null;
} }
if (priv.default_size) |v| {
ext.boxedFree(apprt.action.InitialSize, v);
priv.default_size = null;
}
if (priv.font_size_request) |v| { if (priv.font_size_request) |v| {
glib.ext.destroy(v); glib.ext.destroy(v);
priv.font_size_request = null; priv.font_size_request = null;
@ -1223,6 +1245,28 @@ pub const Surface = extern struct {
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec); self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
} }
/// Return the default size, if set.
pub fn getDefaultSize(self: *Self) ?*apprt.action.InitialSize {
const priv = self.private();
return priv.default_size;
}
/// Set the default size for a window that contains this surface.
/// This is up to the embedding widget to respect this. Generally, only
/// the first surface in a window respects this.
pub fn setDefaultSize(self: *Self, width: u32, height: u32) void {
const priv = self.private();
if (priv.default_size) |v| ext.boxedFree(
apprt.action.InitialSize,
v,
);
priv.default_size = ext.boxedCopy(
apprt.action.InitialSize,
&.{ .width = width, .height = height },
);
self.as(gobject.Object).notifyByPspec(properties.@"default-size".impl.param_spec);
}
fn propConfig( fn propConfig(
self: *Self, self: *Self,
_: *gobject.ParamSpec, _: *gobject.ParamSpec,
@ -2182,6 +2226,7 @@ pub const Surface = extern struct {
gobject.ext.registerProperties(class, &.{ gobject.ext.registerProperties(class, &.{
properties.config.impl, properties.config.impl,
properties.@"child-exited".impl, properties.@"child-exited".impl,
properties.@"default-size".impl,
properties.@"font-size-request".impl, properties.@"font-size-request".impl,
properties.focused.impl, properties.focused.impl,
properties.@"mouse-shape".impl, properties.@"mouse-shape".impl,

View File

@ -983,6 +983,13 @@ pub const Window = extern struct {
self, self,
.{}, .{},
); );
_ = gobject.Object.signals.notify.connect(
surface,
*Self,
surfaceDefaultSize,
self,
.{ .detail = "default-size" },
);
} }
fn tabViewPageDetached( fn tabViewPageDetached(
@ -1172,6 +1179,23 @@ pub const Window = extern struct {
// We react to the changes in the propMaximized callback // We react to the changes in the propMaximized callback
} }
fn surfaceDefaultSize(
surface: *Surface,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
const size = surface.getDefaultSize() orelse return;
// We previously gated this on whether this was called before but
// its useful to always set this to whatever the expected value is
// so we can do a "return to default size" later. This call only
// affects the window on first load. It won't resize it again later.
self.as(gtk.Window).setDefaultSize(
@intCast(size.width),
@intCast(size.height),
);
}
fn actionAbout( fn actionAbout(
_: *gio.SimpleAction, _: *gio.SimpleAction,
_: ?*glib.Variant, _: ?*glib.Variant,

View File

@ -9,6 +9,20 @@ const assert = std.debug.assert;
const gobject = @import("gobject"); const gobject = @import("gobject");
const gtk = @import("gtk"); const gtk = @import("gtk");
/// Wrapper around `gobject.boxedCopy` to copy a boxed type `T`.
pub fn boxedCopy(comptime T: type, ptr: *const T) *T {
const copy = gobject.boxedCopy(T.getGObjectType(), ptr);
return @ptrCast(@alignCast(copy));
}
/// Wrapper around `gobject.boxedFree` to free a boxed type `T`.
pub fn boxedFree(comptime T: type, ptr: ?*T) void {
if (ptr) |p| gobject.boxedFree(
T.getGObjectType(),
p,
);
}
/// Wrapper around `gtk.Widget.getAncestor` to get the widget ancestor /// Wrapper around `gtk.Widget.getAncestor` to get the widget ancestor
/// of the given type `T`, or null if it doesn't exist. /// of the given type `T`, or null if it doesn't exist.
pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T { pub fn getAncestor(comptime T: type, widget: *gtk.Widget) ?*T {