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
|
||||
|
||||
- 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
|
||||
run: nix develop -c zig build -Dapp-runtime=glfw
|
||||
@ -387,10 +387,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
adwaita: ["true", "false"]
|
||||
x11: ["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
|
||||
needs: test
|
||||
env:
|
||||
@ -421,7 +420,6 @@ jobs:
|
||||
nix develop -c \
|
||||
zig build \
|
||||
-Dapp-runtime=gtk \
|
||||
-Dgtk-adwaita=${{ matrix.adwaita }} \
|
||||
-Dgtk-x11=${{ matrix.x11 }} \
|
||||
-Dgtk-wayland=${{ matrix.wayland }}
|
||||
|
||||
|
@ -25,7 +25,6 @@ const Config = configpkg.Config;
|
||||
const CoreApp = @import("../../App.zig");
|
||||
const CoreSurface = @import("../../Surface.zig");
|
||||
|
||||
const adwaita = @import("adwaita.zig");
|
||||
const cgroup = @import("cgroup.zig");
|
||||
const Surface = @import("Surface.zig");
|
||||
const Window = @import("Window.zig");
|
||||
@ -109,6 +108,14 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
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
|
||||
var config = try Config.load(core_app.alloc);
|
||||
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 {
|
||||
// I'm unsure of any scenario where this happens. Because we don't
|
||||
// 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);
|
||||
};
|
||||
|
||||
// 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
|
||||
const cursor_none = c.gdk_cursor_new_from_name("none", null);
|
||||
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.
|
||||
const app: *c.GtkApplication = app: {
|
||||
log.debug("creating GTK application id={s} single-instance={} adwaita={}", .{
|
||||
app_id,
|
||||
single_instance,
|
||||
adwaita,
|
||||
});
|
||||
log.debug("creating GTK application id={s} single-instance={}", .{
|
||||
app_id,
|
||||
single_instance,
|
||||
});
|
||||
|
||||
// If not libadwaita, create a standard GTK application.
|
||||
if ((comptime !adwaita.versionAtLeast(0, 0, 0)) or
|
||||
!adwaita.enabled(&config))
|
||||
{
|
||||
{
|
||||
const provider = c.gtk_css_provider_new();
|
||||
defer c.g_object_unref(provider);
|
||||
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));
|
||||
// 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;
|
||||
errdefer c.g_object_unref(adw_app);
|
||||
|
||||
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,
|
||||
);
|
||||
},
|
||||
.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,
|
||||
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,
|
||||
},
|
||||
);
|
||||
|
||||
break :app @ptrCast(adw_app);
|
||||
};
|
||||
errdefer c.g_object_unref(app);
|
||||
const gapp = @as(*c.GApplication, @ptrCast(app));
|
||||
const app: *c.GtkApplication = @ptrCast(adw_app);
|
||||
const gapp: *c.GApplication = @ptrCast(app);
|
||||
|
||||
// force the resource path to a known value so that it doesn't depend on
|
||||
// the app id and load in compiled resources
|
||||
@ -980,11 +913,9 @@ fn configChange(
|
||||
|
||||
// App changes needs to show a toast that our configuration
|
||||
// has reloaded.
|
||||
if (adwaita.enabled(&self.config)) {
|
||||
if (self.core_app.focusedSurface()) |core_surface| {
|
||||
const surface = core_surface.rt_surface;
|
||||
if (surface.container.window()) |window| window.onConfigReloaded();
|
||||
}
|
||||
if (self.core_app.focusedSurface()) |core_surface| {
|
||||
const surface = core_surface.rt_surface;
|
||||
if (surface.container.window()) |window| window.onConfigReloaded();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ const Tab = @import("Tab.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const adwaita = @import("adwaita.zig");
|
||||
const gtk_key = @import("key.zig");
|
||||
const Notebook = @import("notebook.zig").Notebook;
|
||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
||||
const Notebook = @import("notebook.zig");
|
||||
const HeaderBar = @import("headerbar.zig");
|
||||
const version = @import("version.zig");
|
||||
const winproto = @import("winproto.zig");
|
||||
|
||||
@ -34,9 +34,7 @@ app: *App,
|
||||
/// Our window
|
||||
window: *c.GtkWindow,
|
||||
|
||||
/// The header bar for the window. This is possibly null since it can be
|
||||
/// disabled using gtk-titlebar. This is either an AdwHeaderBar or
|
||||
/// GtkHeaderBar depending on if adw is enabled and linked.
|
||||
/// The header bar for the window.
|
||||
headerbar: HeaderBar,
|
||||
|
||||
/// The tab overview for the window. This is possibly null since there is no
|
||||
@ -44,14 +42,12 @@ headerbar: HeaderBar,
|
||||
tab_overview: ?*c.GtkWidget,
|
||||
|
||||
/// The notebook (tab grouping) for this window.
|
||||
/// can be either c.GtkNotebook or c.AdwTabView.
|
||||
notebook: Notebook,
|
||||
|
||||
context_menu: *c.GtkWidget,
|
||||
|
||||
/// The libadwaita widget for receiving toast send requests. If libadwaita is
|
||||
/// not used, this is null and unused.
|
||||
toast_overlay: ?*c.GtkWidget,
|
||||
/// The libadwaita widget for receiving toast send requests.
|
||||
toast_overlay: *c.GtkWidget,
|
||||
|
||||
/// See adwTabOverviewOpen for why we have this.
|
||||
adw_tab_overview_focus_timer: ?c.guint = null,
|
||||
@ -87,37 +83,27 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
};
|
||||
|
||||
// Create the window
|
||||
const window: *c.GtkWidget = window: {
|
||||
if ((comptime adwaita.versionAtLeast(0, 0, 0)) and adwaita.enabled(&self.app.config)) {
|
||||
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_widget = c.adw_application_window_new(app.app);
|
||||
errdefer c.gtk_window_destroy(@ptrCast(gtk_widget));
|
||||
|
||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||
self.window = gtk_window;
|
||||
c.gtk_window_set_title(gtk_window, "Ghostty");
|
||||
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "window");
|
||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "terminal-window");
|
||||
self.window = @ptrCast(@alignCast(gtk_widget));
|
||||
|
||||
c.gtk_window_set_title(self.window, "Ghostty");
|
||||
c.gtk_window_set_default_size(self.window, 1000, 600);
|
||||
c.gtk_widget_add_css_class(gtk_widget, "window");
|
||||
c.gtk_widget_add_css_class(gtk_widget, "terminal-window");
|
||||
|
||||
// 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)
|
||||
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
|
||||
// GTK version is before 4.16. The conditional is because above 4.16
|
||||
// we use GTK CSS color variables.
|
||||
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.
|
||||
@ -127,9 +113,9 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
self.notebook.init();
|
||||
|
||||
// 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();
|
||||
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.g_signal_connect_data(
|
||||
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 (self.tab_overview) |tab_overview| {
|
||||
if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
assert(self.app.config.@"gtk-adwaita" and adwaita.versionAtLeast(1, 4, 0));
|
||||
assert(adwaita.versionAtLeast(1, 4, 0));
|
||||
const btn = switch (app.config.@"gtk-tabs-location") {
|
||||
.top, .bottom, .left, .right => btn: {
|
||||
.top, .bottom => btn: {
|
||||
const btn = c.gtk_toggle_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
|
||||
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: {
|
||||
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");
|
||||
break :btn btn;
|
||||
},
|
||||
@ -203,13 +188,13 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
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(gtk_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::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), 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(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
|
||||
// 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());
|
||||
}
|
||||
|
||||
@ -218,10 +203,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
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.";
|
||||
if ((comptime adwaita.versionAtLeast(1, 3, 0)) and
|
||||
adwaita.enabled(&app.config) and
|
||||
adwaita.versionAtLeast(1, 3, 0))
|
||||
{
|
||||
if (adwaita.versionAtLeast(1, 3, 0)) {
|
||||
const banner = c.adw_banner_new(warning_text);
|
||||
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
||||
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_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_box_append(@ptrCast(box), warning_box);
|
||||
}
|
||||
|
||||
// Setup our toast overlay if we have one
|
||||
self.toast_overlay = if (adwaita.enabled(&self.app.config)) toast: {
|
||||
const toast_overlay = c.adw_toast_overlay_new();
|
||||
c.adw_toast_overlay_set_child(
|
||||
@ptrCast(toast_overlay),
|
||||
@ptrCast(@alignCast(self.notebook.asWidget())),
|
||||
);
|
||||
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;
|
||||
};
|
||||
self.toast_overlay = c.adw_toast_overlay_new();
|
||||
c.adw_toast_overlay_set_child(
|
||||
@ptrCast(self.toast_overlay),
|
||||
@ptrCast(@alignCast(self.notebook.asWidget())),
|
||||
);
|
||||
c.gtk_box_append(@ptrCast(box), self.toast_overlay);
|
||||
|
||||
// If we have a tab overview then we can set it on our notebook.
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
if (comptime !adwaita.versionAtLeast(1, 3, 0)) unreachable;
|
||||
assert(self.notebook == .adw);
|
||||
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);
|
||||
}
|
||||
|
||||
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).
|
||||
const ec_key_press = c.gtk_event_controller_key_new();
|
||||
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
|
||||
_ = 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(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, "realize", c.G_CALLBACK(>kRealize), 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(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);
|
||||
|
||||
// Our actions for the menu
|
||||
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());
|
||||
|
||||
c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget());
|
||||
|
||||
if (self.app.config.@"gtk-tabs-location" != .hidden) {
|
||||
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);
|
||||
|
||||
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
|
||||
switch (self.app.config.@"gtk-tabs-location") {
|
||||
// left and right are not supported in libadwaita.
|
||||
.top, .left, .right => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget),
|
||||
.top => 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),
|
||||
.hidden => unreachable,
|
||||
}
|
||||
}
|
||||
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,
|
||||
.raised => c.ADW_TOOLBAR_RAISED,
|
||||
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
|
||||
@ -320,51 +293,34 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
@ptrCast(@alignCast(toolbar_view)),
|
||||
);
|
||||
c.adw_application_window_set_content(
|
||||
@ptrCast(gtk_window),
|
||||
@ptrCast(gtk_widget),
|
||||
@ptrCast(@alignCast(self.tab_overview)),
|
||||
);
|
||||
} else tab_bar: {
|
||||
switch (self.notebook) {
|
||||
.adw => |*adw| if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
||||
if (app.config.@"gtk-tabs-location" == .hidden) break :tab_bar;
|
||||
// In earlier adwaita versions, we need to add the tabbar manually since we do not use
|
||||
// an AdwToolbarView.
|
||||
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?;
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline");
|
||||
switch (app.config.@"gtk-tabs-location") {
|
||||
.top,
|
||||
.left,
|
||||
.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),
|
||||
@ptrCast(@alignCast(tab_bar)),
|
||||
),
|
||||
.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 => {},
|
||||
if (app.config.@"gtk-tabs-location" == .hidden) break :tab_bar;
|
||||
// In earlier adwaita versions, we need to add the tabbar manually since we do not use
|
||||
// an AdwToolbarView.
|
||||
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?;
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline");
|
||||
switch (app.config.@"gtk-tabs-location") {
|
||||
.top => c.gtk_box_insert_child_after(
|
||||
@ptrCast(box),
|
||||
@ptrCast(@alignCast(tab_bar)),
|
||||
@ptrCast(@alignCast(self.headerbar.asWidget())),
|
||||
),
|
||||
.bottom => c.gtk_box_append(
|
||||
@ptrCast(box),
|
||||
@ptrCast(@alignCast(tab_bar)),
|
||||
),
|
||||
.hidden => unreachable,
|
||||
}
|
||||
c.adw_tab_bar_set_view(tab_bar, self.notebook.tab_view);
|
||||
|
||||
// The box is our main child
|
||||
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);
|
||||
}
|
||||
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||
}
|
||||
|
||||
// Show the window
|
||||
c.gtk_widget_show(window);
|
||||
c.gtk_widget_show(gtk_widget);
|
||||
}
|
||||
|
||||
pub fn updateConfig(
|
||||
@ -407,19 +363,16 @@ pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
|
||||
// Disable the title buttons (close, maximize, minimize, ...)
|
||||
// *inside* the tab overview if CSDs are disabled.
|
||||
// We do spare the search button, though.
|
||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and
|
||||
adwaita.enabled(&self.app.config))
|
||||
{
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
c.adw_tab_overview_set_show_start_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
);
|
||||
c.adw_tab_overview_set_show_end_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
);
|
||||
}
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
assert(adwaita.versionAtLeast(1, 4, 0));
|
||||
c.adw_tab_overview_set_show_start_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
);
|
||||
c.adw_tab_overview_set_show_end_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,7 +509,7 @@ pub fn gotoTab(self: *Window, n: usize) bool {
|
||||
/// Toggle tab overview (if present)
|
||||
pub fn toggleTabOverview(self: *Window) void {
|
||||
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));
|
||||
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 {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) return;
|
||||
const toast_overlay = self.toast_overlay orelse return;
|
||||
const toast = c.adw_toast_new(title);
|
||||
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 {
|
||||
@ -711,12 +662,12 @@ fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
/// because we need to return an AdwTabPage from this function.
|
||||
fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwTabPage {
|
||||
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 surface = self.actionSurface();
|
||||
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(
|
||||
@ -863,7 +814,7 @@ fn gtkKeyPressed(
|
||||
//
|
||||
// If someone can confidently show or explain that this is not
|
||||
// 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| {
|
||||
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
||||
if (c.adw_tab_overview_get_open(tab_overview) == 0) return 0;
|
||||
@ -891,10 +842,7 @@ fn gtkActionAbout(
|
||||
const icon = "com.mitchellh.ghostty";
|
||||
const website = "https://ghostty.org";
|
||||
|
||||
if ((comptime adwaita.versionAtLeast(1, 5, 0)) and
|
||||
adwaita.versionAtLeast(1, 5, 0) and
|
||||
adwaita.enabled(&self.app.config))
|
||||
{
|
||||
if (adwaita.versionAtLeast(1, 5, 0)) {
|
||||
c.adw_show_about_dialog(
|
||||
@ptrCast(self.window),
|
||||
"application-name",
|
||||
|
@ -1,20 +1,5 @@
|
||||
const std = @import("std");
|
||||
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
|
||||
/// version. This will return false if Ghostty is configured to
|
||||
@ -33,8 +18,6 @@ pub inline fn versionAtLeast(
|
||||
comptime minor: u16,
|
||||
comptime micro: u16,
|
||||
) bool {
|
||||
if (comptime !build_options.adwaita) return false;
|
||||
|
||||
// If our header has lower versions than the given version,
|
||||
// we can return false immediately. This prevents us from
|
||||
// compiling against unknown symbols and makes runtime checks
|
||||
|
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
|
||||
const gtk = @import("gtk");
|
||||
const adw = if (build_options.adwaita) @import("adw") else void;
|
||||
const adw = @import("adw");
|
||||
|
||||
pub fn main() !void {
|
||||
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);
|
||||
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) {
|
||||
std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename});
|
||||
return;
|
||||
}
|
||||
|
||||
if (comptime build_options.adwaita) {
|
||||
adw.init();
|
||||
}
|
||||
adw.init();
|
||||
|
||||
const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len));
|
||||
defer builder.unref();
|
||||
|
@ -3,9 +3,7 @@ const build_options = @import("build_options");
|
||||
/// Imported C API directly from header files
|
||||
pub const c = @cImport({
|
||||
@cInclude("gtk/gtk.h");
|
||||
if (build_options.adwaita) {
|
||||
@cInclude("adwaita.h");
|
||||
}
|
||||
@cInclude("adwaita.h");
|
||||
|
||||
if (build_options.x11) {
|
||||
// Add in X11-specific GDK backend which we use for specific things
|
||||
|
@ -1,58 +1,59 @@
|
||||
const HeaderBar = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const HeaderBarAdw = @import("headerbar_adw.zig");
|
||||
const HeaderBarGtk = @import("headerbar_gtk.zig");
|
||||
/// the Adwaita headerbar widget
|
||||
headerbar: *c.AdwHeaderBar,
|
||||
|
||||
pub const HeaderBar = union(enum) {
|
||||
adw: HeaderBarAdw,
|
||||
gtk: HeaderBarGtk,
|
||||
/// the Adwaita window title widget
|
||||
title: *c.AdwWindowTitle,
|
||||
|
||||
pub fn init(self: *HeaderBar) void {
|
||||
const window: *Window = @fieldParentPtr("headerbar", self);
|
||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&window.app.config)) {
|
||||
HeaderBarAdw.init(self);
|
||||
} else {
|
||||
HeaderBarGtk.init(self);
|
||||
}
|
||||
}
|
||||
pub fn init(self: *HeaderBar) void {
|
||||
const window: *Window = @fieldParentPtr("headerbar", self);
|
||||
self.* = .{
|
||||
.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(
|
||||
self.headerbar,
|
||||
@ptrCast(@alignCast(self.title)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setVisible(self: HeaderBar, visible: bool) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.setVisible(visible),
|
||||
}
|
||||
}
|
||||
pub fn setVisible(self: *const HeaderBar, visible: bool) void {
|
||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
||||
}
|
||||
|
||||
pub fn asWidget(self: HeaderBar) *c.GtkWidget {
|
||||
return switch (self) {
|
||||
inline else => |v| v.asWidget(),
|
||||
};
|
||||
}
|
||||
pub fn asWidget(self: *const HeaderBar) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.headerbar));
|
||||
}
|
||||
|
||||
pub fn packEnd(self: HeaderBar, widget: *c.GtkWidget) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.packEnd(widget),
|
||||
}
|
||||
}
|
||||
pub fn packEnd(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||
c.adw_header_bar_pack_end(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn packStart(self: HeaderBar, widget: *c.GtkWidget) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.packStart(widget),
|
||||
}
|
||||
}
|
||||
pub fn packStart(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||
c.adw_header_bar_pack_start(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setTitle(self: HeaderBar, title: [:0]const u8) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.setTitle(title),
|
||||
}
|
||||
}
|
||||
pub fn setTitle(self: *const HeaderBar, title: [:0]const u8) void {
|
||||
const window: *const Window = @fieldParentPtr("headerbar", self);
|
||||
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 {
|
||||
switch (self) {
|
||||
inline else => |v| v.setSubtitle(subtitle),
|
||||
}
|
||||
}
|
||||
};
|
||||
pub fn setSubtitle(self: *const HeaderBar, subtitle: [:0]const u8) void {
|
||||
c.adw_window_title_set_subtitle(self.title, 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 assert = std.debug.assert;
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.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 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
|
||||
/// all the terminal tabs in a window.
|
||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
||||
/// all the terminal tabs in a window.
|
||||
pub const Notebook = union(enum) {
|
||||
adw: NotebookAdw,
|
||||
gtk: NotebookGtk,
|
||||
/// 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(self: *Notebook) void {
|
||||
const window: *Window = @fieldParentPtr("notebook", self);
|
||||
const app = window.app;
|
||||
if (adwaita.enabled(&app.config)) return NotebookAdw.init(self);
|
||||
pub fn init(self: *Notebook) void {
|
||||
const window: *Window = @fieldParentPtr("notebook", 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 {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.asWidget(),
|
||||
.gtk => |*gtk| gtk.asWidget(),
|
||||
};
|
||||
self.* = .{
|
||||
.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: *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 {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.nPages(),
|
||||
.gtk => |*gtk| gtk.nPages(),
|
||||
};
|
||||
if (new_position == page_idx) return;
|
||||
self.reorderPage(tab, new_position);
|
||||
}
|
||||
|
||||
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.
|
||||
/// Returns null if the notebook has no pages.
|
||||
fn currentPage(self: *Notebook) ?c_int {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.currentPage(),
|
||||
.gtk => |*gtk| gtk.currentPage(),
|
||||
};
|
||||
}
|
||||
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);
|
||||
|
||||
/// Returns the currently selected tab or null if there are none.
|
||||
pub fn currentTab(self: *Notebook) ?*Tab {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.currentTab(),
|
||||
.gtk => |*gtk| gtk.currentTab(),
|
||||
};
|
||||
}
|
||||
// If we have no more tabs we close the window
|
||||
if (self.nPages() == 0) {
|
||||
const window = tab.window.window;
|
||||
|
||||
pub fn gotoNthTab(self: *Notebook, position: c_int) bool {
|
||||
const current_page_ = self.currentPage();
|
||||
if (current_page_) |current_page| if (current_page == position) return false;
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.gotoNthTab(position),
|
||||
.gtk => |*gtk| gtk.gotoNthTab(position),
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (new_position == page_idx) return;
|
||||
self.reorderPage(tab, new_position);
|
||||
// `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);
|
||||
}
|
||||
|
||||
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 {
|
||||
const alloc = currentWindow.app.core_app.alloc;
|
||||
@ -172,3 +196,54 @@ pub fn createWindow(currentWindow: *Window) !*Window {
|
||||
// Create a new window
|
||||
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,
|
||||
|
||||
/// Feature flags
|
||||
adwaita: bool = false,
|
||||
x11: bool = false,
|
||||
wayland: bool = false,
|
||||
sentry: bool = true,
|
||||
@ -132,12 +131,6 @@ pub fn init(b: *std.Build) !Config {
|
||||
//---------------------------------------------------------------
|
||||
// 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(
|
||||
bool,
|
||||
"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
|
||||
// support all types.
|
||||
step.addOption(bool, "flatpak", self.flatpak);
|
||||
step.addOption(bool, "adwaita", self.adwaita);
|
||||
step.addOption(bool, "x11", self.x11);
|
||||
step.addOption(bool, "wayland", self.wayland);
|
||||
step.addOption(bool, "sentry", self.sentry);
|
||||
@ -442,7 +434,6 @@ pub fn fromOptions() Config {
|
||||
|
||||
.version = options.app_version,
|
||||
.flatpak = options.flatpak,
|
||||
.adwaita = options.adwaita,
|
||||
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
|
||||
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
|
||||
.renderer = std.meta.stringToEnum(renderer.Impl, @tagName(options.renderer)).?,
|
||||
|
@ -450,11 +450,8 @@ pub fn add(
|
||||
}
|
||||
|
||||
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
|
||||
|
||||
if (self.config.adwaita) {
|
||||
step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
|
||||
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) {
|
||||
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.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| {
|
||||
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_micro_version(),
|
||||
});
|
||||
if (comptime build_options.adwaita) {
|
||||
try stdout.print(" - libadwaita : enabled\n", .{});
|
||||
try stdout.print(" build : {s}\n", .{
|
||||
gtk.ADW_VERSION_S,
|
||||
});
|
||||
try stdout.print(" runtime : {}.{}.{}\n", .{
|
||||
gtk.adw_get_major_version(),
|
||||
gtk.adw_get_minor_version(),
|
||||
gtk.adw_get_micro_version(),
|
||||
});
|
||||
} else {
|
||||
try stdout.print(" - libadwaita : disabled\n", .{});
|
||||
}
|
||||
try stdout.print(" - libadwaita : enabled\n", .{});
|
||||
try stdout.print(" build : {s}\n", .{
|
||||
gtk.ADW_VERSION_S,
|
||||
});
|
||||
try stdout.print(" runtime : {}.{}.{}\n", .{
|
||||
gtk.adw_get_major_version(),
|
||||
gtk.adw_get_minor_version(),
|
||||
gtk.adw_get_micro_version(),
|
||||
});
|
||||
if (comptime build_options.x11) {
|
||||
try stdout.print(" - libX11 : enabled\n", .{});
|
||||
} else {
|
||||
|
@ -49,6 +49,7 @@ pub const renamed = std.StaticStringMap([]const u8).initComptime(&.{
|
||||
// one field be used for both platforms (macOS retained the ability
|
||||
// to set a radius).
|
||||
.{ "background-blur-radius", "background-blur" },
|
||||
.{ "adw-toolbar-style", "gtk-toolbar-style" },
|
||||
});
|
||||
|
||||
/// The font families to use.
|
||||
@ -1199,7 +1200,7 @@ keybind: Keybinds = .{},
|
||||
/// * `working-directory` - Set the subtitle to the working directory of the
|
||||
/// surface.
|
||||
///
|
||||
/// This feature is only supported on GTK with Adwaita enabled.
|
||||
/// This feature is only supported on GTK.
|
||||
@"window-subtitle": WindowSubtitle = .false,
|
||||
|
||||
/// The theme to use for the windows. Valid values:
|
||||
@ -1212,8 +1213,7 @@ keybind: Keybinds = .{},
|
||||
/// * `light` - Use the light 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 configuration. This is only supported on Linux builds with
|
||||
/// Adwaita and `gtk-adwaita` enabled.
|
||||
/// Ghostty configuration. This is only supported on Linux builds.
|
||||
///
|
||||
/// On macOS, if `macos-titlebar-style` is "tabs", the window theme will be
|
||||
/// 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.
|
||||
///
|
||||
/// On Linux (GTK) with Adwaita, in-app notifications show up as toasts. Toasts
|
||||
/// appear overlaid on top of the terminal window. They are used to show
|
||||
/// information that is not critical but may be important.
|
||||
/// On Linux (GTK), in-app notifications show up as toasts. Toasts appear
|
||||
/// overlaid on top of the terminal window. They are used to show information
|
||||
/// that is not critical but may be important.
|
||||
///
|
||||
/// Possible notifications are:
|
||||
///
|
||||
@ -1799,7 +1799,7 @@ keybind: Keybinds = .{},
|
||||
/// A value of "false" will disable all notifications. A value of "true" will
|
||||
/// enable all notifications.
|
||||
///
|
||||
/// This configuration only applies to GTK with Adwaita enabled.
|
||||
/// This configuration only applies to GTK.
|
||||
@"app-notifications": AppNotifications = .{},
|
||||
|
||||
/// If anything other than false, fullscreen mode on macOS will not use the
|
||||
@ -2129,26 +2129,20 @@ keybind: Keybinds = .{},
|
||||
@"gtk-titlebar": bool = true,
|
||||
|
||||
/// 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
|
||||
/// back to `top`. `hidden`, meaning that tabs don't exist, is not supported
|
||||
/// without using Adwaita, falling back to `top`.
|
||||
///
|
||||
/// When `hidden` is set and Adwaita is enabled, a tab button displaying the
|
||||
/// 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.
|
||||
/// When `hidden` is set, a tab button displaying the 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,
|
||||
|
||||
/// If this is `true`, the titlebar will be hidden when the window is maximized,
|
||||
/// and shown when the titlebar is unmaximized. GTK only.
|
||||
@"gtk-titlebar-hide-when-maximized": bool = false,
|
||||
|
||||
/// Determines the appearance of the top and bottom bars when using the
|
||||
/// Adwaita tab bar. This requires `gtk-adwaita` to be enabled (it is
|
||||
/// by default).
|
||||
/// Determines the appearance of the top and bottom bars tab bar.
|
||||
///
|
||||
/// Valid values are:
|
||||
///
|
||||
@ -2158,7 +2152,7 @@ keybind: Keybinds = .{},
|
||||
/// more subtle border.
|
||||
///
|
||||
/// 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
|
||||
/// are the new typical Gnome style where tabs fill their available space.
|
||||
@ -2166,20 +2160,6 @@ keybind: Keybinds = .{},
|
||||
/// which is the old style.
|
||||
@"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.
|
||||
///
|
||||
/// This configuration can be repeated multiple times to load multiple files.
|
||||
@ -5758,13 +5738,11 @@ pub const GtkSingleInstance = enum {
|
||||
pub const GtkTabsLocation = enum {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
hidden,
|
||||
};
|
||||
|
||||
/// See adw-toolbar-style
|
||||
pub const AdwToolbarStyle = enum {
|
||||
/// See gtk-toolbar-style
|
||||
pub const GtkToolbarStyle = enum {
|
||||
flat,
|
||||
raised,
|
||||
@"raised-border",
|
||||
|
Reference in New Issue
Block a user