summaryrefslogtreecommitdiff
path: root/src/x11.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/x11.zig')
-rw-r--r--src/x11.zig331
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;
+};
+