1 Commits

Author SHA1 Message Date
Tristan Partin
721ab5d134 Things for gtk-ng
Signed-off-by: Tristan Partin <tristan@partin.io>
2025-07-23 02:02:48 -05:00
7 changed files with 376 additions and 16 deletions

View File

@@ -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();
}

View File

@@ -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);
},
}
}

View File

@@ -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",
});
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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 {}
}
};
};
};
}