From 7c77133a8316880fef27d76393d0609f0a6c1416 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 18 Jul 2025 13:07:26 -0700 Subject: [PATCH] apprt/gtk-ng: implement size callbacks for surface --- src/apprt/gtk-ng/Surface.zig | 6 +- src/apprt/gtk-ng/class/surface.zig | 116 ++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/apprt/gtk-ng/Surface.zig b/src/apprt/gtk-ng/Surface.zig index 917bae157..ce7b0ced8 100644 --- a/src/apprt/gtk-ng/Surface.zig +++ b/src/apprt/gtk-ng/Surface.zig @@ -41,13 +41,11 @@ pub fn getTitle(self: *Self) ?[:0]const u8 { } pub fn getContentScale(self: *const Self) !apprt.ContentScale { - _ = self; - return .{ .x = 1, .y = 1 }; + return self.surface.getContentScale(); } pub fn getSize(self: *const Self) !apprt.SurfaceSize { - _ = self; - return .{ .width = 800, .height = 600 }; + return self.surface.getSize(); } pub fn getCursorPos(self: *const Self) !apprt.CursorPos { diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index d88748fd1..0331a3d3f 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -74,6 +74,9 @@ pub const Surface = extern struct { // eventually. core_surface: ?*CoreSurface = null, + /// Cached metrics for libghostty callbacks + size: apprt.SurfaceSize, + pub var offset: c_int = 0; }; @@ -103,6 +106,58 @@ pub const Surface = extern struct { //--------------------------------------------------------------- // Libghostty Callbacks + pub fn getContentScale(self: *Self) apprt.ContentScale { + const priv = self.private(); + const gl_area = priv.gl_area; + + const gtk_scale: f32 = scale: { + const widget = gl_area.as(gtk.Widget); + // Future: detect GTK version 4.12+ and use gdk_surface_get_scale so we + // can support fractional scaling. + const scale = widget.getScaleFactor(); + if (scale <= 0) { + log.warn("gtk_widget_get_scale_factor returned a non-positive number: {}", .{scale}); + break :scale 1.0; + } + break :scale @floatFromInt(scale); + }; + + // Also scale using font-specific DPI, which is often exposed to the user + // via DE accessibility settings (see https://docs.gtk.org/gtk4/class.Settings.html). + const xft_dpi_scale = xft_scale: { + // gtk-xft-dpi is font DPI multiplied by 1024. See + // https://docs.gtk.org/gtk4/property.Settings.gtk-xft-dpi.html + const settings = gtk.Settings.getDefault() orelse break :xft_scale 1.0; + var value = std.mem.zeroes(gobject.Value); + defer value.unset(); + _ = value.init(gobject.ext.typeFor(c_int)); + settings.as(gobject.Object).getProperty("gtk-xft-dpi", &value); + const gtk_xft_dpi = value.getInt(); + + // Use a value of 1.0 for the XFT DPI scale if the setting is <= 0 + // See: + // https://gitlab.gnome.org/GNOME/libadwaita/-/commit/a7738a4d269bfdf4d8d5429ca73ccdd9b2450421 + // https://gitlab.gnome.org/GNOME/libadwaita/-/commit/9759d3fd81129608dd78116001928f2aed974ead + if (gtk_xft_dpi <= 0) { + log.warn("gtk-xft-dpi was not set, using default value", .{}); + break :xft_scale 1.0; + } + + // As noted above gtk-xft-dpi is multiplied by 1024, so we divide by + // 1024, then divide by the default value (96) to derive a scale. Note + // gtk-xft-dpi can be fractional, so we use floating point math here. + const xft_dpi: f32 = @as(f32, @floatFromInt(gtk_xft_dpi)) / 1024.0; + break :xft_scale xft_dpi / 96.0; + }; + + const scale = gtk_scale * xft_dpi_scale; + return .{ .x = scale, .y = scale }; + } + + pub fn getSize(self: *Self) apprt.SurfaceSize { + return self.private().size; + } + pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap { _ = self; @@ -153,8 +208,14 @@ pub const Surface = extern struct { const priv = self.private(); - // Initialize our apprt surface. + // Initialize some private fields so they aren't undefined priv.rt_surface = .{ .surface = self }; + priv.size = .{ + // Funky numbers on purpose so they stand out if for some reason + // our size doesn't get properly set. + .width = 111, + .height = 111, + }; // If our configuration is null then we get the configuration // from the application. @@ -196,6 +257,13 @@ pub const Surface = extern struct { self, .{}, ); + _ = gtk.GLArea.signals.resize.connect( + gl_area, + *Self, + glareaResize, + self, + .{}, + ); } fn dispose(self: *Self) callconv(.C) void { @@ -303,6 +371,52 @@ pub const Surface = extern struct { return 1; } + fn glareaResize( + gl_area: *gtk.GLArea, + width: c_int, + height: c_int, + self: *Surface, + ) callconv(.c) void { + // Some debug output to help understand what GTK is telling us. + { + const widget = gl_area.as(gtk.Widget); + const scale_factor = widget.getScaleFactor(); + const window_scale_factor = scale: { + const root = widget.getRoot() orelse break :scale 0; + const gtk_native = root.as(gtk.Native); + const gdk_surface = gtk_native.getSurface() orelse break :scale 0; + break :scale gdk_surface.getScaleFactor(); + }; + + log.debug("gl resize width={} height={} scale={} window_scale={}", .{ + width, + height, + scale_factor, + window_scale_factor, + }); + } + + // Store our cached size + const priv = self.private(); + priv.size = .{ + .width = @intCast(width), + .height = @intCast(height), + }; + + // If our surface is realize, we send callbacks. + if (priv.core_surface) |surface| { + // We also update the content scale because there is no signal for + // content scale change and it seems to trigger a resize event. + surface.contentScaleCallback(self.getContentScale()) catch |err| { + log.warn("error in content scale callback err={}", .{err}); + }; + + surface.sizeCallback(priv.size) catch |err| { + log.warn("error in size callback err={}", .{err}); + }; + } + } + const RealizeError = Allocator.Error || error{ GLAreaError, RendererError,