440 lines
17 KiB
Zig
440 lines
17 KiB
Zig
const Linux = @import("../Linux.zig");
|
|
const Core = @import("../../Core.zig");
|
|
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();
|
|
|
|
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(
|
|
linux: *Linux,
|
|
core: *Core.Mod,
|
|
options: InitOptions,
|
|
) !X11 {
|
|
_ = core;
|
|
|
|
// TODO(core): return errors.NotSupported if not supported
|
|
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(
|
|
x11: *X11,
|
|
linux: *Linux,
|
|
) void {
|
|
x11.allocator.destroy(x11.surface_descriptor);
|
|
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;
|
|
}
|