diff options
| author | Steven Van Dorp <steven@vandorp.lu> | 2026-02-06 10:00:08 +0100 |
|---|---|---|
| committer | Steven Van Dorp <steven@vandorp.lu> | 2026-02-06 10:00:08 +0100 |
| commit | 0d8aeb6464103ec8e35c10c448bf08b0b9edc156 (patch) | |
| tree | 80ab47bf3e0f137856d8494753e774c8456c85c8 /src/x11.zig | |
Diffstat (limited to 'src/x11.zig')
| -rw-r--r-- | src/x11.zig | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/src/x11.zig b/src/x11.zig new file mode 100644 index 0000000..e348b35 --- /dev/null +++ b/src/x11.zig @@ -0,0 +1,331 @@ +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; +}; + |
