From 29322535a5d372b38e5f2e465210950eb315067f Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sat, 15 Mar 2025 21:56:44 -0500 Subject: gtk: convert Window (and some related files) to zig-gobject --- src/apprt/gtk/App.zig | 3 +- src/apprt/gtk/Surface.zig | 26 +- src/apprt/gtk/Window.zig | 472 +++++++++++++++++++------------------ src/apprt/gtk/c.zig | 3 + src/apprt/gtk/headerbar.zig | 57 ++--- src/apprt/gtk/winproto.zig | 3 +- src/apprt/gtk/winproto/noop.zig | 3 +- src/apprt/gtk/winproto/wayland.zig | 57 +++-- src/apprt/gtk/winproto/x11.zig | 101 ++++---- src/build/SharedDeps.zig | 1 + 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(>kNewTabFromOverview), + *Window, + gtkNewTabFromOverview, self, - null, - c.G_CONNECT_DEFAULT, + .{}, ); - _ = c.g_signal_connect_data( + _ = gobject.Object.signals.notify.connect( tab_overview, - "notify::open", - c.G_CALLBACK(&adwTabOverviewOpen), + *Window, + adwTabOverviewOpen, self, - null, - c.G_CONNECT_DEFAULT, + .{ + .detail = "open", + }, ); - break :overview tab_overview; } else null; // gtk-titlebar can be used to disable the header bar (but keep the window // manager's decorations). We create this no matter if we are decorated or // not because we can have a keybind to toggle the decorations. - self.headerbar.init(); + self.headerbar.init(self); { - const btn = c.gtk_menu_button_new(); - c.gtk_widget_set_tooltip_text(btn, i18n._("Main Menu")); - c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic"); - c.gtk_menu_button_set_popover(@ptrCast(btn), @ptrCast(@alignCast(self.titlebar_menu.asWidget()))); - _ = c.g_signal_connect_data( + const btn = gtk.MenuButton.new(); + btn.as(gtk.Widget).setTooltipText(i18n._("Main Menu")); + btn.setIconName("open-menu-symbolic"); + btn.setPopover(self.titlebar_menu.asWidget()); + _ = gobject.Object.signals.notify.connect( btn, - "notify::active", - c.G_CALLBACK(>kTitlebarMenuActivate), + *Window, + gtkTitlebarMenuActivate, self, - null, - c.G_CONNECT_DEFAULT, + .{ + .detail = "active", + }, ); - self.headerbar.packEnd(btn); + self.headerbar.packEnd(btn.as(gtk.Widget)); } // If we're using an AdwWindow then we can support the tab overview. @@ -218,81 +218,106 @@ pub fn init(self: *Window, app: *App) !void { if (!adwaita.versionAtLeast(1, 4, 0)) unreachable; const btn = switch (self.config.gtk_tabs_location) { .top, .bottom => btn: { - const btn = c.gtk_toggle_button_new(); - c.gtk_widget_set_tooltip_text(btn, i18n._("View Open Tabs")); - c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic"); - _ = c.g_object_bind_property( - btn, + const btn = gtk.ToggleButton.new(); + btn.as(gtk.Widget).setTooltipText(i18n._("View Open Tabs")); + btn.as(gtk.Button).setIconName("view-grid-symbolic"); + _ = btn.as(gobject.Object).bindProperty( "active", - tab_overview, + tab_overview.as(gobject.Object), "open", - c.G_BINDING_BIDIRECTIONAL | c.G_BINDING_SYNC_CREATE, + .{ .bidirectional = true, .sync_create = true }, ); - - break :btn btn; + break :btn btn.as(gtk.Widget); }, .hidden => btn: { - const btn = c.adw_tab_button_new(); - c.adw_tab_button_set_view(@ptrCast(btn), @ptrCast(@alignCast(self.notebook.tab_view))); - c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open"); - break :btn btn; + const btn = adw.TabButton.new(); + btn.setView(self.notebook.tab_view); + btn.as(gtk.Actionable).setActionName("overview.open"); + break :btn btn.as(gtk.Widget); }, }; - c.gtk_widget_set_focus_on_click(btn, c.FALSE); + btn.setFocusOnClick(0); self.headerbar.packEnd(btn); } { - const btn = c.gtk_button_new_from_icon_name("tab-new-symbolic"); - c.gtk_widget_set_tooltip_text(btn, i18n._("New Tab")); - _ = c.g_signal_connect_data(btn, "clicked", c.G_CALLBACK(>kTabNewClick), self, null, c.G_CONNECT_DEFAULT); - self.headerbar.packStart(btn); + const btn = gtk.Button.newFromIconName("tab-new-symbolic"); + btn.as(gtk.Widget).setTooltipText(i18n._("New Tab")); + _ = gtk.Button.signals.clicked.connect( + btn, + *Window, + gtkTabNewClick, + self, + .{}, + ); + self.headerbar.packStart(btn.as(gtk.Widget)); } - _ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT); - _ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT); - _ = c.g_signal_connect_data(self.window, "notify::is-active", c.G_CALLBACK(>kWindowNotifyIsActive), self, null, c.G_CONNECT_DEFAULT); + _ = gobject.Object.signals.notify.connect( + self.window, + *Window, + gtkWindowNotifyMaximized, + self, + .{ + .detail = "maximized", + }, + ); + _ = gobject.Object.signals.notify.connect( + self.window, + *Window, + gtkWindowNotifyFullscreened, + self, + .{ + .detail = "fullscreened", + }, + ); + _ = gobject.Object.signals.notify.connect( + self.window, + *Window, + gtkWindowNotifyIsActive, + self, + .{ + .detail = "is-active", + }, + ); // If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we // need to stick the headerbar into the content box. if (!adwaita.versionAtLeast(1, 4, 0)) { - c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget()); + box.append(self.headerbar.asWidget()); } // In debug we show a warning and apply the 'devel' class to the window. // This is a really common issue where people build from source in debug and performance is really bad. if (comptime std.debug.runtime_safety) { - const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0); + const warning_box = gtk.Box.new(.vertical, 0); const warning_text = i18n._("⚠️ You're running a debug build of Ghostty! Performance will be degraded."); if (adwaita.versionAtLeast(1, 3, 0)) { - const banner = c.adw_banner_new(warning_text); - c.adw_banner_set_revealed(@ptrCast(banner), 1); - c.gtk_box_append(@ptrCast(warning_box), @ptrCast(banner)); + const banner = adw.Banner.new(warning_text); + banner.setRevealed(1); + warning_box.append(banner.as(gtk.Widget)); } else { - const warning = c.gtk_label_new(warning_text); - c.gtk_widget_set_margin_top(warning, 10); - c.gtk_widget_set_margin_bottom(warning, 10); - c.gtk_box_append(@ptrCast(warning_box), warning); + const warning = gtk.Label.new(warning_text); + warning.as(gtk.Widget).setMarginTop(10); + warning.as(gtk.Widget).setMarginBottom(10); + warning_box.append(warning.as(gtk.Widget)); } - c.gtk_widget_add_css_class(gtk_widget, "devel"); - c.gtk_widget_add_css_class(@ptrCast(warning_box), "background"); - c.gtk_box_append(@ptrCast(box), warning_box); + gtk_widget.addCssClass("devel"); + warning_box.as(gtk.Widget).addCssClass("background"); + box.append(warning_box.as(gtk.Widget)); } // Setup our toast overlay if we have one - self.toast_overlay = c.adw_toast_overlay_new(); - c.adw_toast_overlay_set_child( - @ptrCast(self.toast_overlay), - @ptrCast(@alignCast(self.notebook.asWidget())), - ); - c.gtk_box_append(@ptrCast(box), self.toast_overlay); + self.toast_overlay = adw.ToastOverlay.new(); + self.toast_overlay.setChild(self.notebook.asWidget()); + box.append(self.toast_overlay.as(gtk.Widget)); // If we have a tab overview then we can set it on our notebook. if (self.tab_overview) |tab_overview| { if (!adwaita.versionAtLeast(1, 4, 0)) unreachable; - c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view))); + tab_overview.setView(self.notebook.tab_view); } // We register a key event controller with the window so @@ -300,12 +325,30 @@ pub fn init(self: *Window, app: *App) !void { // focused (i.e. when the libadw tab overview is shown). const ec_key_press = gtk.EventControllerKey.new(); errdefer ec_key_press.unref(); - c.gtk_widget_add_controller(gtk_widget, @ptrCast(@alignCast(ec_key_press))); + gtk_widget.addController(ec_key_press.as(gtk.EventController)); // All of our events - _ = c.g_signal_connect_data(self.window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT); - _ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT); - _ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT); + _ = gtk.Widget.signals.realize.connect( + self.window, + *Window, + gtkRealize, + self, + .{}, + ); + _ = gtk.Window.signals.close_request.connect( + self.window, + *Window, + gtkCloseRequest, + self, + .{}, + ); + _ = gtk.Widget.signals.destroy.connect( + self.window, + *Window, + gtkDestroy, + self, + .{}, + ); _ = gtk.EventControllerKey.signals.key_pressed.connect( ec_key_press, *Window, @@ -318,81 +361,68 @@ pub fn init(self: *Window, app: *App) !void { initActions(self); if (adwaita.versionAtLeast(1, 4, 0)) { - const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new()); - - c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget()); + const toolbar_view = adw.ToolbarView.new(); + toolbar_view.addTopBar(self.headerbar.asWidget()); if (self.config.gtk_tabs_location != .hidden) { - const tab_bar = c.adw_tab_bar_new(); - c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view))); + const tab_bar = adw.TabBar.new(); + tab_bar.setView(self.notebook.tab_view); - if (!self.config.gtk_wide_tabs) c.adw_tab_bar_set_expand_tabs(tab_bar, 0); + if (!self.config.gtk_wide_tabs) tab_bar.setExpandTabs(0); - const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar)); switch (self.config.gtk_tabs_location) { - .top => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget), - .bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget), + .top => toolbar_view.addTopBar(tab_bar.as(gtk.Widget)), + .bottom => toolbar_view.addBottomBar(tab_bar.as(gtk.Widget)), .hidden => unreachable, } } - c.adw_toolbar_view_set_content(toolbar_view, box); + toolbar_view.setContent(box.as(gtk.Widget)); - const toolbar_style: c.AdwToolbarStyle = switch (self.config.gtk_toolbar_style) { - .flat => c.ADW_TOOLBAR_FLAT, - .raised => c.ADW_TOOLBAR_RAISED, - .@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER, + const toolbar_style: adw.ToolbarStyle = switch (self.config.gtk_toolbar_style) { + .flat => .flat, + .raised => .raised, + .@"raised-border" => .raised_border, }; - c.adw_toolbar_view_set_top_bar_style(toolbar_view, toolbar_style); - c.adw_toolbar_view_set_bottom_bar_style(toolbar_view, toolbar_style); + toolbar_view.setTopBarStyle(toolbar_style); + toolbar_view.setTopBarStyle(toolbar_style); // Set our application window content. - c.adw_tab_overview_set_child( - @ptrCast(self.tab_overview), - @ptrCast(@alignCast(toolbar_view)), - ); - c.adw_application_window_set_content( - @ptrCast(gtk_widget), - @ptrCast(@alignCast(self.tab_overview)), - ); + self.tab_overview.?.setChild(toolbar_view.as(gtk.Widget)); + self.window.setContent(self.tab_overview.?.as(gtk.Widget)); } else tab_bar: { if (self.config.gtk_tabs_location == .hidden) break :tab_bar; // In earlier adwaita versions, we need to add the tabbar manually since we do not use // an AdwToolbarView. - const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?; - c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline"); + const tab_bar = adw.TabBar.new(); + tab_bar.as(gtk.Widget).addCssClass("inline"); switch (self.config.gtk_tabs_location) { - .top => c.gtk_box_insert_child_after( - @ptrCast(box), - @ptrCast(@alignCast(tab_bar)), - @ptrCast(@alignCast(self.headerbar.asWidget())), - ), - .bottom => c.gtk_box_append( - @ptrCast(box), - @ptrCast(@alignCast(tab_bar)), + .top => box.insertChildAfter( + tab_bar.as(gtk.Widget), + self.headerbar.asWidget(), ), + .bottom => box.append(tab_bar.as(gtk.Widget)), .hidden => unreachable, } - c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view))); + tab_bar.setView(self.notebook.tab_view); - if (!self.config.gtk_wide_tabs) c.adw_tab_bar_set_expand_tabs(tab_bar, 0); + if (!self.config.gtk_wide_tabs) tab_bar.setExpandTabs(0); } // If we want the window to be maximized, we do that here. - if (self.config.maximize) c.gtk_window_maximize(self.window); + if (self.config.maximize) self.window.as(gtk.Window).maximize(); // If we are in fullscreen mode, new windows start fullscreen. - if (self.config.fullscreen) c.gtk_window_fullscreen(self.window); + if (self.config.fullscreen) self.window.as(gtk.Window).fullscreen(); } pub fn present(self: *Window) void { - const window: *gtk.Window = @ptrCast(self.window); - window.present(); + self.window.as(gtk.Window).present(); } pub fn toggleVisibility(self: *Window) void { - const window: *gtk.Widget = @ptrCast(self.window); + const widget = self.window.as(gtk.Widget); - window.setVisible(@intFromBool(window.isVisible() == 0)); + widget.setVisible(@intFromBool(widget.isVisible() == 0)); } pub fn isQuickTerminal(self: *Window) bool { @@ -424,15 +454,17 @@ pub fn updateConfig( /// reactive by moving them here. pub fn syncAppearance(self: *Window) !void { const csd_enabled = self.winproto.clientSideDecorationEnabled(); - c.gtk_window_set_decorated(self.window, @intFromBool(csd_enabled)); + const gtk_window = self.window.as(gtk.Window); + const gtk_widget = self.window.as(gtk.Widget); + gtk_window.setDecorated(@intFromBool(csd_enabled)); // Fix any artifacting that may occur in window corners. The .ssd CSS // class is defined in the GtkWindow documentation: // https://docs.gtk.org/gtk4/class.Window.html#css-nodes. A definition // for .ssd is provided by GTK and Adwaita. - toggleCssClass(@ptrCast(self.window), "csd", csd_enabled); - toggleCssClass(@ptrCast(self.window), "ssd", !csd_enabled); - toggleCssClass(@ptrCast(self.window), "no-border-radius", !csd_enabled); + toggleCssClass(gtk_widget, "csd", csd_enabled); + toggleCssClass(gtk_widget, "ssd", !csd_enabled); + toggleCssClass(gtk_widget, "no-border-radius", !csd_enabled); self.headerbar.setVisible(visible: { // Never display the header bar when CSDs are disabled. @@ -453,7 +485,7 @@ pub fn syncAppearance(self: *Window) !void { }); toggleCssClass( - @ptrCast(self.window), + gtk_widget, "background", self.config.background_opacity >= 1, ); @@ -462,7 +494,7 @@ pub fn syncAppearance(self: *Window) !void { // GTK version is before 4.16. The conditional is because above 4.16 // we use GTK CSS color variables. toggleCssClass( - @ptrCast(self.window), + gtk_widget, "window-theme-ghostty", !version.atLeast(4, 16, 0) and self.config.window_theme == .ghostty, ); @@ -473,18 +505,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; -- cgit v1.2.3