gtk: convert Surface to zig-gobject (#6634)

Marking this is as draft because I want to test this further before
saying that it's ready, but putting it out there for feedback.
This commit is contained in:
Jeffrey C. Ollie
2025-03-14 10:14:33 -05:00
committed by GitHub
11 changed files with 565 additions and 419 deletions

View File

@@ -10,11 +10,12 @@
/// (event loop) along with any global app state.
const App = @This();
const gtk = @import("gtk");
const adw = @import("adw");
const gdk = @import("gdk");
const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
const adw = @import("adw");
const gtk = @import("gtk");
const std = @import("std");
const assert = std.debug.assert;
@@ -64,7 +65,7 @@ winproto: winprotopkg.App,
single_instance: bool,
/// The "none" cursor. We use one that is shared across the entire app.
cursor_none: ?*c.GdkCursor,
cursor_none: ?*gdk.Cursor,
/// The configuration errors window, if it is currently open.
config_errors_window: ?*ConfigErrorsWindow = null,
@@ -280,8 +281,8 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
};
// The "none" cursor is used for hiding the cursor
const cursor_none = c.gdk_cursor_new_from_name("none", null);
errdefer if (cursor_none) |cursor| c.g_object_unref(cursor);
const cursor_none = gdk.Cursor.newFromName("none", null);
errdefer if (cursor_none) |cursor| cursor.unref();
const single_instance = switch (config.@"gtk-single-instance") {
.true => true,

View File

@@ -370,8 +370,9 @@ fn keyEvent(
cimgui.c.igSetCurrentContext(self.ig_ctx);
const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO();
// FIXME: when this file get converted to zig-gobject
// Translate the GTK mods and update the modifiers on every keypress
const mods = key.translateMods(gtk_mods);
const mods = key.translateMods(@bitCast(gtk_mods));
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftShift, mods.shift);
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftCtrl, mods.ctrl);
cimgui.c.ImGuiIO_AddKeyEvent(io, cimgui.c.ImGuiKey_LeftAlt, mods.alt);

File diff suppressed because it is too large Load Diff

View File

