From 830d49c185ead6b0f97ccdeb503ce709d461614e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Jul 2025 10:00:01 -0700 Subject: [PATCH] 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. --- src/apprt/gtk-ng/class/application.zig | 4 +- src/apprt/gtk-ng/class/surface.zig | 74 ++++++++++++++++++++++++++ src/apprt/gtk-ng/class/window.zig | 14 ++++- src/font/face.zig | 11 ++++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index f8f850333..f82e91e77 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -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)); } diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index ce9027ad5..5b4c9da1b 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -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, diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig index 82bf6b247..12999bbb9 100644 --- a/src/apprt/gtk-ng/class/window.zig +++ b/src/apprt/gtk-ng/class/window.zig @@ -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 { diff --git a/src/font/face.zig b/src/font/face.zig index fc5118c3d..968ee1302 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -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