zlurp/main.zig
2022-12-15 18:24:18 +01:00

329 lines
9.6 KiB
Zig

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 {
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 State = struct {
const Uninit = struct {
const Self = @This();
ally: std.mem.Allocator = std.heap.c_allocator,
dpy: *wl.Display = undefined,
registry: *wl.Registry = 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);
self.* = .{
.ally = ally,
.dpy = dpy,
.registry = registry,
};
registry.setListener(*Self, registryListener, self);
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| {
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;
}
},
.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;
// i hope this is fine but appears to be requried to reset the listener on the registry
self.registry.destroy();
const registry = try self.dpy.getRegistry();
init.* = Init {
.ally = self.ally,
.dpy = self.dpy,
.registry = 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.registry.setListener(*Init, Init.registryListener, init);
return init;
}
};
const Init = struct {
const Self = @This();
ally: std.mem.Allocator = std.heap.c_allocator,
dpy: *wl.Display = undefined,
registry: *wl.Registry = 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,
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();
}