summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTristan Partin <tristan@partin.io>2025-07-23 01:57:45 -0500
committerTristan Partin <tristan@partin.io>2025-07-23 02:02:48 -0500
commit721ab5d134f4519dc4a2b27147044a509b187a6e (patch)
treec14e03b742523ba3ea24282523c1c387db40e593
parenteae4124ed852cb1b37637241055fe3e0b640986c (diff)
Things for gtk-ngtristan957/gtk-ng
Signed-off-by: Tristan Partin <tristan@partin.io>
-rw-r--r--src/apprt/gtk-ng/Surface.zig8
-rw-r--r--src/apprt/gtk-ng/class/application.zig17
-rw-r--r--src/apprt/gtk-ng/class/config.zig25
-rw-r--r--src/apprt/gtk-ng/class/surface.zig31
-rw-r--r--src/apprt/gtk-ng/class/window.zig125
-rw-r--r--src/apprt/gtk-ng/ui/1.2/surface.blp1
-rw-r--r--src/apprt/gtk-ng/ui/1.5/window.blp185
7 files changed, 376 insertions, 16 deletions
diff --git a/src/apprt/gtk-ng/Surface.zig b/src/apprt/gtk-ng/Surface.zig
index 7613abd2d..f32d264cd 100644
--- a/src/apprt/gtk-ng/Surface.zig
+++ b/src/apprt/gtk-ng/Surface.zig
@@ -39,10 +39,18 @@ pub fn cgroup(self: *Self) ?[]const u8 {
return self.surface.cgroupPath();
}
+pub fn setPwd(self: *Self, pwd: [:0]const u8) void {
+ return self.surface.setPwd(pwd);
+}
+
pub fn getTitle(self: *Self) ?[:0]const u8 {
return self.surface.getTitle();
}
+pub fn setTitle(self: *Self, title: [:0]const u8) void {
+ return self.surface.setTitle(title);
+}
+
pub fn getContentScale(self: *const Self) !apprt.ContentScale {
return self.surface.getContentScale();
}
diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig
index d3e02e28d..41b550c7e 100644
--- a/src/apprt/gtk-ng/class/application.zig
+++ b/src/apprt/gtk-ng/class/application.zig
@@ -1086,6 +1086,7 @@ const Action = struct {
const win = Window.new(self);
gtk.Window.present(win.as(gtk.Window));
+ win.setupInitialFocus();
}
pub fn pwd(
@@ -1095,13 +1096,7 @@ const Action = struct {
switch (target) {
.app => log.warn("pwd to app is unexpected", .{}),
.surface => |surface| {
- var v = gobject.ext.Value.newFrom(value.pwd);
- defer v.unset();
- gobject.Object.setProperty(
- surface.rt_surface.gobj().as(gobject.Object),
- "pwd",
- &v,
- );
+ surface.rt_surface.setPwd(value.pwd);
},
}
}
@@ -1130,13 +1125,7 @@ const Action = struct {
switch (target) {
.app => log.warn("set_title to app is unexpected", .{}),
.surface => |surface| {
- var v = gobject.ext.Value.newFrom(value.title);
- defer v.unset();
- gobject.Object.setProperty(
- surface.rt_surface.gobj().as(gobject.Object),
- "title",
- &v,
- );
+ surface.rt_surface.setTitle(value.title);
},
}
}
diff --git a/src/apprt/gtk-ng/class/config.zig b/src/apprt/gtk-ng/class/config.zig
index e40602f47..ace1c7bc1 100644
--- a/src/apprt/gtk-ng/class/config.zig
+++ b/src/apprt/gtk-ng/class/config.zig
@@ -52,6 +52,24 @@ pub const Config = extern struct {
},
);
+ pub const @"gtk-wide-tabs" = gobject.ext.defineProperty(
+ "gtk-wide-tabs",
+ Self,
+ bool,
+ .{
+ .nick = "gtk-wide-tabs",
+ .blurb = null,
+ .default = false,
+ .accessor = gobject.ext.typedAccessor(
+ Self,
+ bool,
+ .{
+ .getter = Self.gtkWideTabs,
+ },
+ ),
+ },
+ );
+
pub const @"has-diagnostics" = gobject.ext.defineProperty(
"has-diagnostics",
Self,
@@ -105,6 +123,12 @@ pub const Config = extern struct {
return &self.private().config;
}
+ /// Returns the current value of gtk-wide-tabs.
+ pub fn gtkWideTabs(self: *Self) bool {
+ const config = self.get();
+ return config.@"gtk-wide-tabs";
+ }
+
/// Returns whether this configuration has any diagnostics.
pub fn hasDiagnostics(self: *Self) bool {
const config = self.get();
@@ -163,6 +187,7 @@ pub const Config = extern struct {
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
gobject.ext.registerProperties(class, &.{
properties.@"diagnostics-buffer",
+ properties.@"gtk-wide-tabs",
properties.@"has-diagnostics",
});
}
diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig
index 9a64cd101..7d7482436 100644
--- a/src/apprt/gtk-ng/class/surface.zig
+++ b/src/apprt/gtk-ng/class/surface.zig
@@ -1085,11 +1085,42 @@ pub const Surface = extern struct {
//---------------------------------------------------------------
// Properties
+ /// Returns the pwd property without a copy.
+ pub fn getPwd(self: *Self) ?[:0]const u8 {
+ return self.private().pwd;
+ }
+
+ /// Set the pwd property.
+ pub fn setPwd(self: *Self, pwd: [:0]const u8) void {
+ const priv = self.private();
+
+ if (priv.title) |v| {
+ glib.free(@constCast(@ptrCast(v)));
+ priv.title = null;
+ }
+
+ priv.pwd = std.mem.span(glib.strdup(pwd));
+ self.as(gobject.Object).notifyByPspec(properties.pwd.impl.param_spec);
+ }
+
/// Returns the title property without a copy.
pub fn getTitle(self: *Self) ?[:0]const u8 {
return self.private().title;
}
+ /// Set the title property.
+ pub fn setTitle(self: *Self, title: [:0]const u8) void {
+ const priv = self.private();
+
+ if (priv.title) |v| {
+ glib.free(@constCast(@ptrCast(v)));
+ priv.title = null;
+ }
+
+ priv.title = std.mem.span(glib.strdup(title));
+ self.as(gobject.Object).notifyByPspec(properties.title.impl.param_spec);
+ }
+
fn propConfig(
self: *Self,
_: *gobject.ParamSpec,
diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig
index 6e30389a0..6e0fcf375 100644
--- a/src/apprt/gtk-ng/class/window.zig
+++ b/src/apprt/gtk-ng/class/window.zig
@@ -8,6 +8,7 @@ const gresource = @import("../build/gresource.zig");
const Common = @import("../class.zig").Common;
const Application = @import("application.zig").Application;
const Surface = @import("surface.zig").Surface;
+const Config = @import("config.zig").Config;
const log = std.log.scoped(.gtk_ghostty_window);
@@ -23,7 +24,34 @@ pub const Window = extern struct {
.private = .{ .Type = Private, .offset = &Private.offset },
});
+ pub const properties = struct {
+ pub const config = struct {
+ pub const name = "config";
+ const impl = gobject.ext.defineProperty(
+ name,
+ Self,
+ ?*Config,
+ .{
+ .nick = "Config",
+ .blurb = "The configuration that this window is using.",
+ .accessor = gobject.ext.privateFieldAccessor(
+ Self,
+ Private,
+ &Private.offset,
+ "config",
+ ),
+ },
+ );
+ };
+ };
+
const Private = struct {
+ /// The configuration that this window is using.
+ config: ?*Config = null,
+
+ /// The window title widget.
+ window_title: *adw.WindowTitle = undefined,
+
/// The surface in the view.
surface: *Surface = undefined,
@@ -34,10 +62,19 @@ pub const Window = extern struct {
return gobject.ext.newInstance(Self, .{ .application = app });
}
+ pub fn setupInitialFocus(self: *Self) void {
+ _ = self.private().surface.as(gtk.Widget).grabFocus();
+ }
+
fn init(self: *Self, _: *Class) callconv(.C) void {
gtk.Widget.initTemplate(self.as(gtk.Widget));
- const surface = self.private().surface;
+ const priv = self.private();
+
+ const app = Application.default();
+ priv.config = app.getConfig();
+
+ const surface = priv.surface;
_ = Surface.signals.@"close-request".connect(
surface,
*Self,
@@ -45,6 +82,32 @@ pub const Window = extern struct {
self,
.{},
);
+ _ = gobject.Object.signals.notify.connect(
+ surface,
+ *Self,
+ &surfaceNotifyHasFocus,
+ self,
+ .{ .detail = "has-focus" },
+ );
+ self.setupSurfacePropertyConnections(surface);
+ }
+
+ fn setupSurfacePropertyConnections(self: *Self, surface: *Surface) void {
+ _ = gobject.Object.signals.notify.connect(
+ surface,
+ *Self,
+ &surfaceNotifyTitle,
+ self,
+ .{ .detail = "title" },
+ );
+
+ _ = gobject.Object.signals.notify.connect(
+ surface,
+ *Self,
+ &surfaceNotifyPwd,
+ self,
+ .{ .detail = "pwd" },
+ );
}
//---------------------------------------------------------------
@@ -56,12 +119,30 @@ pub const Window = extern struct {
getGObjectType(),
);
+ const priv = self.private();
+ if (priv.config) |v| {
+ v.unref();
+ priv.config = null;
+ }
+
gobject.Object.virtual_methods.dispose.call(
Class.parent,
self.as(Parent),
);
}
+ /// Set the title of the window.
+ fn setTitle(self: *Self, title: [:0]const u8) void {
+ const window_title = self.private().window_title;
+ window_title.setTitle(title);
+ }
+
+ /// Set the subtitle of the window.
+ fn setSubtitle(self: *Self, subtitle: [:0]const u8) void {
+ const window_title = self.private().window_title;
+ window_title.setSubtitle(subtitle);
+ }
+
//---------------------------------------------------------------
// Signal handlers
@@ -77,6 +158,42 @@ pub const Window = extern struct {
self.as(gtk.Window).close();
}
+ fn surfaceNotifyHasFocus(surface: *Surface, _: *gobject.ParamSpec, self: *Self) callconv(.c) void {
+ assert(surface == self.private().surface);
+
+ self.setTitle(surface.getTitle().?);
+
+ const subtitle: [:0]const u8 = switch (Application.default().getConfig().get().@"window-subtitle") {
+ .@"working-directory" => surface.getPwd() orelse "",
+ .false => "",
+ };
+ self.setSubtitle(subtitle);
+ }
+
+ fn surfaceNotifyTitle(surface: *Surface, _: *gobject.ParamSpec, self: *Self) callconv(.c) void {
+ assert(surface == self.private().surface);
+
+ if (surface.as(gtk.Widget).grabFocus() == 0) {
+ return;
+ }
+
+ self.setTitle(surface.getTitle().?);
+ }
+
+ fn surfaceNotifyPwd(surface: *Surface, _: *gobject.ParamSpec, self: *Self) callconv(.c) void {
+ assert(surface == self.private().surface);
+
+ if (surface.as(gtk.Widget).grabFocus() == 0) {
+ return;
+ }
+
+ if (Application.default().getConfig().get().@"window-subtitle" != .@"working-directory") {
+ return;
+ }
+
+ self.setSubtitle(surface.getPwd() orelse "");
+ }
+
const C = Common(Self, Private);
pub const as = C.as;
pub const ref = C.ref;
@@ -100,8 +217,14 @@ pub const Window = extern struct {
);
// Bindings
+ class.bindTemplateChildPrivate("window_title", .{});
class.bindTemplateChildPrivate("surface", .{});
+ // Properties
+ gobject.ext.registerProperties(class, &.{
+ properties.config.impl,
+ });
+
// Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
}
diff --git a/src/apprt/gtk-ng/ui/1.2/surface.blp b/src/apprt/gtk-ng/ui/1.2/surface.blp
index 3d17b1d56..1b1d4b756 100644
--- a/src/apprt/gtk-ng/ui/1.2/surface.blp
+++ b/src/apprt/gtk-ng/ui/1.2/surface.blp
@@ -2,6 +2,7 @@ using Gtk 4.0;
using Adw 1;
template $GhosttySurface: Adw.Bin {
+ focusable: true;
// We need to wrap our Overlay one more time because if you bind a
// direct child of your widget to a property, it will double free:
// https://gitlab.gnome.org/GNOME/gtk/-/blob/847571a1e314aba79260e4ef282e2ed9ba91a0d9/gtk/gtkwidget.c#L11423-11425
diff --git a/src/apprt/gtk-ng/ui/1.5/window.blp b/src/apprt/gtk-ng/ui/1.5/window.blp
index 930ee53db..5dc94f58c 100644
--- a/src/apprt/gtk-ng/ui/1.5/window.blp
+++ b/src/apprt/gtk-ng/ui/1.5/window.blp
@@ -1,9 +1,192 @@
using Gtk 4.0;
using Adw 1;
+menu split_menu {
+ item {
+ label: _("Split Up");
+ action: "win.split-up";
+ }
+
+ item {
+ label: _("Split Down");
+ action: "win.split-down";
+ }
+
+ item {
+ label: _("Split Left");
+ action: "win.split-left";
+ }
+
+ item {
+ label: _("Split Right");
+ action: "win.split-right";
+ }
+}
+
+menu main_menu {
+ section {
+ item {
+ label: _("Copy");
+ action: "win.copy";
+ }
+
+ item {
+ label: _("Paste");
+ action: "win.paste";
+ }
+ }
+
+ section {
+ item {
+ label: _("New Window");
+ action: "win.new-window";
+ }
+
+ item {
+ label: _("Close Window");
+ action: "win.close";
+ }
+ }
+
+ section {
+ item {
+ label: _("New Tab");
+ action: "win.new-tab";
+ }
+
+ item {
+ label: _("Close Tab");
+ action: "win.close-tab";
+ }
+ }
+
+ section {
+ submenu {
+ label: _("Split");
+
+ item {
+ label: _("Change Title…");
+ action: "win.prompt-title";
+ }
+
+ item {
+ label: _("Split Up");
+ action: "win.split-up";
+ }
+
+ item {
+ label: _("Split Down");
+ action: "win.split-down";
+ }
+
+ item {
+ label: _("Split Left");
+ action: "win.split-left";
+ }
+
+ item {
+ label: _("Split Right");
+ action: "win.split-right";
+ }
+ }
+ }
+
+ section {
+ item {
+ label: _("Clear");
+ action: "win.clear";
+ }
+
+ item {
+ label: _("Reset");
+ action: "win.reset";
+ }
+ }
+
+ section {
+ item {
+ label: _("Command Palette");
+ action: "win.toggle-command-palette";
+ }
+
+ item {
+ label: _("Terminal Inspector");
+ action: "win.toggle-inspector";
+ }
+
+ item {
+ label: _("Open Configuration");
+ action: "app.open-config";
+ }
+
+ item {
+ label: _("Reload Configuration");
+ action: "app.reload-config";
+ }
+ }
+
+ section {
+ item {
+ label: _("About Ghostty");
+ action: "win.about";
+ }
+
+ item {
+ label: _("Quit");
+ action: "app.quit";
+ }
+ }
+}
+
template $GhosttyWindow: Adw.ApplicationWindow {
default-width: 800;
default-height: 600;
- content: $GhosttySurface surface {};
+ content: Adw.TabOverview tab_overview {
+ view: tab_view;
+
+ child: Adw.ToolbarView {
+ content: Gtk.Box {
+ orientation: vertical;
+
+ Adw.HeaderBar {
+ title-widget: Adw.WindowTitle window_title {
+ title: "Ghostty";
+ };
+
+ [start]
+ Adw.SplitButton {
+ icon-name: "tab-new-symbolic";
+ menu-model: split_menu;
+ tooltip-text: _("New Tab");
+ dropdown-tooltip: _("New Split");
+ }
+
+ [end]
+ Gtk.Box {
+ Gtk.ToggleButton {
+ icon-name: "view-grid-symbolic";
+ tooltip-text: _("View Open Tabs");
+ active: bind tab_overview.open bidirectional;
+ }
+
+ Gtk.MenuButton {
+ icon-name: "open-menu-symbolic";
+ menu-model: main_menu;
+ tooltip-text: _("Main Menu");
+ }
+ }
+ }
+
+ Adw.TabBar {
+ view: tab_view;
+ expand-tabs: bind (template.config as <$GhosttyConfig>).gtk-wide-tabs;
+ }
+
+ Adw.TabView tab_view {
+ $GhosttySurface surface {}
+ }
+ };
+ };
+ };
}