const std = @import("std"); const wayland = @import("zig-wayland/wayland.zig"); const zig_args = @import("zig-args"); const wl = wayland.client.wl; const xdg = wayland.client.xdg; const zxdg = wayland.client.zxdg; const zwlr = wayland.client.zwlr; const list = wayland.server.wl.list; const xkb = @cImport({ @cInclude("xkbcommon/xkbcommon.h"); }); const cairo = @cImport({ @cInclude("cairo/cairo.h"); }); const Point = struct { x: i32, y: i32, }; const Size = struct { const Self = @This(); width: u32, height: u32, fn size(self: *Self) usize { return self.width * self.height; } }; const Box = struct { const Self = @This(); position: Point, extents: Size, fn intersects(self: *const Self, other: *const Self) bool { const x1 = self.position.x; const y1 = self.position.y; const w1 = self.extents.width; const h1 = self.extents.height; const x2 = other.position.x; const y2 = other.position.y; const w2 = other.extents.width; const h2 = other.extents.height; // return self.x < other.x + other.width and // self.x + self.width > other.x and // self.y < other.y + other.height and // self.height + self.y > other.y; return x1 < x2 + w2 and x1 + w1 > x2 and y1 < y1 + h2 and y1 + h1 > y2; } fn contains(self: *Self, point: Point) bool { const x1 = self.position.x; const y1 = self.position.y; const w1 = self.extents.width; const h1 = self.extents.height; const x2 = point.x; const y2 = point.y; // return self.x <= point.x and self.x + self.width > point.x and // self.y <= point.y and self.y + self.height > point.y; return x1 <= x2 and x1 + w1 > x2 and y1 <= y2 and y1 + h1 > y2; } fn size(self: *Self) usize { return self.extents.size(); } }; fn ListItem(comptime T: type) type { return struct { const Self = @This(); link: list.Link, value: T, fn Head() type { return list.Head(Self, "link"); } fn fromLink(link: *list.Link) *Self { return @fieldParentPtr(Self, "link", link); } fn remove(self: *Self) void { self.link.remove(); } fn next(self: *Self) ?*list.Link { self.link.next(); } fn prev(self: *Self) ?*list.Link { self.link.prev(); } fn getLink(self: *Self) *list.Link { return &self.link; } fn get(self: *Self) *T { return &self.value; } fn getConst(self: *const Self) *const T { return &self.value; } }; } const Options = struct { const Self = @This(); help: bool = false, @"display-dimensions": bool = false, background: ?u32 = null, border: ?u32 = null, selection: ?u32 = null, choice: ?u32 = null, format: ?[]const u8 = null, @"font-family": ?[]const u8 = null, @"border-weight": ?u32 = null, @"single-point": bool = false, @"output-boxes": bool = false, @"restrict-selection": bool = false, // TODO: parse this in the format of W:H instead of a fraction @"aspect-ratio": ?f64 = null, pub const shorthands = .{ .h = "help", .b = "background", .c = "border", .s = "selection", .B = "choice", .f = "format", .F = "font-family", .w = "border-weight", .p = "single-point", .o = "output-boxes", .r = "restrict-selection", .a = "aspect-ratio", }; const usage = @embedFile("usage"); fn parse() !zig_args.ParseArgsResult(Self, null) { const self = try zig_args.parseForCurrentProcess(Self, std.heap.page_allocator, .print); // TODO: print help return self; } }; const Output = struct { const Self = @This(); state: *State, output: *wl.Output, geometry: Box, logical_geometry: Box, scale: i32, }; /// helper type to handle dynamic dispatch listeners whatever they're called fn Listener(comptime T: type, comptime D: type) type { return struct { const Self = @This(); const Fn = *const fn (obj: *T, event: T.Event, data: *D) void; data: *D, callback: Fn, fn create(data: *D, callback: Fn) Self { return Self{ .data = data, .callback = callback, }; } fn listener(obj: *T, event: T.Event, self: *Self) void { return self.callback(obj, event, self.data); } fn set(self: *Self, obj: *T) void { obj.setListener(*Self, listener, self); } fn cast( self: *Self, comptime D2: type, new_data: *D2, new_callback: @TypeOf(Listener(T, D2)).Fn, ) @TypeOf(Listener(T, D2)) { const other = @ptrCast(Listener(T, D2), self); other.new_data = new_data; other.callback = new_callback; return other; } }; } const State = struct { const Uninit = struct { const Self = @This(); ally: std.mem.Allocator = std.heap.c_allocator, dpy: *wl.Display = undefined, registry: *wl.Registry = undefined, registry_listener: Listener(wl.Registry, Self) = undefined, shm: ?*wl.Shm = null, compositor: ?*wl.Compositor = null, layer_shell: ?*zwlr.LayerShellV1 = null, xdg_output_manager: ?*zxdg.OutputManagerV1 = null, fn create() !*Self { const ally = std.heap.c_allocator; const dpy = try wl.Display.connect(null); const registry = try dpy.getRegistry(); const self = try ally.create(Self); const listener = .{ .data = self, .callback = registryListener, }; self.* = .{ .ally = ally, .dpy = dpy, .registry = registry, .registry_listener = listener, }; self.registry_listener.set(self.registry); if (dpy.roundtrip() != .SUCCESS) return error.RoundtripFailed; return self; } fn deinit(self: *Self) !void { self.ally.destroy(self); } fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, self: *Self) void { switch (event) { .global => |global| { std.debug.print("global: {s}\n", .{global.interface}); if (std.cstr.cmp(global.interface, wl.Compositor.getInterface().name) == 0) { self.compositor = registry.bind(global.name, wl.Compositor, 4) catch return; } else if (std.cstr.cmp(global.interface, wl.Shm.getInterface().name) == 0) { self.shm = registry.bind(global.name, wl.Shm, 1) catch return; } else if (std.cstr.cmp(global.interface, zwlr.LayerShellV1.getInterface().name) == 0) { self.layer_shell = registry.bind(global.name, zwlr.LayerShellV1, 1) catch return; } else if (std.cstr.cmp(global.interface, zxdg.OutputManagerV1.getInterface().name) == 0) { self.xdg_output_manager = registry.bind(global.name, zxdg.OutputManagerV1, 2) catch return; } else if (std.cstr.cmp(global.interface, wl.Seat.getInterface().name) == 0) { const seat = registry.bind(global.name, wl.Seat, 1) catch return; _ = seat; } else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) { const output = registry.bind(global.name, wl.Output, 3) catch return; _ = output; } }, .global_remove => {}, } } fn intoInit(self: *Self) !*Init { // free self at end of this function defer self.ally.destroy(self); const init = try self.ally.create(Init); // free allocated memory if not returning it errdefer self.ally.destroy(init); const xkb_context = xkb.xkb_context_new(xkb.XKB_CONTEXT_NO_FLAGS) orelse return error.NoXkbContext; // free xkb_context if we fail to initialize the state errdefer xkb.xkb_context_unref(xkb_context); const cursor_theme = std.process.getEnvVarOwned(self.ally, "XCURSOR_THEME") catch ""; // only free cursor_theme if bailing errdefer self.ally.free(cursor_theme); const buf = try std.process.getEnvVarOwned(self.ally, "XCURSOR_SIZE"); defer self.ally.free(buf); const cursor_size = std.fmt.parseInt(u32, buf, 10) catch return error.InvalidXCursorSize; init.* = Init{ .ally = self.ally, .dpy = self.dpy, .registry = self.registry, // these 4 fields are set by the registry listener so they may be null at this point // treat that as a hard error tho. .shm = self.shm orelse return error.NoWlShm, .compositor = self.compositor orelse return error.NoWlCompositor, .layer_shell = self.layer_shell orelse return error.NoWlrLayerShell, .xdg_output_manager = self.xdg_output_manager orelse return error.NoXdgOutputManager, // these 3 fields should never be null because init() will bail if these functions fail .xkb_context = xkb_context, .cursor_theme = cursor_theme, .cursor_size = cursor_size, }; init.outputs.init(); // i hope this is fine but appears to be requried to reset the listener on the registry init.registry_listener = self.registry_listener.cast(Init, init, Init.registryListener); return init; } }; const Init = struct { const Self = @This(); ally: std.mem.Allocator = std.heap.c_allocator, dpy: *wl.Display = undefined, registry: *wl.Registry = undefined, registry_listener: Listener(wl.Registry, Self) = undefined, shm: *wl.Shm = undefined, compositor: *wl.Compositor = undefined, layer_shell: *zwlr.LayerShellV1 = undefined, xdg_output_manager: *zxdg.OutputManagerV1 = undefined, xkb_context: *xkb.xkb_context = undefined, outputs: ListItem(Output).Head() = undefined, cursor_theme: []const u8 = undefined, cursor_size: u32 = undefined, fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, self: *Self) void { _ = registry; _ = self; std.debug.print("registryListener called after init", .{}); switch (event) { .global => {}, .global_remove => {}, } } fn deinit(self: *Self) void { defer self.ally.destroy(self); self.shm.destroy(); self.compositor.destroy(); self.layer_shell.destroy(); self.xdg_output_manager.destroy(); xkb.xkb_context_unref(self.xkb_context); self.registry.destroy(); self.ally.free(self.cursor_theme); } }; }; pub fn main() !void { // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); const state = try State.Uninit.create(); const init = try state.intoInit(); _ = try std.io.getStdIn().reader().readByte(); defer init.deinit(); }