mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
gtk: require libadwaita
This commit removes support for building without libadwaita.
This commit is contained in:
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@ -374,7 +374,7 @@ jobs:
|
|||||||
run: nix develop -c zig build -Dapp-runtime=none test
|
run: nix develop -c zig build -Dapp-runtime=none test
|
||||||
|
|
||||||
- name: Test GTK Build
|
- name: Test GTK Build
|
||||||
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs
|
run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs
|
||||||
|
|
||||||
- name: Test GLFW Build
|
- name: Test GLFW Build
|
||||||
run: nix develop -c zig build -Dapp-runtime=glfw
|
run: nix develop -c zig build -Dapp-runtime=glfw
|
||||||
@ -387,10 +387,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
adwaita: ["true", "false"]
|
|
||||||
x11: ["true", "false"]
|
x11: ["true", "false"]
|
||||||
wayland: ["true", "false"]
|
wayland: ["true", "false"]
|
||||||
name: GTK adwaita=${{ matrix.adwaita }} x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
|
name: GTK x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
|
||||||
runs-on: namespace-profile-ghostty-sm
|
runs-on: namespace-profile-ghostty-sm
|
||||||
needs: test
|
needs: test
|
||||||
env:
|
env:
|
||||||
@ -421,7 +420,6 @@ jobs:
|
|||||||
nix develop -c \
|
nix develop -c \
|
||||||
zig build \
|
zig build \
|
||||||
-Dapp-runtime=gtk \
|
-Dapp-runtime=gtk \
|
||||||
-Dgtk-adwaita=${{ matrix.adwaita }} \
|
|
||||||
-Dgtk-x11=${{ matrix.x11 }} \
|
-Dgtk-x11=${{ matrix.x11 }} \
|
||||||
-Dgtk-wayland=${{ matrix.wayland }}
|
-Dgtk-wayland=${{ matrix.wayland }}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ const Config = configpkg.Config;
|
|||||||
const CoreApp = @import("../../App.zig");
|
const CoreApp = @import("../../App.zig");
|
||||||
const CoreSurface = @import("../../Surface.zig");
|
const CoreSurface = @import("../../Surface.zig");
|
||||||
|
|
||||||
const adwaita = @import("adwaita.zig");
|
|
||||||
const cgroup = @import("cgroup.zig");
|
const cgroup = @import("cgroup.zig");
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
@ -109,6 +108,14 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|||||||
c.gtk_get_micro_version(),
|
c.gtk_get_micro_version(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// log the adwaita version
|
||||||
|
log.info("libadwaita version build={s} runtime={}.{}.{}", .{
|
||||||
|
c.ADW_VERSION_S,
|
||||||
|
c.adw_get_major_version(),
|
||||||
|
c.adw_get_minor_version(),
|
||||||
|
c.adw_get_micro_version(),
|
||||||
|
});
|
||||||
|
|
||||||
// Load our configuration
|
// Load our configuration
|
||||||
var config = try Config.load(core_app.alloc);
|
var config = try Config.load(core_app.alloc);
|
||||||
errdefer config.deinit();
|
errdefer config.deinit();
|
||||||
@ -236,7 +243,8 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.gtk_init();
|
c.adw_init();
|
||||||
|
|
||||||
const display: *c.GdkDisplay = c.gdk_display_get_default() orelse {
|
const display: *c.GdkDisplay = c.gdk_display_get_default() orelse {
|
||||||
// I'm unsure of any scenario where this happens. Because we don't
|
// I'm unsure of any scenario where this happens. Because we don't
|
||||||
// want to litter null checks everywhere, we just exit here.
|
// want to litter null checks everywhere, we just exit here.
|
||||||
@ -244,16 +252,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|||||||
std.posix.exit(1);
|
std.posix.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we're using libadwaita, log the version
|
|
||||||
if (adwaita.enabled(&config)) {
|
|
||||||
log.info("libadwaita version build={s} runtime={}.{}.{}", .{
|
|
||||||
c.ADW_VERSION_S,
|
|
||||||
c.adw_get_major_version(),
|
|
||||||
c.adw_get_minor_version(),
|
|
||||||
c.adw_get_micro_version(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// The "none" cursor is used for hiding the cursor
|
// The "none" cursor is used for hiding the cursor
|
||||||
const cursor_none = c.gdk_cursor_new_from_name("none", null);
|
const cursor_none = c.gdk_cursor_new_from_name("none", null);
|
||||||
errdefer if (cursor_none) |cursor| c.g_object_unref(cursor);
|
errdefer if (cursor_none) |cursor| c.g_object_unref(cursor);
|
||||||
@ -288,103 +286,38 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create our GTK Application which encapsulates our process.
|
// Create our GTK Application which encapsulates our process.
|
||||||
const app: *c.GtkApplication = app: {
|
log.debug("creating GTK application id={s} single-instance={}", .{
|
||||||
log.debug("creating GTK application id={s} single-instance={} adwaita={}", .{
|
app_id,
|
||||||
app_id,
|
single_instance,
|
||||||
single_instance,
|
});
|
||||||
adwaita,
|
|
||||||
});
|
|
||||||
|
|
||||||
// If not libadwaita, create a standard GTK application.
|
// Using an AdwApplication lets us use Adwaita widgets and access things
|
||||||
if ((comptime !adwaita.versionAtLeast(0, 0, 0)) or
|
// such as the color scheme.
|
||||||
!adwaita.enabled(&config))
|
const adw_app = @as(?*c.AdwApplication, @ptrCast(c.adw_application_new(
|
||||||
{
|
app_id.ptr,
|
||||||
{
|
app_flags,
|
||||||
const provider = c.gtk_css_provider_new();
|
))) orelse return error.GtkInitFailed;
|
||||||
defer c.g_object_unref(provider);
|
errdefer c.g_object_unref(adw_app);
|
||||||
switch (config.@"window-theme") {
|
|
||||||
.system, .light => {},
|
|
||||||
.dark => {
|
|
||||||
const settings = c.gtk_settings_get_default();
|
|
||||||
c.g_object_set(@ptrCast(@alignCast(settings)), "gtk-application-prefer-dark-theme", true, @as([*c]const u8, null));
|
|
||||||
|
|
||||||
c.gtk_css_provider_load_from_resource(
|
const style_manager = c.adw_application_get_style_manager(adw_app);
|
||||||
provider,
|
c.adw_style_manager_set_color_scheme(
|
||||||
"/com/mitchellh/ghostty/style-dark.css",
|
style_manager,
|
||||||
);
|
switch (config.@"window-theme") {
|
||||||
c.gtk_style_context_add_provider_for_display(
|
.auto, .ghostty => auto: {
|
||||||
display,
|
const lum = config.background.toTerminalRGB().perceivedLuminance();
|
||||||
@ptrCast(provider),
|
break :auto if (lum > 0.5)
|
||||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 2,
|
c.ADW_COLOR_SCHEME_PREFER_LIGHT
|
||||||
);
|
else
|
||||||
},
|
c.ADW_COLOR_SCHEME_PREFER_DARK;
|
||||||
.auto, .ghostty => {
|
|
||||||
const lum = config.background.toTerminalRGB().perceivedLuminance();
|
|
||||||
if (lum <= 0.5) {
|
|
||||||
const settings = c.gtk_settings_get_default();
|
|
||||||
c.g_object_set(@ptrCast(@alignCast(settings)), "gtk-application-prefer-dark-theme", true, @as([*c]const u8, null));
|
|
||||||
|
|
||||||
c.gtk_css_provider_load_from_resource(
|
|
||||||
provider,
|
|
||||||
"/com/mitchellh/ghostty/style-dark.css",
|
|
||||||
);
|
|
||||||
c.gtk_style_context_add_provider_for_display(
|
|
||||||
display,
|
|
||||||
@ptrCast(provider),
|
|
||||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const provider = c.gtk_css_provider_new();
|
|
||||||
defer c.g_object_unref(provider);
|
|
||||||
|
|
||||||
c.gtk_css_provider_load_from_resource(provider, "/com/mitchellh/ghostty/style.css");
|
|
||||||
c.gtk_style_context_add_provider_for_display(
|
|
||||||
display,
|
|
||||||
@ptrCast(provider),
|
|
||||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
break :app @as(?*c.GtkApplication, @ptrCast(c.gtk_application_new(
|
|
||||||
app_id.ptr,
|
|
||||||
app_flags,
|
|
||||||
))) orelse return error.GtkInitFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use libadwaita if requested. Using an AdwApplication lets us use
|
|
||||||
// Adwaita widgets and access things such as the color scheme.
|
|
||||||
const adw_app = @as(?*c.AdwApplication, @ptrCast(c.adw_application_new(
|
|
||||||
app_id.ptr,
|
|
||||||
app_flags,
|
|
||||||
))) orelse return error.GtkInitFailed;
|
|
||||||
|
|
||||||
const style_manager = c.adw_application_get_style_manager(adw_app);
|
|
||||||
c.adw_style_manager_set_color_scheme(
|
|
||||||
style_manager,
|
|
||||||
switch (config.@"window-theme") {
|
|
||||||
.auto, .ghostty => auto: {
|
|
||||||
const lum = config.background.toTerminalRGB().perceivedLuminance();
|
|
||||||
break :auto if (lum > 0.5)
|
|
||||||
c.ADW_COLOR_SCHEME_PREFER_LIGHT
|
|
||||||
else
|
|
||||||
c.ADW_COLOR_SCHEME_PREFER_DARK;
|
|
||||||
},
|
|
||||||
|
|
||||||
.system => c.ADW_COLOR_SCHEME_PREFER_LIGHT,
|
|
||||||
.dark => c.ADW_COLOR_SCHEME_FORCE_DARK,
|
|
||||||
.light => c.ADW_COLOR_SCHEME_FORCE_LIGHT,
|
|
||||||
},
|
},
|
||||||
);
|
.system => c.ADW_COLOR_SCHEME_PREFER_LIGHT,
|
||||||
|
.dark => c.ADW_COLOR_SCHEME_FORCE_DARK,
|
||||||
|
.light => c.ADW_COLOR_SCHEME_FORCE_LIGHT,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
break :app @ptrCast(adw_app);
|
const app: *c.GtkApplication = @ptrCast(adw_app);
|
||||||
};
|
const gapp: *c.GApplication = @ptrCast(app);
|
||||||
errdefer c.g_object_unref(app);
|
|
||||||
const gapp = @as(*c.GApplication, @ptrCast(app));
|
|
||||||
|
|
||||||
// force the resource path to a known value so that it doesn't depend on
|
// force the resource path to a known value so that it doesn't depend on
|
||||||
// the app id and load in compiled resources
|
// the app id and load in compiled resources
|
||||||
@ -980,11 +913,9 @@ fn configChange(
|
|||||||
|
|
||||||
// App changes needs to show a toast that our configuration
|
// App changes needs to show a toast that our configuration
|
||||||
// has reloaded.
|
// has reloaded.
|
||||||
if (adwaita.enabled(&self.config)) {
|
if (self.core_app.focusedSurface()) |core_surface| {
|
||||||
if (self.core_app.focusedSurface()) |core_surface| {
|
const surface = core_surface.rt_surface;
|
||||||
const surface = core_surface.rt_surface;
|
if (surface.container.window()) |window| window.onConfigReloaded();
|
||||||
if (surface.container.window()) |window| window.onConfigReloaded();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,8 @@ const Tab = @import("Tab.zig");
|
|||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const adwaita = @import("adwaita.zig");
|
const adwaita = @import("adwaita.zig");
|
||||||
const gtk_key = @import("key.zig");
|
const gtk_key = @import("key.zig");
|
||||||
const Notebook = @import("notebook.zig").Notebook;
|
const Notebook = @import("notebook.zig");
|
||||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
const HeaderBar = @import("headerbar.zig");
|
||||||
const version = @import("version.zig");
|
const version = @import("version.zig");
|
||||||
const winproto = @import("winproto.zig");
|
const winproto = @import("winproto.zig");
|
||||||
|
|
||||||
@ -34,9 +34,7 @@ app: *App,
|
|||||||
/// Our window
|
/// Our window
|
||||||
window: *c.GtkWindow,
|
window: *c.GtkWindow,
|
||||||
|
|
||||||
/// The header bar for the window. This is possibly null since it can be
|
/// The header bar for the window.
|
||||||
/// disabled using gtk-titlebar. This is either an AdwHeaderBar or
|
|
||||||
/// GtkHeaderBar depending on if adw is enabled and linked.
|
|
||||||
headerbar: HeaderBar,
|
headerbar: HeaderBar,
|
||||||
|
|
||||||
/// The tab overview for the window. This is possibly null since there is no
|
/// The tab overview for the window. This is possibly null since there is no
|
||||||
@ -44,14 +42,12 @@ headerbar: HeaderBar,
|
|||||||
tab_overview: ?*c.GtkWidget,
|
tab_overview: ?*c.GtkWidget,
|
||||||
|
|
||||||
/// The notebook (tab grouping) for this window.
|
/// The notebook (tab grouping) for this window.
|
||||||
/// can be either c.GtkNotebook or c.AdwTabView.
|
|
||||||
notebook: Notebook,
|
notebook: Notebook,
|
||||||
|
|
||||||
context_menu: *c.GtkWidget,
|
context_menu: *c.GtkWidget,
|
||||||
|
|
||||||
/// The libadwaita widget for receiving toast send requests. If libadwaita is
|
/// The libadwaita widget for receiving toast send requests.
|
||||||
/// not used, this is null and unused.
|
toast_overlay: *c.GtkWidget,
|
||||||
toast_overlay: ?*c.GtkWidget,
|
|
||||||
|
|
||||||
/// See adwTabOverviewOpen for why we have this.
|
/// See adwTabOverviewOpen for why we have this.
|
||||||
adw_tab_overview_focus_timer: ?c.guint = null,
|
adw_tab_overview_focus_timer: ?c.guint = null,
|
||||||
@ -87,37 +83,27 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create the window
|
// Create the window
|
||||||
const window: *c.GtkWidget = window: {
|
const gtk_widget = c.adw_application_window_new(app.app);
|
||||||
if ((comptime adwaita.versionAtLeast(0, 0, 0)) and adwaita.enabled(&self.app.config)) {
|
errdefer c.gtk_window_destroy(@ptrCast(gtk_widget));
|
||||||
const window = c.adw_application_window_new(app.app);
|
|
||||||
c.gtk_widget_add_css_class(@ptrCast(window), "adw");
|
|
||||||
break :window window;
|
|
||||||
} else {
|
|
||||||
const window = c.gtk_application_window_new(app.app);
|
|
||||||
c.gtk_widget_add_css_class(@ptrCast(window), "gtk");
|
|
||||||
break :window window;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errdefer c.gtk_window_destroy(@ptrCast(window));
|
|
||||||
|
|
||||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
self.window = @ptrCast(@alignCast(gtk_widget));
|
||||||
self.window = gtk_window;
|
|
||||||
c.gtk_window_set_title(gtk_window, "Ghostty");
|
c.gtk_window_set_title(self.window, "Ghostty");
|
||||||
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
c.gtk_window_set_default_size(self.window, 1000, 600);
|
||||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "window");
|
c.gtk_widget_add_css_class(gtk_widget, "window");
|
||||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "terminal-window");
|
c.gtk_widget_add_css_class(gtk_widget, "terminal-window");
|
||||||
|
|
||||||
// GTK4 grabs F10 input by default to focus the menubar icon. We want
|
// GTK4 grabs F10 input by default to focus the menubar icon. We want
|
||||||
// to disable this so that terminal programs can capture F10 (such as htop)
|
// to disable this so that terminal programs can capture F10 (such as htop)
|
||||||
c.gtk_window_set_handle_menubar_accel(gtk_window, 0);
|
c.gtk_window_set_handle_menubar_accel(self.window, 0);
|
||||||
|
|
||||||
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
c.gtk_window_set_icon_name(self.window, build_config.bundle_id);
|
||||||
|
|
||||||
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
||||||
// GTK version is before 4.16. The conditional is because above 4.16
|
// GTK version is before 4.16. The conditional is because above 4.16
|
||||||
// we use GTK CSS color variables.
|
// we use GTK CSS color variables.
|
||||||
if (!version.atLeast(4, 16, 0) and app.config.@"window-theme" == .ghostty) {
|
if (!version.atLeast(4, 16, 0) and app.config.@"window-theme" == .ghostty) {
|
||||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "window-theme-ghostty");
|
c.gtk_widget_add_css_class(gtk_widget, "window-theme-ghostty");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create our box which will hold our widgets in the main content area.
|
// Create our box which will hold our widgets in the main content area.
|
||||||
@ -127,9 +113,9 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
self.notebook.init();
|
self.notebook.init();
|
||||||
|
|
||||||
// If we are using Adwaita, then we can support the tab overview.
|
// If we are using Adwaita, then we can support the tab overview.
|
||||||
self.tab_overview = if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&self.app.config) and adwaita.versionAtLeast(1, 4, 0)) overview: {
|
self.tab_overview = if (adwaita.versionAtLeast(1, 4, 0)) overview: {
|
||||||
const tab_overview = c.adw_tab_overview_new();
|
const tab_overview = c.adw_tab_overview_new();
|
||||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.adw.tab_view);
|
c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.tab_view);
|
||||||
c.adw_tab_overview_set_enable_new_tab(@ptrCast(tab_overview), 1);
|
c.adw_tab_overview_set_enable_new_tab(@ptrCast(tab_overview), 1);
|
||||||
_ = c.g_signal_connect_data(
|
_ = c.g_signal_connect_data(
|
||||||
tab_overview,
|
tab_overview,
|
||||||
@ -166,10 +152,9 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
|
|
||||||
// If we're using an AdwWindow then we can support the tab overview.
|
// If we're using an AdwWindow then we can support the tab overview.
|
||||||
if (self.tab_overview) |tab_overview| {
|
if (self.tab_overview) |tab_overview| {
|
||||||
if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
assert(adwaita.versionAtLeast(1, 4, 0));
|
||||||
assert(self.app.config.@"gtk-adwaita" and adwaita.versionAtLeast(1, 4, 0));
|
|
||||||
const btn = switch (app.config.@"gtk-tabs-location") {
|
const btn = switch (app.config.@"gtk-tabs-location") {
|
||||||
.top, .bottom, .left, .right => btn: {
|
.top, .bottom => btn: {
|
||||||
const btn = c.gtk_toggle_button_new();
|
const btn = c.gtk_toggle_button_new();
|
||||||
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
|
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
|
||||||
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
||||||
@ -186,7 +171,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
|
|
||||||
.hidden => btn: {
|
.hidden => btn: {
|
||||||
const btn = c.adw_tab_button_new();
|
const btn = c.adw_tab_button_new();
|
||||||
c.adw_tab_button_set_view(@ptrCast(btn), self.notebook.adw.tab_view);
|
c.adw_tab_button_set_view(@ptrCast(btn), self.notebook.tab_view);
|
||||||
c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open");
|
c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open");
|
||||||
break :btn btn;
|
break :btn btn;
|
||||||
},
|
},
|
||||||
@ -203,13 +188,13 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
self.headerbar.packStart(btn);
|
self.headerbar.packStart(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = c.g_signal_connect_data(gtk_window, "notify::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "notify::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(gtk_window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(gtk_window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
||||||
|
|
||||||
// If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we
|
// If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we
|
||||||
// need to stick the headerbar into the content box.
|
// need to stick the headerbar into the content box.
|
||||||
if (!adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
if (!adwaita.versionAtLeast(1, 4, 0)) {
|
||||||
c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget());
|
c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,10 +203,7 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
if (comptime std.debug.runtime_safety) {
|
if (comptime std.debug.runtime_safety) {
|
||||||
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||||
const warning_text = "⚠️ You're running a debug build of Ghostty! Performance will be degraded.";
|
const warning_text = "⚠️ You're running a debug build of Ghostty! Performance will be degraded.";
|
||||||
if ((comptime adwaita.versionAtLeast(1, 3, 0)) and
|
if (adwaita.versionAtLeast(1, 3, 0)) {
|
||||||
adwaita.enabled(&app.config) and
|
|
||||||
adwaita.versionAtLeast(1, 3, 0))
|
|
||||||
{
|
|
||||||
const banner = c.adw_banner_new(warning_text);
|
const banner = c.adw_banner_new(warning_text);
|
||||||
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
||||||
c.gtk_box_append(@ptrCast(warning_box), @ptrCast(banner));
|
c.gtk_box_append(@ptrCast(warning_box), @ptrCast(banner));
|
||||||
@ -231,30 +213,22 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
c.gtk_widget_set_margin_bottom(warning, 10);
|
c.gtk_widget_set_margin_bottom(warning, 10);
|
||||||
c.gtk_box_append(@ptrCast(warning_box), warning);
|
c.gtk_box_append(@ptrCast(warning_box), warning);
|
||||||
}
|
}
|
||||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "devel");
|
c.gtk_widget_add_css_class(gtk_widget, "devel");
|
||||||
c.gtk_widget_add_css_class(@ptrCast(warning_box), "background");
|
c.gtk_widget_add_css_class(@ptrCast(warning_box), "background");
|
||||||
c.gtk_box_append(@ptrCast(box), warning_box);
|
c.gtk_box_append(@ptrCast(box), warning_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup our toast overlay if we have one
|
// Setup our toast overlay if we have one
|
||||||
self.toast_overlay = if (adwaita.enabled(&self.app.config)) toast: {
|
self.toast_overlay = c.adw_toast_overlay_new();
|
||||||
const toast_overlay = c.adw_toast_overlay_new();
|
c.adw_toast_overlay_set_child(
|
||||||
c.adw_toast_overlay_set_child(
|
@ptrCast(self.toast_overlay),
|
||||||
@ptrCast(toast_overlay),
|
@ptrCast(@alignCast(self.notebook.asWidget())),
|
||||||
@ptrCast(@alignCast(self.notebook.asWidget())),
|
);
|
||||||
);
|
c.gtk_box_append(@ptrCast(box), self.toast_overlay);
|
||||||
c.gtk_box_append(@ptrCast(box), toast_overlay);
|
|
||||||
break :toast toast_overlay;
|
|
||||||
} else toast: {
|
|
||||||
c.gtk_box_append(@ptrCast(box), self.notebook.asWidget());
|
|
||||||
break :toast null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we have a tab overview then we can set it on our notebook.
|
// If we have a tab overview then we can set it on our notebook.
|
||||||
if (self.tab_overview) |tab_overview| {
|
if (self.tab_overview) |tab_overview| {
|
||||||
if (comptime !adwaita.versionAtLeast(1, 3, 0)) unreachable;
|
c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.tab_view);
|
||||||
assert(self.notebook == .adw);
|
|
||||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.adw.tab_view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.context_menu = c.gtk_popover_menu_new_from_model(@ptrCast(@alignCast(self.app.context_menu)));
|
self.context_menu = c.gtk_popover_menu_new_from_model(@ptrCast(@alignCast(self.app.context_menu)));
|
||||||
@ -273,40 +247,39 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
// focused (i.e. when the libadw tab overview is shown).
|
// focused (i.e. when the libadw tab overview is shown).
|
||||||
const ec_key_press = c.gtk_event_controller_key_new();
|
const ec_key_press = c.gtk_event_controller_key_new();
|
||||||
errdefer c.g_object_unref(ec_key_press);
|
errdefer c.g_object_unref(ec_key_press);
|
||||||
c.gtk_widget_add_controller(window, ec_key_press);
|
c.gtk_widget_add_controller(gtk_widget, ec_key_press);
|
||||||
|
|
||||||
// All of our events
|
// All of our events
|
||||||
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(>kRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(>kRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||||
_ = c.g_signal_connect_data(ec_key_press, "key-pressed", c.G_CALLBACK(>kKeyPressed), self, null, c.G_CONNECT_DEFAULT);
|
_ = c.g_signal_connect_data(ec_key_press, "key-pressed", c.G_CALLBACK(>kKeyPressed), self, null, c.G_CONNECT_DEFAULT);
|
||||||
|
|
||||||
// Our actions for the menu
|
// Our actions for the menu
|
||||||
initActions(self);
|
initActions(self);
|
||||||
|
|
||||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
if (adwaita.versionAtLeast(1, 4, 0)) {
|
||||||
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new());
|
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new());
|
||||||
|
|
||||||
c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget());
|
c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget());
|
||||||
|
|
||||||
if (self.app.config.@"gtk-tabs-location" != .hidden) {
|
if (self.app.config.@"gtk-tabs-location" != .hidden) {
|
||||||
const tab_bar = c.adw_tab_bar_new();
|
const tab_bar = c.adw_tab_bar_new();
|
||||||
c.adw_tab_bar_set_view(tab_bar, self.notebook.adw.tab_view);
|
c.adw_tab_bar_set_view(tab_bar, self.notebook.tab_view);
|
||||||
|
|
||||||
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||||
|
|
||||||
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
|
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
|
||||||
switch (self.app.config.@"gtk-tabs-location") {
|
switch (self.app.config.@"gtk-tabs-location") {
|
||||||
// left and right are not supported in libadwaita.
|
.top => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget),
|
||||||
.top, .left, .right => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget),
|
|
||||||
.bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget),
|
.bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget),
|
||||||
.hidden => unreachable,
|
.hidden => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.adw_toolbar_view_set_content(toolbar_view, box);
|
c.adw_toolbar_view_set_content(toolbar_view, box);
|
||||||
|
|
||||||
const toolbar_style: c.AdwToolbarStyle = switch (self.app.config.@"adw-toolbar-style") {
|
const toolbar_style: c.AdwToolbarStyle = switch (self.app.config.@"gtk-toolbar-style") {
|
||||||
.flat => c.ADW_TOOLBAR_FLAT,
|
.flat => c.ADW_TOOLBAR_FLAT,
|
||||||
.raised => c.ADW_TOOLBAR_RAISED,
|
.raised => c.ADW_TOOLBAR_RAISED,
|
||||||
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
|
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
|
||||||
@ -320,51 +293,34 @@ pub fn init(self: *Window, app: *App) !void {
|
|||||||
@ptrCast(@alignCast(toolbar_view)),
|
@ptrCast(@alignCast(toolbar_view)),
|
||||||
);
|
);
|
||||||
c.adw_application_window_set_content(
|
c.adw_application_window_set_content(
|
||||||
@ptrCast(gtk_window),
|
@ptrCast(gtk_widget),
|
||||||
@ptrCast(@alignCast(self.tab_overview)),
|
@ptrCast(@alignCast(self.tab_overview)),
|
||||||
);
|
);
|
||||||
} else tab_bar: {
|
} else tab_bar: {
|
||||||
switch (self.notebook) {
|
if (app.config.@"gtk-tabs-location" == .hidden) break :tab_bar;
|
||||||
.adw => |*adw| if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
// In earlier adwaita versions, we need to add the tabbar manually since we do not use
|
||||||
if (app.config.@"gtk-tabs-location" == .hidden) break :tab_bar;
|
// an AdwToolbarView.
|
||||||
// In earlier adwaita versions, we need to add the tabbar manually since we do not use
|
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?;
|
||||||
// an AdwToolbarView.
|
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline");
|
||||||
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?;
|
switch (app.config.@"gtk-tabs-location") {
|
||||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline");
|
.top => c.gtk_box_insert_child_after(
|
||||||
switch (app.config.@"gtk-tabs-location") {
|
@ptrCast(box),
|
||||||
.top,
|
@ptrCast(@alignCast(tab_bar)),
|
||||||
.left,
|
@ptrCast(@alignCast(self.headerbar.asWidget())),
|
||||||
.right,
|
),
|
||||||
=> c.gtk_box_insert_child_after(@ptrCast(box), @ptrCast(@alignCast(tab_bar)), @ptrCast(@alignCast(self.headerbar.asWidget()))),
|
.bottom => c.gtk_box_append(
|
||||||
|
@ptrCast(box),
|
||||||
.bottom => c.gtk_box_append(
|
@ptrCast(@alignCast(tab_bar)),
|
||||||
@ptrCast(box),
|
),
|
||||||
@ptrCast(@alignCast(tab_bar)),
|
.hidden => unreachable,
|
||||||
),
|
|
||||||
.hidden => unreachable,
|
|
||||||
}
|
|
||||||
c.adw_tab_bar_set_view(tab_bar, adw.tab_view);
|
|
||||||
|
|
||||||
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
.gtk => {},
|
|
||||||
}
|
}
|
||||||
|
c.adw_tab_bar_set_view(tab_bar, self.notebook.tab_view);
|
||||||
|
|
||||||
// The box is our main child
|
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||||
if (!adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
|
||||||
c.adw_application_window_set_content(
|
|
||||||
@ptrCast(gtk_window),
|
|
||||||
box,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
c.gtk_window_set_titlebar(gtk_window, self.headerbar.asWidget());
|
|
||||||
c.gtk_window_set_child(gtk_window, box);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the window
|
// Show the window
|
||||||
c.gtk_widget_show(window);
|
c.gtk_widget_show(gtk_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updateConfig(
|
pub fn updateConfig(
|
||||||
@ -407,19 +363,16 @@ pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
|
|||||||
// Disable the title buttons (close, maximize, minimize, ...)
|
// Disable the title buttons (close, maximize, minimize, ...)
|
||||||
// *inside* the tab overview if CSDs are disabled.
|
// *inside* the tab overview if CSDs are disabled.
|
||||||
// We do spare the search button, though.
|
// We do spare the search button, though.
|
||||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and
|
if (self.tab_overview) |tab_overview| {
|
||||||
adwaita.enabled(&self.app.config))
|
assert(adwaita.versionAtLeast(1, 4, 0));
|
||||||
{
|
c.adw_tab_overview_set_show_start_title_buttons(
|
||||||
if (self.tab_overview) |tab_overview| {
|
@ptrCast(tab_overview),
|
||||||
c.adw_tab_overview_set_show_start_title_buttons(
|
@intFromBool(csd_enabled),
|
||||||
@ptrCast(tab_overview),
|
);
|
||||||
@intFromBool(csd_enabled),
|
c.adw_tab_overview_set_show_end_title_buttons(
|
||||||
);
|
@ptrCast(tab_overview),
|
||||||
c.adw_tab_overview_set_show_end_title_buttons(
|
@intFromBool(csd_enabled),
|
||||||
@ptrCast(tab_overview),
|
);
|
||||||
@intFromBool(csd_enabled),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,7 +509,7 @@ pub fn gotoTab(self: *Window, n: usize) bool {
|
|||||||
/// Toggle tab overview (if present)
|
/// Toggle tab overview (if present)
|
||||||
pub fn toggleTabOverview(self: *Window) void {
|
pub fn toggleTabOverview(self: *Window) void {
|
||||||
if (self.tab_overview) |tab_overview_widget| {
|
if (self.tab_overview) |tab_overview_widget| {
|
||||||
if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
assert(adwaita.versionAtLeast(1, 4, 0));
|
||||||
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
||||||
c.adw_tab_overview_set_open(tab_overview, 1 - c.adw_tab_overview_get_open(tab_overview));
|
c.adw_tab_overview_set_open(tab_overview, 1 - c.adw_tab_overview_get_open(tab_overview));
|
||||||
}
|
}
|
||||||
@ -603,11 +556,9 @@ pub fn onConfigReloaded(self: *Window) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendToast(self: *Window, title: [:0]const u8) void {
|
pub fn sendToast(self: *Window, title: [:0]const u8) void {
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) return;
|
|
||||||
const toast_overlay = self.toast_overlay orelse return;
|
|
||||||
const toast = c.adw_toast_new(title);
|
const toast = c.adw_toast_new(title);
|
||||||
c.adw_toast_set_timeout(toast, 3);
|
c.adw_toast_set_timeout(toast, 3);
|
||||||
c.adw_toast_overlay_add_toast(@ptrCast(toast_overlay), toast);
|
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||||
@ -711,12 +662,12 @@ fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
|||||||
/// because we need to return an AdwTabPage from this function.
|
/// because we need to return an AdwTabPage from this function.
|
||||||
fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwTabPage {
|
fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwTabPage {
|
||||||
const self: *Window = userdataSelf(ud.?);
|
const self: *Window = userdataSelf(ud.?);
|
||||||
assert((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config));
|
assert(adwaita.versionAtLeast(1, 4, 0));
|
||||||
|
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
const surface = self.actionSurface();
|
const surface = self.actionSurface();
|
||||||
const tab = Tab.create(alloc, self, surface) catch return null;
|
const tab = Tab.create(alloc, self, surface) catch return null;
|
||||||
return c.adw_tab_view_get_page(self.notebook.adw.tab_view, @ptrCast(@alignCast(tab.box)));
|
return c.adw_tab_view_get_page(self.notebook.tab_view, @ptrCast(@alignCast(tab.box)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adwTabOverviewOpen(
|
fn adwTabOverviewOpen(
|
||||||
@ -863,7 +814,7 @@ fn gtkKeyPressed(
|
|||||||
//
|
//
|
||||||
// If someone can confidently show or explain that this is not
|
// If someone can confidently show or explain that this is not
|
||||||
// necessary, please remove this check.
|
// necessary, please remove this check.
|
||||||
if (comptime adwaita.versionAtLeast(1, 4, 0)) {
|
if (adwaita.versionAtLeast(1, 4, 0)) {
|
||||||
if (self.tab_overview) |tab_overview_widget| {
|
if (self.tab_overview) |tab_overview_widget| {
|
||||||
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
||||||
if (c.adw_tab_overview_get_open(tab_overview) == 0) return 0;
|
if (c.adw_tab_overview_get_open(tab_overview) == 0) return 0;
|
||||||
@ -891,10 +842,7 @@ fn gtkActionAbout(
|
|||||||
const icon = "com.mitchellh.ghostty";
|
const icon = "com.mitchellh.ghostty";
|
||||||
const website = "https://ghostty.org";
|
const website = "https://ghostty.org";
|
||||||
|
|
||||||
if ((comptime adwaita.versionAtLeast(1, 5, 0)) and
|
if (adwaita.versionAtLeast(1, 5, 0)) {
|
||||||
adwaita.versionAtLeast(1, 5, 0) and
|
|
||||||
adwaita.enabled(&self.app.config))
|
|
||||||
{
|
|
||||||
c.adw_show_about_dialog(
|
c.adw_show_about_dialog(
|
||||||
@ptrCast(self.window),
|
@ptrCast(self.window),
|
||||||
"application-name",
|
"application-name",
|
||||||
|
@ -1,20 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const build_options = @import("build_options");
|
|
||||||
const Config = @import("../../config.zig").Config;
|
|
||||||
|
|
||||||
/// Returns true if Ghostty is configured to build with libadwaita and
|
|
||||||
/// the configuration has enabled adwaita.
|
|
||||||
///
|
|
||||||
/// For a comptime version of this function, use `versionAtLeast` in
|
|
||||||
/// a comptime context with all the version numbers set to 0.
|
|
||||||
///
|
|
||||||
/// This must be `inline` so that the comptime check noops conditional
|
|
||||||
/// paths that are not enabled.
|
|
||||||
pub inline fn enabled(config: *const Config) bool {
|
|
||||||
return build_options.adwaita and
|
|
||||||
config.@"gtk-adwaita";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies that the running libadwaita version is at least the given
|
/// Verifies that the running libadwaita version is at least the given
|
||||||
/// version. This will return false if Ghostty is configured to
|
/// version. This will return false if Ghostty is configured to
|
||||||
@ -33,8 +18,6 @@ pub inline fn versionAtLeast(
|
|||||||
comptime minor: u16,
|
comptime minor: u16,
|
||||||
comptime micro: u16,
|
comptime micro: u16,
|
||||||
) bool {
|
) bool {
|
||||||
if (comptime !build_options.adwaita) return false;
|
|
||||||
|
|
||||||
// If our header has lower versions than the given version,
|
// If our header has lower versions than the given version,
|
||||||
// we can return false immediately. This prevents us from
|
// we can return false immediately. This prevents us from
|
||||||
// compiling against unknown symbols and makes runtime checks
|
// compiling against unknown symbols and makes runtime checks
|
||||||
|
@ -2,7 +2,7 @@ const std = @import("std");
|
|||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
|
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
const adw = if (build_options.adwaita) @import("adw") else void;
|
const adw = @import("adw");
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
@ -20,19 +20,12 @@ pub fn main() !void {
|
|||||||
const data = try std.fs.cwd().readFileAllocOptions(alloc, filename, std.math.maxInt(u16), null, 1, 0);
|
const data = try std.fs.cwd().readFileAllocOptions(alloc, filename, std.math.maxInt(u16), null, 1, 0);
|
||||||
defer alloc.free(data);
|
defer alloc.free(data);
|
||||||
|
|
||||||
if ((comptime !build_options.adwaita) and std.mem.indexOf(u8, data, "lib=\"Adw\"") != null) {
|
|
||||||
std.debug.print("{s}: skipping builder check because Adwaita is not enabled!\n", .{filename});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gtk.initCheck() == 0) {
|
if (gtk.initCheck() == 0) {
|
||||||
std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename});
|
std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime build_options.adwaita) {
|
adw.init();
|
||||||
adw.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len));
|
const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len));
|
||||||
defer builder.unref();
|
defer builder.unref();
|
||||||
|
@ -3,9 +3,7 @@ const build_options = @import("build_options");
|
|||||||
/// Imported C API directly from header files
|
/// Imported C API directly from header files
|
||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
@cInclude("gtk/gtk.h");
|
@cInclude("gtk/gtk.h");
|
||||||
if (build_options.adwaita) {
|
@cInclude("adwaita.h");
|
||||||
@cInclude("adwaita.h");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (build_options.x11) {
|
if (build_options.x11) {
|
||||||
// Add in X11-specific GDK backend which we use for specific things
|
// Add in X11-specific GDK backend which we use for specific things
|
||||||
|
@ -1,58 +1,59 @@
|
|||||||
|
const HeaderBar = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
const adwaita = @import("adwaita.zig");
|
|
||||||
|
|
||||||
const HeaderBarAdw = @import("headerbar_adw.zig");
|
/// the Adwaita headerbar widget
|
||||||
const HeaderBarGtk = @import("headerbar_gtk.zig");
|
headerbar: *c.AdwHeaderBar,
|
||||||
|
|
||||||
pub const HeaderBar = union(enum) {
|
/// the Adwaita window title widget
|
||||||
adw: HeaderBarAdw,
|
title: *c.AdwWindowTitle,
|
||||||
gtk: HeaderBarGtk,
|
|
||||||
|
|
||||||
pub fn init(self: *HeaderBar) void {
|
pub fn init(self: *HeaderBar) void {
|
||||||
const window: *Window = @fieldParentPtr("headerbar", self);
|
const window: *Window = @fieldParentPtr("headerbar", self);
|
||||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&window.app.config)) {
|
self.* = .{
|
||||||
HeaderBarAdw.init(self);
|
.headerbar = @ptrCast(@alignCast(c.adw_header_bar_new())),
|
||||||
} else {
|
.title = @ptrCast(@alignCast(c.adw_window_title_new(
|
||||||
HeaderBarGtk.init(self);
|
c.gtk_window_get_title(window.window) orelse "Ghostty",
|
||||||
}
|
null,
|
||||||
}
|
))),
|
||||||
|
};
|
||||||
|
c.adw_header_bar_set_title_widget(
|
||||||
|
self.headerbar,
|
||||||
|
@ptrCast(@alignCast(self.title)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setVisible(self: HeaderBar, visible: bool) void {
|
pub fn setVisible(self: *const HeaderBar, visible: bool) void {
|
||||||
switch (self) {
|
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
||||||
inline else => |v| v.setVisible(visible),
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn asWidget(self: HeaderBar) *c.GtkWidget {
|
pub fn asWidget(self: *const HeaderBar) *c.GtkWidget {
|
||||||
return switch (self) {
|
return @ptrCast(@alignCast(self.headerbar));
|
||||||
inline else => |v| v.asWidget(),
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn packEnd(self: HeaderBar, widget: *c.GtkWidget) void {
|
pub fn packEnd(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||||
switch (self) {
|
c.adw_header_bar_pack_end(
|
||||||
inline else => |v| v.packEnd(widget),
|
@ptrCast(@alignCast(self.headerbar)),
|
||||||
}
|
widget,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn packStart(self: HeaderBar, widget: *c.GtkWidget) void {
|
pub fn packStart(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||||
switch (self) {
|
c.adw_header_bar_pack_start(
|
||||||
inline else => |v| v.packStart(widget),
|
@ptrCast(@alignCast(self.headerbar)),
|
||||||
}
|
widget,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setTitle(self: HeaderBar, title: [:0]const u8) void {
|
pub fn setTitle(self: *const HeaderBar, title: [:0]const u8) void {
|
||||||
switch (self) {
|
const window: *const Window = @fieldParentPtr("headerbar", self);
|
||||||
inline else => |v| v.setTitle(title),
|
c.gtk_window_set_title(window.window, title);
|
||||||
}
|
c.adw_window_title_set_title(self.title, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSubtitle(self: HeaderBar, subtitle: [:0]const u8) void {
|
pub fn setSubtitle(self: *const HeaderBar, subtitle: [:0]const u8) void {
|
||||||
switch (self) {
|
c.adw_window_title_set_subtitle(self.title, subtitle);
|
||||||
inline else => |v| v.setSubtitle(subtitle),
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
const HeaderBarAdw = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const c = @import("c.zig").c;
|
|
||||||
|
|
||||||
const Window = @import("Window.zig");
|
|
||||||
const adwaita = @import("adwaita.zig");
|
|
||||||
|
|
||||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
|
||||||
|
|
||||||
const AdwHeaderBar = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwHeaderBar else anyopaque;
|
|
||||||
const AdwWindowTitle = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwWindowTitle else anyopaque;
|
|
||||||
|
|
||||||
/// the window that this headerbar is attached to
|
|
||||||
window: *Window,
|
|
||||||
/// the Adwaita headerbar widget
|
|
||||||
headerbar: *AdwHeaderBar,
|
|
||||||
/// the Adwaita window title widget
|
|
||||||
title: *AdwWindowTitle,
|
|
||||||
|
|
||||||
pub fn init(headerbar: *HeaderBar) void {
|
|
||||||
if (!adwaita.versionAtLeast(0, 0, 0)) return;
|
|
||||||
|
|
||||||
const window: *Window = @fieldParentPtr("headerbar", headerbar);
|
|
||||||
headerbar.* = .{
|
|
||||||
.adw = .{
|
|
||||||
.window = window,
|
|
||||||
.headerbar = @ptrCast(@alignCast(c.adw_header_bar_new())),
|
|
||||||
.title = @ptrCast(@alignCast(c.adw_window_title_new(
|
|
||||||
c.gtk_window_get_title(window.window) orelse "Ghostty",
|
|
||||||
null,
|
|
||||||
))),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
c.adw_header_bar_set_title_widget(
|
|
||||||
headerbar.adw.headerbar,
|
|
||||||
@ptrCast(@alignCast(headerbar.adw.title)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setVisible(self: HeaderBarAdw, visible: bool) void {
|
|
||||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn asWidget(self: HeaderBarAdw) *c.GtkWidget {
|
|
||||||
return @ptrCast(@alignCast(self.headerbar));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn packEnd(self: HeaderBarAdw, widget: *c.GtkWidget) void {
|
|
||||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
|
||||||
c.adw_header_bar_pack_end(
|
|
||||||
@ptrCast(@alignCast(self.headerbar)),
|
|
||||||
widget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn packStart(self: HeaderBarAdw, widget: *c.GtkWidget) void {
|
|
||||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
|
||||||
c.adw_header_bar_pack_start(
|
|
||||||
@ptrCast(@alignCast(self.headerbar)),
|
|
||||||
widget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTitle(self: HeaderBarAdw, title: [:0]const u8) void {
|
|
||||||
c.gtk_window_set_title(self.window.window, title);
|
|
||||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
|
||||||
c.adw_window_title_set_title(self.title, title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setSubtitle(self: HeaderBarAdw, subtitle: [:0]const u8) void {
|
|
||||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
|
||||||
c.adw_window_title_set_subtitle(self.title, subtitle);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
const HeaderBarGtk = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const c = @import("c.zig").c;
|
|
||||||
|
|
||||||
const Window = @import("Window.zig");
|
|
||||||
const adwaita = @import("adwaita.zig");
|
|
||||||
|
|
||||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
|
||||||
|
|
||||||
/// the window that this headarbar is attached to
|
|
||||||
window: *Window,
|
|
||||||
/// the GTK headerbar widget
|
|
||||||
headerbar: *c.GtkHeaderBar,
|
|
||||||
|
|
||||||
pub fn init(headerbar: *HeaderBar) void {
|
|
||||||
const window: *Window = @fieldParentPtr("headerbar", headerbar);
|
|
||||||
headerbar.* = .{
|
|
||||||
.gtk = .{
|
|
||||||
.window = window,
|
|
||||||
.headerbar = @ptrCast(c.gtk_header_bar_new()),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setVisible(self: HeaderBarGtk, visible: bool) void {
|
|
||||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn asWidget(self: HeaderBarGtk) *c.GtkWidget {
|
|
||||||
return @ptrCast(@alignCast(self.headerbar));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn packEnd(self: HeaderBarGtk, widget: *c.GtkWidget) void {
|
|
||||||
c.gtk_header_bar_pack_end(
|
|
||||||
@ptrCast(@alignCast(self.headerbar)),
|
|
||||||
widget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn packStart(self: HeaderBarGtk, widget: *c.GtkWidget) void {
|
|
||||||
c.gtk_header_bar_pack_start(
|
|
||||||
@ptrCast(@alignCast(self.headerbar)),
|
|
||||||
widget,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTitle(self: HeaderBarGtk, title: [:0]const u8) void {
|
|
||||||
c.gtk_window_set_title(self.window.window, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setSubtitle(_: HeaderBarGtk, _: [:0]const u8) void {}
|
|
@ -1,169 +1,193 @@
|
|||||||
|
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
||||||
|
/// all the terminal tabs in a window.
|
||||||
|
const Notebook = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
const NotebookAdw = @import("notebook_adw.zig").NotebookAdw;
|
|
||||||
const NotebookGtk = @import("notebook_gtk.zig").NotebookGtk;
|
|
||||||
const adwaita = @import("adwaita.zig");
|
const adwaita = @import("adwaita.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
const AdwTabView = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabView else anyopaque;
|
/// the tab view
|
||||||
|
tab_view: *c.AdwTabView,
|
||||||
|
|
||||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
/// Set to true so that the adw close-page handler knows we're forcing
|
||||||
/// all the terminal tabs in a window.
|
/// and to allow a close to happen with no confirm. This is a bit of a hack
|
||||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
/// because we currently use GTK alerts to confirm tab close and they
|
||||||
/// all the terminal tabs in a window.
|
/// don't carry with them the ADW state that we are confirming or not.
|
||||||
pub const Notebook = union(enum) {
|
/// Long term we should move to ADW alerts so we can know if we are
|
||||||
adw: NotebookAdw,
|
/// confirming or not.
|
||||||
gtk: NotebookGtk,
|
forcing_close: bool = false,
|
||||||
|
|
||||||
pub fn init(self: *Notebook) void {
|
pub fn init(self: *Notebook) void {
|
||||||
const window: *Window = @fieldParentPtr("notebook", self);
|
const window: *Window = @fieldParentPtr("notebook", self);
|
||||||
const app = window.app;
|
|
||||||
if (adwaita.enabled(&app.config)) return NotebookAdw.init(self);
|
|
||||||
|
|
||||||
return NotebookGtk.init(self);
|
const tab_view: *c.AdwTabView = c.adw_tab_view_new() orelse unreachable;
|
||||||
|
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_view)), "notebook");
|
||||||
|
|
||||||
|
if (adwaita.versionAtLeast(1, 2, 0)) {
|
||||||
|
// Adwaita enables all of the shortcuts by default.
|
||||||
|
// We want to manage keybindings ourselves.
|
||||||
|
c.adw_tab_view_remove_shortcuts(tab_view, c.ADW_TAB_VIEW_SHORTCUT_ALL_SHORTCUTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn asWidget(self: *Notebook) *c.GtkWidget {
|
self.* = .{
|
||||||
return switch (self.*) {
|
.tab_view = tab_view,
|
||||||
.adw => |*adw| adw.asWidget(),
|
};
|
||||||
.gtk => |*gtk| gtk.asWidget(),
|
|
||||||
};
|
_ = c.g_signal_connect_data(tab_view, "page-attached", c.G_CALLBACK(&adwPageAttached), window, null, c.G_CONNECT_DEFAULT);
|
||||||
|
_ = c.g_signal_connect_data(tab_view, "close-page", c.G_CALLBACK(&adwClosePage), window, null, c.G_CONNECT_DEFAULT);
|
||||||
|
_ = c.g_signal_connect_data(tab_view, "create-window", c.G_CALLBACK(&adwTabViewCreateWindow), window, null, c.G_CONNECT_DEFAULT);
|
||||||
|
_ = c.g_signal_connect_data(tab_view, "notify::selected-page", c.G_CALLBACK(&adwSelectPage), window, null, c.G_CONNECT_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asWidget(self: *Notebook) *c.GtkWidget {
|
||||||
|
return @ptrCast(@alignCast(self.tab_view));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nPages(self: *Notebook) c_int {
|
||||||
|
return c.adw_tab_view_get_n_pages(self.tab_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index of the currently selected page.
|
||||||
|
/// Returns null if the notebook has no pages.
|
||||||
|
fn currentPage(self: *Notebook) ?c_int {
|
||||||
|
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
|
||||||
|
return c.adw_tab_view_get_page_position(self.tab_view, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the currently selected tab or null if there are none.
|
||||||
|
pub fn currentTab(self: *Notebook) ?*Tab {
|
||||||
|
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
|
||||||
|
const child = c.adw_tab_page_get_child(page);
|
||||||
|
return @ptrCast(@alignCast(
|
||||||
|
c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gotoNthTab(self: *Notebook, position: c_int) bool {
|
||||||
|
const page_to_select = c.adw_tab_view_get_nth_page(self.tab_view, position) orelse return false;
|
||||||
|
c.adw_tab_view_set_selected_page(self.tab_view, page_to_select);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTabPosition(self: *Notebook, tab: *Tab) ?c_int {
|
||||||
|
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return null;
|
||||||
|
return c.adw_tab_view_get_page_position(self.tab_view, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gotoPreviousTab(self: *Notebook, tab: *Tab) bool {
|
||||||
|
const page_idx = self.getTabPosition(tab) orelse return false;
|
||||||
|
|
||||||
|
// The next index is the previous or we wrap around.
|
||||||
|
const next_idx = if (page_idx > 0) page_idx - 1 else next_idx: {
|
||||||
|
const max = self.nPages();
|
||||||
|
break :next_idx max -| 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Do nothing if we have one tab
|
||||||
|
if (next_idx == page_idx) return false;
|
||||||
|
|
||||||
|
return self.gotoNthTab(next_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gotoNextTab(self: *Notebook, tab: *Tab) bool {
|
||||||
|
const page_idx = self.getTabPosition(tab) orelse return false;
|
||||||
|
|
||||||
|
const max = self.nPages() -| 1;
|
||||||
|
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
||||||
|
if (next_idx == page_idx) return false;
|
||||||
|
|
||||||
|
return self.gotoNthTab(next_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveTab(self: *Notebook, tab: *Tab, position: c_int) void {
|
||||||
|
const page_idx = self.getTabPosition(tab) orelse return;
|
||||||
|
|
||||||
|
const max = self.nPages() -| 1;
|
||||||
|
var new_position: c_int = page_idx + position;
|
||||||
|
|
||||||
|
if (new_position < 0) {
|
||||||
|
new_position = max + new_position + 1;
|
||||||
|
} else if (new_position > max) {
|
||||||
|
new_position = new_position - max - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nPages(self: *Notebook) c_int {
|
if (new_position == page_idx) return;
|
||||||
return switch (self.*) {
|
self.reorderPage(tab, new_position);
|
||||||
.adw => |*adw| adw.nPages(),
|
}
|
||||||
.gtk => |*gtk| gtk.nPages(),
|
|
||||||
};
|
pub fn reorderPage(self: *Notebook, tab: *Tab, position: c_int) void {
|
||||||
|
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
||||||
|
_ = c.adw_tab_view_reorder_page(self.tab_view, page, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTabLabel(self: *Notebook, tab: *Tab, title: [:0]const u8) void {
|
||||||
|
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
||||||
|
c.adw_tab_page_set_title(page, title.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTabTooltip(self: *Notebook, tab: *Tab, tooltip: [:0]const u8) void {
|
||||||
|
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
||||||
|
c.adw_tab_page_set_tooltip(page, tooltip.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newTabInsertPosition(self: *Notebook, tab: *Tab) c_int {
|
||||||
|
const numPages = self.nPages();
|
||||||
|
return switch (tab.window.app.config.@"window-new-tab-position") {
|
||||||
|
.current => if (self.currentPage()) |page| page + 1 else numPages,
|
||||||
|
.end => numPages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new tab with the given title to the notebook.
|
||||||
|
pub fn addTab(self: *Notebook, tab: *Tab, title: [:0]const u8) void {
|
||||||
|
const position = self.newTabInsertPosition(tab);
|
||||||
|
const box_widget: *c.GtkWidget = @ptrCast(tab.box);
|
||||||
|
const page = c.adw_tab_view_insert(self.tab_view, box_widget, position);
|
||||||
|
c.adw_tab_page_set_title(page, title.ptr);
|
||||||
|
c.adw_tab_view_set_selected_page(self.tab_view, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn closeTab(self: *Notebook, tab: *Tab) void {
|
||||||
|
// closeTab always expects to close unconditionally so we mark this
|
||||||
|
// as true so that the close_page call below doesn't request
|
||||||
|
// confirmation.
|
||||||
|
self.forcing_close = true;
|
||||||
|
const n = self.nPages();
|
||||||
|
defer {
|
||||||
|
// self becomes invalid if we close the last page because we close
|
||||||
|
// the whole window
|
||||||
|
if (n > 1) self.forcing_close = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the index of the currently selected page.
|
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return;
|
||||||
/// Returns null if the notebook has no pages.
|
c.adw_tab_view_close_page(self.tab_view, page);
|
||||||
fn currentPage(self: *Notebook) ?c_int {
|
|
||||||
return switch (self.*) {
|
|
||||||
.adw => |*adw| adw.currentPage(),
|
|
||||||
.gtk => |*gtk| gtk.currentPage(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the currently selected tab or null if there are none.
|
// If we have no more tabs we close the window
|
||||||
pub fn currentTab(self: *Notebook) ?*Tab {
|
if (self.nPages() == 0) {
|
||||||
return switch (self.*) {
|
const window = tab.window.window;
|
||||||
.adw => |*adw| adw.currentTab(),
|
|
||||||
.gtk => |*gtk| gtk.currentTab(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gotoNthTab(self: *Notebook, position: c_int) bool {
|
// libadw versions <= 1.3.x leak the final page view
|
||||||
const current_page_ = self.currentPage();
|
// which causes our surface to not properly cleanup. We
|
||||||
if (current_page_) |current_page| if (current_page == position) return false;
|
// unref to force the cleanup. This will trigger a critical
|
||||||
switch (self.*) {
|
// warning from GTK, but I don't know any other workaround.
|
||||||
.adw => |*adw| adw.gotoNthTab(position),
|
// Note: I'm not actually sure if 1.4.0 contains the fix,
|
||||||
.gtk => |*gtk| gtk.gotoNthTab(position),
|
// I just know that 1.3.x is broken and 1.5.1 is fixed.
|
||||||
}
|
// If we know that 1.4.0 is fixed, we can change this.
|
||||||
return true;
|
if (!adwaita.versionAtLeast(1, 4, 0)) {
|
||||||
}
|
c.g_object_unref(tab.box);
|
||||||
|
|
||||||
pub fn getTabPosition(self: *Notebook, tab: *Tab) ?c_int {
|
|
||||||
return switch (self.*) {
|
|
||||||
.adw => |*adw| adw.getTabPosition(tab),
|
|
||||||
.gtk => |*gtk| gtk.getTabPosition(tab),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gotoPreviousTab(self: *Notebook, tab: *Tab) bool {
|
|
||||||
const page_idx = self.getTabPosition(tab) orelse return false;
|
|
||||||
|
|
||||||
// The next index is the previous or we wrap around.
|
|
||||||
const next_idx = if (page_idx > 0) page_idx - 1 else next_idx: {
|
|
||||||
const max = self.nPages();
|
|
||||||
break :next_idx max -| 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Do nothing if we have one tab
|
|
||||||
if (next_idx == page_idx) return false;
|
|
||||||
|
|
||||||
return self.gotoNthTab(next_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gotoNextTab(self: *Notebook, tab: *Tab) bool {
|
|
||||||
const page_idx = self.getTabPosition(tab) orelse return false;
|
|
||||||
|
|
||||||
const max = self.nPages() -| 1;
|
|
||||||
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
|
||||||
|
|
||||||
// Do nothing if we have one tab
|
|
||||||
if (next_idx == page_idx) return false;
|
|
||||||
|
|
||||||
return self.gotoNthTab(next_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn moveTab(self: *Notebook, tab: *Tab, position: c_int) void {
|
|
||||||
const page_idx = self.getTabPosition(tab) orelse return;
|
|
||||||
|
|
||||||
const max = self.nPages() -| 1;
|
|
||||||
var new_position: c_int = page_idx + position;
|
|
||||||
|
|
||||||
if (new_position < 0) {
|
|
||||||
new_position = max + new_position + 1;
|
|
||||||
} else if (new_position > max) {
|
|
||||||
new_position = new_position - max - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_position == page_idx) return;
|
// `self` will become invalid after this call because it will have
|
||||||
self.reorderPage(tab, new_position);
|
// been freed up as part of the process of closing the window.
|
||||||
|
c.gtk_window_destroy(window);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn reorderPage(self: *Notebook, tab: *Tab, position: c_int) void {
|
|
||||||
switch (self.*) {
|
|
||||||
.adw => |*adw| adw.reorderPage(tab, position),
|
|
||||||
.gtk => |*gtk| gtk.reorderPage(tab, position),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTabLabel(self: *Notebook, tab: *Tab, title: [:0]const u8) void {
|
|
||||||
switch (self.*) {
|
|
||||||
.adw => |*adw| adw.setTabLabel(tab, title),
|
|
||||||
.gtk => |*gtk| gtk.setTabLabel(tab, title),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTabTooltip(self: *Notebook, tab: *Tab, tooltip: [:0]const u8) void {
|
|
||||||
switch (self.*) {
|
|
||||||
.adw => |*adw| adw.setTabTooltip(tab, tooltip),
|
|
||||||
.gtk => |*gtk| gtk.setTabTooltip(tab, tooltip),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newTabInsertPosition(self: *Notebook, tab: *Tab) c_int {
|
|
||||||
const numPages = self.nPages();
|
|
||||||
return switch (tab.window.app.config.@"window-new-tab-position") {
|
|
||||||
.current => if (self.currentPage()) |page| page + 1 else numPages,
|
|
||||||
.end => numPages,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new tab with the given title to the notebook.
|
|
||||||
pub fn addTab(self: *Notebook, tab: *Tab, title: [:0]const u8) void {
|
|
||||||
const position = self.newTabInsertPosition(tab);
|
|
||||||
switch (self.*) {
|
|
||||||
.adw => |*adw| adw.addTab(tab, position, title),
|
|
||||||
.gtk => |*gtk| gtk.addTab(tab, position, title),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn closeTab(self: *Notebook, tab: *Tab) void {
|
|
||||||
switch (self.*) {
|
|
||||||
.adw => |*adw| adw.closeTab(tab),
|
|
||||||
.gtk => |*gtk| gtk.closeTab(tab),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn createWindow(currentWindow: *Window) !*Window {
|
pub fn createWindow(currentWindow: *Window) !*Window {
|
||||||
const alloc = currentWindow.app.core_app.alloc;
|
const alloc = currentWindow.app.core_app.alloc;
|
||||||
@ -172,3 +196,54 @@ pub fn createWindow(currentWindow: *Window) !*Window {
|
|||||||
// Create a new window
|
// Create a new window
|
||||||
return Window.create(alloc, app);
|
return Window.create(alloc, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn adwPageAttached(_: *c.AdwTabView, page: *c.AdwTabPage, _: c_int, ud: ?*anyopaque) callconv(.C) void {
|
||||||
|
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||||
|
|
||||||
|
const child = c.adw_tab_page_get_child(page);
|
||||||
|
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return));
|
||||||
|
tab.window = window;
|
||||||
|
|
||||||
|
window.focusCurrentTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adwClosePage(
|
||||||
|
_: *c.AdwTabView,
|
||||||
|
page: *c.AdwTabPage,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.C) c.gboolean {
|
||||||
|
const child = c.adw_tab_page_get_child(page);
|
||||||
|
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(
|
||||||
|
@ptrCast(child),
|
||||||
|
Tab.GHOSTTY_TAB,
|
||||||
|
) orelse return 0));
|
||||||
|
|
||||||
|
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||||
|
const notebook = window.notebook;
|
||||||
|
c.adw_tab_view_close_page_finish(
|
||||||
|
notebook.tab_view,
|
||||||
|
page,
|
||||||
|
@intFromBool(notebook.forcing_close),
|
||||||
|
);
|
||||||
|
if (!notebook.forcing_close) tab.closeWithConfirmation();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adwTabViewCreateWindow(
|
||||||
|
_: *c.AdwTabView,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.C) ?*c.AdwTabView {
|
||||||
|
const currentWindow: *Window = @ptrCast(@alignCast(ud.?));
|
||||||
|
const window = createWindow(currentWindow) catch |err| {
|
||||||
|
log.warn("error creating new window error={}", .{err});
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
return window.notebook.tab_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.tab_view) orelse return;
|
||||||
|
const title = c.adw_tab_page_get_title(page);
|
||||||
|
window.setTitle(std.mem.span(title));
|
||||||
|
}
|
||||||
|
@ -1,209 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const c = @import("c.zig").c;
|
|
||||||
|
|
||||||
const Window = @import("Window.zig");
|
|
||||||
const Tab = @import("Tab.zig");
|
|
||||||
const Notebook = @import("notebook.zig").Notebook;
|
|
||||||
const createWindow = @import("notebook.zig").createWindow;
|
|
||||||
const adwaita = @import("adwaita.zig");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
|
||||||
|
|
||||||
const AdwTabView = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabView else anyopaque;
|
|
||||||
const AdwTabPage = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabPage else anyopaque;
|
|
||||||
|
|
||||||
pub const NotebookAdw = struct {
|
|
||||||
/// the tab view
|
|
||||||
tab_view: *AdwTabView,
|
|
||||||
|
|
||||||
/// Set to true so that the adw close-page handler knows we're forcing
|
|
||||||
/// and to allow a close to happen with no confirm. This is a bit of a hack
|
|
||||||
/// because we currently use GTK alerts to confirm tab close and they
|
|
||||||
/// don't carry with them the ADW state that we are confirming or not.
|
|
||||||
/// Long term we should move to ADW alerts so we can know if we are
|
|
||||||
/// confirming or not.
|
|
||||||
forcing_close: bool = false,
|
|
||||||
|
|
||||||
pub fn init(notebook: *Notebook) void {
|
|
||||||
const window: *Window = @fieldParentPtr("notebook", notebook);
|
|
||||||
const app = window.app;
|
|
||||||
assert(adwaita.enabled(&app.config));
|
|
||||||
|
|
||||||
const tab_view: *c.AdwTabView = c.adw_tab_view_new().?;
|
|
||||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_view)), "notebook");
|
|
||||||
|
|
||||||
if (comptime adwaita.versionAtLeast(1, 2, 0) and adwaita.versionAtLeast(1, 2, 0)) {
|
|
||||||
// Adwaita enables all of the shortcuts by default.
|
|
||||||
// We want to manage keybindings ourselves.
|
|
||||||
c.adw_tab_view_remove_shortcuts(tab_view, c.ADW_TAB_VIEW_SHORTCUT_ALL_SHORTCUTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
notebook.* = .{
|
|
||||||
.adw = .{
|
|
||||||
.tab_view = tab_view,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = c.g_signal_connect_data(tab_view, "page-attached", c.G_CALLBACK(&adwPageAttached), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(tab_view, "close-page", c.G_CALLBACK(&adwClosePage), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(tab_view, "create-window", c.G_CALLBACK(&adwTabViewCreateWindow), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(tab_view, "notify::selected-page", c.G_CALLBACK(&adwSelectPage), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn asWidget(self: *NotebookAdw) *c.GtkWidget {
|
|
||||||
return @ptrCast(@alignCast(self.tab_view));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nPages(self: *NotebookAdw) c_int {
|
|
||||||
if (comptime adwaita.versionAtLeast(0, 0, 0))
|
|
||||||
return c.adw_tab_view_get_n_pages(self.tab_view)
|
|
||||||
else
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the index of the currently selected page.
|
|
||||||
/// Returns null if the notebook has no pages.
|
|
||||||
pub fn currentPage(self: *NotebookAdw) ?c_int {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
|
|
||||||
return c.adw_tab_view_get_page_position(self.tab_view, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the currently selected tab or null if there are none.
|
|
||||||
pub fn currentTab(self: *NotebookAdw) ?*Tab {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
|
|
||||||
const child = c.adw_tab_page_get_child(page);
|
|
||||||
return @ptrCast(@alignCast(
|
|
||||||
c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return null,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gotoNthTab(self: *NotebookAdw, position: c_int) void {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page_to_select = c.adw_tab_view_get_nth_page(self.tab_view, position);
|
|
||||||
c.adw_tab_view_set_selected_page(self.tab_view, page_to_select);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getTabPosition(self: *NotebookAdw, tab: *Tab) ?c_int {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return null;
|
|
||||||
return c.adw_tab_view_get_page_position(self.tab_view, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reorderPage(self: *NotebookAdw, tab: *Tab, position: c_int) void {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
|
||||||
_ = c.adw_tab_view_reorder_page(self.tab_view, page, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTabLabel(self: *NotebookAdw, tab: *Tab, title: [:0]const u8) void {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
|
||||||
c.adw_tab_page_set_title(page, title.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTabTooltip(self: *NotebookAdw, tab: *Tab, tooltip: [:0]const u8) void {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
|
||||||
c.adw_tab_page_set_tooltip(page, tooltip.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addTab(self: *NotebookAdw, tab: *Tab, position: c_int, title: [:0]const u8) void {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
const box_widget: *c.GtkWidget = @ptrCast(tab.box);
|
|
||||||
const page = c.adw_tab_view_insert(self.tab_view, box_widget, position);
|
|
||||||
c.adw_tab_page_set_title(page, title.ptr);
|
|
||||||
c.adw_tab_view_set_selected_page(self.tab_view, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn closeTab(self: *NotebookAdw, tab: *Tab) void {
|
|
||||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
|
||||||
|
|
||||||
// closeTab always expects to close unconditionally so we mark this
|
|
||||||
// as true so that the close_page call below doesn't request
|
|
||||||
// confirmation.
|
|
||||||
self.forcing_close = true;
|
|
||||||
const n = self.nPages();
|
|
||||||
defer {
|
|
||||||
// self becomes invalid if we close the last page because we close
|
|
||||||
// the whole window
|
|
||||||
if (n > 1) self.forcing_close = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return;
|
|
||||||
c.adw_tab_view_close_page(self.tab_view, page);
|
|
||||||
|
|
||||||
// If we have no more tabs we close the window
|
|
||||||
if (self.nPages() == 0) {
|
|
||||||
const window = tab.window.window;
|
|
||||||
|
|
||||||
// libadw versions <= 1.3.x leak the final page view
|
|
||||||
// which causes our surface to not properly cleanup. We
|
|
||||||
// unref to force the cleanup. This will trigger a critical
|
|
||||||
// warning from GTK, but I don't know any other workaround.
|
|
||||||
// Note: I'm not actually sure if 1.4.0 contains the fix,
|
|
||||||
// I just know that 1.3.x is broken and 1.5.1 is fixed.
|
|
||||||
// If we know that 1.4.0 is fixed, we can change this.
|
|
||||||
if (!adwaita.versionAtLeast(1, 4, 0)) {
|
|
||||||
c.g_object_unref(tab.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `self` will become invalid after this call because it will have
|
|
||||||
// been freed up as part of the process of closing the window.
|
|
||||||
c.gtk_window_destroy(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn adwPageAttached(_: *AdwTabView, page: *c.AdwTabPage, _: c_int, ud: ?*anyopaque) callconv(.C) void {
|
|
||||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
|
|
||||||
const child = c.adw_tab_page_get_child(page);
|
|
||||||
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return));
|
|
||||||
tab.window = window;
|
|
||||||
|
|
||||||
window.focusCurrentTab();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn adwClosePage(
|
|
||||||
_: *AdwTabView,
|
|
||||||
page: *c.AdwTabPage,
|
|
||||||
ud: ?*anyopaque,
|
|
||||||
) callconv(.C) c.gboolean {
|
|
||||||
const child = c.adw_tab_page_get_child(page);
|
|
||||||
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(
|
|
||||||
@ptrCast(child),
|
|
||||||
Tab.GHOSTTY_TAB,
|
|
||||||
) orelse return 0));
|
|
||||||
|
|
||||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
const notebook = window.notebook.adw;
|
|
||||||
c.adw_tab_view_close_page_finish(
|
|
||||||
notebook.tab_view,
|
|
||||||
page,
|
|
||||||
@intFromBool(notebook.forcing_close),
|
|
||||||
);
|
|
||||||
if (!notebook.forcing_close) tab.closeWithConfirmation();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn adwTabViewCreateWindow(
|
|
||||||
_: *AdwTabView,
|
|
||||||
ud: ?*anyopaque,
|
|
||||||
) callconv(.C) ?*AdwTabView {
|
|
||||||
const currentWindow: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
const window = createWindow(currentWindow) catch |err| {
|
|
||||||
log.warn("error creating new window error={}", .{err});
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
return window.notebook.adw.tab_view;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
window.setTitle(std.mem.span(title));
|
|
||||||
}
|
|
@ -1,304 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const c = @import("c.zig").c;
|
|
||||||
|
|
||||||
const Window = @import("Window.zig");
|
|
||||||
const Tab = @import("Tab.zig");
|
|
||||||
const Notebook = @import("notebook.zig").Notebook;
|
|
||||||
const createWindow = @import("notebook.zig").createWindow;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
|
||||||
|
|
||||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
|
||||||
/// all the terminal tabs in a window.
|
|
||||||
pub const NotebookGtk = struct {
|
|
||||||
notebook: *c.GtkNotebook,
|
|
||||||
|
|
||||||
pub fn init(notebook: *Notebook) void {
|
|
||||||
const window: *Window = @fieldParentPtr("notebook", notebook);
|
|
||||||
const app = window.app;
|
|
||||||
|
|
||||||
// Create a notebook to hold our tabs.
|
|
||||||
const notebook_widget: *c.GtkWidget = c.gtk_notebook_new();
|
|
||||||
c.gtk_widget_add_css_class(notebook_widget, "notebook");
|
|
||||||
|
|
||||||
const gtk_notebook: *c.GtkNotebook = @ptrCast(notebook_widget);
|
|
||||||
const notebook_tab_pos: c_uint = switch (app.config.@"gtk-tabs-location") {
|
|
||||||
.top, .hidden => c.GTK_POS_TOP,
|
|
||||||
.bottom => c.GTK_POS_BOTTOM,
|
|
||||||
.left => c.GTK_POS_LEFT,
|
|
||||||
.right => c.GTK_POS_RIGHT,
|
|
||||||
};
|
|
||||||
c.gtk_notebook_set_tab_pos(gtk_notebook, notebook_tab_pos);
|
|
||||||
c.gtk_notebook_set_scrollable(gtk_notebook, 1);
|
|
||||||
c.gtk_notebook_set_show_tabs(gtk_notebook, 0);
|
|
||||||
c.gtk_notebook_set_show_border(gtk_notebook, 0);
|
|
||||||
|
|
||||||
// This enables all Ghostty terminal tabs to be exchanged across windows.
|
|
||||||
c.gtk_notebook_set_group_name(gtk_notebook, "ghostty-terminal-tabs");
|
|
||||||
|
|
||||||
// This is important so the notebook expands to fit available space.
|
|
||||||
// Otherwise, it will be zero/zero in the box below.
|
|
||||||
c.gtk_widget_set_vexpand(notebook_widget, 1);
|
|
||||||
c.gtk_widget_set_hexpand(notebook_widget, 1);
|
|
||||||
|
|
||||||
// Remove the background from the stack widget
|
|
||||||
const stack = c.gtk_widget_get_last_child(notebook_widget);
|
|
||||||
c.gtk_widget_add_css_class(stack, "transparent");
|
|
||||||
|
|
||||||
notebook.* = .{
|
|
||||||
.gtk = .{
|
|
||||||
.notebook = gtk_notebook,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// All of our events
|
|
||||||
_ = c.g_signal_connect_data(gtk_notebook, "page-added", c.G_CALLBACK(>kPageAdded), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(gtk_notebook, "page-removed", c.G_CALLBACK(>kPageRemoved), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(gtk_notebook, "switch-page", c.G_CALLBACK(>kSwitchPage), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(gtk_notebook, "create-window", c.G_CALLBACK(>kNotebookCreateWindow), window, null, c.G_CONNECT_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return the underlying widget as a generic GtkWidget
|
|
||||||
pub fn asWidget(self: *NotebookGtk) *c.GtkWidget {
|
|
||||||
return @ptrCast(@alignCast(self.notebook));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns the number of pages in the notebook
|
|
||||||
pub fn nPages(self: *NotebookGtk) c_int {
|
|
||||||
return c.gtk_notebook_get_n_pages(self.notebook);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the index of the currently selected page.
|
|
||||||
/// Returns null if the notebook has no pages.
|
|
||||||
pub fn currentPage(self: *NotebookGtk) ?c_int {
|
|
||||||
const current = c.gtk_notebook_get_current_page(self.notebook);
|
|
||||||
return if (current == -1) null else current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the currently selected tab or null if there are none.
|
|
||||||
pub fn currentTab(self: *NotebookGtk) ?*Tab {
|
|
||||||
log.warn("currentTab", .{});
|
|
||||||
const page = self.currentPage() orelse return null;
|
|
||||||
const child = c.gtk_notebook_get_nth_page(self.notebook, page);
|
|
||||||
return @ptrCast(@alignCast(
|
|
||||||
c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return null,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// focus the nth tab
|
|
||||||
pub fn gotoNthTab(self: *NotebookGtk, position: c_int) void {
|
|
||||||
c.gtk_notebook_set_current_page(self.notebook, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get the position of the current tab
|
|
||||||
pub fn getTabPosition(self: *NotebookGtk, tab: *Tab) ?c_int {
|
|
||||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(tab.box)) orelse return null;
|
|
||||||
return getNotebookPageIndex(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reorderPage(self: *NotebookGtk, tab: *Tab, position: c_int) void {
|
|
||||||
c.gtk_notebook_reorder_child(self.notebook, @ptrCast(tab.box), position);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTabLabel(_: *NotebookGtk, tab: *Tab, title: [:0]const u8) void {
|
|
||||||
c.gtk_label_set_text(tab.label_text, title.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setTabTooltip(_: *NotebookGtk, tab: *Tab, tooltip: [:0]const u8) void {
|
|
||||||
c.gtk_widget_set_tooltip_text(@ptrCast(@alignCast(tab.label_text)), tooltip.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new tab with the given title to the notebook.
|
|
||||||
pub fn addTab(self: *NotebookGtk, tab: *Tab, position: c_int, title: [:0]const u8) void {
|
|
||||||
const box_widget: *c.GtkWidget = @ptrCast(tab.box);
|
|
||||||
|
|
||||||
// Build the tab label
|
|
||||||
const label_box_widget = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 0);
|
|
||||||
const label_box = @as(*c.GtkBox, @ptrCast(label_box_widget));
|
|
||||||
const label_text_widget = c.gtk_label_new(title.ptr);
|
|
||||||
const label_text: *c.GtkLabel = @ptrCast(label_text_widget);
|
|
||||||
c.gtk_box_append(label_box, label_text_widget);
|
|
||||||
tab.label_text = label_text;
|
|
||||||
|
|
||||||
const window = tab.window;
|
|
||||||
if (window.app.config.@"gtk-wide-tabs") {
|
|
||||||
c.gtk_widget_set_hexpand(label_box_widget, 1);
|
|
||||||
c.gtk_widget_set_halign(label_box_widget, c.GTK_ALIGN_FILL);
|
|
||||||
c.gtk_widget_set_hexpand(label_text_widget, 1);
|
|
||||||
c.gtk_widget_set_halign(label_text_widget, c.GTK_ALIGN_FILL);
|
|
||||||
|
|
||||||
// This ensures that tabs are always equal width. If they're too
|
|
||||||
// long, they'll be truncated with an ellipsis.
|
|
||||||
c.gtk_label_set_max_width_chars(label_text, 1);
|
|
||||||
c.gtk_label_set_ellipsize(label_text, c.PANGO_ELLIPSIZE_END);
|
|
||||||
|
|
||||||
// We need to set a minimum width so that at a certain point
|
|
||||||
// the notebook will have an arrow button rather than shrinking tabs
|
|
||||||
// to an unreadably small size.
|
|
||||||
c.gtk_widget_set_size_request(label_text_widget, 100, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the close button for the tab
|
|
||||||
const label_close_widget = c.gtk_button_new_from_icon_name("window-close-symbolic");
|
|
||||||
const label_close: *c.GtkButton = @ptrCast(label_close_widget);
|
|
||||||
c.gtk_button_set_has_frame(label_close, 0);
|
|
||||||
c.gtk_box_append(label_box, label_close_widget);
|
|
||||||
|
|
||||||
const page_idx = c.gtk_notebook_insert_page(
|
|
||||||
self.notebook,
|
|
||||||
box_widget,
|
|
||||||
label_box_widget,
|
|
||||||
position,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clicks
|
|
||||||
const gesture_tab_click = c.gtk_gesture_click_new();
|
|
||||||
c.gtk_gesture_single_set_button(@ptrCast(gesture_tab_click), 0);
|
|
||||||
c.gtk_widget_add_controller(label_box_widget, @ptrCast(gesture_tab_click));
|
|
||||||
|
|
||||||
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), tab, null, c.G_CONNECT_DEFAULT);
|
|
||||||
_ = c.g_signal_connect_data(gesture_tab_click, "pressed", c.G_CALLBACK(>kTabClick), tab, null, c.G_CONNECT_DEFAULT);
|
|
||||||
|
|
||||||
// Tab settings
|
|
||||||
c.gtk_notebook_set_tab_reorderable(self.notebook, box_widget, 1);
|
|
||||||
c.gtk_notebook_set_tab_detachable(self.notebook, box_widget, 1);
|
|
||||||
|
|
||||||
if (self.nPages() > 1) {
|
|
||||||
c.gtk_notebook_set_show_tabs(self.notebook, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to the new tab
|
|
||||||
c.gtk_notebook_set_current_page(self.notebook, page_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn closeTab(self: *NotebookGtk, tab: *Tab) void {
|
|
||||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(tab.box)) orelse return;
|
|
||||||
|
|
||||||
// Find page and tab which we're closing
|
|
||||||
const page_idx = getNotebookPageIndex(page);
|
|
||||||
|
|
||||||
// Remove the page. This will destroy the GTK widgets in the page which
|
|
||||||
// will trigger Tab cleanup. The `tab` variable is therefore unusable past that point.
|
|
||||||
c.gtk_notebook_remove_page(self.notebook, page_idx);
|
|
||||||
|
|
||||||
const remaining = self.nPages();
|
|
||||||
switch (remaining) {
|
|
||||||
// If we have no more tabs we close the window
|
|
||||||
0 => c.gtk_window_destroy(tab.window.window),
|
|
||||||
|
|
||||||
// If we have one more tab we hide the tab bar
|
|
||||||
1 => c.gtk_notebook_set_show_tabs(self.notebook, 0),
|
|
||||||
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have remaining tabs, we need to make sure we grab focus.
|
|
||||||
if (remaining > 0)
|
|
||||||
(self.currentTab() orelse return).window.focusCurrentTab();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn getNotebookPageIndex(page: *c.GtkNotebookPage) c_int {
|
|
||||||
var value: c.GValue = std.mem.zeroes(c.GValue);
|
|
||||||
defer c.g_value_unset(&value);
|
|
||||||
_ = c.g_value_init(&value, c.G_TYPE_INT);
|
|
||||||
c.g_object_get_property(
|
|
||||||
@ptrCast(@alignCast(page)),
|
|
||||||
"position",
|
|
||||||
&value,
|
|
||||||
);
|
|
||||||
|
|
||||||
return c.g_value_get_int(&value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkPageAdded(
|
|
||||||
notebook: *c.GtkNotebook,
|
|
||||||
_: *c.GtkWidget,
|
|
||||||
page_idx: c.guint,
|
|
||||||
ud: ?*anyopaque,
|
|
||||||
) callconv(.C) void {
|
|
||||||
const self: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
|
|
||||||
// The added page can come from another window with drag and drop, thus we migrate the tab
|
|
||||||
// window to be self.
|
|
||||||
const page = c.gtk_notebook_get_nth_page(notebook, @intCast(page_idx));
|
|
||||||
const tab: *Tab = @ptrCast(@alignCast(
|
|
||||||
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return,
|
|
||||||
));
|
|
||||||
tab.window = self;
|
|
||||||
|
|
||||||
// Whenever a new page is added, we always grab focus of the
|
|
||||||
// currently selected page. This was added specifically so that when
|
|
||||||
// we drag a tab out to create a new window ("create-window" event)
|
|
||||||
// we grab focus in the new window. Without this, the terminal didn't
|
|
||||||
// have focus.
|
|
||||||
self.focusCurrentTab();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkPageRemoved(
|
|
||||||
_: *c.GtkNotebook,
|
|
||||||
_: *c.GtkWidget,
|
|
||||||
_: c.guint,
|
|
||||||
ud: ?*anyopaque,
|
|
||||||
) callconv(.C) void {
|
|
||||||
log.warn("gtkPageRemoved", .{});
|
|
||||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
|
|
||||||
// Hide the tab bar if we only have one tab after removal
|
|
||||||
const remaining = c.gtk_notebook_get_n_pages(window.notebook.gtk.notebook);
|
|
||||||
|
|
||||||
if (remaining == 1) {
|
|
||||||
c.gtk_notebook_set_show_tabs(window.notebook.gtk.notebook, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkSwitchPage(_: *c.GtkNotebook, page: *c.GtkWidget, _: usize, ud: ?*anyopaque) callconv(.C) void {
|
|
||||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
const self = &window.notebook.gtk;
|
|
||||||
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 label_text = c.gtk_label_get_text(gtk_label);
|
|
||||||
window.setTitle(std.mem.span(label_text));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkNotebookCreateWindow(
|
|
||||||
_: *c.GtkNotebook,
|
|
||||||
page: *c.GtkWidget,
|
|
||||||
ud: ?*anyopaque,
|
|
||||||
) callconv(.C) ?*c.GtkNotebook {
|
|
||||||
// The tab for the page is stored in the widget data.
|
|
||||||
const tab: *Tab = @ptrCast(@alignCast(
|
|
||||||
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return null,
|
|
||||||
));
|
|
||||||
|
|
||||||
const currentWindow: *Window = @ptrCast(@alignCast(ud.?));
|
|
||||||
const newWindow = createWindow(currentWindow) catch |err| {
|
|
||||||
log.warn("error creating new window error={}", .{err});
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// And add it to the new window.
|
|
||||||
tab.window = newWindow;
|
|
||||||
|
|
||||||
return newWindow.notebook.gtk.notebook;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
|
||||||
const tab: *Tab = @ptrCast(@alignCast(ud));
|
|
||||||
tab.closeWithConfirmation();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gtkTabClick(
|
|
||||||
gesture: *c.GtkGestureClick,
|
|
||||||
_: c.gint,
|
|
||||||
_: c.gdouble,
|
|
||||||
_: c.gdouble,
|
|
||||||
ud: ?*anyopaque,
|
|
||||||
) callconv(.C) void {
|
|
||||||
const self: *Tab = @ptrCast(@alignCast(ud));
|
|
||||||
const gtk_button = c.gtk_gesture_single_get_current_button(@ptrCast(gesture));
|
|
||||||
if (gtk_button == c.GDK_BUTTON_MIDDLE) {
|
|
||||||
self.closeWithConfirmation();
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,7 +32,6 @@ renderer: renderer.Impl = .opengl,
|
|||||||
font_backend: font.Backend = .freetype,
|
font_backend: font.Backend = .freetype,
|
||||||
|
|
||||||
/// Feature flags
|
/// Feature flags
|
||||||
adwaita: bool = false,
|
|
||||||
x11: bool = false,
|
x11: bool = false,
|
||||||
wayland: bool = false,
|
wayland: bool = false,
|
||||||
sentry: bool = true,
|
sentry: bool = true,
|
||||||
@ -132,12 +131,6 @@ pub fn init(b: *std.Build) !Config {
|
|||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Feature Flags
|
// Feature Flags
|
||||||
|
|
||||||
config.adwaita = b.option(
|
|
||||||
bool,
|
|
||||||
"gtk-adwaita",
|
|
||||||
"Enables the use of Adwaita when using the GTK rendering backend.",
|
|
||||||
) orelse true;
|
|
||||||
|
|
||||||
config.flatpak = b.option(
|
config.flatpak = b.option(
|
||||||
bool,
|
bool,
|
||||||
"flatpak",
|
"flatpak",
|
||||||
@ -397,7 +390,6 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
|
|||||||
// We need to break these down individual because addOption doesn't
|
// We need to break these down individual because addOption doesn't
|
||||||
// support all types.
|
// support all types.
|
||||||
step.addOption(bool, "flatpak", self.flatpak);
|
step.addOption(bool, "flatpak", self.flatpak);
|
||||||
step.addOption(bool, "adwaita", self.adwaita);
|
|
||||||
step.addOption(bool, "x11", self.x11);
|
step.addOption(bool, "x11", self.x11);
|
||||||
step.addOption(bool, "wayland", self.wayland);
|
step.addOption(bool, "wayland", self.wayland);
|
||||||
step.addOption(bool, "sentry", self.sentry);
|
step.addOption(bool, "sentry", self.sentry);
|
||||||
@ -442,7 +434,6 @@ pub fn fromOptions() Config {
|
|||||||
|
|
||||||
.version = options.app_version,
|
.version = options.app_version,
|
||||||
.flatpak = options.flatpak,
|
.flatpak = options.flatpak,
|
||||||
.adwaita = options.adwaita,
|
|
||||||
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
|
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
|
||||||
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
|
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
|
||||||
.renderer = std.meta.stringToEnum(renderer.Impl, @tagName(options.renderer)).?,
|
.renderer = std.meta.stringToEnum(renderer.Impl, @tagName(options.renderer)).?,
|
||||||
|
@ -450,11 +450,8 @@ pub fn add(
|
|||||||
}
|
}
|
||||||
|
|
||||||
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
|
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
|
||||||
|
step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
|
||||||
if (self.config.adwaita) {
|
step.root_module.addImport("adw", gobject.module("adw1"));
|
||||||
step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
|
|
||||||
step.root_module.addImport("adw", gobject.module("adw1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.config.x11) {
|
if (self.config.x11) {
|
||||||
step.linkSystemLibrary2("X11", dynamic_link_opts);
|
step.linkSystemLibrary2("X11", dynamic_link_opts);
|
||||||
@ -525,7 +522,7 @@ pub fn add(
|
|||||||
});
|
});
|
||||||
gtk_builder_check.root_module.addOptions("build_options", self.options);
|
gtk_builder_check.root_module.addOptions("build_options", self.options);
|
||||||
gtk_builder_check.root_module.addImport("gtk", gobject.module("gtk4"));
|
gtk_builder_check.root_module.addImport("gtk", gobject.module("gtk4"));
|
||||||
if (self.config.adwaita) gtk_builder_check.root_module.addImport("adw", gobject.module("adw1"));
|
gtk_builder_check.root_module.addImport("adw", gobject.module("adw1"));
|
||||||
|
|
||||||
for (gresource.dependencies) |pathname| {
|
for (gresource.dependencies) |pathname| {
|
||||||
const extension = std.fs.path.extension(pathname);
|
const extension = std.fs.path.extension(pathname);
|
||||||
|
@ -51,19 +51,15 @@ pub fn run(alloc: Allocator) !u8 {
|
|||||||
gtk.gtk_get_minor_version(),
|
gtk.gtk_get_minor_version(),
|
||||||
gtk.gtk_get_micro_version(),
|
gtk.gtk_get_micro_version(),
|
||||||
});
|
});
|
||||||
if (comptime build_options.adwaita) {
|
try stdout.print(" - libadwaita : enabled\n", .{});
|
||||||
try stdout.print(" - libadwaita : enabled\n", .{});
|
try stdout.print(" build : {s}\n", .{
|
||||||
try stdout.print(" build : {s}\n", .{
|
gtk.ADW_VERSION_S,
|
||||||
gtk.ADW_VERSION_S,
|
});
|
||||||
});
|
try stdout.print(" runtime : {}.{}.{}\n", .{
|
||||||
try stdout.print(" runtime : {}.{}.{}\n", .{
|
gtk.adw_get_major_version(),
|
||||||
gtk.adw_get_major_version(),
|
gtk.adw_get_minor_version(),
|
||||||
gtk.adw_get_minor_version(),
|
gtk.adw_get_micro_version(),
|
||||||
gtk.adw_get_micro_version(),
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try stdout.print(" - libadwaita : disabled\n", .{});
|
|
||||||
}
|
|
||||||
if (comptime build_options.x11) {
|
if (comptime build_options.x11) {
|
||||||
try stdout.print(" - libX11 : enabled\n", .{});
|
try stdout.print(" - libX11 : enabled\n", .{});
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,6 +49,7 @@ pub const renamed = std.StaticStringMap([]const u8).initComptime(&.{
|
|||||||
// one field be used for both platforms (macOS retained the ability
|
// one field be used for both platforms (macOS retained the ability
|
||||||
// to set a radius).
|
// to set a radius).
|
||||||
.{ "background-blur-radius", "background-blur" },
|
.{ "background-blur-radius", "background-blur" },
|
||||||
|
.{ "adw-toolbar-style", "gtk-toolbar-style" },
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The font families to use.
|
/// The font families to use.
|
||||||
@ -1199,7 +1200,7 @@ keybind: Keybinds = .{},
|
|||||||
/// * `working-directory` - Set the subtitle to the working directory of the
|
/// * `working-directory` - Set the subtitle to the working directory of the
|
||||||
/// surface.
|
/// surface.
|
||||||
///
|
///
|
||||||
/// This feature is only supported on GTK with Adwaita enabled.
|
/// This feature is only supported on GTK.
|
||||||
@"window-subtitle": WindowSubtitle = .false,
|
@"window-subtitle": WindowSubtitle = .false,
|
||||||
|
|
||||||
/// The theme to use for the windows. Valid values:
|
/// The theme to use for the windows. Valid values:
|
||||||
@ -1212,8 +1213,7 @@ keybind: Keybinds = .{},
|
|||||||
/// * `light` - Use the light theme regardless of system theme.
|
/// * `light` - Use the light theme regardless of system theme.
|
||||||
/// * `dark` - Use the dark theme regardless of system theme.
|
/// * `dark` - Use the dark theme regardless of system theme.
|
||||||
/// * `ghostty` - Use the background and foreground colors specified in the
|
/// * `ghostty` - Use the background and foreground colors specified in the
|
||||||
/// Ghostty configuration. This is only supported on Linux builds with
|
/// Ghostty configuration. This is only supported on Linux builds.
|
||||||
/// Adwaita and `gtk-adwaita` enabled.
|
|
||||||
///
|
///
|
||||||
/// On macOS, if `macos-titlebar-style` is "tabs", the window theme will be
|
/// On macOS, if `macos-titlebar-style` is "tabs", the window theme will be
|
||||||
/// automatically set based on the luminosity of the terminal background color.
|
/// automatically set based on the luminosity of the terminal background color.
|
||||||
@ -1779,9 +1779,9 @@ keybind: Keybinds = .{},
|
|||||||
|
|
||||||
/// Control the in-app notifications that Ghostty shows.
|
/// Control the in-app notifications that Ghostty shows.
|
||||||
///
|
///
|
||||||
/// On Linux (GTK) with Adwaita, in-app notifications show up as toasts. Toasts
|
/// On Linux (GTK), in-app notifications show up as toasts. Toasts appear
|
||||||
/// appear overlaid on top of the terminal window. They are used to show
|
/// overlaid on top of the terminal window. They are used to show information
|
||||||
/// information that is not critical but may be important.
|
/// that is not critical but may be important.
|
||||||
///
|
///
|
||||||
/// Possible notifications are:
|
/// Possible notifications are:
|
||||||
///
|
///
|
||||||
@ -1799,7 +1799,7 @@ keybind: Keybinds = .{},
|
|||||||
/// A value of "false" will disable all notifications. A value of "true" will
|
/// A value of "false" will disable all notifications. A value of "true" will
|
||||||
/// enable all notifications.
|
/// enable all notifications.
|
||||||
///
|
///
|
||||||
/// This configuration only applies to GTK with Adwaita enabled.
|
/// This configuration only applies to GTK.
|
||||||
@"app-notifications": AppNotifications = .{},
|
@"app-notifications": AppNotifications = .{},
|
||||||
|
|
||||||
/// If anything other than false, fullscreen mode on macOS will not use the
|
/// If anything other than false, fullscreen mode on macOS will not use the
|
||||||
@ -2129,26 +2129,20 @@ keybind: Keybinds = .{},
|
|||||||
@"gtk-titlebar": bool = true,
|
@"gtk-titlebar": bool = true,
|
||||||
|
|
||||||
/// Determines the side of the screen that the GTK tab bar will stick to.
|
/// Determines the side of the screen that the GTK tab bar will stick to.
|
||||||
/// Top, bottom, left, right, and hidden are supported. The default is top.
|
/// Top, bottom, and hidden are supported. The default is top.
|
||||||
///
|
///
|
||||||
/// If this option has value `left` or `right` when using Adwaita, it falls
|
/// When `hidden` is set, a tab button displaying the number of tabs will appear
|
||||||
/// back to `top`. `hidden`, meaning that tabs don't exist, is not supported
|
/// in the title bar. It has the ability to open a tab overview for displaying
|
||||||
/// without using Adwaita, falling back to `top`.
|
/// tabs. Alternatively, you can use the `toggle_tab_overview` action in a
|
||||||
///
|
/// keybind if your window doesn't have a title bar, or you can switch tabs
|
||||||
/// When `hidden` is set and Adwaita is enabled, a tab button displaying the
|
/// with keybinds.
|
||||||
/// number of tabs will appear in the title bar. It has the ability to open a
|
|
||||||
/// tab overview for displaying tabs. Alternatively, you can use the
|
|
||||||
/// `toggle_tab_overview` action in a keybind if your window doesn't have a
|
|
||||||
/// title bar, or you can switch tabs with keybinds.
|
|
||||||
@"gtk-tabs-location": GtkTabsLocation = .top,
|
@"gtk-tabs-location": GtkTabsLocation = .top,
|
||||||
|
|
||||||
/// If this is `true`, the titlebar will be hidden when the window is maximized,
|
/// If this is `true`, the titlebar will be hidden when the window is maximized,
|
||||||
/// and shown when the titlebar is unmaximized. GTK only.
|
/// and shown when the titlebar is unmaximized. GTK only.
|
||||||
@"gtk-titlebar-hide-when-maximized": bool = false,
|
@"gtk-titlebar-hide-when-maximized": bool = false,
|
||||||
|
|
||||||
/// Determines the appearance of the top and bottom bars when using the
|
/// Determines the appearance of the top and bottom bars tab bar.
|
||||||
/// Adwaita tab bar. This requires `gtk-adwaita` to be enabled (it is
|
|
||||||
/// by default).
|
|
||||||
///
|
///
|
||||||
/// Valid values are:
|
/// Valid values are:
|
||||||
///
|
///
|
||||||
@ -2158,7 +2152,7 @@ keybind: Keybinds = .{},
|
|||||||
/// more subtle border.
|
/// more subtle border.
|
||||||
///
|
///
|
||||||
/// Changing this value at runtime will only affect new windows.
|
/// Changing this value at runtime will only affect new windows.
|
||||||
@"adw-toolbar-style": AdwToolbarStyle = .raised,
|
@"gtk-toolbar-style": GtkToolbarStyle = .raised,
|
||||||
|
|
||||||
/// If `true` (default), then the Ghostty GTK tabs will be "wide." Wide tabs
|
/// If `true` (default), then the Ghostty GTK tabs will be "wide." Wide tabs
|
||||||
/// are the new typical Gnome style where tabs fill their available space.
|
/// are the new typical Gnome style where tabs fill their available space.
|
||||||
@ -2166,20 +2160,6 @@ keybind: Keybinds = .{},
|
|||||||
/// which is the old style.
|
/// which is the old style.
|
||||||
@"gtk-wide-tabs": bool = true,
|
@"gtk-wide-tabs": bool = true,
|
||||||
|
|
||||||
/// If `true` (default), Ghostty will enable Adwaita theme support. This
|
|
||||||
/// will make `window-theme` work properly and will also allow Ghostty to
|
|
||||||
/// properly respond to system theme changes, light/dark mode changing, etc.
|
|
||||||
/// This requires a GTK4 desktop with a GTK4 theme.
|
|
||||||
///
|
|
||||||
/// If you are running GTK3 or have a GTK3 theme, you may have to set this
|
|
||||||
/// to false to get your theme picked up properly. Having this set to true
|
|
||||||
/// with GTK3 should not cause any problems, but it may not work exactly as
|
|
||||||
/// expected.
|
|
||||||
///
|
|
||||||
/// This configuration only has an effect if Ghostty was built with
|
|
||||||
/// Adwaita support.
|
|
||||||
@"gtk-adwaita": bool = true,
|
|
||||||
|
|
||||||
/// Custom CSS files to be loaded.
|
/// Custom CSS files to be loaded.
|
||||||
///
|
///
|
||||||
/// This configuration can be repeated multiple times to load multiple files.
|
/// This configuration can be repeated multiple times to load multiple files.
|
||||||
@ -5758,13 +5738,11 @@ pub const GtkSingleInstance = enum {
|
|||||||
pub const GtkTabsLocation = enum {
|
pub const GtkTabsLocation = enum {
|
||||||
top,
|
top,
|
||||||
bottom,
|
bottom,
|
||||||
left,
|
|
||||||
right,
|
|
||||||
hidden,
|
hidden,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// See adw-toolbar-style
|
/// See gtk-toolbar-style
|
||||||
pub const AdwToolbarStyle = enum {
|
pub const GtkToolbarStyle = enum {
|
||||||
flat,
|
flat,
|
||||||
raised,
|
raised,
|
||||||
@"raised-border",
|
@"raised-border",
|
||||||
|
Reference in New Issue
Block a user