apprt/gtk: use a subtitle to mark the current working directory

If the title is already the current working directory, hide the
subtitle. Otherwise show the current working directory, like if
a command is running for instance.

Signed-off-by: Tristan Partin <tristan@partin.io>
This commit is contained in:
Tristan Partin
2024-11-17 13:38:44 -06:00
parent c53532ddb7
commit 4e68e1b288
4 changed files with 81 additions and 8 deletions

View File

@ -346,6 +346,11 @@ cursor: ?*c.GdkCursor = null,
/// pass it to GTK.
title_text: ?[:0]const u8 = null,
/// Our current working directory. We use this value for setting tooltips in
/// the headerbar subtitle if we have focus. When set, the text in this buf
/// will be null-terminated because we need to pass it to GTK.
pwd: ?[:0]const u8 = null,
/// The core surface backing this surface
core_surface: CoreSurface,
@ -624,6 +629,7 @@ fn realize(self: *Surface) !void {
pub fn deinit(self: *Surface) void {
self.init_config.deinit(self.app.core_app.alloc);
if (self.title_text) |title| self.app.core_app.alloc.free(title);
if (self.pwd) |pwd| self.app.core_app.alloc.free(pwd);
// We don't allocate anything if we aren't realized.
if (!self.realized) return;
@ -871,7 +877,7 @@ fn updateTitleLabels(self: *Surface) void {
// I don't know a way around this yet. I've tried re-hiding the
// cursor after setting the title but it doesn't work, I think
// due to some gtk event loop things...
c.gtk_window_set_title(window.window, title.ptr);
window.setTitle(title);
}
}
}
@ -908,11 +914,33 @@ pub fn getTitle(self: *Surface) ?[:0]const u8 {
return null;
}
pub fn setPwd(self: *Surface, pwd: [:0]const u8) !void {
// If we have a tab and are the focused child, then we have to update the tab
/// Update the subtitle of the surface's window if it has one.
fn updateSubtitle(self: *Surface, subtitle: [:0]const u8) void {
const window = self.container.window() orelse return;
window.setSubtitle(subtitle);
}
/// Set the current working directory of the surface.
///
/// In addition, update the tab's tooltip text, and if we are the focused child,
/// update the subtitle of the containing window.
pub fn setPwd(self: *Surface, slice: [:0]const u8) !void {
if (self.container.tab()) |tab| {
tab.setTooltipText(pwd);
tab.setTooltipText(slice);
if (tab.focus_child == self) {
if (self.container.window()) |window| {
window.setSubtitle(slice);
}
}
}
const alloc = self.app.core_app.alloc;
// Failing to set the surface's current working directory is not a big
// deal since we just used our slice parameter which is the same value.
if (self.pwd) |old| alloc.free(old);
self.pwd = alloc.dupeZ(u8, slice) catch null;
}
pub fn setMouseShape(
@ -1860,6 +1888,12 @@ fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) vo
self.unfocused_widget = null;
}
if (self.pwd) |pwd| {
if (self.container.window()) |window| {
window.setSubtitle(pwd);
}
}
// Notify our surface
self.core_surface.focusCallback(true) catch |err| {
log.err("error in focus callback err={}", .{err});

View File

@ -426,6 +426,22 @@ inline fn isAdwWindow(self: *Window) bool {
self.app.config.@"gtk-titlebar";
}
/// Set the title of the window.
pub fn setTitle(self: *Window, title: [:0]const u8) void {
if (self.isAdwWindow() and self.app.config.@"gtk-titlebar") {
self.header.?.setTitle(title);
} else {
c.gtk_window_set_title(self.window, title);
}
}
/// Set the subtitle of the window if it has one.
pub fn setSubtitle(self: *Window, subtitle: [:0]const u8) void {
if (self.isAdwWindow() and self.app.config.@"gtk-titlebar") {
self.header.?.setSubtitle(subtitle);
}
}
/// Add a new tab to this window.
pub fn newTab(self: *Window, parent: ?*CoreSurface) !void {
const alloc = self.app.core_app.alloc;

View File

@ -14,14 +14,15 @@ pub const HeaderBar = union(enum) {
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and
adwaita.enabled(&window.app.config))
{
return initAdw();
return initAdw(window);
}
return initGtk();
}
fn initAdw() HeaderBar {
fn initAdw(window: *Window) HeaderBar {
const headerbar = c.adw_header_bar_new();
c.adw_header_bar_set_title_widget(@ptrCast(headerbar), @ptrCast(c.adw_window_title_new(c.gtk_window_get_title(window.window) orelse "Ghostty", null)));
return .{ .adw = @ptrCast(headerbar) };
}
@ -66,4 +67,26 @@ pub const HeaderBar = union(enum) {
),
}
}
pub fn setTitle(self: HeaderBar, title: [:0]const u8) void {
switch (self) {
.adw => |headerbar| if (comptime adwaita.versionAtLeast(0, 0, 0)) {
const window_title: *c.AdwWindowTitle = @ptrCast(c.adw_header_bar_get_title_widget(@ptrCast(headerbar)));
c.adw_window_title_set_title(window_title, title);
},
// The title is owned by the window when not using Adwaita
.gtk => unreachable,
}
}
pub fn setSubtitle(self: HeaderBar, subtitle: [:0]const u8) void {
switch (self) {
.adw => |headerbar| if (comptime adwaita.versionAtLeast(0, 0, 0)) {
const window_title: *c.AdwWindowTitle = @ptrCast(c.adw_header_bar_get_title_widget(@ptrCast(headerbar)));
c.adw_window_title_set_subtitle(window_title, subtitle);
},
// There is no subtitle unless Adwaita is used
.gtk => unreachable,
}
}
};

View File

@ -440,7 +440,7 @@ fn adwSelectPage(_: *c.GObject, _: *c.GParamSpec, ud: ?*anyopaque) void {
const window: *Window = @ptrCast(@alignCast(ud.?));
const page = c.adw_tab_view_get_selected_page(window.notebook.adw_tab_view) orelse return;
const title = c.adw_tab_page_get_title(page);
c.gtk_window_set_title(window.window, title);
window.setTitle(std.mem.span(title));
}
fn gtkSwitchPage(_: *c.GtkNotebook, page: *c.GtkWidget, _: usize, ud: ?*anyopaque) callconv(.C) void {
@ -448,7 +448,7 @@ fn gtkSwitchPage(_: *c.GtkNotebook, page: *c.GtkWidget, _: usize, ud: ?*anyopaqu
const gtk_label_box = @as(*c.GtkWidget, @ptrCast(c.gtk_notebook_get_tab_label(window.notebook.gtk_notebook, page)));
const gtk_label = @as(*c.GtkLabel, @ptrCast(c.gtk_widget_get_first_child(gtk_label_box)));
const label_text = c.gtk_label_get_text(gtk_label);
c.gtk_window_set_title(window.window, label_text);
window.setTitle(std.mem.span(label_text));
}
fn adwTabViewCreateWindow(