summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeffrey C. Ollie <jeff@ocjtech.us>2025-03-15 21:56:44 -0500
committerJeffrey C. Ollie <jeff@ocjtech.us>2025-03-17 12:06:57 -0500
commit29322535a5d372b38e5f2e465210950eb315067f (patch)
tree4e39e0217a8ec4ea951719d2a182be66bb572471
parente0fe12cc05ed859bd8bd7fa3993ded341275063f (diff)
gtk: convert Window (and some related files) to zig-gobject
-rw-r--r--src/apprt/gtk/App.zig3
-rw-r--r--src/apprt/gtk/Surface.zig26
-rw-r--r--src/apprt/gtk/Window.zig472
-rw-r--r--src/apprt/gtk/c.zig3
-rw-r--r--src/apprt/gtk/headerbar.zig57
-rw-r--r--src/apprt/gtk/winproto.zig3
-rw-r--r--src/apprt/gtk/winproto/noop.zig3
-rw-r--r--src/apprt/gtk/winproto/wayland.zig57
-rw-r--r--src/apprt/gtk/winproto/x11.zig101
-rw-r--r--src/build/SharedDeps.zig1
10 files changed, 380 insertions, 346 deletions
diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig
index cc33fd5e4..da621117d 100644
--- a/src/apprt/gtk/App.zig
+++ b/src/apprt/gtk/App.zig
@@ -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,
);
diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig
index e96b25c16..da3c60df9 100644
--- a/src/apprt/gtk/Surface.zig
+++ b/src/apprt/gtk/Surface.zig
@@ -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();
@@ -2173,9 +2169,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();
@@ -2201,16 +2195,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;
diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig
index 62e3a8b86..e98879dea 100644
--- a/src/apprt/gtk/Window.zig
+++ b/src/apprt/gtk/Window.zig
@@ -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(&gtkNewTabFromOverview),
+ *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(&gtkTitlebarMenuActivate),
+ *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(&gtkTabNewClick), 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(&gtkWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
- _ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(&gtkWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
- _ = c.g_signal_connect_data(self.window, "notify::is-active", c.G_CALLBACK(&gtkWindowNotifyIsActive), 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(&gtkRealize), self, null, c.G_CONNECT_DEFAULT);
- _ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(&gtkCloseRequest), self, null, c.G_CONNECT_DEFAULT);
- _ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(&gtkDestroy), 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,20 @@ 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 toolbar_view = gobject.ext.cast(adw.ToolbarView, tab_overview.getChild() orelse break :toolbar_view) 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 +527,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 +542,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 +580,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 +660,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 +681,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 +711,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 +723,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 +744,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 +793,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 +820,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 +846,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 +871,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 +893,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 +903,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 +928,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 +945,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 +1131,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));
-}
diff --git a/src/apprt/gtk/c.zig b/src/apprt/gtk/c.zig
index c42c35d46..09a75e25f 100644
--- a/src/apprt/gtk/c.zig
+++ b/src/apprt/gtk/c.zig
@@ -9,6 +9,9 @@ pub const c = @cImport({
// Add in X11-specific GDK backend which we use for specific things
// (e.g. X11 window class).
@cInclude("gdk/x11/gdkx.h");
+
+ // The following includes can't be removed until X11 support is dropped.
+
@cInclude("X11/Xlib.h");
@cInclude("X11/Xatom.h");
// Xkb for X11 state handling
diff --git a/src/apprt/gtk/headerbar.zig b/src/apprt/gtk/headerbar.zig
index 8f4c58fc4..03c1b427b 100644
--- a/src/apprt/gtk/headerbar.zig
+++ b/src/apprt/gtk/headerbar.zig
@@ -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);
}
diff --git a/src/apprt/gtk/winproto.zig b/src/apprt/gtk/winproto.zig
index 0f36b1622..ff83e6851 100644
--- a/src/apprt/gtk/winproto.zig
+++ b/src/apprt/gtk/winproto.zig
@@ -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 {
diff --git a/src/apprt/gtk/winproto/noop.zig b/src/apprt/gtk/winproto/noop.zig
index 33ce65bc4..5cb5887c9 100644
--- a/src/apprt/gtk/winproto/noop.zig
+++ b/src/apprt/gtk/winproto/noop.zig
@@ -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 {
diff --git a/src/apprt/gtk/winproto/wayland.zig b/src/apprt/gtk/winproto/wayland.zig
index 1b64d19d9..d1f3835e5 100644
--- a/src/apprt/gtk/winproto/wayland.zig
+++ b/src/apprt/gtk/winproto/wayland.zig
@@ -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;
-
- const wl_surface: *wl.Surface = @ptrCast(c.gdk_wayland_surface_get_wl_surface(
+ if (gobject.typeCheckInstanceIsA(
+ gdk_surface.as(gobject.TypeInstance),
+ gdk_wayland.WaylandSurface.getGObjectType(),
+ ) == 0)
+ return error.NoWaylandSurface;
+
+ 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 .{
diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig
index 89f8d65e4..63d66a59d 100644
--- a/src/apprt/gtk/winproto/x11.zig
+++ b/src/apprt/gtk/winproto/x11.zig
@@ -4,9 +4,17 @@ 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");
+// This needs to remain because of the legacy X11 API calls
const c = @import("../c.zig").c;
+
const input = @import("../../../input.zig");
const Config = @import("../../../config.zig").Config;
const adwaita = @import("../adwaita.zig");
@@ -15,28 +23,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 +69,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 +81,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 +94,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 +105,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 +136,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 +174,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 +186,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 +217,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 +231,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 +349,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 +387,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 +404,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 +427,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",
),
diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig
index 88d64ca3c..cf7602c2f 100644
--- a/src/build/SharedDeps.zig
+++ b/src/build/SharedDeps.zig
@@ -555,6 +555,7 @@ fn addGTK(
.{ "glib", "glib2" },
.{ "gobject", "gobject2" },
.{ "gtk", "gtk4" },
+ .{ "xlib", "xlib2" },
};
inline for (gobject_imports) |import| {
const name, const module = import;