gtk: convert Window (and some related files) to zig-gobject

pull/6775/head
Jeffrey C. Ollie 2025-03-15 21:56:44 -05:00
parent e0fe12cc05
commit 29322535a5
No known key found for this signature in database
GPG Key ID: 6F86035A6D97044E
10 changed files with 379 additions and 345 deletions

View File

@ -401,10 +401,11 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
return error.GtkApplicationRegisterFailed; return error.GtkApplicationRegisterFailed;
} }
// FIXME: when App.zig is converted to zig-gobject
// Setup our windowing protocol logic // Setup our windowing protocol logic
var winproto_app = try winprotopkg.App.init( var winproto_app = try winprotopkg.App.init(
core_app.alloc, core_app.alloc,
display, @ptrCast(@alignCast(display)),
app_id, app_id,
&config, &config,
); );

View File

@ -920,8 +920,7 @@ pub fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void
const window = self.container.window() orelse return; const window = self.container.window() orelse return;
if (window.notebook.nPages() > 1) return; if (window.notebook.nPages() > 1) return;
// FIXME: when Window is converted to zig-gobject const gtk_window = window.window.as(gtk.Window);
const gtk_window: *gtk.Window = @ptrCast(@alignCast(window.window));
// Note: this doesn't properly take into account the window decorations. // Note: this doesn't properly take into account the window decorations.
// I'm not currently sure how to do that. // 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; const window = self.container.window() orelse return;
if (window.notebook.nPages() > 1) return; if (window.notebook.nPages() > 1) return;
// FIXME: when Window is converted to zig-gobject const widget = window.window.as(gtk.Widget);
const widget: *gtk.Widget = @ptrCast(@alignCast(window.window));
// Note: this doesn't properly take into account the window decorations. // Note: this doesn't properly take into account the window decorations.
// I'm not currently sure how to do that. // I'm not currently sure how to do that.
@ -1167,9 +1165,8 @@ pub fn setMouseVisibility(self: *Surface, visible: bool) void {
return; return;
} }
// FIXME: when App is converted to zig-gobject
// Set our new cursor to the app "none" cursor // 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 { 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_scale_factor = scale: {
const window = self.container.window() orelse break :scale 0; const window = self.container.window() orelse break :scale 0;
// FIXME: when Window.zig is converted to zig-gobject const gtk_window = window.window.as(gtk.Window);
const gtk_window: *gtk.Window = @ptrCast(@alignCast(window.window));
const gtk_native = gtk_window.as(gtk.Native); const gtk_native = gtk_window.as(gtk.Native);
const gdk_surface = gtk_native.getSurface() orelse break :scale 0; const gdk_surface = gtk_native.getSurface() orelse break :scale 0;
break :scale gdk_surface.getScaleFactor(); break :scale gdk_surface.getScaleFactor();
@ -2173,9 +2169,7 @@ pub fn present(self: *Surface) void {
if (window.notebook.getTabPosition(tab)) |position| if (window.notebook.getTabPosition(tab)) |position|
_ = window.notebook.gotoNthTab(position); _ = window.notebook.gotoNthTab(position);
} }
// FIXME: when Window.zig is converted to zig-gobject window.window.as(gtk.Window).present();
const gtk_window: *gtk.Window = @ptrCast(@alignCast(window.window));
gtk_window.present();
} }
self.grabFocus(); self.grabFocus();
@ -2201,16 +2195,14 @@ pub fn setSplitZoom(self: *Surface, new_split_zoom: bool) void {
const tab_widget = tab.elem.widget(); const tab_widget = tab.elem.widget();
const surface_widget = self.primaryWidget(); 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) { if (new_split_zoom) {
self.detachFromSplit(); self.detachFromSplit();
box.remove(tab_widget); tab.box.remove(tab_widget);
box.append(surface_widget); tab.box.append(surface_widget);
} else { } else {
box.remove(surface_widget); tab.box.remove(surface_widget);
self.attachToSplit(); self.attachToSplit();
box.append(tab_widget); tab.box.append(tab_widget);
} }
self.zoomed_in = new_split_zoom; self.zoomed_in = new_split_zoom;

View File

@ -10,6 +10,7 @@ const builtin = @import("builtin");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const adw = @import("adw");
const gdk = @import("gdk"); const gdk = @import("gdk");
const gio = @import("gio"); const gio = @import("gio");
const glib = @import("glib"); const glib = @import("glib");
@ -28,7 +29,6 @@ const Color = configpkg.Config.Color;
const Surface = @import("Surface.zig"); const Surface = @import("Surface.zig");
const Menu = @import("menu.zig").Menu; const Menu = @import("menu.zig").Menu;
const Tab = @import("Tab.zig"); const Tab = @import("Tab.zig");
const c = @import("c.zig").c;
const adwaita = @import("adwaita.zig"); const adwaita = @import("adwaita.zig");
const gtk_key = @import("key.zig"); const gtk_key = @import("key.zig");
const TabView = @import("TabView.zig"); const TabView = @import("TabView.zig");
@ -48,14 +48,14 @@ last_config: usize,
config: DerivedConfig, config: DerivedConfig,
/// Our window /// Our window
window: *c.GtkWindow, window: *adw.ApplicationWindow,
/// The header bar for the window. /// The header bar for the window.
headerbar: HeaderBar, headerbar: HeaderBar,
/// The tab overview for the window. This is possibly null since there is no /// The tab overview for the window. This is possibly null since there is no
/// taboverview without a AdwApplicationWindow (libadwaita >= 1.4.0). /// taboverview without a AdwApplicationWindow (libadwaita >= 1.4.0).
tab_overview: ?*c.GtkWidget, tab_overview: ?*adw.TabOverview,
/// The notebook (tab grouping) for this window. /// The notebook (tab grouping) for this window.
notebook: TabView, notebook: TabView,
@ -64,10 +64,10 @@ notebook: TabView,
titlebar_menu: Menu(Window, "titlebar_menu", true), titlebar_menu: Menu(Window, "titlebar_menu", true),
/// The libadwaita widget for receiving toast send requests. /// The libadwaita widget for receiving toast send requests.
toast_overlay: *c.GtkWidget, toast_overlay: *adw.ToastOverlay,
/// See adwTabOverviewOpen for why we have this. /// See adwTabOverviewOpen for why we have this.
adw_tab_overview_focus_timer: ?c.guint = null, adw_tab_overview_focus_timer: ?c_uint = null,
/// State and logic for windowing protocol for a window. /// State and logic for windowing protocol for a window.
winproto: winprotopkg.Window, winproto: winprotopkg.Window,
@ -141,25 +141,25 @@ pub fn init(self: *Window, app: *App) !void {
.winproto = .none, .winproto = .none,
}; };
// FIXME: when App.zig is converted to zig-gobject
// Create the window // Create the window
const gtk_widget = c.adw_application_window_new(app.app); self.window = adw.ApplicationWindow.new(@ptrCast(@alignCast(app.app)));
errdefer c.gtk_window_destroy(@ptrCast(gtk_widget)); 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)); gtk_window.setTitle("Ghostty");
gtk_window.setDefaultSize(1000, 600);
c.gtk_window_set_title(self.window, "Ghostty"); gtk_widget.addCssClass("window");
c.gtk_window_set_default_size(self.window, 1000, 600); gtk_widget.addCssClass("terminal-window");
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 // GTK4 grabs F10 input by default to focus the menubar icon. We want
// to disable this so that terminal programs can capture F10 (such as htop) // to disable this so that terminal programs can capture F10 (such as htop)
c.gtk_window_set_handle_menubar_accel(self.window, 0); gtk_window.setHandleMenubarAccel(0);
gtk_window.setIconName(build_config.bundle_id);
c.gtk_window_set_icon_name(self.window, build_config.bundle_id);
// Create our box which will hold our widgets in the main content area. // Create our box which will hold our widgets in the main content area.
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0); const box = gtk.Box.new(.vertical, 0);
// Set up the menus // Set up the menus
self.titlebar_menu.init(self); 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. // If we are using Adwaita, then we can support the tab overview.
self.tab_overview = if (adwaita.versionAtLeast(1, 4, 0)) overview: { self.tab_overview = if (adwaita.versionAtLeast(1, 4, 0)) overview: {
const tab_overview = c.adw_tab_overview_new(); const tab_overview = adw.TabOverview.new();
c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view))); tab_overview.setView(self.notebook.tab_view);
c.adw_tab_overview_set_enable_new_tab(@ptrCast(tab_overview), 1); tab_overview.setEnableNewTab(1);
_ = c.g_signal_connect_data( _ = adw.TabOverview.signals.create_tab.connect(
tab_overview, tab_overview,
"create-tab", *Window,
c.G_CALLBACK(&gtkNewTabFromOverview), gtkNewTabFromOverview,
self, self,
null, .{},
c.G_CONNECT_DEFAULT,
); );
_ = c.g_signal_connect_data( _ = gobject.Object.signals.notify.connect(
tab_overview, tab_overview,
"notify::open", *Window,
c.G_CALLBACK(&adwTabOverviewOpen), adwTabOverviewOpen,
self, self,
null, .{
c.G_CONNECT_DEFAULT, .detail = "open",
},
); );
break :overview tab_overview; break :overview tab_overview;
} else null; } else null;
// gtk-titlebar can be used to disable the header bar (but keep the window // 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 // manager's decorations). We create this no matter if we are decorated or
// not because we can have a keybind to toggle the decorations. // 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(); const btn = gtk.MenuButton.new();
c.gtk_widget_set_tooltip_text(btn, i18n._("Main Menu")); btn.as(gtk.Widget).setTooltipText(i18n._("Main Menu"));
c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic"); btn.setIconName("open-menu-symbolic");
c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget()))); btn.setPopover(self.titlebar_menu.asWidget());
_ = c.g_signal_connect_data( _ = gobject.Object.signals.notify.connect(
btn, btn,
"notify::active", *Window,
c.G_CALLBACK(&gtkTitlebarMenuActivate), gtkTitlebarMenuActivate,
self, 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. // 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; if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
const btn = switch (self.config.gtk_tabs_location) { const btn = switch (self.config.gtk_tabs_location) {
.top, .bottom => btn: { .top, .bottom => btn: {
const btn = c.gtk_toggle_button_new(); const btn = gtk.ToggleButton.new();
c.gtk_widget_set_tooltip_text(btn, i18n._("View Open Tabs")); btn.as(gtk.Widget).setTooltipText(i18n._("View Open Tabs"));
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic"); btn.as(gtk.Button).setIconName("view-grid-symbolic");
_ = c.g_object_bind_property( _ = btn.as(gobject.Object).bindProperty(
btn,
"active", "active",
tab_overview, tab_overview.as(gobject.Object),
"open", "open",
c.G_BINDING_BIDIRECTIONAL | c.G_BINDING_SYNC_CREATE, .{ .bidirectional = true, .sync_create = true },
); );
break :btn btn.as(gtk.Widget);
break :btn btn;
}, },
.hidden => btn: { .hidden => btn: {
const btn = c.adw_tab_button_new(); const btn = adw.TabButton.new();
c.adw_tab_button_set_view(@ptrCast(btn), @ptrCast(@alignCast(self.notebook.tab_view))); btn.setView(self.notebook.tab_view);
c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open"); btn.as(gtk.Actionable).setActionName("overview.open");
break :btn btn; break :btn btn.as(gtk.Widget);
}, },
}; };
c.gtk_widget_set_focus_on_click(btn, c.FALSE); btn.setFocusOnClick(0);
self.headerbar.packEnd(btn); self.headerbar.packEnd(btn);
} }
{ {
const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic"); const btn = gtk.Button.newFromIconName("tab-new-symbolic");
c.gtk_widget_set_tooltip_text(btn, i18n._("New Tab")); btn.as(gtk.Widget).setTooltipText(i18n._("New Tab"));
_ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(&gtkTabNewClick), self, null, c.G_CONNECT_DEFAULT); _ = gtk.Button.signals.clicked.connect(
self.headerbar.packStart(btn); btn,
*Window,
gtkTabNewClick,
self,
.{},
);
self.headerbar.packStart(btn.as(gtk.Widget));
} }
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(&gtkWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT); _ = gobject.Object.signals.notify.connect(
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(&gtkWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT); self.window,
_ = c.g_signal_connect_data(self.window, "notify::is-active", c.G_CALLBACK(&gtkWindowNotifyIsActive), self, null, c.G_CONNECT_DEFAULT); *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 // If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we
// need to stick the headerbar into the content box. // need to stick the headerbar into the content box.
if (!adwaita.versionAtLeast(1, 4, 0)) { 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. // 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. // This is a really common issue where people build from source in debug and performance is really bad.
if (comptime std.debug.runtime_safety) { if (comptime std.debug.runtime_safety) {
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0); const warning_box = gtk.Box.new(.vertical, 0);
const warning_text = i18n._("⚠️ You're running a debug build of Ghostty! Performance will be degraded."); const warning_text = i18n._("⚠️ You're running a debug build of Ghostty! Performance will be degraded.");
if (adwaita.versionAtLeast(1, 3, 0)) { if (adwaita.versionAtLeast(1, 3, 0)) {
const banner = c.adw_banner_new(warning_text); const banner = adw.Banner.new(warning_text);
c.adw_banner_set_revealed(@ptrCast(banner), 1); banner.setRevealed(1);
c.gtk_box_append(@ptrCast(warning_box), @ptrCast(banner)); warning_box.append(banner.as(gtk.Widget));
} else { } else {
const warning = c.gtk_label_new(warning_text); const warning = gtk.Label.new(warning_text);
c.gtk_widget_set_margin_top(warning, 10); warning.as(gtk.Widget).setMarginTop(10);
c.gtk_widget_set_margin_bottom(warning, 10); warning.as(gtk.Widget).setMarginBottom(10);
c.gtk_box_append(@ptrCast(warning_box), warning); warning_box.append(warning.as(gtk.Widget));
} }
c.gtk_widget_add_css_class(gtk_widget, "devel"); gtk_widget.addCssClass("devel");
c.gtk_widget_add_css_class(@ptrCast(warning_box), "background"); warning_box.as(gtk.Widget).addCssClass("background");
c.gtk_box_append(@ptrCast(box), warning_box); box.append(warning_box.as(gtk.Widget));
} }
// Setup our toast overlay if we have one // Setup our toast overlay if we have one
self.toast_overlay = c.adw_toast_overlay_new(); self.toast_overlay = adw.ToastOverlay.new();
c.adw_toast_overlay_set_child( self.toast_overlay.setChild(self.notebook.asWidget());
@ptrCast(self.toast_overlay), box.append(self.toast_overlay.as(gtk.Widget));
@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 we have a tab overview then we can set it on our notebook.
if (self.tab_overview) |tab_overview| { if (self.tab_overview) |tab_overview| {
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable; 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 // 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). // focused (i.e. when the libadw tab overview is shown).
const ec_key_press = gtk.EventControllerKey.new(); const ec_key_press = gtk.EventControllerKey.new();
errdefer ec_key_press.unref(); 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 // All of our events
_ = c.g_signal_connect_data(self.window, "realize", c.G_CALLBACK(&gtkRealize), self, null, c.G_CONNECT_DEFAULT); _ = gtk.Widget.signals.realize.connect(
_ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(&gtkCloseRequest), self, null, c.G_CONNECT_DEFAULT); self.window,
_ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(&gtkDestroy), self, null, c.G_CONNECT_DEFAULT); *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( _ = gtk.EventControllerKey.signals.key_pressed.connect(
ec_key_press, ec_key_press,
*Window, *Window,
@ -318,81 +361,68 @@ pub fn init(self: *Window, app: *App) !void {
initActions(self); initActions(self);
if (adwaita.versionAtLeast(1, 4, 0)) { if (adwaita.versionAtLeast(1, 4, 0)) {
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new()); const toolbar_view = adw.ToolbarView.new();
toolbar_view.addTopBar(self.headerbar.asWidget());
c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget());
if (self.config.gtk_tabs_location != .hidden) { if (self.config.gtk_tabs_location != .hidden) {
const tab_bar = c.adw_tab_bar_new(); const tab_bar = adw.TabBar.new();
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);
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
switch (self.config.gtk_tabs_location) { switch (self.config.gtk_tabs_location) {
.top => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget), .top => toolbar_view.addTopBar(tab_bar.as(gtk.Widget)),
.bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget), .bottom => toolbar_view.addBottomBar(tab_bar.as(gtk.Widget)),
.hidden => unreachable, .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) { const toolbar_style: adw.ToolbarStyle = switch (self.config.gtk_toolbar_style) {
.flat => c.ADW_TOOLBAR_FLAT, .flat => .flat,
.raised => c.ADW_TOOLBAR_RAISED, .raised => .raised,
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER, .@"raised-border" => .raised_border,
}; };
c.adw_toolbar_view_set_top_bar_style(toolbar_view, toolbar_style); toolbar_view.setTopBarStyle(toolbar_style);
c.adw_toolbar_view_set_bottom_bar_style(toolbar_view, toolbar_style); toolbar_view.setTopBarStyle(toolbar_style);
// Set our application window content. // Set our application window content.
c.adw_tab_overview_set_child( self.tab_overview.?.setChild(toolbar_view.as(gtk.Widget));
@ptrCast(self.tab_overview), self.window.setContent(self.tab_overview.?.as(gtk.Widget));
@ptrCast(@alignCast(toolbar_view)),
);
c.adw_application_window_set_content(
@ptrCast(gtk_widget),
@ptrCast(@alignCast(self.tab_overview)),
);
} else tab_bar: { } else tab_bar: {
if (self.config.gtk_tabs_location == .hidden) break :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 // In earlier adwaita versions, we need to add the tabbar manually since we do not use
// an AdwToolbarView. // an AdwToolbarView.
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?; const tab_bar = adw.TabBar.new();
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline"); tab_bar.as(gtk.Widget).addCssClass("inline");
switch (self.config.gtk_tabs_location) { switch (self.config.gtk_tabs_location) {
.top => c.gtk_box_insert_child_after( .top => box.insertChildAfter(
@ptrCast(box), tab_bar.as(gtk.Widget),
@ptrCast(@alignCast(tab_bar)), self.headerbar.asWidget(),
@ptrCast(@alignCast(self.headerbar.asWidget())),
),
.bottom => c.gtk_box_append(
@ptrCast(box),
@ptrCast(@alignCast(tab_bar)),
), ),
.bottom => box.append(tab_bar.as(gtk.Widget)),
.hidden => unreachable, .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 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 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 { pub fn present(self: *Window) void {
const window: *gtk.Window = @ptrCast(self.window); self.window.as(gtk.Window).present();
window.present();
} }
pub fn toggleVisibility(self: *Window) void { 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 { pub fn isQuickTerminal(self: *Window) bool {
@ -424,15 +454,17 @@ pub fn updateConfig(
/// reactive by moving them here. /// reactive by moving them here.
pub fn syncAppearance(self: *Window) !void { pub fn syncAppearance(self: *Window) !void {
const csd_enabled = self.winproto.clientSideDecorationEnabled(); 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 // Fix any artifacting that may occur in window corners. The .ssd CSS
// class is defined in the GtkWindow documentation: // class is defined in the GtkWindow documentation:
// https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition // https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition
// for .ssd is provided by GTK and Adwaita. // for .ssd is provided by GTK and Adwaita.
toggleCssClass(@ptrCast(self.window), "csd", csd_enabled); toggleCssClass(gtk_widget, "csd", csd_enabled);
toggleCssClass(@ptrCast(self.window), "ssd", !csd_enabled); toggleCssClass(gtk_widget, "ssd", !csd_enabled);
toggleCssClass(@ptrCast(self.window), "no-border-radius", !csd_enabled); toggleCssClass(gtk_widget, "no-border-radius", !csd_enabled);
self.headerbar.setVisible(visible: { self.headerbar.setVisible(visible: {
// Never display the header bar when CSDs are disabled. // Never display the header bar when CSDs are disabled.
@ -453,7 +485,7 @@ pub fn syncAppearance(self: *Window) !void {
}); });
toggleCssClass( toggleCssClass(
@ptrCast(self.window), gtk_widget,
"background", "background",
self.config.background_opacity >= 1, 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 // GTK version is before 4.16. The conditional is because above 4.16
// we use GTK CSS color variables. // we use GTK CSS color variables.
toggleCssClass( toggleCssClass(
@ptrCast(self.window), gtk_widget,
"window-theme-ghostty", "window-theme-ghostty",
!version.atLeast(4, 16, 0) and self.config.window_theme == .ghostty, !version.atLeast(4, 16, 0) and self.config.window_theme == .ghostty,
); );
@ -473,18 +505,20 @@ pub fn syncAppearance(self: *Window) !void {
// Disable the title buttons (close, maximize, minimize, ...) // Disable the title buttons (close, maximize, minimize, ...)
// *inside* the tab overview if CSDs are disabled. // *inside* the tab overview if CSDs are disabled.
// We do spare the search button, though. // We do spare the search button, though.
c.adw_tab_overview_set_show_start_title_buttons(@ptrCast(tab_overview), @intFromBool(csd_enabled)); tab_overview.setShowStartTitleButtons(@intFromBool(csd_enabled));
c.adw_tab_overview_set_show_end_title_buttons(@ptrCast(tab_overview), @intFromBool(csd_enabled)); tab_overview.setShowEndTitleButtons(@intFromBool(csd_enabled));
// Update toolbar view style // Update toolbar view style
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_tab_overview_get_child(@ptrCast(tab_overview))); toolbar_view: {
const toolbar_style: c.AdwToolbarStyle = switch (self.config.gtk_toolbar_style) { const toolbar_view = gobject.ext.cast(adw.ToolbarView, tab_overview.getChild() orelse break :toolbar_view) orelse break :toolbar_view;
.flat => c.ADW_TOOLBAR_FLAT, const toolbar_style: adw.ToolbarStyle = switch (self.config.gtk_toolbar_style) {
.raised => c.ADW_TOOLBAR_RAISED, .flat => .flat,
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER, .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.setBottomBarStyle(toolbar_style);
}
} }
self.winproto.syncAppearance() catch |err| { self.winproto.syncAppearance() catch |err| {
@ -493,14 +527,14 @@ pub fn syncAppearance(self: *Window) !void {
} }
fn toggleCssClass( fn toggleCssClass(
widget: *c.GtkWidget, widget: *gtk.Widget,
class: [:0]const u8, class: [:0]const u8,
v: bool, v: bool,
) void { ) void {
if (v) { if (v) {
c.gtk_widget_add_css_class(widget, class); widget.addCssClass(class);
} else { } else {
c.gtk_widget_remove_css_class(widget, class); widget.removeCssClass(class);
} }
} }
@ -508,8 +542,7 @@ fn toggleCssClass(
/// menus and such. The menu is defined in App.zig but the action is defined /// menus and such. The menu is defined in App.zig but the action is defined
/// here. The string name binds them. /// here. The string name binds them.
fn initActions(self: *Window) void { fn initActions(self: *Window) void {
// FIXME: when rest of file is converted to gobject const window = self.window.as(gtk.ApplicationWindow);
const window: *gtk.ApplicationWindow = @ptrCast(@alignCast(self.window));
const action_map = window.as(gio.ActionMap); const action_map = window.as(gio.ActionMap);
const actions = .{ const actions = .{
.{ "about", gtkActionAbout }, .{ "about", gtkActionAbout },
@ -547,7 +580,7 @@ pub fn deinit(self: *Window) void {
self.winproto.deinit(self.app.core_app.alloc); self.winproto.deinit(self.app.core_app.alloc);
if (self.adw_tab_overview_focus_timer) |timer| { if (self.adw_tab_overview_focus_timer) |timer| {
_ = c.g_source_remove(timer); _ = glib.Source.remove(timer);
} }
} }
@ -627,19 +660,19 @@ pub fn gotoTab(self: *Window, n: usize) bool {
/// Toggle tab overview (if present) /// Toggle tab overview (if present)
pub fn toggleTabOverview(self: *Window) void { pub fn toggleTabOverview(self: *Window) void {
if (self.tab_overview) |tab_overview_widget| { if (self.tab_overview) |tab_overview| {
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable; if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget)); const is_open = tab_overview.getOpen() != 0;
c.adw_tab_overview_set_open(tab_overview, 1 - c.adw_tab_overview_get_open(tab_overview)); tab_overview.setOpen(@intFromBool(!is_open));
} }
} }
/// Toggle the maximized state for this window. /// Toggle the maximized state for this window.
pub fn toggleMaximize(self: *Window) void { pub fn toggleMaximize(self: *Window) void {
if (self.config.maximize) { if (self.config.maximize) {
c.gtk_window_unmaximize(self.window); self.window.as(gtk.Window).unmaximize();
} else { } else {
c.gtk_window_maximize(self.window); self.window.as(gtk.Window).maximize();
} }
// We update the config and call syncAppearance // We update the config and call syncAppearance
// in the gtkWindowNotifyMaximized callback // in the gtkWindowNotifyMaximized callback
@ -648,9 +681,9 @@ pub fn toggleMaximize(self: *Window) void {
/// Toggle fullscreen for this window. /// Toggle fullscreen for this window.
pub fn toggleFullscreen(self: *Window) void { pub fn toggleFullscreen(self: *Window) void {
if (self.config.fullscreen) { if (self.config.fullscreen) {
c.gtk_window_unfullscreen(self.window); self.window.as(gtk.Window).unfullscreen();
} else { } else {
c.gtk_window_fullscreen(self.window); self.window.as(gtk.Window).fullscreen();
} }
// We update the config and call syncAppearance // We update the config and call syncAppearance
// in the gtkWindowNotifyFullscreened callback // in the gtkWindowNotifyFullscreened callback
@ -678,8 +711,7 @@ pub fn toggleWindowDecorations(self: *Window) void {
pub fn focusCurrentTab(self: *Window) void { pub fn focusCurrentTab(self: *Window) void {
const tab = self.notebook.currentTab() orelse return; const tab = self.notebook.currentTab() orelse return;
const surface = tab.focus_child orelse return; const surface = tab.focus_child orelse return;
const gl_area = @as(*c.GtkWidget, @ptrCast(surface.gl_area)); _ = surface.gl_area.as(gtk.Widget).grabFocus();
_ = c.gtk_widget_grab_focus(gl_area);
if (surface.getTitle()) |title| { if (surface.getTitle()) |title| {
self.setTitle(title); self.setTitle(title);
@ -691,14 +723,12 @@ pub fn onConfigReloaded(self: *Window) void {
} }
pub fn sendToast(self: *Window, title: [*:0]const u8) void { pub fn sendToast(self: *Window, title: [*:0]const u8) void {
const toast = c.adw_toast_new(title); const toast = adw.Toast.new(title);
c.adw_toast_set_timeout(toast, 3); toast.setTimeout(3);
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast); self.toast_overlay.addToast(toast);
} }
fn gtkRealize(_: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool { fn gtkRealize(_: *adw.ApplicationWindow, self: *Window) callconv(.c) void {
const self = userdataSelf(ud.?);
// Initialize our window protocol logic // Initialize our window protocol logic
if (winprotopkg.Window.init( if (winprotopkg.Window.init(
self.app.core_app.alloc, self.app.core_app.alloc,
@ -714,52 +744,46 @@ fn gtkRealize(_: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
self.syncAppearance() catch |err| { self.syncAppearance() catch |err| {
log.err("failed to initialize appearance={}", .{err}); log.err("failed to initialize appearance={}", .{err});
}; };
return true;
} }
fn gtkWindowNotifyMaximized( fn gtkWindowNotifyMaximized(
_: *c.GObject, _: *adw.ApplicationWindow,
_: *c.GParamSpec, _: *gobject.ParamSpec,
ud: ?*anyopaque, self: *Window,
) callconv(.C) void { ) callconv(.c) void {
const self = userdataSelf(ud orelse return); self.config.maximize = self.window.as(gtk.Window).isMaximized() != 0;
self.config.maximize = c.gtk_window_is_maximized(self.window) != 0;
self.syncAppearance() catch |err| { self.syncAppearance() catch |err| {
log.err("failed to sync appearance={}", .{err}); log.err("failed to sync appearance={}", .{err});
}; };
} }
fn gtkWindowNotifyFullscreened( fn gtkWindowNotifyFullscreened(
_: *c.GObject, _: *adw.ApplicationWindow,
_: *c.GParamSpec, _: *gobject.ParamSpec,
ud: ?*anyopaque, self: *Window,
) callconv(.C) void { ) callconv(.c) void {
const self = userdataSelf(ud orelse return); self.config.fullscreen = self.window.as(gtk.Window).isFullscreen() != 0;
self.config.fullscreen = c.gtk_window_is_fullscreen(self.window) != 0;
self.syncAppearance() catch |err| { self.syncAppearance() catch |err| {
log.err("failed to sync appearance={}", .{err}); log.err("failed to sync appearance={}", .{err});
}; };
} }
fn gtkWindowNotifyIsActive( fn gtkWindowNotifyIsActive(
_: *c.GObject, _: *adw.ApplicationWindow,
_: *c.GParamSpec, _: *gobject.ParamSpec,
ud: ?*anyopaque, self: *Window,
) callconv(.C) void { ) callconv(.C) void {
const self = userdataSelf(ud orelse return);
if (!self.isQuickTerminal()) return; if (!self.isQuickTerminal()) return;
// Hide when we're unfocused // 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(); self.toggleVisibility();
} }
} }
// Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab // Note: we MUST NOT use the GtkButton parameter because gtkActionNewTab
// sends an undefined value. // sends an undefined value.
fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void { fn gtkTabNewClick(_: *gtk.Button, self: *Window) callconv(.c) void {
const self: *Window = @ptrCast(@alignCast(ud orelse return));
const surface = self.actionSurface() orelse return; const surface = self.actionSurface() orelse return;
_ = surface.performBindingAction(.{ .new_tab = {} }) catch |err| { _ = surface.performBindingAction(.{ .new_tab = {} }) catch |err| {
log.warn("error performing binding action error={}", .{err}); log.warn("error performing binding action error={}", .{err});
@ -769,27 +793,23 @@ fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
/// Create a new tab from the AdwTabOverview. We can't copy gtkTabNewClick /// Create a new tab from the AdwTabOverview. We can't copy gtkTabNewClick
/// because we need to return an AdwTabPage from this function. /// because we need to return an AdwTabPage from this function.
fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwTabPage { fn gtkNewTabFromOverview(_: *adw.TabOverview, self: *Window) callconv(.c) *adw.TabPage {
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable; if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
const self: *Window = userdataSelf(ud.?);
const alloc = self.app.core_app.alloc; const alloc = self.app.core_app.alloc;
const surface = self.actionSurface(); const surface = self.actionSurface();
const tab = Tab.create(alloc, self, surface) catch return null; const tab = Tab.create(alloc, self, surface) catch unreachable;
return c.adw_tab_view_get_page(@ptrCast(@alignCast(self.notebook.tab_view)), @ptrCast(@alignCast(tab.box))); return self.notebook.tab_view.getPage(tab.box.as(gtk.Widget));
} }
fn adwTabOverviewOpen( fn adwTabOverviewOpen(
object: *c.GObject, tab_overview: *adw.TabOverview,
_: *c.GParamSpec, _: *gobject.ParamSpec,
ud: ?*anyopaque, self: *Window,
) void { ) callconv(.c) void {
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(object));
// We only care about when the tab overview is closed. // We only care about when the tab overview is closed.
if (c.adw_tab_overview_get_open(tab_overview) == 1) { if (tab_overview.getOpen() != 0) return;
return;
}
// On tab overview close, focus is sometimes lost. This is an // On tab overview close, focus is sometimes lost. This is an
// upstream issue in libadwaita[1]. When this is resolved we // upstream issue in libadwaita[1]. When this is resolved we
@ -800,24 +820,24 @@ fn adwTabOverviewOpen(
// animation is 400ms. // animation is 400ms.
// //
// [1]: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/670 // [1]: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/670
const window: *Window = @ptrCast(@alignCast(ud.?));
// If we have an old timer remove it // If we have an old timer remove it
if (window.adw_tab_overview_focus_timer) |timer| { if (self.adw_tab_overview_focus_timer) |timer| {
_ = c.g_source_remove(timer); _ = glib.Source.remove(timer);
} }
// Restart our timer // Restart our timer
window.adw_tab_overview_focus_timer = c.g_timeout_add( self.adw_tab_overview_focus_timer = glib.timeoutAdd(
500, 500,
@ptrCast(&adwTabOverviewFocusTimer), adwTabOverviewFocusTimer,
window, self,
); );
} }
fn adwTabOverviewFocusTimer( fn adwTabOverviewFocusTimer(
self: *Window, ud: ?*anyopaque,
) callconv(.C) c.gboolean { ) callconv(.C) c_int {
const self: *Window = @ptrCast(@alignCast(ud orelse return 0));
self.adw_tab_overview_focus_timer = null; self.adw_tab_overview_focus_timer = null;
self.focusCurrentTab(); self.focusCurrentTab();
@ -826,7 +846,7 @@ fn adwTabOverviewFocusTimer(
} }
pub fn close(self: *Window) void { 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 // Unset the quick terminal on the app level
if (self.isQuickTerminal()) self.app.quick_terminal = null; if (self.isQuickTerminal()) self.app.quick_terminal = null;
@ -851,21 +871,17 @@ pub fn closeWithConfirmation(self: *Window) void {
}; };
} }
fn gtkCloseRequest(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool { fn gtkCloseRequest(_: *adw.ApplicationWindow, self: *Window) callconv(.c) c_int {
_ = v;
log.debug("window close request", .{}); log.debug("window close request", .{});
const self = userdataSelf(ud.?);
self.closeWithConfirmation(); self.closeWithConfirmation();
return true; return 1;
} }
/// "destroy" signal for the window /// "destroy" signal for the window
fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { fn gtkDestroy(_: *adw.ApplicationWindow, self: *Window) callconv(.c) void {
_ = v;
log.debug("window destroy", .{}); log.debug("window destroy", .{});
const self = userdataSelf(ud.?);
const alloc = self.app.core_app.alloc; const alloc = self.app.core_app.alloc;
self.deinit(); self.deinit();
alloc.destroy(self); alloc.destroy(self);
@ -877,7 +893,7 @@ fn gtkKeyPressed(
keycode: c_uint, keycode: c_uint,
gtk_mods: gdk.ModifierType, gtk_mods: gdk.ModifierType,
self: *Window, self: *Window,
) callconv(.C) c.gboolean { ) callconv(.c) c_int {
// We only process window-level events currently for the tab // We only process window-level events currently for the tab
// overview. This is primarily defensive programming because // overview. This is primarily defensive programming because
// I'm not 100% certain how our logic below will interact with // I'm not 100% certain how our logic below will interact with
@ -887,9 +903,8 @@ fn gtkKeyPressed(
// If someone can confidently show or explain that this is not // If someone can confidently show or explain that this is not
// necessary, please remove this check. // necessary, please remove this check.
if (adwaita.versionAtLeast(1, 4, 0)) { if (adwaita.versionAtLeast(1, 4, 0)) {
if (self.tab_overview) |tab_overview_widget| { if (self.tab_overview) |tab_overview| {
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget)); if (tab_overview.getOpen() == 0) return 0;
if (c.adw_tab_overview_get_open(tab_overview) == 0) return 0;
} }
} }
@ -913,8 +928,8 @@ fn gtkActionAbout(
const website = "https://ghostty.org"; const website = "https://ghostty.org";
if (adwaita.versionAtLeast(1, 5, 0)) { if (adwaita.versionAtLeast(1, 5, 0)) {
c.adw_show_about_dialog( adw.showAboutDialog(
@ptrCast(self.window), self.window.as(gtk.Widget),
"application-name", "application-name",
name, name,
"developer-name", "developer-name",
@ -930,8 +945,8 @@ fn gtkActionAbout(
@as(?*anyopaque, null), @as(?*anyopaque, null),
); );
} else { } else {
c.gtk_show_about_dialog( gtk.showAboutDialog(
self.window, self.window.as(gtk.Window),
"program-name", "program-name",
name, name,
"logo-icon-name", "logo-icon-name",
@ -1116,21 +1131,16 @@ pub fn actionSurface(self: *Window) ?*CoreSurface {
} }
fn gtkTitlebarMenuActivate( fn gtkTitlebarMenuActivate(
btn: *c.GtkMenuButton, btn: *gtk.MenuButton,
_: *c.GParamSpec, _: *gobject.ParamSpec,
ud: ?*anyopaque, self: *Window,
) callconv(.C) void { ) callconv(.C) void {
// debian 12 is stuck on GTK 4.8 // debian 12 is stuck on GTK 4.8
if (!version.atLeast(4, 10, 0)) return; if (!version.atLeast(4, 10, 0)) return;
const active = c.gtk_menu_button_get_active(btn) != 0; const active = btn.getActive() != 0;
const self = userdataSelf(ud orelse return);
if (active) { if (active) {
self.titlebar_menu.refresh(); self.titlebar_menu.refresh();
} else { } else {
self.focusCurrentTab(); self.focusCurrentTab();
} }
} }
fn userdataSelf(ud: *anyopaque) *Window {
return @ptrCast(@alignCast(ud));
}

View File

@ -9,6 +9,9 @@ pub const c = @cImport({
// Add in X11-specific GDK backend which we use for specific things // Add in X11-specific GDK backend which we use for specific things
// (e.g. X11 window class). // (e.g. X11 window class).
@cInclude("gdk/x11/gdkx.h"); @cInclude("gdk/x11/gdkx.h");
// The following includes can't be removed until X11 support is dropped.
@cInclude("X11/Xlib.h"); @cInclude("X11/Xlib.h");
@cInclude("X11/Xatom.h"); @cInclude("X11/Xatom.h");
// Xkb for X11 state handling // Xkb for X11 state handling

View File

@ -1,59 +1,54 @@
const HeaderBar = @This(); const HeaderBar = @This();
const std = @import("std"); const std = @import("std");
const c = @import("c.zig").c;
const adw = @import("adw");
const gtk = @import("gtk");
const Window = @import("Window.zig"); const Window = @import("Window.zig");
/// the Adwaita headerbar widget /// the Adwaita headerbar widget
headerbar: *c.AdwHeaderBar, headerbar: *adw.HeaderBar,
/// the Window that we belong to
window: *Window,
/// the Adwaita window title widget /// the Adwaita window title widget
title: *c.AdwWindowTitle, title: *adw.WindowTitle,
pub fn init(self: *HeaderBar) void { pub fn init(self: *HeaderBar, window: *Window) void {
const window: *Window = @fieldParentPtr("headerbar", self);
self.* = .{ self.* = .{
.headerbar = @ptrCast(@alignCast(c.adw_header_bar_new())), .headerbar = adw.HeaderBar.new(),
.title = @ptrCast(@alignCast(c.adw_window_title_new( .window = window,
c.gtk_window_get_title(window.window) orelse "Ghostty", .title = adw.WindowTitle.new(
null, window.window.as(gtk.Window).getTitle() orelse "Ghostty",
))), "",
),
}; };
c.adw_header_bar_set_title_widget( self.headerbar.setTitleWidget(self.title.as(gtk.Widget));
self.headerbar,
@ptrCast(@alignCast(self.title)),
);
} }
pub fn setVisible(self: *const HeaderBar, visible: bool) void { 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 { pub fn asWidget(self: *const HeaderBar) *gtk.Widget {
return @ptrCast(@alignCast(self.headerbar)); return self.headerbar.as(gtk.Widget);
} }
pub fn packEnd(self: *const HeaderBar, widget: *c.GtkWidget) void { pub fn packEnd(self: *const HeaderBar, widget: *gtk.Widget) void {
c.adw_header_bar_pack_end( self.headerbar.packEnd(widget);
@ptrCast(@alignCast(self.headerbar)),
widget,
);
} }
pub fn packStart(self: *const HeaderBar, widget: *c.GtkWidget) void { pub fn packStart(self: *const HeaderBar, widget: *gtk.Widget) void {
c.adw_header_bar_pack_start( self.headerbar.packStart(widget);
@ptrCast(@alignCast(self.headerbar)),
widget,
);
} }
pub fn setTitle(self: *const HeaderBar, title: [:0]const u8) void { pub fn setTitle(self: *const HeaderBar, title: [:0]const u8) void {
const window: *const Window = @fieldParentPtr("headerbar", self); self.window.window.as(gtk.Window).setTitle(title);
c.gtk_window_set_title(window.window, title); self.title.setTitle(title);
c.adw_window_title_set_title(self.title, title);
} }
pub fn setSubtitle(self: *const HeaderBar, subtitle: [:0]const u8) void { pub fn setSubtitle(self: *const HeaderBar, subtitle: [:0]const u8) void {
c.adw_window_title_set_subtitle(self.title, subtitle); self.title.setSubtitle(subtitle);
} }

View File

@ -4,7 +4,6 @@ const Allocator = std.mem.Allocator;
const gdk = @import("gdk"); const gdk = @import("gdk");
const c = @import("c.zig").c;
const Config = @import("../../config.zig").Config; const Config = @import("../../config.zig").Config;
const input = @import("../../input.zig"); const input = @import("../../input.zig");
const key = @import("key.zig"); const key = @import("key.zig");
@ -29,7 +28,7 @@ pub const App = union(Protocol) {
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
gdk_display: *c.GdkDisplay, gdk_display: *gdk.Display,
app_id: [:0]const u8, app_id: [:0]const u8,
config: *const Config, config: *const Config,
) !App { ) !App {

View File

@ -3,7 +3,6 @@ const Allocator = std.mem.Allocator;
const gdk = @import("gdk"); const gdk = @import("gdk");
const c = @import("../c.zig").c;
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const ApprtWindow = @import("../Window.zig"); const ApprtWindow = @import("../Window.zig");
@ -13,7 +12,7 @@ const log = std.log.scoped(.winproto_noop);
pub const App = struct { pub const App = struct {
pub fn init( pub fn init(
_: Allocator, _: Allocator,
_: *c.GdkDisplay, _: *gdk.Display,
_: [:0]const u8, _: [:0]const u8,
_: *const Config, _: *const Config,
) !?App { ) !?App {

View File

@ -3,12 +3,13 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const build_options = @import("build_options"); 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 = @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 Config = @import("../../../config.zig").Config;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const ApprtWindow = @import("../Window.zig"); const ApprtWindow = @import("../Window.zig");
@ -37,7 +38,7 @@ pub const App = struct {
pub fn init( pub fn init(
alloc: Allocator, alloc: Allocator,
gdk_display: *c.GdkDisplay, gdk_display: *gdk.Display,
app_id: [:0]const u8, app_id: [:0]const u8,
config: *const Config, config: *const Config,
) !?App { ) !?App {
@ -45,14 +46,18 @@ pub const App = struct {
_ = app_id; _ = app_id;
// Check if we're actually on Wayland // Check if we're actually on Wayland
if (c.g_type_check_instance_is_a( if (gobject.typeCheckInstanceIsA(
@ptrCast(@alignCast(gdk_display)), gdk_display.as(gobject.TypeInstance),
c.gdk_wayland_display_get_type(), gdk_wayland.WaylandDisplay.getGObjectType(),
) == 0) return null; ) == 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, 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. // 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 // Note: at the time of writing this comment, we don't really need
@ -219,20 +224,24 @@ pub const Window = struct {
) !Window { ) !Window {
_ = alloc; _ = alloc;
const gdk_surface = c.gtk_native_get_surface( const gtk_native = apprt_window.window.as(gtk.Native);
@ptrCast(apprt_window.window), const gdk_surface = gtk_native.getSurface() orelse return error.NotWaylandSurface;
) orelse return error.NotWaylandSurface;
// This should never fail, because if we're being called at this point // This should never fail, because if we're being called at this point
// then we've already asserted that our app state is Wayland. // then we've already asserted that our app state is Wayland.
if (c.g_type_check_instance_is_a( if (gobject.typeCheckInstanceIsA(
@ptrCast(@alignCast(gdk_surface)), gdk_surface.as(gobject.TypeInstance),
c.gdk_wayland_surface_get_type(), gdk_wayland.WaylandSurface.getGObjectType(),
) == 0) return error.NotWaylandSurface; ) == 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, 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 // Get our decoration object so we can control the
// CSD vs SSD status of this surface. // CSD vs SSD status of this surface.
@ -252,7 +261,13 @@ pub const Window = struct {
if (apprt_window.isQuickTerminal()) { if (apprt_window.isQuickTerminal()) {
const surface: *gdk.Surface = @ptrCast(gdk_surface); 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 .{ return .{

View File

@ -4,9 +4,17 @@ const builtin = @import("builtin");
const build_options = @import("build_options"); const build_options = @import("build_options");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const adw = @import("adw");
const gdk = @import("gdk"); 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");
// This needs to remain because of the legacy X11 API calls
const c = @import("../c.zig").c; const c = @import("../c.zig").c;
const input = @import("../../../input.zig"); const input = @import("../../../input.zig");
const Config = @import("../../../config.zig").Config; const Config = @import("../../../config.zig").Config;
const adwaita = @import("../adwaita.zig"); const adwaita = @import("../adwaita.zig");
@ -15,28 +23,28 @@ const ApprtWindow = @import("../Window.zig");
const log = std.log.scoped(.gtk_x11); const log = std.log.scoped(.gtk_x11);
pub const App = struct { pub const App = struct {
display: *c.Display, display: *xlib.Display,
base_event_code: c_int, base_event_code: c_int,
atoms: Atoms, atoms: Atoms,
pub fn init( pub fn init(
alloc: Allocator, _: Allocator,
gdk_display: *c.GdkDisplay, gdk_display: *gdk.Display,
app_id: [:0]const u8, app_id: [:0]const u8,
config: *const Config, config: *const Config,
) !?App { ) !?App {
_ = alloc;
// If the display isn't X11, then we don't need to do anything. // If the display isn't X11, then we don't need to do anything.
if (c.g_type_check_instance_is_a( if (gobject.typeCheckInstanceIsA(
@ptrCast(@alignCast(gdk_display)), gdk_display.as(gobject.TypeInstance),
c.gdk_x11_display_get_type(), gdk_x11.X11Display.getGObjectType(),
) == 0) return null; ) == 0) return null;
// Get our X11 display // 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, 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| const x11_program_name: [:0]const u8 = if (config.@"x11-instance-name") |pn|
pn pn
@ -61,8 +69,8 @@ pub const App = struct {
// WM_CLASS(STRING) = "ghostty", "com.mitchellh.ghostty" // WM_CLASS(STRING) = "ghostty", "com.mitchellh.ghostty"
// //
// Append "-debug" on both when using the debug build. // Append "-debug" on both when using the debug build.
c.g_set_prgname(x11_program_name); glib.setPrgname(x11_program_name);
c.gdk_x11_display_set_program_class(gdk_display, app_id); gdk_x11.X11Display.setProgramClass(gdk_display, app_id);
// XKB // XKB
log.debug("Xkb.init: initializing Xkb", .{}); log.debug("Xkb.init: initializing Xkb", .{});
@ -73,7 +81,7 @@ pub const App = struct {
var major = c.XkbMajorVersion; var major = c.XkbMajorVersion;
var minor = c.XkbMinorVersion; var minor = c.XkbMinorVersion;
if (c.XkbQueryExtension( if (c.XkbQueryExtension(
display, @ptrCast(@alignCast(xlib_display)),
&opcode, &opcode,
&base_event_code, &base_event_code,
&base_error_code, &base_error_code,
@ -86,7 +94,7 @@ pub const App = struct {
log.debug("Xkb.init: running XkbSelectEventDetails", .{}); log.debug("Xkb.init: running XkbSelectEventDetails", .{});
if (c.XkbSelectEventDetails( if (c.XkbSelectEventDetails(
display, @ptrCast(@alignCast(xlib_display)),
c.XkbUseCoreKbd, c.XkbUseCoreKbd,
c.XkbStateNotify, c.XkbStateNotify,
c.XkbModifierStateMask, c.XkbModifierStateMask,
@ -97,9 +105,9 @@ pub const App = struct {
} }
return .{ return .{
.display = display, .display = xlib_display,
.base_event_code = base_event_code, .base_event_code = base_event_code,
.atoms = Atoms.init(gdk_display), .atoms = Atoms.init(gdk_x11_display),
}; };
} }
@ -128,10 +136,13 @@ pub const App = struct {
// Shoutout to Mozilla for figuring out a clean way to do this, this is // Shoutout to Mozilla for figuring out a clean way to do this, this is
// paraphrased from Firefox/Gecko in widget/gtk/nsGtkKeyUtils.cpp. // 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; 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; if (nextEvent.type != self.base_event_code) return null;
const xkb_event: *c.XkbEvent = @ptrCast(&nextEvent); const xkb_event: *c.XkbEvent = @ptrCast(&nextEvent);
@ -163,8 +174,8 @@ pub const App = struct {
pub const Window = struct { pub const Window = struct {
app: *App, app: *App,
config: *const ApprtWindow.DerivedConfig, config: *const ApprtWindow.DerivedConfig,
window: c.Window, window: xlib.Window,
gtk_window: *c.GtkWindow, gtk_window: *adw.ApplicationWindow,
blur_region: Region = .{}, blur_region: Region = .{},
@ -175,20 +186,26 @@ pub const Window = struct {
) !Window { ) !Window {
_ = alloc; _ = alloc;
const surface = c.gtk_native_get_surface( const surface = apprt_window.window.as(
@ptrCast(apprt_window.window), gtk.Native,
) orelse return error.NotX11Surface; ).getSurface() orelse return error.NotX11Surface;
// Check if we're actually on X11 // Check if we're actually on X11
if (c.g_type_check_instance_is_a( if (gobject.typeCheckInstanceIsA(
@ptrCast(@alignCast(surface)), surface.as(gobject.TypeInstance),
c.gdk_x11_surface_get_type(), gdk_x11.X11Surface.getGObjectType(),
) == 0) return error.NotX11Surface; ) == 0)
return error.NotX11Surface;
const x11_surface = gobject.ext.cast(
gdk_x11.X11Surface,
surface,
) orelse return error.NotX11Surface;
return .{ return .{
.app = app, .app = app,
.config = &apprt_window.config, .config = &apprt_window.config,
.window = c.gdk_x11_surface_get_xid(surface), .window = x11_surface.getXid(),
.gtk_window = apprt_window.window, .gtk_window = apprt_window.window,
}; };
} }
@ -200,8 +217,9 @@ pub const Window = struct {
pub fn resizeEvent(self: *Window) !void { pub fn resizeEvent(self: *Window) !void {
// The blur region must update with window resizes // The blur region must update with window resizes
self.blur_region.width = c.gtk_widget_get_width(@ptrCast(self.gtk_window)); const gtk_widget = self.gtk_window.as(gtk.Widget);
self.blur_region.height = c.gtk_widget_get_height(@ptrCast(self.gtk_window)); self.blur_region.width = gtk_widget.getWidth();
self.blur_region.height = gtk_widget.getHeight();
try self.syncBlur(); try self.syncBlur();
} }
@ -213,11 +231,8 @@ pub const Window = struct {
// rounded corners and all that fluff. Please. I beg of you. // rounded corners and all that fluff. Please. I beg of you.
var x: f64 = 0; var x: f64 = 0;
var y: f64 = 0; var y: f64 = 0;
c.gtk_native_get_surface_transform(
@ptrCast(self.gtk_window), self.gtk_window.as(gtk.Native).getSurfaceTransform(&x, &y);
&x,
&y,
);
break :blur .{ break :blur .{
.x = @intFromFloat(x), .x = @intFromFloat(x),
@ -334,7 +349,7 @@ pub const Window = struct {
var prop_return: ?format.bufferType() = null; var prop_return: ?format.bufferType() = null;
const code = c.XGetWindowProperty( const code = c.XGetWindowProperty(
self.app.display, @ptrCast(@alignCast(self.app.display)),
self.window, self.window,
name, name,
options.offset, options.offset,
@ -372,7 +387,7 @@ pub const Window = struct {
const data: format.bufferType() = @ptrCast(value); const data: format.bufferType() = @ptrCast(value);
const status = c.XChangeProperty( const status = c.XChangeProperty(
self.app.display, @ptrCast(@alignCast(self.app.display)),
self.window, self.window,
name, name,
typ, typ,
@ -389,7 +404,11 @@ pub const Window = struct {
} }
fn deleteProperty(self: *Window, name: c.Atom) X11Error!void { 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; if (status == 0) return error.RequestFailed;
} }
}; };
@ -408,13 +427,13 @@ const Atoms = struct {
kde_blur: c.Atom, kde_blur: c.Atom,
motif_wm_hints: c.Atom, motif_wm_hints: c.Atom,
fn init(display: *c.GdkDisplay) Atoms { fn init(display: *gdk_x11.X11Display) Atoms {
return .{ return .{
.kde_blur = c.gdk_x11_get_xatom_by_name_for_display( .kde_blur = gdk_x11.x11GetXatomByNameForDisplay(
display, display,
"_KDE_NET_WM_BLUR_BEHIND_REGION", "_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, display,
"_MOTIF_WM_HINTS", "_MOTIF_WM_HINTS",
), ),

View File

@ -555,6 +555,7 @@ fn addGTK(
.{ "glib", "glib2" }, .{ "glib", "glib2" },
.{ "gobject", "gobject2" }, .{ "gobject", "gobject2" },
.{ "gtk", "gtk4" }, .{ "gtk", "gtk4" },
.{ "xlib", "xlib2" },
}; };
inline for (gobject_imports) |import| { inline for (gobject_imports) |import| {
const name, const module = import; const name, const module = import;