apprt/gtk-ng: surface inheritance, new window

This makes the `new_window` action properly inherit properties from the
parent surface that initiated the action. Today, that is only the pwd
and font size.
This commit is contained in:
Mitchell Hashimoto
2025-07-25 10:00:01 -07:00
parent 787960e56d
commit 830d49c185
4 changed files with 98 additions and 5 deletions

View File

@ -1085,9 +1085,7 @@ const Action = struct {
self: *Application,
parent: ?*CoreSurface,
) !void {
_ = parent;
const win = Window.new(self);
const win = Window.new(self, parent);
gtk.Window.present(win.as(gtk.Window));
}

View File

@ -9,6 +9,7 @@ const gobject = @import("gobject");
const gtk = @import("gtk");
const apprt = @import("../../../apprt.zig");
const font = @import("../../../font/main.zig");
const input = @import("../../../input.zig");
const internal_os = @import("../../../os/main.zig");
const renderer = @import("../../../renderer.zig");
@ -79,6 +80,25 @@ pub const Surface = extern struct {
);
};
pub const @"font-size-request" = struct {
pub const name = "font-size-request";
const impl = gobject.ext.defineProperty(
name,
Self,
?*font.face.DesiredSize,
.{
.nick = "Desired Font Size",
.blurb = "The desired font size, only affects initialization.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"font_size_request",
),
},
);
};
pub const focused = struct {
pub const name = "focused";
const impl = gobject.ext.defineProperty(
@ -261,6 +281,10 @@ pub const Surface = extern struct {
/// if `Application.transient_cgroup_base` is set.
cgroup_path: ?[]const u8 = null,
/// The requested font size. This only applies to initialization
/// and has no effect later.
font_size_request: ?*font.face.DesiredSize = null,
/// The mouse shape to show for the surface.
mouse_shape: terminal.MouseShape = .default,
@ -273,6 +297,10 @@ pub const Surface = extern struct {
/// The current working directory. This has to be reported externally,
/// usually by shell integration which then talks to libghostty
/// which triggers this property.
///
/// If this is set prior to initialization then the surface will
/// start in this pwd. If it is set after, it has no impact on the
/// core surface.
pwd: ?[:0]const u8 = null,
/// The title of this surface, if any has been set.
@ -349,6 +377,38 @@ pub const Surface = extern struct {
return &priv.rt_surface;
}
/// Set the parent of this surface. This will extract the information
/// required to initialize this surface with the proper values but doesn't
/// retain any memory.
///
/// If the surface is already realized this does nothing.
pub fn setParent(
self: *Self,
parent: *CoreSurface,
) void {
const priv = self.private();
// This is a mistake! We can only set a parent before surface
// realization. We log this because this is probably a logic error.
if (priv.core_surface != null) {
log.warn("setParent called after surface is already realized", .{});
return;
}
// Setup our font size
const font_size_ptr = glib.ext.create(font.face.DesiredSize);
errdefer glib.ext.destroy(font_size_ptr);
font_size_ptr.* = parent.font_size;
priv.font_size_request = font_size_ptr;
self.as(gobject.Object).notifyByPspec(properties.@"font-size-request".impl.param_spec);
// Setup our pwd
if (parent.rt_surface.surface.getPwd()) |pwd| {
priv.pwd = glib.ext.dupeZ(u8, pwd);
self.as(gobject.Object).notifyByPspec(properties.pwd.impl.param_spec);
}
}
/// Force the surface to redraw itself. Ghostty often will only redraw
/// the terminal in reaction to internal changes. If there are external
/// events that invalidate the surface, such as the widget moving parents,
@ -1029,6 +1089,10 @@ pub const Surface = extern struct {
glib.free(@constCast(@ptrCast(v)));
priv.mouse_hover_url = null;
}
if (priv.font_size_request) |v| {
glib.ext.destroy(v);
priv.font_size_request = null;
}
if (priv.pwd) |v| {
glib.free(@constCast(@ptrCast(v)));
priv.pwd = null;
@ -1053,6 +1117,11 @@ pub const Surface = extern struct {
return self.private().title;
}
/// Returns the pwd property without a copy.
pub fn getPwd(self: *Self) ?[:0]const u8 {
return self.private().pwd;
}
fn propConfig(
self: *Self,
_: *gobject.ParamSpec,
@ -1893,6 +1962,10 @@ pub const Surface = extern struct {
);
defer config.deinit();
// Properties that can impact surface init
if (priv.font_size_request) |size| config.@"font-size" = size.points;
if (priv.pwd) |pwd| config.@"working-directory" = pwd;
// Initialize the surface
surface.init(
alloc,
@ -1996,6 +2069,7 @@ pub const Surface = extern struct {
gobject.ext.registerProperties(class, &.{
properties.config.impl,
properties.@"child-exited".impl,
properties.@"font-size-request".impl,
properties.focused.impl,
properties.@"mouse-shape".impl,
properties.@"mouse-hidden".impl,

View File

@ -4,6 +4,7 @@ const adw = @import("adw");
const gobject = @import("gobject");
const gtk = @import("gtk");
const CoreSurface = @import("../../../Surface.zig");
const gresource = @import("../build/gresource.zig");
const Common = @import("../class.zig").Common;
const Application = @import("application.zig").Application;
@ -30,8 +31,17 @@ pub const Window = extern struct {
pub var offset: c_int = 0;
};
pub fn new(app: *Application) *Self {
return gobject.ext.newInstance(Self, .{ .application = app });
pub fn new(app: *Application, parent_: ?*CoreSurface) *Self {
const self = gobject.ext.newInstance(Self, .{
.application = app,
});
if (parent_) |parent| {
const priv = self.private();
priv.surface.setParent(parent);
}
return self;
}
fn init(self: *Self, _: *Class) callconv(.C) void {

View File

@ -1,5 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const build_config = @import("../build_config.zig");
const options = @import("main.zig").options;
const Metrics = @import("main.zig").Metrics;
const config = @import("../config.zig");
@ -55,6 +56,16 @@ pub const DesiredSize = struct {
// 1 point = 1/72 inch
return @intFromFloat(@round((self.points * @as(f32, @floatFromInt(self.ydpi))) / 72));
}
/// 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(
DesiredSize,
.{ .name = "GhosttyFontDesiredSize" },
),
.none => void,
};
};
/// A font variation setting. The best documentation for this I know of