mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-14 05:46:17 +00:00
gtk(wayland): prevent gtk4-layer-shell crash on old versions
Supersedes #7154 In gtk4-layer-shell versions < 1.0.4, the app could crash upon opening a quick terminal window on certain compositors that implement the `xdg_wm_dialog_v1` protocol. The exact reason is a bit complicated, but is nicely summarized in the upstream issue (wmww/gtk4-layer-shell#50). The circumstances that could cause this crash to occur should gradually diminish as distros update to newer gtk4-layer-shell versions, but this is known to crash on Fedora 41 and Hyprland, which could be a sizable chunk of our userbase given that this would also occur on GNOME/Mutter and KDE/KWin. The diff should be minimal enough that this can be removed or reverted once this band-aid fix is no longer necessary.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("gtk4-layer-shell.h");
|
@cInclude("gtk4-layer-shell.h");
|
||||||
});
|
});
|
||||||
@@ -31,6 +33,14 @@ pub fn getProtocolVersion() c_uint {
|
|||||||
return c.gtk_layer_get_protocol_version();
|
return c.gtk_layer_get_protocol_version();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getLibraryVersion() std.SemanticVersion {
|
||||||
|
return .{
|
||||||
|
.major = c.gtk_layer_get_major_version(),
|
||||||
|
.minor = c.gtk_layer_get_minor_version(),
|
||||||
|
.patch = c.gtk_layer_get_micro_version(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn initForWindow(window: *gtk.Window) void {
|
pub fn initForWindow(window: *gtk.Window) void {
|
||||||
c.gtk_layer_init_for_window(@ptrCast(window));
|
c.gtk_layer_init_for_window(@ptrCast(window));
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,19 @@ pub const App = struct {
|
|||||||
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
|
default_deco_mode: ?org.KdeKwinServerDecorationManager.Mode = null,
|
||||||
|
|
||||||
xdg_activation: ?*xdg.ActivationV1 = null,
|
xdg_activation: ?*xdg.ActivationV1 = null,
|
||||||
|
|
||||||
|
/// Whether the xdg_wm_dialog_v1 protocol is present.
|
||||||
|
///
|
||||||
|
/// If it is present, gtk4-layer-shell < 1.0.4 may crash when the user
|
||||||
|
/// creates a quick terminal, and we need to ensure this fails
|
||||||
|
/// gracefully if this situation occurs.
|
||||||
|
///
|
||||||
|
/// FIXME: This is a temporary workaround - we should remove this when
|
||||||
|
/// all of our supported distros drop support for affected old
|
||||||
|
/// gtk4-layer-shell versions.
|
||||||
|
///
|
||||||
|
/// See https://github.com/wmww/gtk4-layer-shell/issues/50
|
||||||
|
xdg_wm_dialog_present: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
@@ -95,11 +108,21 @@ pub const App = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supportsQuickTerminal(_: App) bool {
|
pub fn supportsQuickTerminal(self: App) bool {
|
||||||
if (!layer_shell.isSupported()) {
|
if (!layer_shell.isSupported()) {
|
||||||
log.warn("your compositor does not support the wlr-layer-shell protocol; disabling quick terminal", .{});
|
log.warn("your compositor does not support the wlr-layer-shell protocol; disabling quick terminal", .{});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.context.xdg_wm_dialog_present and layer_shell.getLibraryVersion().order(.{
|
||||||
|
.major = 1,
|
||||||
|
.minor = 0,
|
||||||
|
.patch = 4,
|
||||||
|
}) == .lt) {
|
||||||
|
log.warn("the version of gtk4-layer-shell installed on your system is too old (must be 1.0.4 or newer); disabling quick terminal", .{});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,26 +134,38 @@ pub const App = struct {
|
|||||||
layer_shell.setNamespace(window, "ghostty-quick-terminal");
|
layer_shell.setNamespace(window, "ghostty-quick-terminal");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getInterfaceType(comptime field: std.builtin.Type.StructField) ?type {
|
||||||
|
// Globals should be optional pointers
|
||||||
|
const T = switch (@typeInfo(field.type)) {
|
||||||
|
.optional => |o| switch (@typeInfo(o.child)) {
|
||||||
|
.pointer => |v| v.child,
|
||||||
|
else => return null,
|
||||||
|
},
|
||||||
|
else => return null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only process Wayland interfaces
|
||||||
|
if (!@hasDecl(T, "interface")) return null;
|
||||||
|
return T;
|
||||||
|
}
|
||||||
|
|
||||||
fn registryListener(
|
fn registryListener(
|
||||||
registry: *wl.Registry,
|
registry: *wl.Registry,
|
||||||
event: wl.Registry.Event,
|
event: wl.Registry.Event,
|
||||||
context: *Context,
|
context: *Context,
|
||||||
) void {
|
) void {
|
||||||
inline for (@typeInfo(Context).@"struct".fields) |field| {
|
const ctx_fields = @typeInfo(Context).@"struct".fields;
|
||||||
// Globals should be optional pointers
|
|
||||||
const T = switch (@typeInfo(field.type)) {
|
|
||||||
.optional => |o| switch (@typeInfo(o.child)) {
|
|
||||||
.pointer => |v| v.child,
|
|
||||||
else => continue,
|
|
||||||
},
|
|
||||||
else => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only process Wayland interfaces
|
|
||||||
if (!@hasDecl(T, "interface")) continue;
|
|
||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.global => |v| global: {
|
.global => |v| global: {
|
||||||
|
// We don't actually do anything with this other than checking
|
||||||
|
// for its existence, so we process this separately.
|
||||||
|
if (std.mem.orderZ(u8, v.interface, "xdg_wm_dialog_v1") == .eq)
|
||||||
|
context.xdg_wm_dialog_present = true;
|
||||||
|
|
||||||
|
inline for (ctx_fields) |field| {
|
||||||
|
const T = getInterfaceType(field) orelse continue;
|
||||||
|
|
||||||
if (std.mem.orderZ(
|
if (std.mem.orderZ(
|
||||||
u8,
|
u8,
|
||||||
v.interface,
|
v.interface,
|
||||||
@@ -148,19 +183,22 @@ pub const App = struct {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// This should be a rare occurrence, but in case a global
|
// This should be a rare occurrence, but in case a global
|
||||||
// is suddenly no longer available, we destroy and unset it
|
// is suddenly no longer available, we destroy and unset it
|
||||||
// as the protocol mandates.
|
// as the protocol mandates.
|
||||||
.global_remove => |v| remove: {
|
.global_remove => |v| remove: {
|
||||||
|
inline for (ctx_fields) |field| {
|
||||||
|
if (getInterfaceType(field) == null) continue;
|
||||||
const global = @field(context, field.name) orelse break :remove;
|
const global = @field(context, field.name) orelse break :remove;
|
||||||
if (global.getId() == v.name) {
|
if (global.getId() == v.name) {
|
||||||
global.destroy();
|
global.destroy();
|
||||||
@field(context, field.name) = null;
|
@field(context, field.name) = null;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user