mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
gtk: convert Window (and some related files) to zig-gobject (#6775)
This commit is contained in:
@@ -401,10 +401,11 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
return error.GtkApplicationRegisterFailed;
|
||||
}
|
||||
|
||||
// FIXME: when App.zig is converted to zig-gobject
|
||||
// Setup our windowing protocol logic
|
||||
var winproto_app = try winprotopkg.App.init(
|
||||
core_app.alloc,
|
||||
display,
|
||||
@ptrCast(@alignCast(display)),
|
||||
app_id,
|
||||
&config,
|
||||
);
|
||||
|
@@ -920,8 +920,7 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void
|
||||
const window = self.container.window() orelse return;
|
||||
if (window.notebook.nPages() > 1) return;
|
||||
|
||||
// FIXME: when Window is converted to zig-gobject
|
||||
const gtk_window: *gtk.Window = @ptrCast(@alignCast(window.window));
|
||||
const gtk_window = window.window.as(gtk.Window);
|
||||
|
||||
// Note: this doesn't properly take into account the window decorations.
|
||||
// I'm not currently sure how to do that.
|
||||
@@ -941,8 +940,7 @@ pub fn setSizeLimits(self: *const Surface, min: apprt.SurfaceSize, max_: ?apprt.
|
||||
const window = self.container.window() orelse return;
|
||||
if (window.notebook.nPages() > 1) return;
|
||||
|
||||
// FIXME: when Window is converted to zig-gobject
|
||||
const widget: *gtk.Widget = @ptrCast(@alignCast(window.window));
|
||||
const widget = window.window.as(gtk.Widget);
|
||||
|
||||
// Note: this doesn't properly take into account the window decorations.
|
||||
// I'm not currently sure how to do that.
|
||||
@@ -1167,9 +1165,8 @@ pub fn setMouseVisibility(self: *Surface, visible: bool) void {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: when App is converted to zig-gobject
|
||||
// Set our new cursor to the app "none" cursor
|
||||
widget.setCursor(@ptrCast(@alignCast(self.app.cursor_none)));
|
||||
widget.setCursor(self.app.cursor_none);
|
||||
}
|
||||
|
||||
pub fn mouseOverLink(self: *Surface, uri_: ?[]const u8) void {
|
||||
@@ -1409,8 +1406,7 @@ fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface)
|
||||
|
||||
const window_scale_factor = scale: {
|
||||
const window = self.container.window() orelse break :scale 0;
|
||||
// FIXME: when Window.zig is converted to zig-gobject
|
||||
const gtk_window: *gtk.Window = @ptrCast(@alignCast(window.window));
|
||||
const gtk_window = window.window.as(gtk.Window);
|
||||
const gtk_native = gtk_window.as(gtk.Native);
|
||||
const gdk_surface = gtk_native.getSurface() orelse break :scale 0;
|
||||
break :scale gdk_surface.getScaleFactor();
|
||||
@@ -2181,9 +2177,7 @@ pub fn present(self: *Surface) void {
|
||||
if (window.notebook.getTabPosition(tab)) |position|
|
||||
_ = window.notebook.gotoNthTab(position);
|
||||
}
|
||||
// FIXME: when Window.zig is converted to zig-gobject
|
||||
const gtk_window: *gtk.Window = @ptrCast(@alignCast(window.window));
|
||||
gtk_window.present();
|
||||
window.window.as(gtk.Window).present();
|
||||
}
|
||||
|
||||
self.grabFocus();
|
||||
@@ -2209,16 +2203,14 @@ pub fn setSplitZoom(self: *Surface, new_split_zoom: bool) void {
|
||||
const tab_widget = tab.elem.widget();
|
||||
const surface_widget = self.primaryWidget();
|
||||
|
||||
// FIXME: when Tab.zig is converted to zig-gobject
|
||||
const box: *gtk.Box = @ptrCast(@alignCast(tab.box));
|
||||
if (new_split_zoom) {
|
||||
self.detachFromSplit();
|
||||
box.remove(tab_widget);
|
||||
box.append(surface_widget);
|
||||
tab.box.remove(tab_widget);
|
||||
tab.box.append(surface_widget);
|
||||
} else {
|
||||
box.remove(surface_widget);
|
||||
tab.box.remove(surface_widget);
|
||||
self.attachToSplit();
|
||||
box.append(tab_widget);
|
||||
tab.box.append(tab_widget);
|
||||
}
|
||||
|
||||
self.zoomed_in = new_split_zoom;
|
||||
|
@@ -10,6 +10,7 @@ const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
@@ -28,7 +29,6 @@ const Color = configpkg.Config.Color;
|
||||
const Surface = @import("Surface.zig");
|
||||
const Menu = @import("menu.zig").Menu;
|
||||
const Tab = @import("Tab.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const adwaita = @import("adwaita.zig");
|
||||
const gtk_key = @import("key.zig");
|
||||
const TabView = @import("TabView.zig");
|
||||
@@ -48,14 +48,14 @@ last_config: usize,
|
||||
config: DerivedConfig,
|
||||
|
||||
/// Our window
|
||||
window: *c.GtkWindow,
|
||||
window: *adw.ApplicationWindow,
|
||||
|
||||
/// The header bar for the window.
|
||||
headerbar: HeaderBar,
|
||||
|
||||
/// The tab overview for the window. This is possibly null since there is no
|
||||
/// taboverview without a AdwApplicationWindow (libadwaita >= 1.4.0).
|
||||
tab_overview: ?*c.GtkWidget,
|
||||
tab_overview: ?*adw.TabOverview,
|
||||
|
||||
/// The notebook (tab grouping) for this window.
|
||||
notebook: TabView,
|
||||
@@ -64,10 +64,10 @@ notebook: TabView,
|
||||
titlebar_menu: Menu(Window, "titlebar_menu", true),
|
||||
|
||||
/// The libadwaita widget for receiving toast send requests.
|
||||
toast_overlay: *c.GtkWidget,
|
||||
toast_overlay: *adw.ToastOverlay,
|
||||
|
||||
/// See adwTabOverviewOpen for why we have this.
|
||||
adw_tab_overview_focus_timer: ?c.guint = null,
|
||||
adw_tab_overview_focus_timer: ?c_uint = null,
|
||||
|
||||
/// State and logic for windowing protocol for a window.
|
||||
winproto: winprotopkg.Window,
|
||||
@@ -141,25 +141,25 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
.winproto = .none,
|
||||
};
|
||||
|
||||
// FIXME: when App.zig is converted to zig-gobject
|
||||
// Create the window
|
||||
const gtk_widget = c.adw_application_window_new(app.app);
|
||||
errdefer c.gtk_window_destroy(@ptrCast(gtk_widget));
|
||||
self.window = adw.ApplicationWindow.new(@ptrCast(@alignCast(app.app)));
|
||||
const gtk_window = self.window.as(gtk.Window);
|
||||
const gtk_widget = self.window.as(gtk.Widget);
|
||||
errdefer gtk_window.destroy();
|
||||
|
||||
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");
|
||||
gtk_window.setTitle("Ghostty");
|
||||
gtk_window.setDefaultSize(1000, 600);
|
||||
gtk_widget.addCssClass("window");
|
||||
gtk_widget.addCssClass("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(self.window, 0);
|
||||
|
||||
c.gtk_window_set_icon_name(self.window, build_config.bundle_id);
|
||||
gtk_window.setHandleMenubarAccel(0);
|
||||
gtk_window.setIconName(build_config.bundle_id);
|
||||
|
||||
// Create our box which will hold our widgets in the main content area.
|
||||
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||
const box = gtk.Box.new(.vertical, 0);
|
||||
|
||||
// Set up the menus
|
||||
self.titlebar_menu.init(self);
|
||||
@@ -169,48 +169,48 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
|
||||
// If we are using Adwaita, then we can support the tab 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), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
c.adw_tab_overview_set_enable_new_tab(@ptrCast(tab_overview), 1);
|
||||
_ = c.g_signal_connect_data(
|
||||
const tab_overview = adw.TabOverview.new();
|
||||
tab_overview.setView(self.notebook.tab_view);
|
||||
tab_overview.setEnableNewTab(1);
|
||||
_ = adw.TabOverview.signals.create_tab.connect(
|
||||
tab_overview,
|
||||
"create-tab",
|
||||
c.G_CALLBACK(>kNewTabFromOverview),
|
||||
*Window,
|
||||
gtkNewTabFromOverview,
|
||||
self,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
.{},
|
||||
);
|
||||
_ = c.g_signal_connect_data(
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
tab_overview,
|
||||
"notify::open",
|
||||
c.G_CALLBACK(&adwTabOverviewOpen),
|
||||
*Window,
|
||||
adwTabOverviewOpen,
|
||||
self,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
.{
|
||||
.detail = "open",
|
||||
},
|
||||
);
|
||||
|
||||
break :overview tab_overview;
|
||||
} else null;
|
||||
|
||||
// gtk-titlebar can be used to disable the header bar (but keep the window
|
||||
// manager's decorations). We create this no matter if we are decorated or
|
||||
// not because we can have a keybind to toggle the decorations.
|
||||
self.headerbar.init();
|
||||
self.headerbar.init(self);
|
||||
|
||||
{
|
||||
const btn = c.gtk_menu_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, i18n._("Main Menu"));
|
||||
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic");
|
||||
c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget())));
|
||||
_ = c.g_signal_connect_data(
|
||||
const btn = gtk.MenuButton.new();
|
||||
btn.as(gtk.Widget).setTooltipText(i18n._("Main Menu"));
|
||||
btn.setIconName("open-menu-symbolic");
|
||||
btn.setPopover(self.titlebar_menu.asWidget());
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
btn,
|
||||
"notify::active",
|
||||
c.G_CALLBACK(>kTitlebarMenuActivate),
|
||||
*Window,
|
||||
gtkTitlebarMenuActivate,
|
||||
self,
|
||||
null,
|
||||
c.G_CONNECT_DEFAULT,
|
||||
.{
|
||||
.detail = "active",
|
||||
},
|
||||
);
|
||||
self.headerbar.packEnd(btn);
|
||||
self.headerbar.packEnd(btn.as(gtk.Widget));
|
||||
}
|
||||
|
||||
// If we're using an AdwWindow then we can support the tab overview.
|
||||
@@ -218,81 +218,106 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
const btn = switch (self.config.gtk_tabs_location) {
|
||||
.top, .bottom => btn: {
|
||||
const btn = c.gtk_toggle_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, i18n._("View Open Tabs"));
|
||||
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
||||
_ = c.g_object_bind_property(
|
||||
btn,
|
||||
const btn = gtk.ToggleButton.new();
|
||||
btn.as(gtk.Widget).setTooltipText(i18n._("View Open Tabs"));
|
||||
btn.as(gtk.Button).setIconName("view-grid-symbolic");
|
||||
_ = btn.as(gobject.Object).bindProperty(
|
||||
"active",
|
||||
tab_overview,
|
||||
tab_overview.as(gobject.Object),
|
||||
"open",
|
||||
c.G_BINDING_BIDIRECTIONAL | c.G_BINDING_SYNC_CREATE,
|
||||
.{ .bidirectional = true, .sync_create = true },
|
||||
);
|
||||
|
||||
break :btn btn;
|
||||
break :btn btn.as(gtk.Widget);
|
||||
},
|
||||
|
||||
.hidden => btn: {
|
||||
const btn = c.adw_tab_button_new();
|
||||
c.adw_tab_button_set_view(@ptrCast(btn), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open");
|
||||
break :btn btn;
|
||||
const btn = adw.TabButton.new();
|
||||
btn.setView(self.notebook.tab_view);
|
||||
btn.as(gtk.Actionable).setActionName("overview.open");
|
||||
break :btn btn.as(gtk.Widget);
|
||||
},
|
||||
};
|
||||
|
||||
c.gtk_widget_set_focus_on_click(btn, c.FALSE);
|
||||
btn.setFocusOnClick(0);
|
||||
self.headerbar.packEnd(btn);
|
||||
}
|
||||
|
||||
{
|
||||
const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic");
|
||||
c.gtk_widget_set_tooltip_text(btn, i18n._("New Tab"));
|
||||
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT);
|
||||
self.headerbar.packStart(btn);
|
||||
const btn = gtk.Button.newFromIconName("tab-new-symbolic");
|
||||
btn.as(gtk.Widget).setTooltipText(i18n._("New Tab"));
|
||||
_ = gtk.Button.signals.clicked.connect(
|
||||
btn,
|
||||
*Window,
|
||||
gtkTabNewClick,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
self.headerbar.packStart(btn.as(gtk.Widget));
|
||||
}
|
||||
|
||||
_ = 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);
|
||||
_ = c.g_signal_connect_data(self.window, "notify::is-active", c.G_CALLBACK(>kWindowNotifyIsActive), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
self.window,
|
||||
*Window,
|
||||
gtkWindowNotifyMaximized,
|
||||
self,
|
||||
.{
|
||||
.detail = "maximized",
|
||||
},
|
||||
);
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
self.window,
|
||||
*Window,
|
||||
gtkWindowNotifyFullscreened,
|
||||
self,
|
||||
.{
|
||||
.detail = "fullscreened",
|
||||
},
|
||||
);
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
self.window,
|
||||
*Window,
|
||||
gtkWindowNotifyIsActive,
|
||||
self,
|
||||
.{
|
||||
.detail = "is-active",
|
||||
},
|
||||
);
|
||||
|
||||
// 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)) {
|
||||
c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget());
|
||||
box.append(self.headerbar.asWidget());
|
||||
}
|
||||
|
||||
// In debug we show a warning and apply the 'devel' class to the window.
|
||||
// This is a really common issue where people build from source in debug and performance is really bad.
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||
const warning_box = gtk.Box.new(.vertical, 0);
|
||||
const warning_text = i18n._("⚠️ You're running a debug build of Ghostty! Performance will be degraded.");
|
||||
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));
|
||||
const banner = adw.Banner.new(warning_text);
|
||||
banner.setRevealed(1);
|
||||
warning_box.append(banner.as(gtk.Widget));
|
||||
} else {
|
||||
const warning = c.gtk_label_new(warning_text);
|
||||
c.gtk_widget_set_margin_top(warning, 10);
|
||||
c.gtk_widget_set_margin_bottom(warning, 10);
|
||||
c.gtk_box_append(@ptrCast(warning_box), warning);
|
||||
const warning = gtk.Label.new(warning_text);
|
||||
warning.as(gtk.Widget).setMarginTop(10);
|
||||
warning.as(gtk.Widget).setMarginBottom(10);
|
||||
warning_box.append(warning.as(gtk.Widget));
|
||||
}
|
||||
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);
|
||||
gtk_widget.addCssClass("devel");
|
||||
warning_box.as(gtk.Widget).addCssClass("background");
|
||||
box.append(warning_box.as(gtk.Widget));
|
||||
}
|
||||
|
||||
// Setup our toast overlay if we have one
|
||||
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);
|
||||
self.toast_overlay = adw.ToastOverlay.new();
|
||||
self.toast_overlay.setChild(self.notebook.asWidget());
|
||||
box.append(self.toast_overlay.as(gtk.Widget));
|
||||
|
||||
// If we have a tab overview then we can set it on our notebook.
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
tab_overview.setView(self.notebook.tab_view);
|
||||
}
|
||||
|
||||
// We register a key event controller with the window so
|
||||
@@ -300,12 +325,30 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
// focused (i.e. when the libadw tab overview is shown).
|
||||
const ec_key_press = gtk.EventControllerKey.new();
|
||||
errdefer ec_key_press.unref();
|
||||
c.gtk_widget_add_controller(gtk_widget, @ptrCast(@alignCast(ec_key_press)));
|
||||
gtk_widget.addController(ec_key_press.as(gtk.EventController));
|
||||
|
||||
// All of our events
|
||||
_ = 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);
|
||||
_ = gtk.Widget.signals.realize.connect(
|
||||
self.window,
|
||||
*Window,
|
||||
gtkRealize,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
_ = gtk.Window.signals.close_request.connect(
|
||||
self.window,
|
||||
*Window,
|
||||
gtkCloseRequest,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
_ = gtk.Widget.signals.destroy.connect(
|
||||
self.window,
|
||||
*Window,
|
||||
gtkDestroy,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
_ = gtk.EventControllerKey.signals.key_pressed.connect(
|
||||
ec_key_press,
|
||||
*Window,
|
||||
@@ -318,81 +361,68 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
initActions(self);
|
||||
|
||||
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());
|
||||
const toolbar_view = adw.ToolbarView.new();
|
||||
toolbar_view.addTopBar(self.headerbar.asWidget());
|
||||
|
||||
if (self.config.gtk_tabs_location != .hidden) {
|
||||
const tab_bar = c.adw_tab_bar_new();
|
||||
c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
const tab_bar = adw.TabBar.new();
|
||||
tab_bar.setView(self.notebook.tab_view);
|
||||
|
||||
if (!self.config.gtk_wide_tabs) c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||
if (!self.config.gtk_wide_tabs) tab_bar.setExpandTabs(0);
|
||||
|
||||
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
|
||||
switch (self.config.gtk_tabs_location) {
|
||||
.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),
|
||||
.top => toolbar_view.addTopBar(tab_bar.as(gtk.Widget)),
|
||||
.bottom => toolbar_view.addBottomBar(tab_bar.as(gtk.Widget)),
|
||||
.hidden => unreachable,
|
||||
}
|
||||
}
|
||||
c.adw_toolbar_view_set_content(toolbar_view, box);
|
||||
toolbar_view.setContent(box.as(gtk.Widget));
|
||||
|
||||
const toolbar_style: c.AdwToolbarStyle = switch (self.config.gtk_toolbar_style) {
|
||||
.flat => c.ADW_TOOLBAR_FLAT,
|
||||
.raised => c.ADW_TOOLBAR_RAISED,
|
||||
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
|
||||
const toolbar_style: adw.ToolbarStyle = switch (self.config.gtk_toolbar_style) {
|
||||
.flat => .flat,
|
||||
.raised => .raised,
|
||||
.@"raised-border" => .raised_border,
|
||||
};
|
||||
c.adw_toolbar_view_set_top_bar_style(toolbar_view, toolbar_style);
|
||||
c.adw_toolbar_view_set_bottom_bar_style(toolbar_view, toolbar_style);
|
||||
toolbar_view.setTopBarStyle(toolbar_style);
|
||||
toolbar_view.setTopBarStyle(toolbar_style);
|
||||
|
||||
// Set our application window content.
|
||||
c.adw_tab_overview_set_child(
|
||||
@ptrCast(self.tab_overview),
|
||||
@ptrCast(@alignCast(toolbar_view)),
|
||||
);
|
||||
c.adw_application_window_set_content(
|
||||
@ptrCast(gtk_widget),
|
||||
@ptrCast(@alignCast(self.tab_overview)),
|
||||
);
|
||||
self.tab_overview.?.setChild(toolbar_view.as(gtk.Widget));
|
||||
self.window.setContent(self.tab_overview.?.as(gtk.Widget));
|
||||
} else tab_bar: {
|
||||
if (self.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");
|
||||
const tab_bar = adw.TabBar.new();
|
||||
tab_bar.as(gtk.Widget).addCssClass("inline");
|
||||
switch (self.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)),
|
||||
.top => box.insertChildAfter(
|
||||
tab_bar.as(gtk.Widget),
|
||||
self.headerbar.asWidget(),
|
||||
),
|
||||
.bottom => box.append(tab_bar.as(gtk.Widget)),
|
||||
.hidden => unreachable,
|
||||
}
|
||||
c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
tab_bar.setView(self.notebook.tab_view);
|
||||
|
||||
if (!self.config.gtk_wide_tabs) c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||
if (!self.config.gtk_wide_tabs) tab_bar.setExpandTabs(0);
|
||||
}
|
||||
|
||||
// If we want the window to be maximized, we do that here.
|
||||
if (self.config.maximize) c.gtk_window_maximize(self.window);
|
||||
if (self.config.maximize) self.window.as(gtk.Window).maximize();
|
||||
|
||||
// If we are in fullscreen mode, new windows start fullscreen.
|
||||
if (self.config.fullscreen) c.gtk_window_fullscreen(self.window);
|
||||
if (self.config.fullscreen) self.window.as(gtk.Window).fullscreen();
|
||||
}
|
||||
|
||||
pub fn present(self: *Window) void {
|
||||
const window: *gtk.Window = @ptrCast(self.window);
|
||||
window.present();
|
||||
self.window.as(gtk.Window).present();
|
||||
}
|
||||
|
||||
pub fn toggleVisibility(self: *Window) void {
|
||||
const window: *gtk.Widget = @ptrCast(self.window);
|
||||
const widget = self.window.as(gtk.Widget);
|
||||
|
||||
window.setVisible(@intFromBool(window.isVisible() == 0));
|
||||
widget.setVisible(@intFromBool(widget.isVisible() == 0));
|
||||
}
|
||||
|
||||
pub fn isQuickTerminal(self: *Window) bool {
|
||||
@@ -424,15 +454,17 @@ pub fn updateConfig(
|
||||
/// reactive by moving them here.
|
||||
pub fn syncAppearance(self: *Window) !void {
|
||||
const csd_enabled = self.winproto.clientSideDecorationEnabled();
|
||||
c.gtk_window_set_decorated(self.window, @intFromBool(csd_enabled));
|
||||
const gtk_window = self.window.as(gtk.Window);
|
||||
const gtk_widget = self.window.as(gtk.Widget);
|
||||
gtk_window.setDecorated(@intFromBool(csd_enabled));
|
||||
|
||||
// Fix any artifacting that may occur in window corners. The .ssd CSS
|
||||
// class is defined in the GtkWindow documentation:
|
||||
// https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition
|
||||
// for .ssd is provided by GTK and Adwaita.
|
||||
toggleCssClass(@ptrCast(self.window), "csd", csd_enabled);
|
||||
toggleCssClass(@ptrCast(self.window), "ssd", !csd_enabled);
|
||||
toggleCssClass(@ptrCast(self.window), "no-border-radius", !csd_enabled);
|
||||
toggleCssClass(gtk_widget, "csd", csd_enabled);
|
||||
toggleCssClass(gtk_widget, "ssd", !csd_enabled);
|
||||
toggleCssClass(gtk_widget, "no-border-radius", !csd_enabled);
|
||||
|
||||
self.headerbar.setVisible(visible: {
|
||||
// Never display the header bar when CSDs are disabled.
|
||||
@@ -453,7 +485,7 @@ pub fn syncAppearance(self: *Window) !void {
|
||||
});
|
||||
|
||||
toggleCssClass(
|
||||
@ptrCast(self.window),
|
||||
gtk_widget,
|
||||
"background",
|
||||
self.config.background_opacity >= 1,
|
||||
);
|
||||
@@ -462,7 +494,7 @@ pub fn syncAppearance(self: *Window) !void {
|
||||
// GTK version is before 4.16. The conditional is because above 4.16
|
||||
// we use GTK CSS color variables.
|
||||
toggleCssClass(
|
||||
@ptrCast(self.window),
|
||||
gtk_widget,
|
||||
"window-theme-ghostty",
|
||||
!version.atLeast(4, 16, 0) and self.config.window_theme == .ghostty,
|
||||
);
|
||||
@@ -473,18 +505,24 @@ pub fn syncAppearance(self: *Window) !void {
|
||||
// Disable the title buttons (close, maximize, minimize, ...)
|
||||
// *inside* the tab overview if CSDs are disabled.
|
||||
// We do spare the search button, though.
|
||||
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));
|
||||
tab_overview.setShowStartTitleButtons(@intFromBool(csd_enabled));
|
||||
tab_overview.setShowEndTitleButtons(@intFromBool(csd_enabled));
|
||||
|
||||
// Update toolbar view style
|
||||
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_tab_overview_get_child(@ptrCast(tab_overview)));
|
||||
const toolbar_style: c.AdwToolbarStyle = switch (self.config.gtk_toolbar_style) {
|
||||
.flat => c.ADW_TOOLBAR_FLAT,
|
||||
.raised => c.ADW_TOOLBAR_RAISED,
|
||||
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
|
||||
};
|
||||
c.adw_toolbar_view_set_top_bar_style(toolbar_view, toolbar_style);
|
||||
c.adw_toolbar_view_set_bottom_bar_style(toolbar_view, toolbar_style);
|
||||
toolbar_view: {
|
||||
const tab_overview_child = tab_overview.getChild() orelse break :toolbar_view;
|
||||
const toolbar_view = gobject.ext.cast(
|
||||
adw.ToolbarView,
|
||||
tab_overview_child,
|
||||
) orelse break :toolbar_view;
|
||||
const toolbar_style: adw.ToolbarStyle = switch (self.config.gtk_toolbar_style) {
|
||||
.flat => .flat,
|
||||
.raised => .raised,
|
||||
.@"raised-border" => .raised_border,
|
||||
};
|
||||
toolbar_view.setTopBarStyle(toolbar_style);
|
||||
toolbar_view.setBottomBarStyle(toolbar_style);
|
||||
}
|
||||
}
|
||||
|
||||
self.winproto.syncAppearance() catch |err| {
|
||||
@@ -493,14 +531,14 @@ pub fn syncAppearance(self: *Window) !void {
|
||||
}
|
||||
|
||||
fn toggleCssClass(
|
||||
widget: *c.GtkWidget,
|
||||
widget: *gtk.Widget,
|
||||
class: [:0]const u8,
|
||||
v: bool,
|
||||
) void {
|
||||
if (v) {
|
||||
c.gtk_widget_add_css_class(widget, class);
|
||||
widget.addCssClass(class);
|
||||
} else {
|
||||
c.gtk_widget_remove_css_class(widget, class);
|
||||
widget.removeCssClass(class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,8 +546,7 @@ fn toggleCssClass(
|
||||
/// menus and such. The menu is defined in App.zig but the action is defined
|
||||
/// here. The string name binds them.
|
||||
fn initActions(self: *Window) void {
|
||||
// FIXME: when rest of file is converted to gobject
|
||||
const window: *gtk.ApplicationWindow = @ptrCast(@alignCast(self.window));
|
||||
const window = self.window.as(gtk.ApplicationWindow);
|
||||
const action_map = window.as(gio.ActionMap);
|
||||
const actions = .{
|
||||
.{ "about", gtkActionAbout },
|
||||
@@ -547,7 +584,7 @@ pub fn deinit(self: *Window) void {
|
||||
self.winproto.deinit(self.app.core_app.alloc);
|
||||
|
||||
if (self.adw_tab_overview_focus_timer) |timer| {
|
||||
_ = c.g_source_remove(timer);
|
||||
_ = glib.Source.remove(timer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,19 +664,19 @@ 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 (self.tab_overview) |tab_overview| {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
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));
|
||||
const is_open = tab_overview.getOpen() != 0;
|
||||
tab_overview.setOpen(@intFromBool(!is_open));
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle the maximized state for this window.
|
||||
pub fn toggleMaximize(self: *Window) void {
|
||||
if (self.config.maximize) {
|
||||
c.gtk_window_unmaximize(self.window);
|
||||
self.window.as(gtk.Window).unmaximize();
|
||||
} else {
|
||||
c.gtk_window_maximize(self.window);
|
||||
self.window.as(gtk.Window).maximize();
|
||||
}
|
||||
// We update the config and call syncAppearance
|
||||
// in the gtkWindowNotifyMaximized callback
|
||||
@@ -648,9 +685,9 @@ pub fn toggleMaximize(self: *Window) void {
|
||||
/// Toggle fullscreen for this window.
|
||||
pub fn toggleFullscreen(self: *Window) void {
|
||||
if (self.config.fullscreen) {
|
||||
c.gtk_window_unfullscreen(self.window);
|
||||
self.window.as(gtk.Window).unfullscreen();
|
||||
} else {
|
||||
c.gtk_window_fullscreen(self.window);
|
||||
self.window.as(gtk.Window).fullscreen();
|
||||
}
|
||||
// We update the config and call syncAppearance
|
||||
// in the gtkWindowNotifyFullscreened callback
|
||||
@@ -678,8 +715,7 @@ pub fn toggleWindowDecorations(self: *Window) void {
|
||||
pub fn focusCurrentTab(self: *Window) void {
|
||||
const tab = self.notebook.currentTab() orelse return;
|
||||
const surface = tab.focus_child orelse return;
|
||||
const gl_area = @as(*c.GtkWidget, @ptrCast(surface.gl_area));
|
||||
_ = c.gtk_widget_grab_focus(gl_area);
|
||||
_ = surface.gl_area.as(gtk.Widget).grabFocus();
|
||||
|
||||
if (surface.getTitle()) |title| {
|
||||
self.setTitle(title);
|
||||
@@ -691,14 +727,12 @@ pub fn onConfigReloaded(self: *Window) void {
|
||||
}
|
||||
|
||||
pub fn sendToast(self: *Window, title: [*:0]const u8) void {
|
||||
const toast = c.adw_toast_new(title);
|
||||
c.adw_toast_set_timeout(toast, 3);
|
||||
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
|
||||
const toast = adw.Toast.new(title);
|
||||
toast.setTimeout(3);
|
||||
self.toast_overlay.addToast(toast);
|
||||
}
|
||||
|
||||
fn gtkRealize(_: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||
const self = userdataSelf(ud.?);
|
||||
|
||||
fn gtkRealize(_: *adw.ApplicationWindow, self: *Window) callconv(.c) void {
|
||||
// Initialize our window protocol logic
|
||||
if (winprotopkg.Window.init(
|
||||
self.app.core_app.alloc,
|
||||
@@ -714,52 +748,46 @@ fn gtkRealize(_: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||
self.syncAppearance() catch |err| {
|
||||
log.err("failed to initialize appearance={}", .{err});
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn gtkWindowNotifyMaximized(
|
||||
_: *c.GObject,
|
||||
_: *c.GParamSpec,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self = userdataSelf(ud orelse return);
|
||||
self.config.maximize = c.gtk_window_is_maximized(self.window) != 0;
|
||||
_: *adw.ApplicationWindow,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Window,
|
||||
) callconv(.c) void {
|
||||
self.config.maximize = self.window.as(gtk.Window).isMaximized() != 0;
|
||||
self.syncAppearance() catch |err| {
|
||||
log.err("failed to sync appearance={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkWindowNotifyFullscreened(
|
||||
_: *c.GObject,
|
||||
_: *c.GParamSpec,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self = userdataSelf(ud orelse return);
|
||||
self.config.fullscreen = c.gtk_window_is_fullscreen(self.window) != 0;
|
||||
_: *adw.ApplicationWindow,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Window,
|
||||
) callconv(.c) void {
|
||||
self.config.fullscreen = self.window.as(gtk.Window).isFullscreen() != 0;
|
||||
self.syncAppearance() catch |err| {
|
||||
log.err("failed to sync appearance={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkWindowNotifyIsActive(
|
||||
_: *c.GObject,
|
||||
_: *c.GParamSpec,
|
||||
ud: ?*anyopaque,
|
||||
_: *adw.ApplicationWindow,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Window,
|
||||
) callconv(.C) void {
|
||||
const self = userdataSelf(ud orelse return);
|
||||
if (!self.isQuickTerminal()) return;
|
||||
|
||||
// Hide when we're unfocused
|
||||
if (self.config.quick_terminal_autohide and c.gtk_window_is_active(self.window) == 0) {
|
||||
if (self.config.quick_terminal_autohide and self.window.as(gtk.Window).isActive() == 0) {
|
||||
self.toggleVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
|
||||
// sends an undefined value.
|
||||
fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
fn gtkTabNewClick(_: *gtk.Button, self: *Window) callconv(.c) void {
|
||||
const surface = self.actionSurface() orelse return;
|
||||
_ = surface.performBindingAction(.{ .new_tab = {} }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
@@ -769,27 +797,23 @@ fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
|
||||
/// Create a new tab from the AdwTabOverview. We can't copy gtkTabNewClick
|
||||
/// because we need to return an AdwTabPage from this function.
|
||||
fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwTabPage {
|
||||
fn gtkNewTabFromOverview(_: *adw.TabOverview, self: *Window) callconv(.c) *adw.TabPage {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
const self: *Window = userdataSelf(ud.?);
|
||||
|
||||
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(@ptrCast(@alignCast(self.notebook.tab_view)), @ptrCast(@alignCast(tab.box)));
|
||||
const tab = Tab.create(alloc, self, surface) catch unreachable;
|
||||
return self.notebook.tab_view.getPage(tab.box.as(gtk.Widget));
|
||||
}
|
||||
|
||||
fn adwTabOverviewOpen(
|
||||
object: *c.GObject,
|
||||
_: *c.GParamSpec,
|
||||
ud: ?*anyopaque,
|
||||
) void {
|
||||
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(object));
|
||||
tab_overview: *adw.TabOverview,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Window,
|
||||
) callconv(.c) void {
|
||||
|
||||
// We only care about when the tab overview is closed.
|
||||
if (c.adw_tab_overview_get_open(tab_overview) == 1) {
|
||||
return;
|
||||
}
|
||||
if (tab_overview.getOpen() != 0) return;
|
||||
|
||||
// On tab overview close, focus is sometimes lost. This is an
|
||||
// upstream issue in libadwaita[1]. When this is resolved we
|
||||
@@ -800,24 +824,24 @@ fn adwTabOverviewOpen(
|
||||
// animation is 400ms.
|
||||
//
|
||||
// [1]: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/670
|
||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||
|
||||
// If we have an old timer remove it
|
||||
if (window.adw_tab_overview_focus_timer) |timer| {
|
||||
_ = c.g_source_remove(timer);
|
||||
if (self.adw_tab_overview_focus_timer) |timer| {
|
||||
_ = glib.Source.remove(timer);
|
||||
}
|
||||
|
||||
// Restart our timer
|
||||
window.adw_tab_overview_focus_timer = c.g_timeout_add(
|
||||
self.adw_tab_overview_focus_timer = glib.timeoutAdd(
|
||||
500,
|
||||
@ptrCast(&adwTabOverviewFocusTimer),
|
||||
window,
|
||||
adwTabOverviewFocusTimer,
|
||||
self,
|
||||
);
|
||||
}
|
||||
|
||||
fn adwTabOverviewFocusTimer(
|
||||
self: *Window,
|
||||
) callconv(.C) c.gboolean {
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) c_int {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return 0));
|
||||
self.adw_tab_overview_focus_timer = null;
|
||||
self.focusCurrentTab();
|
||||
|
||||
@@ -826,7 +850,7 @@ fn adwTabOverviewFocusTimer(
|
||||
}
|
||||
|
||||
pub fn close(self: *Window) void {
|
||||
const window: *gtk.Window = @ptrCast(self.window);
|
||||
const window = self.window.as(gtk.Window);
|
||||
|
||||
// Unset the quick terminal on the app level
|
||||
if (self.isQuickTerminal()) self.app.quick_terminal = null;
|
||||
@@ -851,21 +875,17 @@ pub fn closeWithConfirmation(self: *Window) void {
|
||||
};
|
||||
}
|
||||
|
||||
fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||
_ = v;
|
||||
fn gtkCloseRequest(_: *adw.ApplicationWindow, self: *Window) callconv(.c) c_int {
|
||||
log.debug("window close request", .{});
|
||||
const self = userdataSelf(ud.?);
|
||||
|
||||
self.closeWithConfirmation();
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// "destroy" signal for the window
|
||||
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
_ = v;
|
||||
fn gtkDestroy(_: *adw.ApplicationWindow, self: *Window) callconv(.c) void {
|
||||
log.debug("window destroy", .{});
|
||||
|
||||
const self = userdataSelf(ud.?);
|
||||
const alloc = self.app.core_app.alloc;
|
||||
self.deinit();
|
||||
alloc.destroy(self);
|
||||
@@ -877,7 +897,7 @@ fn gtkKeyPressed(
|
||||
keycode: c_uint,
|
||||
gtk_mods: gdk.ModifierType,
|
||||
self: *Window,
|
||||
) callconv(.C) c.gboolean {
|
||||
) callconv(.c) c_int {
|
||||
// We only process window-level events currently for the tab
|
||||
// overview. This is primarily defensive programming because
|
||||
// I'm not 100% certain how our logic below will interact with
|
||||
@@ -887,9 +907,8 @@ fn gtkKeyPressed(
|
||||
// If someone can confidently show or explain that this is not
|
||||
// necessary, please remove this check.
|
||||
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;
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
if (tab_overview.getOpen() == 0) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,8 +932,8 @@ fn gtkActionAbout(
|
||||
const website = "https://ghostty.org";
|
||||
|
||||
if (adwaita.versionAtLeast(1, 5, 0)) {
|
||||
c.adw_show_about_dialog(
|
||||
@ptrCast(self.window),
|
||||
adw.showAboutDialog(
|
||||
self.window.as(gtk.Widget),
|
||||
"application-name",
|
||||
name,
|
||||
"developer-name",
|
||||
@@ -930,8 +949,8 @@ fn gtkActionAbout(
|
||||
@as(?*anyopaque, null),
|
||||
);
|
||||
} else {
|
||||
c.gtk_show_about_dialog(
|
||||
self.window,
|
||||
gtk.showAboutDialog(
|
||||
self.window.as(gtk.Window),
|
||||
"program-name",
|
||||
name,
|
||||
"logo-icon-name",
|
||||
@@ -1116,21 +1135,16 @@ pub fn actionSurface(self: *Window) ?*CoreSurface {
|
||||
}
|
||||
|
||||
fn gtkTitlebarMenuActivate(
|
||||
btn: *c.GtkMenuButton,
|
||||
_: *c.GParamSpec,
|
||||
ud: ?*anyopaque,
|
||||
btn: *gtk.MenuButton,
|
||||
_: *gobject.ParamSpec,
|
||||
self: *Window,
|
||||
) callconv(.C) void {
|
||||
// debian 12 is stuck on GTK 4.8
|
||||
if (!version.atLeast(4, 10, 0)) return;
|
||||
const active = c.gtk_menu_button_get_active(btn) != 0;
|
||||
const self = userdataSelf(ud orelse return);
|
||||
const active = btn.getActive() != 0;
|
||||
if (active) {
|
||||
self.titlebar_menu.refresh();
|
||||
} else {
|
||||
self.focusCurrentTab();
|
||||
}
|
||||
}
|
||||
|
||||
fn userdataSelf(ud: *anyopaque) *Window {
|
||||
return @ptrCast(@alignCast(ud));
|
||||
}
|
||||
|
@@ -1,23 +1,8 @@
|
||||
const build_options = @import("build_options");
|
||||
|
||||
/// Imported C API directly from header files
|
||||
pub const c = @cImport({
|
||||
@cInclude("gtk/gtk.h");
|
||||
@cInclude("adwaita.h");
|
||||
|
||||
if (build_options.x11) {
|
||||
// Add in X11-specific GDK backend which we use for specific things
|
||||
// (e.g. X11 window class).
|
||||
@cInclude("gdk/x11/gdkx.h");
|
||||
@cInclude("X11/Xlib.h");
|
||||
@cInclude("X11/Xatom.h");
|
||||
// Xkb for X11 state handling
|
||||
@cInclude("X11/XKBlib.h");
|
||||
}
|
||||
if (build_options.wayland) {
|
||||
@cInclude("gdk/wayland/gdkwayland.h");
|
||||
}
|
||||
|
||||
// generated header files
|
||||
@cInclude("ghostty_resources.h");
|
||||
|
||||
|
@@ -1,59 +1,54 @@
|
||||
const HeaderBar = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const adw = @import("adw");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
|
||||
/// the Adwaita headerbar widget
|
||||
headerbar: *c.AdwHeaderBar,
|
||||
headerbar: *adw.HeaderBar,
|
||||
|
||||
/// the Window that we belong to
|
||||
window: *Window,
|
||||
|
||||
/// the Adwaita window title widget
|
||||
title: *c.AdwWindowTitle,
|
||||
title: *adw.WindowTitle,
|
||||
|
||||
pub fn init(self: *HeaderBar) void {
|
||||
const window: *Window = @fieldParentPtr("headerbar", self);
|
||||
pub fn init(self: *HeaderBar, window: *Window) void {
|
||||
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,
|
||||
))),
|
||||
.headerbar = adw.HeaderBar.new(),
|
||||
.window = window,
|
||||
.title = adw.WindowTitle.new(
|
||||
window.window.as(gtk.Window).getTitle() orelse "Ghostty",
|
||||
"",
|
||||
),
|
||||
};
|
||||
c.adw_header_bar_set_title_widget(
|
||||
self.headerbar,
|
||||
@ptrCast(@alignCast(self.title)),
|
||||
);
|
||||
self.headerbar.setTitleWidget(self.title.as(gtk.Widget));
|
||||
}
|
||||
|
||||
pub fn setVisible(self: *const HeaderBar, visible: bool) void {
|
||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
||||
self.headerbar.as(gtk.Widget).setVisible(@intFromBool(visible));
|
||||
}
|
||||
|
||||
pub fn asWidget(self: *const HeaderBar) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.headerbar));
|
||||
pub fn asWidget(self: *const HeaderBar) *gtk.Widget {
|
||||
return self.headerbar.as(gtk.Widget);
|
||||
}
|
||||
|
||||
pub fn packEnd(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||
c.adw_header_bar_pack_end(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
pub fn packEnd(self: *const HeaderBar, widget: *gtk.Widget) void {
|
||||
self.headerbar.packEnd(widget);
|
||||
}
|
||||
|
||||
pub fn packStart(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||
c.adw_header_bar_pack_start(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
pub fn packStart(self: *const HeaderBar, widget: *gtk.Widget) void {
|
||||
self.headerbar.packStart(widget);
|
||||
}
|
||||
|
||||
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);
|
||||
self.window.window.as(gtk.Window).setTitle(title);
|
||||
self.title.setTitle(title);
|
||||
}
|
||||
|
||||
pub fn setSubtitle(self: *const HeaderBar, subtitle: [:0]const u8) void {
|
||||
c.adw_window_title_set_subtitle(self.title, subtitle);
|
||||
self.title.setSubtitle(subtitle);
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const gdk = @import("gdk");
|
||||
|
||||
const c = @import("c.zig").c;
|
||||
const Config = @import("../../config.zig").Config;
|
||||
const input = @import("../../input.zig");
|
||||
const key = @import("key.zig");
|
||||
@@ -29,7 +28,7 @@ pub const App = union(Protocol) {
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
gdk_display: *c.GdkDisplay,
|
||||
gdk_display: *gdk.Display,
|
||||
app_id: [:0]const u8,
|
||||
config: *const Config,
|
||||
) !App {
|
||||
|
@@ -3,7 +3,6 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const gdk = @import("gdk");
|
||||
|
||||
const c = @import("../c.zig").c;
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const input = @import("../../../input.zig");
|
||||
const ApprtWindow = @import("../Window.zig");
|
||||
@@ -13,7 +12,7 @@ const log = std.log.scoped(.winproto_noop);
|
||||
pub const App = struct {
|
||||
pub fn init(
|
||||
_: Allocator,
|
||||
_: *c.GdkDisplay,
|
||||
_: *gdk.Display,
|
||||
_: [:0]const u8,
|
||||
_: *const Config,
|
||||
) !?App {
|
||||
|
@@ -3,12 +3,13 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_options = @import("build_options");
|
||||
|
||||
const wayland = @import("wayland");
|
||||
const gtk = @import("gtk");
|
||||
const gtk4_layer_shell = @import("gtk4-layer-shell");
|
||||
const gdk = @import("gdk");
|
||||
const gdk_wayland = @import("gdk_wayland");
|
||||
const gobject = @import("gobject");
|
||||
const gtk4_layer_shell = @import("gtk4-layer-shell");
|
||||
const gtk = @import("gtk");
|
||||
const wayland = @import("wayland");
|
||||
|
||||
const c = @import("../c.zig").c;
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const input = @import("../../../input.zig");
|
||||
const ApprtWindow = @import("../Window.zig");
|
||||
@@ -37,7 +38,7 @@ pub const App = struct {
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
gdk_display: *c.GdkDisplay,
|
||||
gdk_display: *gdk.Display,
|
||||
app_id: [:0]const u8,
|
||||
config: *const Config,
|
||||
) !?App {
|
||||
@@ -45,14 +46,18 @@ pub const App = struct {
|
||||
_ = app_id;
|
||||
|
||||
// Check if we're actually on Wayland
|
||||
if (c.g_type_check_instance_is_a(
|
||||
@ptrCast(@alignCast(gdk_display)),
|
||||
c.gdk_wayland_display_get_type(),
|
||||
if (gobject.typeCheckInstanceIsA(
|
||||
gdk_display.as(gobject.TypeInstance),
|
||||
gdk_wayland.WaylandDisplay.getGObjectType(),
|
||||
) == 0) return null;
|
||||
|
||||
const display: *wl.Display = @ptrCast(c.gdk_wayland_display_get_wl_display(
|
||||
const gdk_wayland_display = gobject.ext.cast(
|
||||
gdk_wayland.WaylandDisplay,
|
||||
gdk_display,
|
||||
) orelse return error.NoWaylandDisplay);
|
||||
) orelse return error.NoWaylandDisplay;
|
||||
const display: *wl.Display = @ptrCast(@alignCast(
|
||||
gdk_wayland_display.getWlDisplay() orelse return error.NoWaylandDisplay,
|
||||
));
|
||||
|
||||
// Create our context for our callbacks so we have a stable pointer.
|
||||
// Note: at the time of writing this comment, we don't really need
|
||||
@@ -219,20 +224,24 @@ pub const Window = struct {
|
||||
) !Window {
|
||||
_ = alloc;
|
||||
|
||||
const gdk_surface = c.gtk_native_get_surface(
|
||||
@ptrCast(apprt_window.window),
|
||||
) orelse return error.NotWaylandSurface;
|
||||
const gtk_native = apprt_window.window.as(gtk.Native);
|
||||
const gdk_surface = gtk_native.getSurface() orelse return error.NotWaylandSurface;
|
||||
|
||||
// This should never fail, because if we're being called at this point
|
||||
// then we've already asserted that our app state is Wayland.
|
||||
if (c.g_type_check_instance_is_a(
|
||||
@ptrCast(@alignCast(gdk_surface)),
|
||||
c.gdk_wayland_surface_get_type(),
|
||||
) == 0) return error.NotWaylandSurface;
|
||||
if (gobject.typeCheckInstanceIsA(
|
||||
gdk_surface.as(gobject.TypeInstance),
|
||||
gdk_wayland.WaylandSurface.getGObjectType(),
|
||||
) == 0)
|
||||
return error.NoWaylandSurface;
|
||||
|
||||
const wl_surface: *wl.Surface = @ptrCast(c.gdk_wayland_surface_get_wl_surface(
|
||||
const gdk_wl_surface = gobject.ext.cast(
|
||||
gdk_wayland.WaylandSurface,
|
||||
gdk_surface,
|
||||
) orelse return error.NoWaylandSurface);
|
||||
) orelse return error.NoWaylandSurface;
|
||||
const wl_surface: *wl.Surface = @ptrCast(@alignCast(
|
||||
gdk_wl_surface.getWlSurface() orelse return error.NoWaylandSurface,
|
||||
));
|
||||
|
||||
// Get our decoration object so we can control the
|
||||
// CSD vs SSD status of this surface.
|
||||
@@ -252,7 +261,13 @@ pub const Window = struct {
|
||||
|
||||
if (apprt_window.isQuickTerminal()) {
|
||||
const surface: *gdk.Surface = @ptrCast(gdk_surface);
|
||||
_ = gdk.Surface.signals.enter_monitor.connect(surface, *ApprtWindow, enteredMonitor, apprt_window, .{});
|
||||
_ = gdk.Surface.signals.enter_monitor.connect(
|
||||
surface,
|
||||
*ApprtWindow,
|
||||
enteredMonitor,
|
||||
apprt_window,
|
||||
.{},
|
||||
);
|
||||
}
|
||||
|
||||
return .{
|
||||
|
@@ -4,9 +4,20 @@ const builtin = @import("builtin");
|
||||
const build_options = @import("build_options");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gdk_x11 = @import("gdk_x11");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
const xlib = @import("xlib");
|
||||
|
||||
pub const c = @cImport({
|
||||
@cInclude("X11/Xlib.h");
|
||||
@cInclude("X11/Xatom.h");
|
||||
@cInclude("X11/XKBlib.h");
|
||||
});
|
||||
|
||||
const c = @import("../c.zig").c;
|
||||
const input = @import("../../../input.zig");
|
||||
const Config = @import("../../../config.zig").Config;
|
||||
const adwaita = @import("../adwaita.zig");
|
||||
@@ -15,28 +26,28 @@ const ApprtWindow = @import("../Window.zig");
|
||||
const log = std.log.scoped(.gtk_x11);
|
||||
|
||||
pub const App = struct {
|
||||
display: *c.Display,
|
||||
display: *xlib.Display,
|
||||
base_event_code: c_int,
|
||||
atoms: Atoms,
|
||||
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
gdk_display: *c.GdkDisplay,
|
||||
_: Allocator,
|
||||
gdk_display: *gdk.Display,
|
||||
app_id: [:0]const u8,
|
||||
config: *const Config,
|
||||
) !?App {
|
||||
_ = alloc;
|
||||
|
||||
// If the display isn't X11, then we don't need to do anything.
|
||||
if (c.g_type_check_instance_is_a(
|
||||
@ptrCast(@alignCast(gdk_display)),
|
||||
c.gdk_x11_display_get_type(),
|
||||
if (gobject.typeCheckInstanceIsA(
|
||||
gdk_display.as(gobject.TypeInstance),
|
||||
gdk_x11.X11Display.getGObjectType(),
|
||||
) == 0) return null;
|
||||
|
||||
// Get our X11 display
|
||||
const display: *c.Display = c.gdk_x11_display_get_xdisplay(
|
||||
const gdk_x11_display = gobject.ext.cast(
|
||||
gdk_x11.X11Display,
|
||||
gdk_display,
|
||||
) orelse return error.NoX11Display;
|
||||
) orelse return null;
|
||||
const xlib_display = gdk_x11_display.getXdisplay();
|
||||
|
||||
const x11_program_name: [:0]const u8 = if (config.@"x11-instance-name") |pn|
|
||||
pn
|
||||
@@ -61,8 +72,8 @@ pub const App = struct {
|
||||
// WM_CLASS(STRING) = "ghostty", "com.mitchellh.ghostty"
|
||||
//
|
||||
// Append "-debug" on both when using the debug build.
|
||||
c.g_set_prgname(x11_program_name);
|
||||
c.gdk_x11_display_set_program_class(gdk_display, app_id);
|
||||
glib.setPrgname(x11_program_name);
|
||||
gdk_x11.X11Display.setProgramClass(gdk_display, app_id);
|
||||
|
||||
// XKB
|
||||
log.debug("Xkb.init: initializing Xkb", .{});
|
||||
@@ -73,7 +84,7 @@ pub const App = struct {
|
||||
var major = c.XkbMajorVersion;
|
||||
var minor = c.XkbMinorVersion;
|
||||
if (c.XkbQueryExtension(
|
||||
display,
|
||||
@ptrCast(@alignCast(xlib_display)),
|
||||
&opcode,
|
||||
&base_event_code,
|
||||
&base_error_code,
|
||||
@@ -86,7 +97,7 @@ pub const App = struct {
|
||||
|
||||
log.debug("Xkb.init: running XkbSelectEventDetails", .{});
|
||||
if (c.XkbSelectEventDetails(
|
||||
display,
|
||||
@ptrCast(@alignCast(xlib_display)),
|
||||
c.XkbUseCoreKbd,
|
||||
c.XkbStateNotify,
|
||||
c.XkbModifierStateMask,
|
||||
@@ -97,9 +108,9 @@ pub const App = struct {
|
||||
}
|
||||
|
||||
return .{
|
||||
.display = display,
|
||||
.display = xlib_display,
|
||||
.base_event_code = base_event_code,
|
||||
.atoms = Atoms.init(gdk_display),
|
||||
.atoms = Atoms.init(gdk_x11_display),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,10 +139,13 @@ pub const App = struct {
|
||||
|
||||
// Shoutout to Mozilla for figuring out a clean way to do this, this is
|
||||
// paraphrased from Firefox/Gecko in widget/gtk/nsGtkKeyUtils.cpp.
|
||||
if (c.XEventsQueued(self.display, c.QueuedAfterReading) == 0) return null;
|
||||
if (c.XEventsQueued(
|
||||
@ptrCast(@alignCast(self.display)),
|
||||
c.QueuedAfterReading,
|
||||
) == 0) return null;
|
||||
|
||||
var nextEvent: c.XEvent = undefined;
|
||||
_ = c.XPeekEvent(self.display, &nextEvent);
|
||||
_ = c.XPeekEvent(@ptrCast(@alignCast(self.display)), &nextEvent);
|
||||
if (nextEvent.type != self.base_event_code) return null;
|
||||
|
||||
const xkb_event: *c.XkbEvent = @ptrCast(&nextEvent);
|
||||
@@ -163,8 +177,8 @@ pub const App = struct {
|
||||
pub const Window = struct {
|
||||
app: *App,
|
||||
config: *const ApprtWindow.DerivedConfig,
|
||||
window: c.Window,
|
||||
gtk_window: *c.GtkWindow,
|
||||
window: xlib.Window,
|
||||
gtk_window: *adw.ApplicationWindow,
|
||||
|
||||
blur_region: Region = .{},
|
||||
|
||||
@@ -175,20 +189,26 @@ pub const Window = struct {
|
||||
) !Window {
|
||||
_ = alloc;
|
||||
|
||||
const surface = c.gtk_native_get_surface(
|
||||
@ptrCast(apprt_window.window),
|
||||
) orelse return error.NotX11Surface;
|
||||
const surface = apprt_window.window.as(
|
||||
gtk.Native,
|
||||
).getSurface() orelse return error.NotX11Surface;
|
||||
|
||||
// Check if we're actually on X11
|
||||
if (c.g_type_check_instance_is_a(
|
||||
@ptrCast(@alignCast(surface)),
|
||||
c.gdk_x11_surface_get_type(),
|
||||
) == 0) return error.NotX11Surface;
|
||||
if (gobject.typeCheckInstanceIsA(
|
||||
surface.as(gobject.TypeInstance),
|
||||
gdk_x11.X11Surface.getGObjectType(),
|
||||
) == 0)
|
||||
return error.NotX11Surface;
|
||||
|
||||
const x11_surface = gobject.ext.cast(
|
||||
gdk_x11.X11Surface,
|
||||
surface,
|
||||
) orelse return error.NotX11Surface;
|
||||
|
||||
return .{
|
||||
.app = app,
|
||||
.config = &apprt_window.config,
|
||||
.window = c.gdk_x11_surface_get_xid(surface),
|
||||
.window = x11_surface.getXid(),
|
||||
.gtk_window = apprt_window.window,
|
||||
};
|
||||
}
|
||||
@@ -200,8 +220,9 @@ pub const Window = struct {
|
||||
|
||||
pub fn resizeEvent(self: *Window) !void {
|
||||
// The blur region must update with window resizes
|
||||
self.blur_region.width = c.gtk_widget_get_width(@ptrCast(self.gtk_window));
|
||||
self.blur_region.height = c.gtk_widget_get_height(@ptrCast(self.gtk_window));
|
||||
const gtk_widget = self.gtk_window.as(gtk.Widget);
|
||||
self.blur_region.width = gtk_widget.getWidth();
|
||||
self.blur_region.height = gtk_widget.getHeight();
|
||||
try self.syncBlur();
|
||||
}
|
||||
|
||||
@@ -213,11 +234,8 @@ pub const Window = struct {
|
||||
// rounded corners and all that fluff. Please. I beg of you.
|
||||
var x: f64 = 0;
|
||||
var y: f64 = 0;
|
||||
c.gtk_native_get_surface_transform(
|
||||
@ptrCast(self.gtk_window),
|
||||
&x,
|
||||
&y,
|
||||
);
|
||||
|
||||
self.gtk_window.as(gtk.Native).getSurfaceTransform(&x, &y);
|
||||
|
||||
break :blur .{
|
||||
.x = @intFromFloat(x),
|
||||
@@ -334,7 +352,7 @@ pub const Window = struct {
|
||||
var prop_return: ?format.bufferType() = null;
|
||||
|
||||
const code = c.XGetWindowProperty(
|
||||
self.app.display,
|
||||
@ptrCast(@alignCast(self.app.display)),
|
||||
self.window,
|
||||
name,
|
||||
options.offset,
|
||||
@@ -372,7 +390,7 @@ pub const Window = struct {
|
||||
const data: format.bufferType() = @ptrCast(value);
|
||||
|
||||
const status = c.XChangeProperty(
|
||||
self.app.display,
|
||||
@ptrCast(@alignCast(self.app.display)),
|
||||
self.window,
|
||||
name,
|
||||
typ,
|
||||
@@ -389,7 +407,11 @@ pub const Window = struct {
|
||||
}
|
||||
|
||||
fn deleteProperty(self: *Window, name: c.Atom) X11Error!void {
|
||||
const status = c.XDeleteProperty(self.app.display, self.window, name);
|
||||
const status = c.XDeleteProperty(
|
||||
@ptrCast(@alignCast(self.app.display)),
|
||||
self.window,
|
||||
name,
|
||||
);
|
||||
if (status == 0) return error.RequestFailed;
|
||||
}
|
||||
};
|
||||
@@ -408,13 +430,13 @@ const Atoms = struct {
|
||||
kde_blur: c.Atom,
|
||||
motif_wm_hints: c.Atom,
|
||||
|
||||
fn init(display: *c.GdkDisplay) Atoms {
|
||||
fn init(display: *gdk_x11.X11Display) Atoms {
|
||||
return .{
|
||||
.kde_blur = c.gdk_x11_get_xatom_by_name_for_display(
|
||||
.kde_blur = gdk_x11.x11GetXatomByNameForDisplay(
|
||||
display,
|
||||
"_KDE_NET_WM_BLUR_BEHIND_REGION",
|
||||
),
|
||||
.motif_wm_hints = c.gdk_x11_get_xatom_by_name_for_display(
|
||||
.motif_wm_hints = gdk_x11.x11GetXatomByNameForDisplay(
|
||||
display,
|
||||
"_MOTIF_WM_HINTS",
|
||||
),
|
||||
|
@@ -555,6 +555,7 @@ fn addGTK(
|
||||
.{ "glib", "glib2" },
|
||||
.{ "gobject", "gobject2" },
|
||||
.{ "gtk", "gtk4" },
|
||||
.{ "xlib", "xlib2" },
|
||||
};
|
||||
inline for (gobject_imports) |import| {
|
||||
const name, const module = import;
|
||||
|
Reference in New Issue
Block a user