diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 3d80d9259..cdc63555b 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -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}); diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 48e88e491..6de1d933c 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -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; diff --git a/src/apprt/gtk/headerbar.zig b/src/apprt/gtk/headerbar.zig index b1567ce27..ccc655195 100644 --- a/src/apprt/gtk/headerbar.zig +++ b/src/apprt/gtk/headerbar.zig @@ -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, + } + } }; diff --git a/src/apprt/gtk/notebook.zig b/src/apprt/gtk/notebook.zig index 5faebd788..225d31781 100644 --- a/src/apprt/gtk/notebook.zig +++ b/src/apprt/gtk/notebook.zig @@ -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(