const std = @import("std"); pub const c = @cImport({ @cInclude("X11/Xlib.h"); @cInclude("X11/Xutil.h"); @cInclude("X11/extensions/XShm.h"); @cInclude("sys/shm.h"); @cInclude("GL/glx.h"); }); pub const Atom = c.Atom; fn from(x: bool) c.Bool { return if (x) c.True else c.False; } pub const GraphicsContext = struct { const Shape = enum(c_int) { complex = c.Complex, convex = c.Convex, non_convex = c.Nonconvex, }; const Mode = enum(c_int) { coord_origin = c.CoordModeOrigin, coord_previous = c.CoordModePrevious, }; display: *Display, window: Window, gc: c.GC, pub fn drawLine(ctx: GraphicsContext, p1: @Vector(2, c_int), p2: @Vector(2, c_int)) void { _ = c.XDrawLine(@ptrCast(ctx.display), ctx.window.id, ctx.gc, p1[0], p1[1], p2[0], p2[1]); } pub fn fillRectangle(ctx: GraphicsContext, pos: @Vector(2, c_int), size: @Vector(2, c_uint)) void { _ = c.XFillRectangle(@ptrCast(ctx.display), ctx.window.id, ctx.gc, pos[0], pos[1], size[0], size[1]); } pub fn fillPolygon(ctx: GraphicsContext, points: []@Vector(2, c_short), shape: Shape, mode: Mode) void { _ = c.XFillPolygon( @ptrCast(ctx.display), ctx.window.id, ctx.gc, @ptrCast(points.ptr), @intCast(points.len), @intFromEnum(shape), @intFromEnum(mode), ); } pub fn drawString(ctx: GraphicsContext, pos: @Vector(2, c_int), string: []const u8) void { _ = c.XDrawString( @ptrCast(ctx.display), ctx.window.id, ctx.gc, pos[0], pos[1], string.ptr, @intCast(string.len), ); } pub fn setBackground(ctx: GraphicsContext, color: c_ulong) void { _ = c.XSetBackground(@ptrCast(ctx.display), ctx.gc, color); } pub fn setForeground(ctx: GraphicsContext, color: c_ulong) void { _ = c.XSetForeground(@ptrCast(ctx.display), ctx.gc, color); } pub fn free(ctx: GraphicsContext) void { _ = c.XFreeGC(@ptrCast(ctx.display), ctx.gc); } }; pub const Display = opaque { pub fn open() !*Display { var display: *c.Display = undefined; display = c.XOpenDisplay(null) orelse return error.FailedToOpenWindow; return @ptrCast(display); } pub fn close(display: *Display) void { _ = c.XCloseDisplay(@ptrCast(display)); } pub fn selectInput(display: *Display, window: Window, event_mask: c_long) void { _ = c.XSelectInput(@ptrCast(display), window.id, event_mask); } pub fn mapWindow(display: *Display, window: Window) void { _ = c.XMapWindow(@ptrCast(display), window.id); } pub fn createGc(display: *Display, window: Window, value_mask: c_ulong, values: [*c]c.XGCValues) GraphicsContext { return .{ .display = display, .window = window, .gc = c.XCreateGC(@ptrCast(display), window.id, value_mask, values), }; } pub fn internAtom(display: *Display, atom_name: [:0]const u8, only_if_existds: bool) Atom { return c.XInternAtom(@ptrCast(display), atom_name.ptr, from(only_if_existds)); } pub fn setWmProtocols(display: *Display, window: Window, protocols: []Atom) void { _ = c.XSetWMProtocols(@ptrCast(display), window.id, protocols.ptr, @intCast(protocols.len)); } pub fn pending(display: *Display) c_int { return c.XPending(@ptrCast(display)); } pub fn nextEvent(display: *Display) c.XEvent { var xevent: c.XEvent = undefined; _ = c.XNextEvent(@ptrCast(display), &xevent); return xevent; } pub fn peekEvent(display: *Display) c.XEvent { var xevent: c.XEvent = undefined; _ = c.XPeekEvent(@ptrCast(display), &xevent); return xevent; } pub fn displayKeycodes(display: *Display, min_keycode: *c_int, max_keycode: *c_int) void { _ = c.XDisplayKeycodes(@ptrCast(display), min_keycode, max_keycode); } pub fn grabPointer( display: *Display, window: Window, owner_events: bool, event_mask: c_uint, pointer_mode: c_int, keyboard_mode: c_int, confine_to: Window, cursor: c.Cursor, time: c.Time, ) void { _ = c.XGrabPointer( @ptrCast(display), window.id, from(owner_events), event_mask, pointer_mode, keyboard_mode, confine_to.id, cursor, time, ); } pub fn defaultDepth(display: *Display, screen: Screen) c_int { return c.DefaultDepth(@as(*c.Display, @ptrCast(display)), screen.id); } pub fn defaultVisual(display: *Display, screen: Screen) *c.Visual { return c.DefaultVisual(@as(*c.Display, @ptrCast(display)), screen.id) orelse @panic("unreachable?"); } pub fn defaultGC(display: *Display, screen: Screen) c.GC { return c.DefaultGC(@as(*c.Display, @ptrCast(display)), screen.id) orelse @panic("unreachable?"); } pub fn blackPixel(display: *Display, screen: Screen) c_ulong { return c.BlackPixel(display, screen.id); } pub fn whitePixel(display: *Display, screen: Screen) c_ulong { return c.WhitePixel(display, screen.id); } // --- GLX --- pub fn glChooseVisual(display: *Display, screen: Screen, attrib_list: [:0]const c_int) *c.XVisualInfo { return c.glXChooseVisual(@ptrCast(display), screen.id, @constCast(attrib_list.ptr)) orelse @panic("TODO"); } pub fn glCreateContext( display: *Display, // vi: *c.XVisualInfo, fb_config: c.GLXFBConfig, share_list: c.GLXContext, direct: bool, context_attribs: []c_int, ) c.GLXContext { const GlXCreateContextAttribsARBProc = *const fn ( *c.Display, c.GLXFBConfig, share_list: c.GLXContext, c.Bool, [*c]c_int, ) callconv(.C) c.GLXContext; const glXCreateContextAttribsARBProc = @as( GlXCreateContextAttribsARBProc, @ptrCast(c.glXGetProcAddressARB(@ptrCast("glXCreateContextAttribsARB".ptr))), ); return glXCreateContextAttribsARBProc(@ptrCast(display), fb_config, share_list, from(direct), context_attribs.ptr); // return c.glXCreateContext(@ptrCast(display), vi, share_list, from(direct)); } pub fn glDestroyContext(display: *Display, glc: c.GLXContext) void { c.glXDestroyContext(@ptrCast(display), glc); } pub fn glMakeCurrent(display: *Display, window: Window, glc: c.GLXContext) void { _ = c.glXMakeCurrent(@ptrCast(display), window.id, glc); } pub fn glSwapBuffers(display: *Display, window: Window) void { c.glXSwapBuffers(@ptrCast(display), window.id); } pub fn glSwapInterval(display: *Display, drawable: anytype, interval: c_int) void { const T = @TypeOf(drawable); std.debug.assert(T == Window); // TODO internal.glSwapInterval(display, drawable.id, interval); } }; pub const Screen = struct { id: c_int, pub fn default(display: *Display) Screen { return .{ .id = c.DefaultScreen(@as(*c.Display, @ptrCast(display))) }; } }; pub const Window = struct { id: c.Window, pub fn root(display: *Display, screen: Screen) Window { return .{ .id = c.XRootWindow(@ptrCast(display), screen.id) }; } pub fn createSimple(args: struct { display: *Display, parent: union(enum) { window: Window, root: Screen }, pos: @Vector(2, c_int), size: @Vector(2, c_uint), border_width: c_uint, border: c_ulong, background: c_ulong, }) !Window { const parent = switch (args.parent) { .window => |wnd| wnd, .root => |screen| root(args.display, screen), }; const window = .{ .id = c.XCreateSimpleWindow( @ptrCast(args.display), parent.id, args.pos[0], args.pos[1], args.size[0], args.size[1], args.border_width, args.border, args.background, ) }; if (window.id == c.None) return error.FailedToCreateWindow; return window; } pub fn create(args: struct { display: *Display, parent: union(enum) { window: Window, root: Screen }, pos: @Vector(2, c_int), size: @Vector(2, c_uint), border_width: c_uint = 0, depth: c_int, class: c_uint = c.InputOutput, visual: *c.Visual, value_mask: c_ulong, attributes: *c.XSetWindowAttributes, }) !Window { const parent = switch (args.parent) { .window => |wnd| wnd, .root => |screen| root(args.display, screen), }; const window = Window{ .id = c.XCreateWindow( @ptrCast(args.display), parent.id, args.pos[0], args.pos[1], args.size[0], args.size[1], args.border_width, args.depth, args.class, args.visual, args.value_mask, args.attributes, ) }; if (window.id == c.None) return error.FailedToCreateWindow; return window; } pub fn destroy(window: Window, display: *Display) void { _ = c.XDestroyWindow(@ptrCast(display), window.id); } }; pub fn initGl() !void { // TODO glXGetProcAddress returns non-null even when procname doesn't exist. Might want to handle that. const GlInternal = @import("root").gl.internal; inline for (comptime std.meta.declarations(GlInternal)) |field| { comptime var proc_name: [field.name.len + 2:0]u8 = undefined; comptime { proc_name[0] = 'g'; proc_name[1] = 'l'; for (field.name, proc_name[2..]) |field_c, *proc_c| { proc_c.* = field_c; } proc_name[2] = std.ascii.toUpper(proc_name[2]); } const workaround = proc_name; @field(GlInternal, field.name) = @ptrCast(c.glXGetProcAddress(&workaround) orelse return error.GlGetProcAddressFailed); } //TODO(steven) check if extension available first internal.glSwapInterval = @ptrCast(c.glXGetProcAddress("glXSwapIntervalEXT") orelse return error.GlGetProcAddressFailed); @import("root").gl.is_initialized = true; } pub const internal = struct { pub var glSwapInterval: *const fn (display: *Display, drawable: c.GLXDrawable, interval: c_int) callconv(.C) void = undefined; };