gtk: convert App to zig-gobject (#6787)
commit
d75c5ec038
|
|
@ -55,8 +55,8 @@
|
||||||
.gobject = .{
|
.gobject = .{
|
||||||
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
|
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
|
||||||
// Temporary until we generate them at build time automatically.
|
// Temporary until we generate them at build time automatically.
|
||||||
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz",
|
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst",
|
||||||
.hash = "gobject-0.2.0-Skun7H6DlQDWCiNQtdE5TXYcCvx7MyjW01OQe5M_n_jV",
|
.hash = "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR",
|
||||||
.lazy = true,
|
.lazy = true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@
|
||||||
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
|
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
|
||||||
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
|
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
|
||||||
},
|
},
|
||||||
"gobject-0.2.0-Skun7H6DlQDWCiNQtdE5TXYcCvx7MyjW01OQe5M_n_jV": {
|
"gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR": {
|
||||||
"name": "gobject",
|
"name": "gobject",
|
||||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz",
|
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst",
|
||||||
"hash": "sha256-eMmS9oysZheHwSCCvmOUSDJmP9zN7cAr6qqDIbz6EmY="
|
"hash": "sha256-hWcpl0Wd3XydT+RY7+VIoxXPhCzele1Ip76YSh+KmLI="
|
||||||
},
|
},
|
||||||
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
|
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
|
||||||
"name": "gtk4_layer_shell",
|
"name": "gtk4_layer_shell",
|
||||||
|
|
|
||||||
|
|
@ -130,11 +130,11 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = "gobject-0.2.0-Skun7H6DlQDWCiNQtdE5TXYcCvx7MyjW01OQe5M_n_jV";
|
name = "gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR";
|
||||||
path = fetchZigArtifact {
|
path = fetchZigArtifact {
|
||||||
name = "gobject";
|
name = "gobject";
|
||||||
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz";
|
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst";
|
||||||
hash = "sha256-eMmS9oysZheHwSCCvmOUSDJmP9zN7cAr6qqDIbz6EmY=";
|
hash = "sha256-hWcpl0Wd3XydT+RY7+VIoxXPhCzele1Ip76YSh+KmLI=";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21a
|
||||||
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
||||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||||
https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip
|
https://github.com/glfw/glfw/archive/73948e6c0f15b1053cf74b7c4e6b04fd36e97e29.zip
|
||||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-11-16-1/ghostty-gobject-0.14.0-2025-03-11-16-1.tar.gz
|
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst
|
||||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz
|
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/e348884a00ef6c98dc837a873c4a867c9164d8a0.tar.gz
|
||||||
https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz
|
https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz
|
||||||
https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz
|
https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ const ConfigErrorsWindow = @import("ConfigErrorsWindow.zig");
|
||||||
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
const ClipboardConfirmationWindow = @import("ClipboardConfirmationWindow.zig");
|
||||||
const CloseDialog = @import("CloseDialog.zig");
|
const CloseDialog = @import("CloseDialog.zig");
|
||||||
const Split = @import("Split.zig");
|
const Split = @import("Split.zig");
|
||||||
const c = @import("c.zig").c;
|
|
||||||
const version = @import("version.zig");
|
const version = @import("version.zig");
|
||||||
const inspector = @import("inspector.zig");
|
const inspector = @import("inspector.zig");
|
||||||
const key = @import("key.zig");
|
const key = @import("key.zig");
|
||||||
|
|
@ -48,6 +47,11 @@ const winprotopkg = @import("winproto.zig");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const adwaita = @import("adwaita.zig");
|
const adwaita = @import("adwaita.zig");
|
||||||
|
|
||||||
|
pub const c = @cImport({
|
||||||
|
// generated header files
|
||||||
|
@cInclude("ghostty_resources.h");
|
||||||
|
});
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk);
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
pub const Options = struct {};
|
pub const Options = struct {};
|
||||||
|
|
@ -55,8 +59,8 @@ pub const Options = struct {};
|
||||||
core_app: *CoreApp,
|
core_app: *CoreApp,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
|
||||||
app: *c.GtkApplication,
|
app: *adw.Application,
|
||||||
ctx: *c.GMainContext,
|
ctx: *glib.MainContext,
|
||||||
|
|
||||||
/// State and logic for the underlying windowing protocol.
|
/// State and logic for the underlying windowing protocol.
|
||||||
winproto: winprotopkg.App,
|
winproto: winprotopkg.App,
|
||||||
|
|
@ -86,15 +90,15 @@ running: bool = true,
|
||||||
transient_cgroup_base: ?[]const u8 = null,
|
transient_cgroup_base: ?[]const u8 = null,
|
||||||
|
|
||||||
/// CSS Provider for any styles based on ghostty configuration values
|
/// CSS Provider for any styles based on ghostty configuration values
|
||||||
css_provider: *c.GtkCssProvider,
|
css_provider: *gtk.CssProvider,
|
||||||
|
|
||||||
/// Providers for loading custom stylesheets defined by user
|
/// Providers for loading custom stylesheets defined by user
|
||||||
custom_css_providers: std.ArrayListUnmanaged(*c.GtkCssProvider) = .{},
|
custom_css_providers: std.ArrayListUnmanaged(*gtk.CssProvider) = .{},
|
||||||
|
|
||||||
/// The timer used to quit the application after the last window is closed.
|
/// The timer used to quit the application after the last window is closed.
|
||||||
quit_timer: union(enum) {
|
quit_timer: union(enum) {
|
||||||
off: void,
|
off: void,
|
||||||
active: c.guint,
|
active: c_uint,
|
||||||
expired: void,
|
expired: void,
|
||||||
} = .{ .off = {} },
|
} = .{ .off = {} },
|
||||||
|
|
||||||
|
|
@ -102,22 +106,10 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
_ = opts;
|
_ = opts;
|
||||||
|
|
||||||
// Log our GTK version
|
// Log our GTK version
|
||||||
log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
|
version.logVersion();
|
||||||
c.GTK_MAJOR_VERSION,
|
|
||||||
c.GTK_MINOR_VERSION,
|
|
||||||
c.GTK_MICRO_VERSION,
|
|
||||||
c.gtk_get_major_version(),
|
|
||||||
c.gtk_get_minor_version(),
|
|
||||||
c.gtk_get_micro_version(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// log the adwaita version
|
// log the adwaita version
|
||||||
log.info("libadwaita version build={s} runtime={}.{}.{}", .{
|
adwaita.logVersion();
|
||||||
c.ADW_VERSION_S,
|
|
||||||
c.adw_get_major_version(),
|
|
||||||
c.adw_get_minor_version(),
|
|
||||||
c.adw_get_micro_version(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set gettext global domain to be our app so that our unqualified
|
// Set gettext global domain to be our app so that our unqualified
|
||||||
// translations map to our translations.
|
// translations map to our translations.
|
||||||
|
|
@ -271,9 +263,9 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.adw_init();
|
adw.init();
|
||||||
|
|
||||||
const display: *c.GdkDisplay = c.gdk_display_get_default() orelse {
|
const display: *gdk.Display = gdk.Display.getDefault() orelse {
|
||||||
// I'm unsure of any scenario where this happens. Because we don't
|
// I'm unsure of any scenario where this happens. Because we don't
|
||||||
// want to litter null checks everywhere, we just exit here.
|
// want to litter null checks everywhere, we just exit here.
|
||||||
log.warn("gdk display is null, exiting", .{});
|
log.warn("gdk display is null, exiting", .{});
|
||||||
|
|
@ -291,9 +283,9 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Setup the flags for our application.
|
// Setup the flags for our application.
|
||||||
const app_flags: c.GApplicationFlags = app_flags: {
|
const app_flags: gio.ApplicationFlags = app_flags: {
|
||||||
var flags: c.GApplicationFlags = c.G_APPLICATION_DEFAULT_FLAGS;
|
var flags: gio.ApplicationFlags = .flags_default_flags;
|
||||||
if (!single_instance) flags |= c.G_APPLICATION_NON_UNIQUE;
|
if (!single_instance) flags.non_unique = true;
|
||||||
break :app_flags flags;
|
break :app_flags flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -321,91 +313,87 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
|
|
||||||
// Using an AdwApplication lets us use Adwaita widgets and access things
|
// Using an AdwApplication lets us use Adwaita widgets and access things
|
||||||
// such as the color scheme.
|
// such as the color scheme.
|
||||||
const adw_app = @as(?*c.AdwApplication, @ptrCast(c.adw_application_new(
|
const adw_app = adw.Application.new(
|
||||||
app_id.ptr,
|
app_id.ptr,
|
||||||
app_flags,
|
app_flags,
|
||||||
))) orelse return error.GtkInitFailed;
|
);
|
||||||
errdefer c.g_object_unref(adw_app);
|
errdefer adw_app.unref();
|
||||||
|
|
||||||
const style_manager = c.adw_application_get_style_manager(adw_app);
|
const style_manager = adw_app.getStyleManager();
|
||||||
c.adw_style_manager_set_color_scheme(
|
style_manager.setColorScheme(
|
||||||
style_manager,
|
|
||||||
switch (config.@"window-theme") {
|
switch (config.@"window-theme") {
|
||||||
.auto, .ghostty => auto: {
|
.auto, .ghostty => auto: {
|
||||||
const lum = config.background.toTerminalRGB().perceivedLuminance();
|
const lum = config.background.toTerminalRGB().perceivedLuminance();
|
||||||
break :auto if (lum > 0.5)
|
break :auto if (lum > 0.5)
|
||||||
c.ADW_COLOR_SCHEME_PREFER_LIGHT
|
.prefer_light
|
||||||
else
|
else
|
||||||
c.ADW_COLOR_SCHEME_PREFER_DARK;
|
.prefer_dark;
|
||||||
},
|
},
|
||||||
.system => c.ADW_COLOR_SCHEME_PREFER_LIGHT,
|
.system => .prefer_light,
|
||||||
.dark => c.ADW_COLOR_SCHEME_FORCE_DARK,
|
.dark => .prefer_dark,
|
||||||
.light => c.ADW_COLOR_SCHEME_FORCE_LIGHT,
|
.light => .force_dark,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const app: *c.GtkApplication = @ptrCast(adw_app);
|
const gio_app = adw_app.as(gio.Application);
|
||||||
const gapp: *c.GApplication = @ptrCast(app);
|
|
||||||
|
|
||||||
// force the resource path to a known value so that it doesn't depend on
|
// force the resource path to a known value so that it doesn't depend on
|
||||||
// the app id and load in compiled resources
|
// the app id and load in compiled resources
|
||||||
c.g_application_set_resource_base_path(gapp, "/com/mitchellh/ghostty");
|
gio_app.setResourceBasePath("/com/mitchellh/ghostty");
|
||||||
c.g_resources_register(c.ghostty_get_resource());
|
gio.resourcesRegister(@ptrCast(@alignCast(c.ghostty_get_resource() orelse {
|
||||||
|
log.err("unable to load resources", .{});
|
||||||
|
return error.GtkNoResources;
|
||||||
|
})));
|
||||||
|
|
||||||
// The `activate` signal is used when Ghostty is first launched and when a
|
// The `activate` signal is used when Ghostty is first launched and when a
|
||||||
// secondary Ghostty is launched and requests a new window.
|
// secondary Ghostty is launched and requests a new window.
|
||||||
_ = c.g_signal_connect_data(
|
_ = gio.Application.signals.activate.connect(
|
||||||
app,
|
adw_app,
|
||||||
"activate",
|
*CoreApp,
|
||||||
c.G_CALLBACK(>kActivate),
|
gtkActivate,
|
||||||
core_app,
|
core_app,
|
||||||
null,
|
.{},
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Other signals
|
// Other signals
|
||||||
_ = c.g_signal_connect_data(
|
_ = gtk.Application.signals.window_added.connect(
|
||||||
app,
|
adw_app,
|
||||||
"window-added",
|
*CoreApp,
|
||||||
c.G_CALLBACK(>kWindowAdded),
|
gtkWindowAdded,
|
||||||
core_app,
|
core_app,
|
||||||
null,
|
.{},
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
);
|
||||||
_ = c.g_signal_connect_data(
|
_ = gtk.Application.signals.window_removed.connect(
|
||||||
app,
|
adw_app,
|
||||||
"window-removed",
|
*CoreApp,
|
||||||
c.G_CALLBACK(>kWindowRemoved),
|
gtkWindowRemoved,
|
||||||
core_app,
|
core_app,
|
||||||
null,
|
.{},
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// We don't use g_application_run, we want to manually control the
|
// We don't use g_application_run, we want to manually control the
|
||||||
// loop so we have to do the same things the run function does:
|
// loop so we have to do the same things the run function does:
|
||||||
// https://github.com/GNOME/glib/blob/a8e8b742e7926e33eb635a8edceac74cf239d6ed/gio/gapplication.c#L2533
|
// https://github.com/GNOME/glib/blob/a8e8b742e7926e33eb635a8edceac74cf239d6ed/gio/gapplication.c#L2533
|
||||||
const ctx = c.g_main_context_default() orelse return error.GtkContextFailed;
|
const ctx = glib.MainContext.default();
|
||||||
if (c.g_main_context_acquire(ctx) == 0) return error.GtkContextAcquireFailed;
|
if (glib.MainContext.acquire(ctx) == 0) return error.GtkContextAcquireFailed;
|
||||||
errdefer c.g_main_context_release(ctx);
|
errdefer glib.MainContext.release(ctx);
|
||||||
|
|
||||||
var err_: ?*c.GError = null;
|
var err_: ?*glib.Error = null;
|
||||||
if (c.g_application_register(
|
if (gio_app.register(
|
||||||
gapp,
|
|
||||||
null,
|
null,
|
||||||
@ptrCast(&err_),
|
&err_,
|
||||||
) == 0) {
|
) == 0) {
|
||||||
if (err_) |err| {
|
if (err_) |err| {
|
||||||
log.warn("error registering application: {s}", .{err.message});
|
log.warn("error registering application: {s}", .{err.f_message orelse "(unknown)"});
|
||||||
c.g_error_free(err);
|
err.free();
|
||||||
}
|
}
|
||||||
return error.GtkApplicationRegisterFailed;
|
return error.GtkApplicationRegisterFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: when App.zig is converted to zig-gobject
|
|
||||||
// Setup our windowing protocol logic
|
// Setup our windowing protocol logic
|
||||||
var winproto_app = try winprotopkg.App.init(
|
var winproto_app = try winprotopkg.App.init(
|
||||||
core_app.alloc,
|
core_app.alloc,
|
||||||
@ptrCast(@alignCast(display)),
|
display,
|
||||||
app_id,
|
app_id,
|
||||||
&config,
|
&config,
|
||||||
);
|
);
|
||||||
|
|
@ -419,20 +407,20 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
//
|
//
|
||||||
// https://gitlab.gnome.org/GNOME/glib/-/blob/bd2ccc2f69ecfd78ca3f34ab59e42e2b462bad65/gio/gapplication.c#L2302
|
// https://gitlab.gnome.org/GNOME/glib/-/blob/bd2ccc2f69ecfd78ca3f34ab59e42e2b462bad65/gio/gapplication.c#L2302
|
||||||
if (config.@"initial-window")
|
if (config.@"initial-window")
|
||||||
c.g_application_activate(gapp);
|
gio_app.activate();
|
||||||
|
|
||||||
// Internally, GTK ensures that only one instance of this provider exists in the provider list
|
// Internally, GTK ensures that only one instance of this provider exists in the provider list
|
||||||
// for the display.
|
// for the display.
|
||||||
const css_provider = c.gtk_css_provider_new();
|
const css_provider = gtk.CssProvider.new();
|
||||||
c.gtk_style_context_add_provider_for_display(
|
gtk.StyleContext.addProviderForDisplay(
|
||||||
display,
|
display,
|
||||||
@ptrCast(css_provider),
|
css_provider.as(gtk.StyleProvider),
|
||||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 3,
|
gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 3,
|
||||||
);
|
);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.core_app = core_app,
|
.core_app = core_app,
|
||||||
.app = app,
|
.app = adw_app,
|
||||||
.config = config,
|
.config = config,
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.cursor_none = cursor_none,
|
.cursor_none = cursor_none,
|
||||||
|
|
@ -441,7 +429,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
// If we are NOT the primary instance, then we never want to run.
|
// If we are NOT the primary instance, then we never want to run.
|
||||||
// This means that another instance of the GTK app is running and
|
// This means that another instance of the GTK app is running and
|
||||||
// our "activate" call above will open a window.
|
// our "activate" call above will open a window.
|
||||||
.running = c.g_application_get_is_remote(gapp) == 0,
|
.running = gio_app.getIsRemote() == 0,
|
||||||
.css_provider = css_provider,
|
.css_provider = css_provider,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -449,16 +437,16 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||||
// Terminate the application. The application will not be restarted after
|
// Terminate the application. The application will not be restarted after
|
||||||
// this so all global state can be cleaned up.
|
// this so all global state can be cleaned up.
|
||||||
pub fn terminate(self: *App) void {
|
pub fn terminate(self: *App) void {
|
||||||
c.g_settings_sync();
|
gio.Settings.sync();
|
||||||
while (c.g_main_context_iteration(self.ctx, 0) != 0) {}
|
while (glib.MainContext.iteration(self.ctx, 0) != 0) {}
|
||||||
c.g_main_context_release(self.ctx);
|
glib.MainContext.release(self.ctx);
|
||||||
c.g_object_unref(self.app);
|
self.app.unref();
|
||||||
|
|
||||||
if (self.cursor_none) |cursor| c.g_object_unref(cursor);
|
if (self.cursor_none) |cursor| cursor.unref();
|
||||||
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
|
if (self.transient_cgroup_base) |path| self.core_app.alloc.free(path);
|
||||||
|
|
||||||
for (self.custom_css_providers.items) |provider| {
|
for (self.custom_css_providers.items) |provider| {
|
||||||
c.g_object_unref(provider);
|
provider.unref();
|
||||||
}
|
}
|
||||||
self.custom_css_providers.deinit(self.core_app.alloc);
|
self.custom_css_providers.deinit(self.core_app.alloc);
|
||||||
|
|
||||||
|
|
@ -922,29 +910,25 @@ fn showDesktopNotification(
|
||||||
else => n.title,
|
else => n.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
const notification = c.g_notification_new(t.ptr);
|
const notification = gio.Notification.new(t);
|
||||||
defer c.g_object_unref(notification);
|
defer notification.unref();
|
||||||
c.g_notification_set_body(notification, n.body.ptr);
|
notification.setBody(n.body);
|
||||||
|
|
||||||
const icon = c.g_themed_icon_new("com.mitchellh.ghostty");
|
const icon = gio.ThemedIcon.new("com.mitchellh.ghostty");
|
||||||
defer c.g_object_unref(icon);
|
defer icon.unref();
|
||||||
c.g_notification_set_icon(notification, icon);
|
notification.setIcon(icon.as(gio.Icon));
|
||||||
|
|
||||||
const pointer = c.g_variant_new_uint64(switch (target) {
|
const pointer = glib.Variant.newUint64(switch (target) {
|
||||||
.app => 0,
|
.app => 0,
|
||||||
.surface => |v| @intFromPtr(v),
|
.surface => |v| @intFromPtr(v),
|
||||||
});
|
});
|
||||||
c.g_notification_set_default_action_and_target_value(
|
notification.setDefaultActionAndTargetValue("app.present-surface", pointer);
|
||||||
notification,
|
|
||||||
"app.present-surface",
|
|
||||||
pointer,
|
|
||||||
);
|
|
||||||
|
|
||||||
const g_app: *c.GApplication = @ptrCast(self.app);
|
const gio_app = self.app.as(gio.Application);
|
||||||
|
|
||||||
// We set the notification ID to the body content. If the content is the
|
// We set the notification ID to the body content. If the content is the
|
||||||
// same, this notification may replace a previous notification
|
// same, this notification may replace a previous notification
|
||||||
c.g_application_send_notification(g_app, n.body.ptr, notification);
|
gio_app.sendNotification(n.body, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configChange(
|
fn configChange(
|
||||||
|
|
@ -1076,29 +1060,29 @@ fn syncActionAccelerator(
|
||||||
gtk_action: [:0]const u8,
|
gtk_action: [:0]const u8,
|
||||||
action: input.Binding.Action,
|
action: input.Binding.Action,
|
||||||
) !void {
|
) !void {
|
||||||
|
const gtk_app = self.app.as(gtk.Application);
|
||||||
|
|
||||||
// Reset it initially
|
// Reset it initially
|
||||||
const zero = [_]?[*:0]const u8{null};
|
const zero = [_:null]?[*:0]const u8{};
|
||||||
c.gtk_application_set_accels_for_action(@ptrCast(self.app), gtk_action.ptr, &zero);
|
gtk_app.setAccelsForAction(gtk_action, &zero);
|
||||||
|
|
||||||
const trigger = self.config.keybind.set.getTrigger(action) orelse return;
|
const trigger = self.config.keybind.set.getTrigger(action) orelse return;
|
||||||
var buf: [256]u8 = undefined;
|
var buf: [256]u8 = undefined;
|
||||||
const accel = try key.accelFromTrigger(&buf, trigger) orelse return;
|
const accel = try key.accelFromTrigger(&buf, trigger) orelse return;
|
||||||
const accels = [_]?[*:0]const u8{ accel, null };
|
const accels = [_:null]?[*:0]const u8{accel};
|
||||||
|
|
||||||
c.gtk_application_set_accels_for_action(
|
gtk_app.setAccelsForAction(gtk_action, &accels);
|
||||||
@ptrCast(self.app),
|
|
||||||
gtk_action.ptr,
|
|
||||||
&accels,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadRuntimeCss(
|
fn loadRuntimeCss(
|
||||||
self: *const App,
|
self: *const App,
|
||||||
) Allocator.Error!void {
|
) Allocator.Error!void {
|
||||||
var stack_alloc = std.heap.stackFallback(4096, self.core_app.alloc);
|
const alloc = self.core_app.alloc;
|
||||||
var buf = std.ArrayList(u8).init(stack_alloc.get());
|
|
||||||
defer buf.deinit();
|
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
const writer = buf.writer();
|
defer buf.deinit(alloc);
|
||||||
|
|
||||||
|
const writer = buf.writer(alloc);
|
||||||
|
|
||||||
const config: *const Config = &self.config;
|
const config: *const Config = &self.config;
|
||||||
const window_theme = config.@"window-theme";
|
const window_theme = config.@"window-theme";
|
||||||
|
|
@ -1190,20 +1174,28 @@ fn loadRuntimeCss(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = try alloc.dupeZ(u8, buf.items);
|
||||||
|
defer alloc.free(data);
|
||||||
|
|
||||||
// Clears any previously loaded CSS from this provider
|
// Clears any previously loaded CSS from this provider
|
||||||
loadCssProviderFromData(self.css_provider, buf.items);
|
loadCssProviderFromData(self.css_provider, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadCustomCss(self: *App) !void {
|
fn loadCustomCss(self: *App) !void {
|
||||||
const display = c.gdk_display_get_default();
|
const alloc = self.core_app.alloc;
|
||||||
|
|
||||||
|
const display = gdk.Display.getDefault() orelse {
|
||||||
|
log.warn("unable to get display", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// unload the previously loaded style providers
|
// unload the previously loaded style providers
|
||||||
for (self.custom_css_providers.items) |provider| {
|
for (self.custom_css_providers.items) |provider| {
|
||||||
c.gtk_style_context_remove_provider_for_display(
|
gtk.StyleContext.removeProviderForDisplay(
|
||||||
display,
|
display,
|
||||||
@ptrCast(provider),
|
provider.as(gtk.StyleProvider),
|
||||||
);
|
);
|
||||||
c.g_object_unref(provider);
|
provider.unref();
|
||||||
}
|
}
|
||||||
self.custom_css_providers.clearRetainingCapacity();
|
self.custom_css_providers.clearRetainingCapacity();
|
||||||
|
|
||||||
|
|
@ -1214,49 +1206,51 @@ fn loadCustomCss(self: *App) !void {
|
||||||
};
|
};
|
||||||
const file = std.fs.openFileAbsolute(path, .{}) catch |err| {
|
const file = std.fs.openFileAbsolute(path, .{}) catch |err| {
|
||||||
if (err != error.FileNotFound or !optional) {
|
if (err != error.FileNotFound or !optional) {
|
||||||
log.err("error opening gtk-custom-css file {s}: {}", .{ path, err });
|
log.err(
|
||||||
|
"error opening gtk-custom-css file {s}: {}",
|
||||||
|
.{ path, err },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
log.info("loading gtk-custom-css path={s}", .{path});
|
log.info("loading gtk-custom-css path={s}", .{path});
|
||||||
const contents = try file.reader().readAllAlloc(self.core_app.alloc, 5 * 1024 * 1024 // 5MB
|
const contents = try file.reader().readAllAlloc(
|
||||||
|
self.core_app.alloc,
|
||||||
|
5 * 1024 * 1024, // 5MB,
|
||||||
);
|
);
|
||||||
defer self.core_app.alloc.free(contents);
|
defer alloc.free(contents);
|
||||||
|
|
||||||
const provider = c.gtk_css_provider_new();
|
const data = try alloc.dupeZ(u8, contents);
|
||||||
c.gtk_style_context_add_provider_for_display(
|
defer alloc.free(data);
|
||||||
|
|
||||||
|
const provider = gtk.CssProvider.new();
|
||||||
|
loadCssProviderFromData(provider, data);
|
||||||
|
gtk.StyleContext.addProviderForDisplay(
|
||||||
display,
|
display,
|
||||||
@ptrCast(provider),
|
provider.as(gtk.StyleProvider),
|
||||||
c.GTK_STYLE_PROVIDER_PRIORITY_USER,
|
gtk.STYLE_PROVIDER_PRIORITY_USER,
|
||||||
);
|
);
|
||||||
|
|
||||||
loadCssProviderFromData(provider, contents);
|
|
||||||
|
|
||||||
try self.custom_css_providers.append(self.core_app.alloc, provider);
|
try self.custom_css_providers.append(self.core_app.alloc, provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadCssProviderFromData(provider: *c.GtkCssProvider, data: []const u8) void {
|
fn loadCssProviderFromData(provider: *gtk.CssProvider, data: [:0]const u8) void {
|
||||||
if (version.atLeast(4, 12, 0)) {
|
if (version.atLeast(4, 12, 0)) {
|
||||||
const g_bytes = c.g_bytes_new(data.ptr, data.len);
|
const g_bytes = glib.Bytes.new(data.ptr, data.len);
|
||||||
defer c.g_bytes_unref(g_bytes);
|
defer g_bytes.unref();
|
||||||
|
|
||||||
c.gtk_css_provider_load_from_bytes(provider, g_bytes);
|
provider.loadFromBytes(g_bytes);
|
||||||
} else {
|
} else {
|
||||||
c.gtk_css_provider_load_from_data(
|
provider.loadFromData(data, @intCast(data.len));
|
||||||
provider,
|
|
||||||
data.ptr,
|
|
||||||
@intCast(data.len),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called by CoreApp to wake up the event loop.
|
/// Called by CoreApp to wake up the event loop.
|
||||||
pub fn wakeup(self: App) void {
|
pub fn wakeup(_: App) void {
|
||||||
_ = self;
|
glib.MainContext.wakeup(null);
|
||||||
c.g_main_context_wakeup(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the event loop. This doesn't return until the app exits.
|
/// Run the event loop. This doesn't return until the app exits.
|
||||||
|
|
@ -1297,8 +1291,7 @@ pub fn run(self: *App) !void {
|
||||||
} else log.debug("cgroup isolation disabled config={}", .{self.config.@"linux-cgroup"});
|
} else log.debug("cgroup isolation disabled config={}", .{self.config.@"linux-cgroup"});
|
||||||
|
|
||||||
// Setup color scheme notifications
|
// Setup color scheme notifications
|
||||||
const adw_app: *adw.Application = @ptrCast(@alignCast(self.app));
|
const style_manager: *adw.StyleManager = self.app.getStyleManager();
|
||||||
const style_manager: *adw.StyleManager = adw_app.getStyleManager();
|
|
||||||
_ = gobject.Object.signals.notify.connect(
|
_ = gobject.Object.signals.notify.connect(
|
||||||
style_manager,
|
style_manager,
|
||||||
*App,
|
*App,
|
||||||
|
|
@ -1324,7 +1317,7 @@ pub fn run(self: *App) !void {
|
||||||
};
|
};
|
||||||
|
|
||||||
while (self.running) {
|
while (self.running) {
|
||||||
_ = c.g_main_context_iteration(self.ctx, 1);
|
_ = glib.MainContext.iteration(self.ctx, 1);
|
||||||
|
|
||||||
// Tick the terminal app and see if we should quit.
|
// Tick the terminal app and see if we should quit.
|
||||||
try self.core_app.tick(self);
|
try self.core_app.tick(self);
|
||||||
|
|
@ -1363,7 +1356,13 @@ fn startQuitTimer(self: *App) void {
|
||||||
|
|
||||||
if (self.config.@"quit-after-last-window-closed-delay") |v| {
|
if (self.config.@"quit-after-last-window-closed-delay") |v| {
|
||||||
// If a delay is configured, set a timeout function to quit after the delay.
|
// If a delay is configured, set a timeout function to quit after the delay.
|
||||||
self.quit_timer = .{ .active = c.g_timeout_add(v.asMilliseconds(), gtkQuitTimerExpired, self) };
|
self.quit_timer = .{
|
||||||
|
.active = glib.timeoutAdd(
|
||||||
|
v.asMilliseconds(),
|
||||||
|
gtkQuitTimerExpired,
|
||||||
|
self,
|
||||||
|
),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
// If no delay is configured, treat it as expired.
|
// If no delay is configured, treat it as expired.
|
||||||
self.quit_timer = .{ .expired = {} };
|
self.quit_timer = .{ .expired = {} };
|
||||||
|
|
@ -1453,14 +1452,13 @@ fn quit(self: *App) void {
|
||||||
|
|
||||||
/// This immediately destroys all windows, forcing the application to quit.
|
/// This immediately destroys all windows, forcing the application to quit.
|
||||||
pub fn quitNow(self: *App) void {
|
pub fn quitNow(self: *App) void {
|
||||||
const list = c.gtk_window_list_toplevels();
|
const list = gtk.Window.listToplevels();
|
||||||
defer c.g_list_free(list);
|
defer list.free();
|
||||||
c.g_list_foreach(list, struct {
|
list.foreach(struct {
|
||||||
fn callback(data: c.gpointer, _: c.gpointer) callconv(.C) void {
|
fn callback(data: ?*anyopaque, _: ?*anyopaque) callconv(.c) void {
|
||||||
const ptr = data orelse return;
|
const ptr = data orelse return;
|
||||||
const widget: *c.GtkWidget = @ptrCast(@alignCast(ptr));
|
const window: *gtk.Window = @ptrCast(@alignCast(ptr));
|
||||||
const window: *c.GtkWindow = @ptrCast(widget);
|
window.destroy();
|
||||||
c.gtk_window_destroy(window);
|
|
||||||
}
|
}
|
||||||
}.callback, null);
|
}.callback, null);
|
||||||
|
|
||||||
|
|
@ -1469,11 +1467,7 @@ pub fn quitNow(self: *App) void {
|
||||||
|
|
||||||
/// This is called by the `activate` signal. This is sent on program startup and
|
/// This is called by the `activate` signal. This is sent on program startup and
|
||||||
/// also when a secondary instance launches and requests a new window.
|
/// also when a secondary instance launches and requests a new window.
|
||||||
fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkActivate(_: *adw.Application, core_app: *CoreApp) callconv(.c) void {
|
||||||
_ = app;
|
|
||||||
|
|
||||||
const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
|
|
||||||
// Queue a new window
|
// Queue a new window
|
||||||
_ = core_app.mailbox.push(.{
|
_ = core_app.mailbox.push(.{
|
||||||
.new_window = .{},
|
.new_window = .{},
|
||||||
|
|
@ -1481,46 +1475,41 @@ fn gtkActivate(app: *c.GtkApplication, ud: ?*anyopaque) callconv(.C) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkWindowAdded(
|
fn gtkWindowAdded(
|
||||||
_: *c.GtkApplication,
|
_: *adw.Application,
|
||||||
window: *c.GtkWindow,
|
window: *gtk.Window,
|
||||||
ud: ?*anyopaque,
|
core_app: *CoreApp,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
|
|
||||||
// Request the is-active property change so we can detect
|
// Request the is-active property change so we can detect
|
||||||
// when our app loses focus.
|
// when our app loses focus.
|
||||||
_ = c.g_signal_connect_data(
|
_ = gobject.Object.signals.notify.connect(
|
||||||
window,
|
window,
|
||||||
"notify::is-active",
|
*CoreApp,
|
||||||
c.G_CALLBACK(>kWindowIsActive),
|
gtkWindowIsActive,
|
||||||
core_app,
|
core_app,
|
||||||
null,
|
.{
|
||||||
c.G_CONNECT_DEFAULT,
|
.detail = "is-active",
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkWindowRemoved(
|
fn gtkWindowRemoved(
|
||||||
_: *c.GtkApplication,
|
_: *adw.Application,
|
||||||
_: *c.GtkWindow,
|
_: *gtk.Window,
|
||||||
ud: ?*anyopaque,
|
core_app: *CoreApp,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
|
|
||||||
// Recheck if we are focused
|
// Recheck if we are focused
|
||||||
gtkWindowIsActive(null, undefined, core_app);
|
gtkWindowIsActive(null, undefined, core_app);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkWindowIsActive(
|
fn gtkWindowIsActive(
|
||||||
window: ?*c.GtkWindow,
|
window: ?*gtk.Window,
|
||||||
_: *c.GParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
ud: ?*anyopaque,
|
core_app: *CoreApp,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const core_app: *CoreApp = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
|
|
||||||
// If our window is active, then we can tell the app
|
// If our window is active, then we can tell the app
|
||||||
// that we are focused.
|
// that we are focused.
|
||||||
if (window) |w| {
|
if (window) |w| {
|
||||||
if (c.gtk_window_is_active(w) == 1) {
|
if (w.isActive() != 0) {
|
||||||
core_app.focusEvent(true);
|
core_app.focusEvent(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1529,16 +1518,17 @@ fn gtkWindowIsActive(
|
||||||
// If the window becomes inactive, we need to check if any
|
// If the window becomes inactive, we need to check if any
|
||||||
// other windows are active. If not, then we are no longer
|
// other windows are active. If not, then we are no longer
|
||||||
// focused.
|
// focused.
|
||||||
if (c.gtk_window_list_toplevels()) |list| {
|
{
|
||||||
defer c.g_list_free(list);
|
const list = gtk.Window.listToplevels();
|
||||||
var current: ?*c.GList = list;
|
defer list.free();
|
||||||
while (current) |elem| : (current = elem.next) {
|
var current: ?*glib.List = list;
|
||||||
|
while (current) |elem| : (current = elem.f_next) {
|
||||||
// If the window is active then we are still focused.
|
// If the window is active then we are still focused.
|
||||||
// This is another window since we did our check above.
|
// This is another window since we did our check above.
|
||||||
// That window should trigger its own is-active
|
// That window should trigger its own is-active
|
||||||
// callback so we don't need to call it here.
|
// callback so we don't need to call it here.
|
||||||
const w: *c.GtkWindow = @alignCast(@ptrCast(elem.data));
|
const w: *gtk.Window = @alignCast(@ptrCast(elem.f_data));
|
||||||
if (c.gtk_window_is_active(w) == 1) return;
|
if (w.isActive() == 1) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1575,33 +1565,30 @@ fn colorSchemeEvent(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionOpenConfig(
|
fn gtkActionOpenConfig(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *App,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const self: *App = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
_ = self.core_app.mailbox.push(.{
|
_ = self.core_app.mailbox.push(.{
|
||||||
.open_config = {},
|
.open_config = {},
|
||||||
}, .{ .forever = {} });
|
}, .{ .forever = {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionReloadConfig(
|
fn gtkActionReloadConfig(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *App,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const self: *App = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
self.reloadConfig(.app, .{}) catch |err| {
|
self.reloadConfig(.app, .{}) catch |err| {
|
||||||
log.err("error reloading configuration: {}", .{err});
|
log.err("error reloading configuration: {}", .{err});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkActionQuit(
|
fn gtkActionQuit(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: *c.GVariant,
|
_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *App,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const self: *App = @ptrCast(@alignCast(ud orelse return));
|
|
||||||
self.core_app.performAction(self, .quit) catch |err| {
|
self.core_app.performAction(self, .quit) catch |err| {
|
||||||
log.err("error quitting err={}", .{err});
|
log.err("error quitting err={}", .{err});
|
||||||
};
|
};
|
||||||
|
|
@ -1610,21 +1597,24 @@ fn gtkActionQuit(
|
||||||
/// Action sent by the window manager asking us to present a specific surface to
|
/// Action sent by the window manager asking us to present a specific surface to
|
||||||
/// the user. Usually because the user clicked on a desktop notification.
|
/// the user. Usually because the user clicked on a desktop notification.
|
||||||
fn gtkActionPresentSurface(
|
fn gtkActionPresentSurface(
|
||||||
_: *c.GSimpleAction,
|
_: *gio.SimpleAction,
|
||||||
parameter: *c.GVariant,
|
parameter_: ?*glib.Variant,
|
||||||
ud: ?*anyopaque,
|
self: *App,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const self: *App = @ptrCast(@alignCast(ud orelse return));
|
const parameter = parameter_ orelse return;
|
||||||
|
|
||||||
|
const t = glib.ext.VariantType.newFor(u64);
|
||||||
|
defer glib.VariantType.free(t);
|
||||||
|
|
||||||
// Make sure that we've receiived a u64 from the system.
|
// Make sure that we've receiived a u64 from the system.
|
||||||
if (c.g_variant_is_of_type(parameter, c.G_VARIANT_TYPE("t")) == 0) {
|
if (glib.Variant.isOfType(parameter, t) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert that u64 to pointer to a core surface. A value of zero
|
// Convert that u64 to pointer to a core surface. A value of zero
|
||||||
// means that there was no target surface for the notification so
|
// means that there was no target surface for the notification so
|
||||||
// we don't focus any surface.
|
// we don't focus any surface.
|
||||||
const ptr_int: u64 = c.g_variant_get_uint64(parameter);
|
const ptr_int = parameter.getUint64();
|
||||||
if (ptr_int == 0) return;
|
if (ptr_int == 0) return;
|
||||||
const surface: *CoreSurface = @ptrFromInt(ptr_int);
|
const surface: *CoreSurface = @ptrFromInt(ptr_int);
|
||||||
|
|
||||||
|
|
@ -1654,25 +1644,27 @@ fn initActions(self: *App) void {
|
||||||
//
|
//
|
||||||
// For action names:
|
// For action names:
|
||||||
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
|
||||||
const actions = .{
|
const t = glib.ext.VariantType.newFor(u64);
|
||||||
.{ "quit", >kActionQuit, null },
|
defer glib.VariantType.free(t);
|
||||||
.{ "open-config", >kActionOpenConfig, null },
|
|
||||||
.{ "reload-config", >kActionReloadConfig, null },
|
|
||||||
.{ "present-surface", >kActionPresentSurface, c.G_VARIANT_TYPE("t") },
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const actions = .{
|
||||||
|
.{ "quit", gtkActionQuit, null },
|
||||||
|
.{ "open-config", gtkActionOpenConfig, null },
|
||||||
|
.{ "reload-config", gtkActionReloadConfig, null },
|
||||||
|
.{ "present-surface", gtkActionPresentSurface, t },
|
||||||
|
};
|
||||||
inline for (actions) |entry| {
|
inline for (actions) |entry| {
|
||||||
const action = c.g_simple_action_new(entry[0], entry[2]);
|
const action = gio.SimpleAction.new(entry[0], entry[2]);
|
||||||
defer c.g_object_unref(action);
|
defer action.unref();
|
||||||
_ = c.g_signal_connect_data(
|
_ = gio.SimpleAction.signals.activate.connect(
|
||||||
action,
|
action,
|
||||||
"activate",
|
*App,
|
||||||
c.G_CALLBACK(entry[1]),
|
entry[1],
|
||||||
self,
|
self,
|
||||||
null,
|
.{},
|
||||||
c.G_CONNECT_DEFAULT,
|
|
||||||
);
|
);
|
||||||
c.g_action_map_add_action(@ptrCast(self.app), @ptrCast(action));
|
const action_map = self.app.as(gio.ActionMap);
|
||||||
|
action_map.addAction(action.as(gio.Action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1341,8 +1341,7 @@ pub fn showDesktopNotification(
|
||||||
const pointer = glib.Variant.newUint64(@intFromPtr(&self.core_surface));
|
const pointer = glib.Variant.newUint64(@intFromPtr(&self.core_surface));
|
||||||
notification.setDefaultActionAndTargetValue("app.present-surface", pointer);
|
notification.setDefaultActionAndTargetValue("app.present-surface", pointer);
|
||||||
|
|
||||||
// FIXME: when App.zig gets converted to zig-gobject
|
const app = self.app.app.as(gio.Application);
|
||||||
const app: gio.Application = @ptrCast(@alignCast(self.app.app));
|
|
||||||
|
|
||||||
// We set the notification ID to the body content. If the content is the
|
// We set the notification ID to the body content. If the content is the
|
||||||
// same, this notification may replace a previous notification
|
// same, this notification may replace a previous notification
|
||||||
|
|
|
||||||
|
|
@ -141,9 +141,8 @@ pub fn init(self: *Window, app: *App) !void {
|
||||||
.winproto = .none,
|
.winproto = .none,
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: when App.zig is converted to zig-gobject
|
|
||||||
// Create the window
|
// Create the window
|
||||||
self.window = adw.ApplicationWindow.new(@ptrCast(@alignCast(app.app)));
|
self.window = adw.ApplicationWindow.new(app.app.as(gtk.Application));
|
||||||
const gtk_window = self.window.as(gtk.Window);
|
const gtk_window = self.window.as(gtk.Window);
|
||||||
const gtk_widget = self.window.as(gtk.Widget);
|
const gtk_widget = self.window.as(gtk.Widget);
|
||||||
errdefer gtk_window.destroy();
|
errdefer gtk_window.destroy();
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,45 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const c = @import("c.zig").c;
|
|
||||||
|
// Until the gobject bindings are built at the same time we are building
|
||||||
|
// Ghostty, we need to import `adwaita.h` directly to ensure that the version
|
||||||
|
// macros match the version of `libadwaita` that we are building/linking
|
||||||
|
// against.
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("adwaita.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adw = @import("adw");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
|
pub fn logVersion() void {
|
||||||
|
log.info("libadwaita version build={s} runtime={}.{}.{}", .{
|
||||||
|
c.ADW_VERSION_S,
|
||||||
|
adw.getMajorVersion(),
|
||||||
|
adw.getMinorVersion(),
|
||||||
|
adw.getMicroVersion(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Verifies that the running libadwaita version is at least the given
|
/// Verifies that the running libadwaita version is at least the given
|
||||||
/// version. This will return false if Ghostty is configured to
|
/// version. This will return false if Ghostty is configured to not build with
|
||||||
/// not build with libadwaita.
|
/// libadwaita.
|
||||||
///
|
///
|
||||||
/// This can be run in both a comptime and runtime context. If it
|
/// This can be run in both a comptime and runtime context. If it is run in a
|
||||||
/// is run in a comptime context, it will only check the version
|
/// comptime context, it will only check the version in the headers. If it is
|
||||||
/// in the headers. If it is run in a runtime context, it will
|
/// run in a runtime context, it will check the actual version of the library we
|
||||||
/// check the actual version of the library we are linked against.
|
/// are linked against. So generally you probably want to do both checks!
|
||||||
/// So generally you probably want to do both checks!
|
|
||||||
///
|
///
|
||||||
/// This is inlined so that the comptime checks will disable the
|
/// This is inlined so that the comptime checks will disable the runtime checks
|
||||||
/// runtime checks if the comptime checks fail.
|
/// if the comptime checks fail.
|
||||||
pub inline fn versionAtLeast(
|
pub inline fn versionAtLeast(
|
||||||
comptime major: u16,
|
comptime major: u16,
|
||||||
comptime minor: u16,
|
comptime minor: u16,
|
||||||
comptime micro: u16,
|
comptime micro: u16,
|
||||||
) bool {
|
) bool {
|
||||||
// If our header has lower versions than the given version,
|
// If our header has lower versions than the given version, we can return
|
||||||
// we can return false immediately. This prevents us from
|
// false immediately. This prevents us from compiling against unknown
|
||||||
// compiling against unknown symbols and makes runtime checks
|
// symbols and makes runtime checks very slightly faster.
|
||||||
// very slightly faster.
|
|
||||||
if (comptime c.ADW_MAJOR_VERSION < major or
|
if (comptime c.ADW_MAJOR_VERSION < major or
|
||||||
(c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor) or
|
(c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor) or
|
||||||
(c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION == minor and c.ADW_MICRO_VERSION < micro))
|
(c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION == minor and c.ADW_MICRO_VERSION < micro))
|
||||||
|
|
@ -30,14 +48,13 @@ pub inline fn versionAtLeast(
|
||||||
// If we're in comptime then we can't check the runtime version.
|
// If we're in comptime then we can't check the runtime version.
|
||||||
if (@inComptime()) return true;
|
if (@inComptime()) return true;
|
||||||
|
|
||||||
// We use the functions instead of the constants such as
|
// We use the functions instead of the constants such as c.ADW_MINOR_VERSION
|
||||||
// c.ADW_MINOR_VERSION because the function gets the actual
|
// because the function gets the actual runtime version.
|
||||||
// runtime version.
|
if (adw.getMajorVersion() >= major) {
|
||||||
if (c.adw_get_major_version() >= major) {
|
if (adw.getMajorVersion() > major) return true;
|
||||||
if (c.adw_get_major_version() > major) return true;
|
if (adw.getMinorVersion() >= minor) {
|
||||||
if (c.adw_get_minor_version() >= minor) {
|
if (adw.getMinorVersion() > minor) return true;
|
||||||
if (c.adw_get_minor_version() > minor) return true;
|
return adw.getMicroVersion() >= micro;
|
||||||
return c.adw_get_micro_version() >= micro;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@ pub const c = @cImport({
|
||||||
@cInclude("gtk/gtk.h");
|
@cInclude("gtk/gtk.h");
|
||||||
@cInclude("adwaita.h");
|
@cInclude("adwaita.h");
|
||||||
|
|
||||||
// generated header files
|
|
||||||
@cInclude("ghostty_resources.h");
|
|
||||||
|
|
||||||
// compatibility
|
// compatibility
|
||||||
@cInclude("ghostty_gtk_compat.h");
|
@cInclude("ghostty_gtk_compat.h");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,8 @@ fn enableControllers(alloc: Allocator, cgroup: []const u8) !void {
|
||||||
/// On success this will return the name of the transient scope
|
/// On success this will return the name of the transient scope
|
||||||
/// cgroup prefix, allocated with the given allocator.
|
/// cgroup prefix, allocated with the given allocator.
|
||||||
fn createScope(app: *App, pid_: std.os.linux.pid_t) !void {
|
fn createScope(app: *App, pid_: std.os.linux.pid_t) !void {
|
||||||
// FIXME: when app.app gets converted to gobject
|
const gio_app = app.app.as(gio.Application);
|
||||||
const g_app: *gio.Application = @ptrCast(@alignCast(app.app));
|
const connection = gio_app.getDbusConnection() orelse
|
||||||
const connection = g_app.getDbusConnection() orelse
|
|
||||||
return error.DbusConnectionRequired;
|
return error.DbusConnectionRequired;
|
||||||
|
|
||||||
const pid: u32 = @intCast(pid_);
|
const pid: u32 = @intCast(pid_);
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ const Window = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the window
|
// Create the window
|
||||||
const window = c.gtk_application_window_new(inspector.surface.app.app);
|
const window = c.gtk_application_window_new(@ptrCast(@alignCast(inspector.surface.app.app)));
|
||||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||||
errdefer c.gtk_window_destroy(gtk_window);
|
errdefer c.gtk_window_destroy(gtk_window);
|
||||||
self.window = gtk_window;
|
self.window = gtk_window;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,41 @@
|
||||||
const c = @import("c.zig").c;
|
const std = @import("std");
|
||||||
|
|
||||||
|
// Until the gobject bindings are built at the same time we are building
|
||||||
|
// Ghostty, we need to import `gtk/gtk.h` directly to ensure that the version
|
||||||
|
// macros match the version of `gtk4` that we are building/linking against.
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("gtk/gtk.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.gtk);
|
||||||
|
|
||||||
|
pub fn logVersion() void {
|
||||||
|
log.info("GTK version build={d}.{d}.{d} runtime={d}.{d}.{d}", .{
|
||||||
|
c.GTK_MAJOR_VERSION,
|
||||||
|
c.GTK_MINOR_VERSION,
|
||||||
|
c.GTK_MICRO_VERSION,
|
||||||
|
gtk.getMajorVersion(),
|
||||||
|
gtk.getMinorVersion(),
|
||||||
|
gtk.getMicroVersion(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Verifies that the GTK version is at least the given version.
|
/// Verifies that the GTK version is at least the given version.
|
||||||
///
|
///
|
||||||
/// This can be run in both a comptime and runtime context. If it
|
/// This can be run in both a comptime and runtime context. If it is run in a
|
||||||
/// is run in a comptime context, it will only check the version
|
/// comptime context, it will only check the version in the headers. If it is
|
||||||
/// in the headers. If it is run in a runtime context, it will
|
/// run in a runtime context, it will check the actual version of the library we
|
||||||
/// check the actual version of the library we are linked against.
|
/// are linked against.
|
||||||
///
|
///
|
||||||
/// This function should be used in cases where the version check
|
/// This function should be used in cases where the version check would affect
|
||||||
/// would affect code generation, such as using symbols that are
|
/// code generation, such as using symbols that are only available beyond a
|
||||||
/// only available beyond a certain version. For checks which only
|
/// certain version. For checks which only depend on GTK's runtime behavior,
|
||||||
/// depend on GTK's runtime behavior, use `runtimeAtLeast`.
|
/// use `runtimeAtLeast`.
|
||||||
///
|
///
|
||||||
/// This is inlined so that the comptime checks will disable the
|
/// This is inlined so that the comptime checks will disable the runtime checks
|
||||||
/// runtime checks if the comptime checks fail.
|
/// if the comptime checks fail.
|
||||||
pub inline fn atLeast(
|
pub inline fn atLeast(
|
||||||
comptime major: u16,
|
comptime major: u16,
|
||||||
comptime minor: u16,
|
comptime minor: u16,
|
||||||
|
|
@ -34,25 +56,23 @@ pub inline fn atLeast(
|
||||||
return runtimeAtLeast(major, minor, micro);
|
return runtimeAtLeast(major, minor, micro);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies that the GTK version at runtime is at least the given
|
/// Verifies that the GTK version at runtime is at least the given version.
|
||||||
/// version.
|
|
||||||
///
|
///
|
||||||
/// This function should be used in cases where the only the runtime
|
/// This function should be used in cases where the only the runtime behavior
|
||||||
/// behavior is affected by the version check. For checks which would
|
/// is affected by the version check. For checks which would affect code
|
||||||
/// affect code generation, use `atLeast`.
|
/// generation, use `atLeast`.
|
||||||
pub inline fn runtimeAtLeast(
|
pub inline fn runtimeAtLeast(
|
||||||
comptime major: u16,
|
comptime major: u16,
|
||||||
comptime minor: u16,
|
comptime minor: u16,
|
||||||
comptime micro: u16,
|
comptime micro: u16,
|
||||||
) bool {
|
) bool {
|
||||||
// We use the functions instead of the constants such as
|
// We use the functions instead of the constants such as c.GTK_MINOR_VERSION
|
||||||
// c.GTK_MINOR_VERSION because the function gets the actual
|
// because the function gets the actual runtime version.
|
||||||
// runtime version.
|
if (gtk.getMajorVersion() >= major) {
|
||||||
if (c.gtk_get_major_version() >= major) {
|
if (gtk.getMajorVersion() > major) return true;
|
||||||
if (c.gtk_get_major_version() > major) return true;
|
if (gtk.getMinorVersion() >= minor) {
|
||||||
if (c.gtk_get_minor_version() >= minor) {
|
if (gtk.getMinorVersion() > minor) return true;
|
||||||
if (c.gtk_get_minor_version() > minor) return true;
|
return gtk.getMicroVersion() >= micro;
|
||||||
return c.gtk_get_micro_version() >= micro;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +80,6 @@ pub inline fn runtimeAtLeast(
|
||||||
}
|
}
|
||||||
|
|
||||||
test "atLeast" {
|
test "atLeast" {
|
||||||
const std = @import("std");
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
const funs = &.{ atLeast, runtimeAtLeast };
|
const funs = &.{ atLeast, runtimeAtLeast };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue