gtk/x11: link directly to libX11, no more dlopen (#3857)

As a follow-up to #3477 and #3748, this eliminates the use of dlopen to
access `libX11` functions by directly linking `libX11` if X11 is
enabled. This should also fix problems with systems like NixOS and Void
Linux that have reported problems using Ghostty on X11 when using the
distribution packages.
This commit is contained in:
Mitchell Hashimoto
2024-12-29 07:02:28 -08:00
committed by GitHub
3 changed files with 14 additions and 51 deletions

View File

@ -1427,6 +1427,7 @@ fn addDeps(
.gtk => {
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
if (config.adwaita) step.linkSystemLibrary2("adwaita-1", dynamic_link_opts);
if (config.x11) step.linkSystemLibrary2("X11", dynamic_link_opts);
{
const gresource = @import("src/apprt/gtk/gresource.zig");

View File

@ -26,7 +26,7 @@
pandoc,
revision ? "dirty",
optimize ? "Debug",
x11 ? false,
x11 ? true,
}: let
# The Zig hook has no way to select the release type without actual
# overriding of the default flags.
@ -151,7 +151,7 @@ in
dontConfigure = true;
zigBuildFlags = "-Dversion-string=${finalAttrs.version}-${revision}-nix";
zigBuildFlags = "-Dversion-string=${finalAttrs.version}-${revision}-nix -Dgtk-x11=${lib.boolToString x11}";
preBuild = ''
rm -rf $ZIG_GLOBAL_CACHE_DIR
@ -190,10 +190,6 @@ in
echo "$vim" >> "$out/nix-support/propagated-user-env-packages"
'';
postFixup = lib.optionalString x11 ''
patchelf --add-rpath "${lib.makeLibraryPath [libX11]}" "$out/bin/.ghostty-wrapped"
'';
meta = {
homepage = "https://github.com/ghostty-org/ghostty";
license = lib.licenses.mit;

View File

@ -22,13 +22,14 @@ pub fn is_current_display_server() bool {
return is_display(display);
}
pub const Xkb = if (build_options.x11) struct {
pub const Xkb = struct {
base_event_code: c_int,
funcs: Funcs,
/// Initialize an Xkb struct, for the given GDK display. If the display
/// isn't backed by X then this will return null.
/// Initialize an Xkb struct for the given GDK display. If the display isn't
/// backed by X then this will return null.
pub fn init(display_: ?*c.GdkDisplay) !?Xkb {
if (comptime !build_options.x11) return null;
// Display should never be null but we just treat that as a non-X11
// display so that the caller can just ignore it and not unwrap it.
const display = display_ orelse return null;
@ -40,7 +41,6 @@ pub const Xkb = if (build_options.x11) struct {
const xdisplay = c.gdk_x11_display_get_xdisplay(display);
var result: Xkb = .{
.base_event_code = 0,
.funcs = try Funcs.init(),
};
log.debug("Xkb.init: running XkbQueryExtension", .{});
@ -48,7 +48,7 @@ pub const Xkb = if (build_options.x11) struct {
var base_error_code: c_int = 0;
var major = c.XkbMajorVersion;
var minor = c.XkbMinorVersion;
if (result.funcs.XkbQueryExtension(
if (c.XkbQueryExtension(
xdisplay,
&opcode,
&result.base_event_code,
@ -61,7 +61,7 @@ pub const Xkb = if (build_options.x11) struct {
}
log.debug("Xkb.init: running XkbSelectEventDetails", .{});
if (result.funcs.XkbSelectEventDetails(
if (c.XkbSelectEventDetails(
xdisplay,
c.XkbUseCoreKbd,
c.XkbStateNotify,
@ -86,15 +86,17 @@ pub const Xkb = if (build_options.x11) struct {
/// back to the standard GDK modifier state (this likely means the key
/// event did not result in a modifier change).
pub fn modifier_state_from_notify(self: Xkb, display_: ?*c.GdkDisplay) ?input.Mods {
if (comptime !build_options.x11) return null;
const display = display_ orelse return null;
// Shoutout to Mozilla for figuring out a clean way to do this, this is
// paraphrased from Firefox/Gecko in widget/gtk/nsGtkKeyUtils.cpp.
const xdisplay = c.gdk_x11_display_get_xdisplay(display);
if (self.funcs.XEventsQueued(xdisplay, c.QueuedAfterReading) == 0) return null;
if (c.XEventsQueued(xdisplay, c.QueuedAfterReading) == 0) return null;
var nextEvent: c.XEvent = undefined;
_ = self.funcs.XPeekEvent(xdisplay, &nextEvent);
_ = c.XPeekEvent(xdisplay, &nextEvent);
if (nextEvent.type != self.base_event_code) return null;
const xkb_event: *c.XkbEvent = @ptrCast(&nextEvent);
@ -114,40 +116,4 @@ pub const Xkb = if (build_options.x11) struct {
return mods;
}
} else struct {};
/// The functions that we load dynamically from libX11.so.
const Funcs = struct {
XkbQueryExtension: XkbQueryExtensionType,
XkbSelectEventDetails: XkbSelectEventDetailsType,
XEventsQueued: XEventsQueuedType,
XPeekEvent: XPeekEventType,
const XkbQueryExtensionType = *const fn (?*c.struct__XDisplay, [*c]c_int, [*c]c_int, [*c]c_int, [*c]c_int, [*c]c_int) callconv(.C) c_int;
const XkbSelectEventDetailsType = *const fn (?*c.struct__XDisplay, c_uint, c_uint, c_ulong, c_ulong) callconv(.C) c_int;
const XEventsQueuedType = *const fn (?*c.struct__XDisplay, c_int) callconv(.C) c_int;
const XPeekEventType = *const fn (?*c.struct__XDisplay, [*c]c.union__XEvent) callconv(.C) c_int;
pub fn init() !Funcs {
var libX11 = try std.DynLib.open("libX11.so");
defer libX11.close();
var result: Funcs = undefined;
inline for (@typeInfo(Funcs).Struct.fields) |field| {
const name = comptime name: {
const null_term = field.name ++ .{0};
break :name null_term[0..field.name.len :0];
};
@field(result, field.name) = libX11.lookup(
field.type,
name,
) orelse {
log.err(" error dynamic loading libX11: missing symbol {s}", .{field.name});
return error.XkbInitializationError;
};
}
return result;
}
};