@@ -73,8 +73,9 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
surface.container = .{ .tab_ = self };
self.elem = .{ .surface = surface };
// FIXME: when Tab.zig is converted to zig-gobject
// Add Surface to the Tab
c.gtk_box_append(self.box, surface.primaryWidget());
c.gtk_box_append(self.box, @ptrCast(@alignCast(surface.primaryWidget())));
// Set the userdata of the box to point to this tab.
c.g_object_set_data(@ptrCast(box_widget), GHOSTTY_TAB, self);
@@ -103,10 +104,11 @@ pub fn destroy(self: *Tab, alloc: Allocator) void {
/// Replace the surface element that this tab is showing.
pub fn replaceElem(self: *Tab, elem: Surface.Container.Elem) void {
// Remove our previous widget
c.gtk_box_remove(self.box, self.elem.widget());
// FIXME: when Tab.zig is converted to zig-gobject
c.gtk_box_remove(self.box, @ptrCast(@alignCast(self.elem.widget())));
// Add our new one
c.gtk_box_append(self.box, elem.widget());
c.gtk_box_append(self.box, @ptrCast(@alignCast(elem.widget())));
self.elem = elem;
}

View File

@@ -10,6 +10,7 @@ const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const gdk = @import("gdk");
const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
@@ -297,15 +298,21 @@ pub fn init(self: *Window, app: *App) !void {
// We register a key event controller with the window so
// we can catch key events when our surface may not be
// focused (i.e. when the libadw tab overview is shown).
const ec_key_press = c.gtk_event_controller_key_new();
errdefer c.g_object_unref(ec_key_press);
c.gtk_widget_add_controller(gtk_widget, ec_key_press);
const ec_key_press = gtk.EventControllerKey.new();
errdefer ec_key_press.unref();
c.gtk_widget_add_controller(gtk_widget, @ptrCast(@alignCast(ec_key_press)));
// 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);
_ = c.g_signal_connect_data(ec_key_press, "key-pressed", c.G_CALLBACK(&gtkKeyPressed), self, null, c.G_CONNECT_DEFAULT);
_ = gtk.EventControllerKey.signals.key_pressed.connect(
ec_key_press,
*Window,
gtkKeyPressed,
self,
.{},
);
// Our actions for the menu
initActions(self);
@@ -865,14 +872,12 @@ fn gtkDestroy(v: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
}
fn gtkKeyPressed(
ec_key: *c.GtkEventControllerKey,
keyval: c.guint,
keycode: c.guint,
gtk_mods: c.GdkModifierType,
ud: ?*anyopaque,
ec_key: *gtk.EventControllerKey,
keyval: c_uint,
keycode: c_uint,
gtk_mods: gdk.ModifierType,
self: *Window,
) callconv(.C) c.gboolean {
const self = userdataSelf(ud.?);
// 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

View File

@@ -1,5 +1,10 @@
const std = @import("std");
const build_options = @import("build_options");
const gdk = @import("gdk");
const glib = @import("glib");
const gtk = @import("gtk");
const input = @import("../../input.zig");
const c = @import("c.zig").c;
const winproto = @import("winproto.zig");
@@ -37,63 +42,56 @@ pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u
return slice[0 .. slice.len - 1 :0];
}
pub fn translateMods(state: c.GdkModifierType) input.Mods {
var mods: input.Mods = .{};
if (state & c.GDK_SHIFT_MASK != 0) mods.shift = true;
if (state & c.GDK_CONTROL_MASK != 0) mods.ctrl = true;
if (state & c.GDK_ALT_MASK != 0) mods.alt = true;
if (state & c.GDK_SUPER_MASK != 0) mods.super = true;
// Lock is dependent on the X settings but we just assume caps lock.
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
return mods;
pub fn translateMods(state: gdk.ModifierType) input.Mods {
return .{
.shift = state.shift_mask,
.ctrl = state.control_mask,
.alt = state.alt_mask,
.super = state.super_mask,
// Lock is dependent on the X settings but we just assume caps lock.
.caps_lock = state.lock_mask,
};
}
// Get the unshifted unicode value of the keyval. This is used
// by the Kitty keyboard protocol.
pub fn keyvalUnicodeUnshifted(
widget: *c.GtkWidget,
event: *c.GdkEvent,
keycode: c.guint,
widget: *gtk.Widget,
event: *gdk.KeyEvent,
keycode: u32,
) u21 {
const display = c.gtk_widget_get_display(widget);
const display = widget.getDisplay();
// We need to get the currently active keyboard layout so we know
// what group to look at.
const layout = c.gdk_key_event_get_layout(@ptrCast(event));
const layout = event.getLayout();
// Get all the possible keyboard mappings for this keycode. A keycode
// is the physical key pressed.
var keys: [*]c.GdkKeymapKey = undefined;
var keyvals: [*]c.guint = undefined;
var n_keys: c_int = 0;
if (c.gdk_display_map_keycode(
display,
keycode,
@ptrCast(&keys),
@ptrCast(&keyvals),
&n_keys,
) == 0) return 0;
// Get all the possible keyboard mappings for this keycode. A keycode is the
// physical key pressed.
var keys: [*]gdk.KeymapKey = undefined;
var keyvals: [*]c_uint = undefined;
var n_entries: c_int = 0;
if (display.mapKeycode(keycode, &keys, &keyvals, &n_entries) == 0) return 0;
defer c.g_free(keys);
defer c.g_free(keyvals);
defer glib.free(keys);
defer glib.free(keyvals);
// debugging:
// log.debug("layout={}", .{layout});
// for (0..@intCast(n_keys)) |i| {
// log.debug("keymap key={} codepoint={x}", .{
// std.log.debug("layout={}", .{layout});
// for (0..@intCast(n_entries)) |i| {
// std.log.debug("keymap key={} codepoint={x}", .{
// keys[i],
// c.gdk_keyval_to_unicode(keyvals[i]),
// gdk.keyvalToUnicode(keyvals[i]),
// });
// }
for (0..@intCast(n_keys)) |i| {
if (keys[i].group == layout and
keys[i].level == 0)
for (0..@intCast(n_entries)) |i| {
if (keys[i].f_group == layout and
keys[i].f_level == 0)
{
return std.math.cast(
u21,
c.gdk_keyval_to_unicode(keyvals[i]),
gdk.keyvalToUnicode(keyvals[i]),
) orelse 0;
}
}
@@ -105,16 +103,16 @@ pub fn keyvalUnicodeUnshifted(
/// This requires a lot of context because the GdkEvent
/// doesn't contain enough on its own.
pub fn eventMods(
event: *c.GdkEvent,
event: *gdk.Event,
physical_key: input.Key,
gtk_mods: c.GdkModifierType,
gtk_mods: gdk.ModifierType,
action: input.Action,
app_winproto: *winproto.App,
) input.Mods {
const device = c.gdk_event_get_device(event);
const device = event.getDevice();
var mods = app_winproto.eventMods(device, gtk_mods);
mods.num_lock = c.gdk_device_get_num_lock_state(device) == 1;
mods.num_lock = if (device) |d| d.getNumLockState() != 0 else false;
// We use the physical key to determine sided modifiers. As
// far as I can tell there's no other way to reliably determine

View File

@@ -1,6 +1,9 @@
const std = @import("std");
const build_options = @import("build_options");
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");
@@ -52,8 +55,8 @@ pub const App = union(Protocol) {
pub fn eventMods(
self: *App,
device: ?*c.GdkDevice,
gtk_mods: c.GdkModifierType,
device: ?*gdk.Device,
gtk_mods: gdk.ModifierType,
) input.Mods {
return switch (self.*) {
inline else => |*v| v.eventMods(device, gtk_mods),

View File

@@ -1,5 +1,8 @@
const std = @import("std");
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");
@@ -24,8 +27,8 @@ pub const App = struct {
pub fn eventMods(
_: *App,
_: ?*c.GdkDevice,
_: c.GdkModifierType,
_: ?*gdk.Device,
_: gdk.ModifierType,
) ?input.Mods {
return null;
}

View File

@@ -1,8 +1,8 @@
//! Wayland protocol implementation for the Ghostty GTK apprt.
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");
@@ -86,8 +86,8 @@ pub const App = struct {
pub fn eventMods(
_: *App,
_: ?*c.GdkDevice,
_: c.GdkModifierType,
_: ?*gdk.Device,
_: gdk.ModifierType,
) ?input.Mods {
return null;
}

View File

@@ -3,6 +3,9 @@ const std = @import("std");
const builtin = @import("builtin");
const build_options = @import("build_options");
const Allocator = std.mem.Allocator;
const gdk = @import("gdk");
const c = @import("../c.zig").c;
const input = @import("../../../input.zig");
const Config = @import("../../../config.zig").Config;
@@ -117,8 +120,8 @@ pub const App = struct {
/// event did not result in a modifier change).
pub fn eventMods(
self: App,
device: ?*c.GdkDevice,
gtk_mods: c.GdkModifierType,
device: ?*gdk.Device,
gtk_mods: gdk.ModifierType,
) ?input.Mods {
_ = device;
_ = gtk_mods;

View File

@@ -455,12 +455,12 @@ pub fn add(
.optimize = optimize,
});
const gobject_imports = .{
.{ "gobject", "gobject2" },
.{ "adw", "adw1" },
.{ "gdk", "gdk4" },
.{ "gio", "gio2" },
.{ "glib", "glib2" },
.{ "gobject", "gobject2" },
.{ "gtk", "gtk4" },
.{ "gdk", "gdk4" },
.{ "adw", "adw1" },
};
inline for (gobject_imports) |import| {
const name, const module = import;