core: get x11 to build

This commit is contained in:
Joshua Holmes 2024-10-07 13:00:46 -07:00 committed by Stephen Gutekanst
parent ac8c2f8f33
commit a791089967
2 changed files with 433 additions and 16 deletions

View file

@ -81,14 +81,14 @@ pub fn init(
// Try to initialize the desired backend, falling back to the other if that one is not supported // Try to initialize the desired backend, falling back to the other if that one is not supported
switch (desired_backend) { switch (desired_backend) {
.x11 => { .x11 => {
const x11 = X11.init(linux, core, options) catch |err| switch (err) { // const x11 = X11.init(linux, core, options) catch |err| switch (err) {
error.NotSupported => { // error.NotSupported => {
log.err("failed to initialize X11 backend, falling back to Wayland", .{}); // log.err("failed to initialize X11 backend, falling back to Wayland", .{});
linux.backend = .{ .wayland = try Wayland.init(linux, core, options) }; // linux.backend = .{ .wayland = try Wayland.init(linux, core, options) };
}, // },
else => return err, // else => return err,
}; // };
linux.backend = .{ .x11 = x11 }; // linux.backend = .{ .x11 = x11 };
}, },
.wayland => { .wayland => {
const wayland = Wayland.init(linux, core, options) catch |err| switch (err) { const wayland = Wayland.init(linux, core, options) catch |err| switch (err) {
@ -109,7 +109,9 @@ pub fn init(
.wayland => |be| { .wayland => |be| {
linux.surface_descriptor = .{ .next_in_chain = .{ .from_wayland_surface = be.surface_descriptor } }; linux.surface_descriptor = .{ .next_in_chain = .{ .from_wayland_surface = be.surface_descriptor } };
}, },
.x11 => {}, // TODO: setup surface descriptor .x11 => |be| {
linux.surface_descriptor = .{ .next_in_chain = .{ .from_xlib_window = be.surface_descriptor } };
},
} }
linux.refresh_rate = 60; // TODO: set to something meaningful linux.refresh_rate = 60; // TODO: set to something meaningful
@ -121,7 +123,7 @@ pub fn deinit(linux: *Linux) void {
if (linux.gamemode != null and linux.gamemode.?) deinitLinuxGamemode(); if (linux.gamemode != null and linux.gamemode.?) deinitLinuxGamemode();
switch (linux.backend) { switch (linux.backend) {
.wayland => linux.backend.wayland.deinit(linux), .wayland => linux.backend.wayland.deinit(linux),
.x11 => {}, // TODO: set to something meaningful .x11 => linux.backend.x11.deinit(linux),
} }
return; return;

View file

@ -2,24 +2,439 @@ const Linux = @import("../Linux.zig");
const Core = @import("../../Core.zig"); const Core = @import("../../Core.zig");
const InitOptions = Core.InitOptions; const InitOptions = Core.InitOptions;
const builtin = @import("builtin");
const std = @import("std");
const c = @cImport({
@cInclude("X11/Xlib.h");
@cInclude("X11/Xatom.h");
@cInclude("X11/cursorfont.h");
@cInclude("X11/Xcursor/Xcursor.h");
@cInclude("X11/extensions/Xrandr.h");
});
const mach = @import("../../main.zig");
const gpu = mach.gpu;
const Event = Core.Event;
const KeyEvent = Core.KeyEvent;
const MouseButtonEvent = Core.MouseButtonEvent;
const MouseButton = Core.MouseButton;
const Size = Core.Size;
const DisplayMode = Core.DisplayMode;
const CursorShape = Core.CursorShape;
const VSyncMode = Core.VSyncMode;
const CursorMode = Core.CursorMode;
const Key = Core.Key;
const KeyMods = Core.KeyMods;
const Joystick = Core.Joystick;
const Position = Core.Position;
const log = std.log.scoped(.mach);
pub const defaultLog = std.log.defaultLog;
pub const defaultPanic = std.debug.panicImpl;
pub const X11 = @This(); pub const X11 = @This();
allocator: std.mem.Allocator,
core: *Core,
libx11: LibX11,
libxrr: ?LibXRR,
libgl: ?LibGL,
libxcursor: ?LibXCursor,
gl_ctx: ?*LibGL.Context,
display: *c.Display,
width: c_int,
height: c_int,
empty_event_pipe: [2]std.c.fd_t,
wm_protocols: c.Atom,
wm_delete_window: c.Atom,
net_wm_ping: c.Atom,
net_wm_state_fullscreen: c.Atom,
net_wm_state: c.Atom,
net_wm_state_above: c.Atom,
net_wm_bypass_compositor: c.Atom,
motif_wm_hints: c.Atom,
net_wm_window_type: c.Atom,
net_wm_window_type_dock: c.Atom,
root_window: c.Window,
window: c.Window,
backend_type: gpu.BackendType,
refresh_rate: u32,
hidden_cursor: c.Cursor,
// Mutable fields only used by main thread
cursors: [@typeInfo(CursorShape).Enum.fields.len]?c.Cursor,
// Input state; written from main thread; read from any
input_mu: std.Thread.RwLock = .{},
// Mutable state fields; read/write by any thread
title: [:0]const u8,
display_mode: DisplayMode = .windowed,
vsync_mode: VSyncMode = .triple,
border: bool,
headless: bool,
size: Size,
cursor_mode: CursorMode = .normal,
cursor_shape: CursorShape = .arrow,
surface_descriptor: *gpu.Surface.DescriptorFromXlibWindow,
pub fn init( pub fn init(
linux: *Linux, linux: *Linux,
core: *Core.Mod, core: *Core.Mod,
options: InitOptions, options: InitOptions,
) !X11 { ) !X11 {
_ = linux;
_ = core; _ = core;
_ = options;
// TODO(core): return errors.NotSupported if not supported // TODO(core): return errors.NotSupported if not supported
return .{}; const libx11 = try LibX11.load();
const libgl: ?LibGL = LibGL.load() catch |err| switch (err) {
error.LibraryNotFound => null,
else => return err,
};
const libxcursor: ?LibXCursor = LibXCursor.load() catch |err| switch (err) {
error.LibraryNotFound => null,
else => return err,
};
const libxrr: ?LibXRR = LibXRR.load() catch |err| switch (err) {
error.LibraryNotFound => null,
else => return err,
};
const display = libx11.XOpenDisplay(null) orelse {
std.log.err("X11: Cannot open display", .{});
return error.CannotOpenDisplay;
};
const screen = c.DefaultScreen(display);
const visual = c.DefaultVisual(display, screen);
const root_window = c.RootWindow(display, screen);
const colormap = libx11.XCreateColormap(display, root_window, visual, c.AllocNone);
var set_window_attrs = c.XSetWindowAttributes{
.colormap = colormap,
// TODO: reduce
.event_mask = c.StructureNotifyMask | c.KeyPressMask | c.KeyReleaseMask |
c.PointerMotionMask | c.ButtonPressMask | c.ButtonReleaseMask |
c.ExposureMask | c.FocusChangeMask | c.VisibilityChangeMask |
c.EnterWindowMask | c.LeaveWindowMask | c.PropertyChangeMask,
};
const window = libx11.XCreateWindow(
display,
root_window,
@divFloor(libx11.XDisplayWidth(display, screen), 2), // TODO: add window width?
@divFloor(libx11.XDisplayHeight(display, screen), 2), // TODO: add window height?
options.size.width,
options.size.height,
0,
c.DefaultDepth(display, screen),
c.InputOutput,
visual,
c.CWColormap | c.CWEventMask,
&set_window_attrs,
);
var window_attrs: c.XWindowAttributes = undefined;
_ = libx11.XGetWindowAttributes(display, window, &window_attrs);
const window_size = Size{
.width = @intCast(window_attrs.width),
.height = @intCast(window_attrs.height),
};
const blank_pixmap = libx11.XCreatePixmap(display, window, 1, 1, 1);
var color = c.XColor{};
const refresh_rate: u16 = blk: {
if (libxrr != null) {
const conf = libxrr.?.XRRGetScreenInfo(display, root_window);
break :blk @intCast(libxrr.?.XRRConfigCurrentRate(conf));
}
break :blk 60;
};
const surface_descriptor = try options.allocator.create(gpu.Surface.DescriptorFromXlibWindow);
surface_descriptor.* = .{
.display = display,
.window = @intCast(window),
};
var x11 = X11{
.core = @fieldParentPtr("platform", linux),
.allocator = options.allocator,
.display = display,
.libx11 = libx11,
.libgl = libgl,
.libxcursor = libxcursor,
.libxrr = libxrr,
.empty_event_pipe = try std.posix.pipe(),
.gl_ctx = null,
.width = window_attrs.width,
.height = window_attrs.height,
.wm_protocols = libx11.XInternAtom(display, "WM_PROTOCOLS", c.False),
.wm_delete_window = libx11.XInternAtom(display, "WM_DELETE_WINDOW", c.False),
.net_wm_ping = libx11.XInternAtom(display, "NET_WM_PING", c.False),
.net_wm_state_fullscreen = libx11.XInternAtom(display, "_NET_WM_STATE_FULLSCREEN", c.False),
.net_wm_state = libx11.XInternAtom(display, "_NET_WM_STATE", c.False),
.net_wm_state_above = libx11.XInternAtom(display, "_NET_WM_STATE_ABOVE", c.False),
.net_wm_window_type = libx11.XInternAtom(display, "_NET_WM_WINDOW_TYPE", c.False),
.net_wm_window_type_dock = libx11.XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", c.False),
.net_wm_bypass_compositor = libx11.XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", c.False),
.motif_wm_hints = libx11.XInternAtom(display, "_MOTIF_WM_HINTS", c.False),
.root_window = root_window,
.window = window,
.hidden_cursor = libx11.XCreatePixmapCursor(display, blank_pixmap, blank_pixmap, &color, &color, 0, 0),
.backend_type = try Core.detectBackendType(options.allocator),
.refresh_rate = refresh_rate,
.title = options.title,
.display_mode = .windowed,
.border = options.border,
.headless = options.headless,
.size = window_size,
.cursors = std.mem.zeroes([@typeInfo(CursorShape).Enum.fields.len]?c.Cursor),
.surface_descriptor = surface_descriptor,
};
_ = libx11.XSetErrorHandler(errorHandler);
_ = libx11.XInitThreads();
_ = libx11.XrmInitialize();
defer _ = libx11.XFreeColormap(display, colormap);
for (0..2) |i| {
const sf = try std.posix.fcntl(x11.empty_event_pipe[i], std.posix.F.GETFL, 0);
const df = try std.posix.fcntl(x11.empty_event_pipe[i], std.posix.F.GETFD, 0);
_ = try std.posix.fcntl(x11.empty_event_pipe[i], std.posix.F.SETFL, sf | @as(u32, @bitCast(std.posix.O{ .NONBLOCK = true })));
_ = try std.posix.fcntl(x11.empty_event_pipe[i], std.posix.F.SETFD, df | std.posix.FD_CLOEXEC);
}
var protocols = [_]c.Atom{ x11.wm_delete_window, x11.net_wm_ping };
_ = libx11.XSetWMProtocols(x11.display, x11.window, &protocols, protocols.len);
_ = libx11.XStoreName(x11.display, x11.window, options.title.ptr);
_ = libx11.XSelectInput(x11.display, x11.window, set_window_attrs.event_mask);
_ = libx11.XMapWindow(x11.display, x11.window);
_ = libx11.XGetWindowAttributes(x11.display, x11.window, &window_attrs);
const backend_type = try Core.detectBackendType(options.allocator);
switch (backend_type) {
.opengl, .opengles => {
if (libgl != null) {
// zig fmt: off
const attrs = &[_]c_int{
LibGL.rgba,
LibGL.doublebuffer,
LibGL.depth_size, 24,
LibGL.stencil_size, 8,
LibGL.red_size, 8,
LibGL.green_size, 8,
LibGL.blue_size, 8,
LibGL.sample_buffers, 0,
LibGL.samples, 0,
c.None,
};
// zig fmt: on
const visual_info = libgl.?.glXChooseVisual(x11.display, screen, attrs.ptr);
defer _ = libx11.XFree(visual_info);
x11.gl_ctx = libgl.?.glXCreateContext(x11.display, visual_info, null, true);
_ = libgl.?.glXMakeCurrent(x11.display, x11.window, x11.gl_ctx);
} else {
return error.LibGLNotFound;
}
},
else => {},
}
// Create hidden cursor
const gc = libx11.XCreateGC(x11.display, blank_pixmap, 0, null);
if (gc != null) {
_ = libx11.XDrawPoint(x11.display, blank_pixmap, gc, 0, 0);
_ = libx11.XFreeGC(x11.display, gc);
}
// TODO: remove allocation
x11.cursors[@intFromEnum(CursorShape.arrow)] = try x11.createStandardCursor(.arrow);
return x11;
} }
pub fn deinit( pub fn deinit(
w: *X11, x11: *X11,
linux: *Linux, linux: *Linux,
) void { ) void {
_ = w; x11.allocator.destroy(x11.surface_descriptor);
_ = linux; linux.allocator.destroy(x11.surface_descriptor);
for (x11.cursors) |cur| {
if (cur) |_| {
// _ = x11.libx11.XFreeCursor(x11.display, cur.?);
}
}
if (x11.libxcursor) |*libxcursor| {
libxcursor.handle.close();
}
if (x11.libxrr) |*libxrr| {
libxrr.handle.close();
}
if (x11.libgl) |*libgl| {
if (x11.gl_ctx) |gl_ctx| {
libgl.glXDestroyContext(x11.display, gl_ctx);
}
libgl.handle.close();
}
_ = x11.libx11.XUnmapWindow(x11.display, x11.window);
_ = x11.libx11.XDestroyWindow(x11.display, x11.window);
_ = x11.libx11.XCloseDisplay(x11.display);
x11.libx11.handle.close();
std.posix.close(x11.empty_event_pipe[0]);
std.posix.close(x11.empty_event_pipe[1]);
}
const LibX11 = struct {
handle: std.DynLib,
XInitThreads: *const @TypeOf(c.XInitThreads),
XrmInitialize: *const @TypeOf(c.XrmInitialize),
XOpenDisplay: *const @TypeOf(c.XOpenDisplay),
XCloseDisplay: *const @TypeOf(c.XCloseDisplay),
XCreateWindow: *const @TypeOf(c.XCreateWindow),
XSelectInput: *const @TypeOf(c.XSelectInput),
XMapWindow: *const @TypeOf(c.XMapWindow),
XNextEvent: *const @TypeOf(c.XNextEvent),
XDisplayWidth: *const @TypeOf(c.XDisplayWidth),
XDisplayHeight: *const @TypeOf(c.XDisplayHeight),
XCreateColormap: *const @TypeOf(c.XCreateColormap),
XSetErrorHandler: *const @TypeOf(c.XSetErrorHandler),
XGetWindowAttributes: *const @TypeOf(c.XGetWindowAttributes),
XStoreName: *const @TypeOf(c.XStoreName),
XFreeColormap: *const @TypeOf(c.XFreeColormap),
XUnmapWindow: *const @TypeOf(c.XUnmapWindow),
XDestroyWindow: *const @TypeOf(c.XDestroyWindow),
XFlush: *const @TypeOf(c.XFlush),
XLookupString: *const @TypeOf(c.XLookupString),
XQueryPointer: *const @TypeOf(c.XQueryPointer),
XInternAtom: *const @TypeOf(c.XInternAtom),
XSendEvent: *const @TypeOf(c.XSendEvent),
XSetWMProtocols: *const @TypeOf(c.XSetWMProtocols),
XDefineCursor: *const @TypeOf(c.XDefineCursor),
XUndefineCursor: *const @TypeOf(c.XUndefineCursor),
XCreatePixmap: *const @TypeOf(c.XCreatePixmap),
XCreateGC: *const @TypeOf(c.XCreateGC),
XDrawPoint: *const @TypeOf(c.XDrawPoint),
XFreeGC: *const @TypeOf(c.XFreeGC),
XCreatePixmapCursor: *const @TypeOf(c.XCreatePixmapCursor),
XGrabPointer: *const @TypeOf(c.XGrabPointer),
XUngrabPointer: *const @TypeOf(c.XUngrabPointer),
XCreateFontCursor: *const @TypeOf(c.XCreateFontCursor),
XFreeCursor: *const @TypeOf(c.XFreeCursor),
XChangeProperty: *const @TypeOf(c.XChangeProperty),
XResizeWindow: *const @TypeOf(c.XResizeWindow),
XConfigureWindow: *const @TypeOf(c.XConfigureWindow),
XSetWMHints: *const @TypeOf(c.XSetWMHints),
XDeleteProperty: *const @TypeOf(c.XDeleteProperty),
XAllocSizeHints: *const @TypeOf(c.XAllocSizeHints),
XSetWMNormalHints: *const @TypeOf(c.XSetWMNormalHints),
XFree: *const @TypeOf(c.XFree),
pub fn load() !LibX11 {
var lib: LibX11 = undefined;
lib.handle = std.DynLib.open("libX11.so.6") catch return error.LibraryNotFound;
inline for (@typeInfo(LibX11).Struct.fields[1..]) |field| {
const name = std.fmt.comptimePrint("{s}\x00", .{field.name});
const name_z: [:0]const u8 = @ptrCast(name[0 .. name.len - 1]);
@field(lib, field.name) = lib.handle.lookup(field.type, name_z) orelse return error.SymbolLookup;
}
return lib;
}
};
const LibXCursor = struct {
handle: std.DynLib,
XcursorImageCreate: *const @TypeOf(c.XcursorImageCreate),
XcursorImageDestroy: *const @TypeOf(c.XcursorImageDestroy),
XcursorImageLoadCursor: *const @TypeOf(c.XcursorImageLoadCursor),
XcursorGetTheme: *const @TypeOf(c.XcursorGetTheme),
XcursorGetDefaultSize: *const @TypeOf(c.XcursorGetDefaultSize),
XcursorLibraryLoadImage: *const @TypeOf(c.XcursorLibraryLoadImage),
pub fn load() !LibXCursor {
var lib: LibXCursor = undefined;
lib.handle = std.DynLib.open("libXcursor.so.1") catch return error.LibraryNotFound;
inline for (@typeInfo(LibXCursor).Struct.fields[1..]) |field| {
const name = std.fmt.comptimePrint("{s}\x00", .{field.name});
const name_z: [:0]const u8 = @ptrCast(name[0 .. name.len - 1]);
@field(lib, field.name) = lib.handle.lookup(field.type, name_z) orelse return error.SymbolLookup;
}
return lib;
}
};
const LibXRR = struct {
handle: std.DynLib,
XRRGetScreenInfo: *const @TypeOf(c.XRRGetScreenInfo),
XRRConfigCurrentRate: *const @TypeOf(c.XRRConfigCurrentRate),
pub fn load() !LibXRR {
var lib: LibXRR = undefined;
lib.handle = std.DynLib.open("libXrandr.so.1") catch return error.LibraryNotFound;
inline for (@typeInfo(LibXRR).Struct.fields[1..]) |field| {
const name = std.fmt.comptimePrint("{s}\x00", .{field.name});
const name_z: [:0]const u8 = @ptrCast(name[0 .. name.len - 1]);
@field(lib, field.name) = lib.handle.lookup(field.type, name_z) orelse return error.SymbolLookup;
}
return lib;
}
};
const LibGL = struct {
const Drawable = c.XID;
const Context = opaque {};
const FBConfig = opaque {};
const rgba = 4;
const doublebuffer = 5;
const red_size = 8;
const green_size = 9;
const blue_size = 10;
const depth_size = 12;
const stencil_size = 13;
const sample_buffers = 0x186a0;
const samples = 0x186a1;
handle: std.DynLib,
glXCreateContext: *const fn (*c.Display, *c.XVisualInfo, ?*Context, bool) callconv(.C) ?*Context,
glXDestroyContext: *const fn (*c.Display, ?*Context) callconv(.C) void,
glXMakeCurrent: *const fn (*c.Display, Drawable, ?*Context) callconv(.C) bool,
glXChooseVisual: *const fn (*c.Display, c_int, [*]const c_int) callconv(.C) *c.XVisualInfo,
glXSwapBuffers: *const fn (*c.Display, Drawable) callconv(.C) bool,
pub fn load() !LibGL {
var lib: LibGL = undefined;
lib.handle = std.DynLib.open("libGL.so.1") catch return error.LibraryNotFound;
inline for (@typeInfo(LibGL).Struct.fields[1..]) |field| {
const name = std.fmt.comptimePrint("{s}\x00", .{field.name});
const name_z: [:0]const u8 = @ptrCast(name[0 .. name.len - 1]);
@field(lib, field.name) = lib.handle.lookup(field.type, name_z) orelse return error.SymbolLookup;
}
return lib;
}
};
fn errorHandler(display: ?*c.Display, event: [*c]c.XErrorEvent) callconv(.C) c_int {
_ = display;
log.err("X11: error code {d}\n", .{event.*.error_code});
return 0;
}
fn createStandardCursor(x11: *X11, shape: CursorShape) !c.Cursor {
if (x11.libxcursor) |libxcursor| {
const theme = libxcursor.XcursorGetTheme(x11.display);
if (theme != null) {
const name = switch (shape) {
.arrow => "default",
.ibeam => "text",
.crosshair => "crosshair",
.pointing_hand => "pointer",
.resize_ew => "ew-resize",
.resize_ns => "ns-resize",
.resize_nwse => "nwse-resize",
.resize_nesw => "nesw-resize",
.resize_all => "all-scroll",
.not_allowed => "not-allowed",
};
const cursor_size = libxcursor.XcursorGetDefaultSize(x11.display);
const image = libxcursor.XcursorLibraryLoadImage(name, theme, cursor_size);
defer libxcursor.XcursorImageDestroy(image);
if (image != null) {
return libxcursor.XcursorImageLoadCursor(x11.display, image);
}
}
}
const xc: c_uint = switch (shape) {
.arrow => c.XC_left_ptr,
.ibeam => c.XC_xterm,
.crosshair => c.XC_crosshair,
.pointing_hand => c.XC_hand2,
.resize_ew => c.XC_sb_h_double_arrow,
.resize_ns => c.XC_sb_v_double_arrow,
.resize_nwse => c.XC_sb_h_double_arrow,
.resize_nesw => c.XC_sb_h_double_arrow,
.resize_all => c.XC_fleur,
.not_allowed => c.XC_X_cursor,
};
const cursor = x11.libx11.XCreateFontCursor(x11.display, xc);
if (cursor == 0) return error.FailedToCreateCursor;
return cursor;
} }