mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
apprt/gtk: use a subtitle to mark the current working directory (#3570)
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. This is a re-opening of my original PR because I had to delete my fork and re-fork it.
This commit is contained in:
@ -347,6 +347,11 @@ cursor: ?*c.GdkCursor = null,
|
|||||||
/// pass it to GTK.
|
/// pass it to GTK.
|
||||||
title_text: ?[:0]const u8 = null,
|
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 timer used to delay title updates in order to prevent flickering.
|
/// The timer used to delay title updates in order to prevent flickering.
|
||||||
update_title_timer: ?c.guint = null,
|
update_title_timer: ?c.guint = null,
|
||||||
|
|
||||||
@ -628,6 +633,7 @@ fn realize(self: *Surface) !void {
|
|||||||
pub fn deinit(self: *Surface) void {
|
pub fn deinit(self: *Surface) void {
|
||||||
self.init_config.deinit(self.app.core_app.alloc);
|
self.init_config.deinit(self.app.core_app.alloc);
|
||||||
if (self.title_text) |title| self.app.core_app.alloc.free(title);
|
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.
|
// We don't allocate anything if we aren't realized.
|
||||||
if (!self.realized) return;
|
if (!self.realized) return;
|
||||||
@ -876,7 +882,7 @@ fn updateTitleLabels(self: *Surface) void {
|
|||||||
// I don't know a way around this yet. I've tried re-hiding the
|
// 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
|
// cursor after setting the title but it doesn't work, I think
|
||||||
// due to some gtk event loop things...
|
// due to some gtk event loop things...
|
||||||
c.gtk_window_set_title(window.window, title.ptr);
|
window.setTitle(title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -929,11 +935,27 @@ pub fn getTitle(self: *Surface) ?[:0]const u8 {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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, pwd: [:0]const u8) !void {
|
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
|
|
||||||
if (self.container.tab()) |tab| {
|
if (self.container.tab()) |tab| {
|
||||||
tab.setTooltipText(pwd);
|
tab.setTooltipText(pwd);
|
||||||
|
|
||||||
|
if (tab.focus_child == self) {
|
||||||
|
if (self.container.window()) |window| {
|
||||||
|
if (self.app.config.@"window-subtitle" == .@"working-directory") window.setSubtitle(pwd);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, pwd) catch null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setMouseShape(
|
pub fn setMouseShape(
|
||||||
@ -1896,6 +1918,12 @@ fn gtkFocusEnter(_: *c.GtkEventControllerFocus, ud: ?*anyopaque) callconv(.C) vo
|
|||||||
self.unfocused_widget = null;
|
self.unfocused_widget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.pwd) |pwd| {
|
||||||
|
if (self.container.window()) |window| {
|
||||||
|
if (self.app.config.@"window-subtitle" == .@"working-directory") window.setSubtitle(pwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Notify our surface
|
// Notify our surface
|
||||||
self.core_surface.focusCallback(true) catch |err| {
|
self.core_surface.focusCallback(true) catch |err| {
|
||||||
log.err("error in focus callback err={}", .{err});
|
log.err("error in focus callback err={}", .{err});
|
||||||
|
@ -450,6 +450,22 @@ pub fn deinit(self: *Window) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the title of the window.
|
||||||
|
pub fn setTitle(self: *Window, title: [:0]const u8) void {
|
||||||
|
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config) and self.app.config.@"gtk-titlebar") {
|
||||||
|
if (self.header) |header| 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 ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config) and self.app.config.@"gtk-titlebar") {
|
||||||
|
if (self.header) |header| header.setSubtitle(subtitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a new tab to this window.
|
/// Add a new tab to this window.
|
||||||
pub fn newTab(self: *Window, parent: ?*CoreSurface) !void {
|
pub fn newTab(self: *Window, parent: ?*CoreSurface) !void {
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
|
@ -14,14 +14,15 @@ pub const HeaderBar = union(enum) {
|
|||||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and
|
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and
|
||||||
adwaita.enabled(&window.app.config))
|
adwaita.enabled(&window.app.config))
|
||||||
{
|
{
|
||||||
return initAdw();
|
return initAdw(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
return initGtk();
|
return initGtk();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initAdw() HeaderBar {
|
fn initAdw(window: *Window) HeaderBar {
|
||||||
const headerbar = c.adw_header_bar_new();
|
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) };
|
return .{ .adw = @ptrCast(headerbar) };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,4 +71,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,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -159,5 +159,5 @@ fn adwSelectPage(_: *c.GObject, _: *c.GParamSpec, ud: ?*anyopaque) void {
|
|||||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||||
const page = c.adw_tab_view_get_selected_page(window.notebook.adw.tab_view) orelse return;
|
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);
|
const title = c.adw_tab_page_get_title(page);
|
||||||
c.gtk_window_set_title(window.window, title);
|
window.setTitle(std.mem.span(title));
|
||||||
}
|
}
|
||||||
|
@ -259,7 +259,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(self.notebook, page)));
|
const gtk_label_box = @as(*c.GtkWidget, @ptrCast(c.gtk_notebook_get_tab_label(self.notebook, page)));
|
||||||
const gtk_label = @as(*c.GtkLabel, @ptrCast(c.gtk_widget_get_first_child(gtk_label_box)));
|
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);
|
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 gtkNotebookCreateWindow(
|
fn gtkNotebookCreateWindow(
|
||||||
|
@ -1120,6 +1120,15 @@ keybind: Keybinds = .{},
|
|||||||
/// required to be a fixed-width font.
|
/// required to be a fixed-width font.
|
||||||
@"window-title-font-family": ?[:0]const u8 = null,
|
@"window-title-font-family": ?[:0]const u8 = null,
|
||||||
|
|
||||||
|
/// The text that will be displayed in the subtitle of the window. Valid values:
|
||||||
|
///
|
||||||
|
/// * `false` - Disable the subtitle.
|
||||||
|
/// * `working-directory` - Set the subtitle to the working directory of the
|
||||||
|
/// surface.
|
||||||
|
///
|
||||||
|
/// This feature is only supported on GTK with Adwaita enabled.
|
||||||
|
@"window-subtitle": WindowSubtitle = .false,
|
||||||
|
|
||||||
/// The theme to use for the windows. Valid values:
|
/// The theme to use for the windows. Valid values:
|
||||||
///
|
///
|
||||||
/// * `auto` - Determine the theme based on the configured terminal
|
/// * `auto` - Determine the theme based on the configured terminal
|
||||||
@ -3974,6 +3983,11 @@ pub const WindowPaddingColor = enum {
|
|||||||
@"extend-always",
|
@"extend-always",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const WindowSubtitle = enum {
|
||||||
|
false,
|
||||||
|
@"working-directory",
|
||||||
|
};
|
||||||
|
|
||||||
/// Color represents a color using RGB.
|
/// Color represents a color using RGB.
|
||||||
///
|
///
|
||||||
/// This is a packed struct so that the C API to read color values just
|
/// This is a packed struct so that the C API to read color values just
|
||||||
|
Reference in New Issue
Block a user