zlurp/main.zig
2022-12-17 17:15:27 +01:00

1347 lines
41 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 {
const Self = @This();
x: i32 = 0,
y: i32 = 0,
fn inverted(self: *const Self) Self {
return Self{
.x = self.x * -1,
.y = self.y * -1,
};
}
fn add(self: *Self, other: Self) void {
self.x += other.x;
self.y += other.y;
}
fn added(self: *const Self, other: Self) Self {
var new = self.*;
new.add(other);
return new;
}
};
const Size = struct {
const Self = @This();
width: u32 = 0,
height: u32 = 0,
fn size(self: *Self) usize {
return self.width * self.height;
}
};
const Selection = struct {
const Self = @This();
start: Point,
end: Point,
fn fromPoint(point: Point) Self {
return .{
.start = point,
.end = point,
};
}
fn asBox(self: Self) Box {
return Box.fromCorners(self.start, self.end);
}
};
const Buffer = struct {
const Self = @This();
buffer: *wl.Buffer = undefined,
cairo: struct {
surface: *Cairo.cairo_surface_t = undefined,
ctx: *Cairo.cairo_t = undefined,
} = .{},
size: Size = .{},
data: []u8 = undefined,
in_use: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),
fn randomName() [13]u8 {
var name = [_]u8{'/', 'z', 'l', 'u','r','p','-','0','0','0','0','0','0'};
var rng = std.rand.DefaultPrng.init(@intCast(u64, std.time.milliTimestamp()));
for (name[7..]) |*c| {
c.* = rng.random().intRangeAtMost(u8, 'A', 'Z');
}
return name;
}
fn init(self: *Self, shm: *wl.Shm, size: Size) !void {
const name = randomName();
const stride = size.width * 4;
const len = stride * size.height;
const fd = try std.os.memfd_create(&name, 0);
defer std.os.close(fd);
try std.os.ftruncate(fd, len);
const data = try std.os.mmap(
null,
len,
std.os.PROT.READ | std.os.PROT.WRITE,
std.os.MAP.SHARED,
fd,
0,
);
const pool = try shm.createPool(fd, @intCast(i32, len));
defer pool.destroy();
const buffer = try pool.createBuffer(
0,
@intCast(i32, size.width),
@intCast(i32, size.height),
@intCast(i32, stride),
.argb8888,
);
const surface = Cairo.cairo_image_surface_create_for_data(
@ptrCast([*c]u8, data),
Cairo.CAIRO_FORMAT_ARGB32,
@intCast(c_int, size.width),
@intCast(c_int, size.height),
@intCast(c_int, stride),
) orelse return error.NoCairoSurface;
const cairo_ctx = Cairo.cairo_create(surface) orelse return error.NoCairoContext;
self.* = .{
.buffer = buffer,
.size = size,
.data = data,
.cairo = .{
.surface = surface,
.ctx = cairo_ctx,
},
};
self.buffer.setListener(*Self, bufferListener, self);
}
fn deinit(self: *Self) void {
std.os.munmap(@alignCast(0x1000, self.data));
self.buffer.destroy();
Cairo.cairo_destroy(self.cairo.ctx);
Cairo.cairo_surface_destroy(self.cairo.surface);
}
fn bufferListener(buffer: *wl.Buffer, event: wl.Buffer.Event, self: *Self) void {
_ = buffer;
switch (event) {
.release => {
self.in_use.store(false, .Monotonic);
},
}
}
};
const Buffers = struct {
const Self = @This();
state: *State.Init,
buffers: [2]?Buffer = undefined,
fn init(self: *Self, state: *State.Init) !void {
self.* = Self{
.state = state,
};
for (self.buffers) |*buffer| {
buffer.* = null;
}
}
// returns any already initialized and not currently used buffer
fn getAvailableBuffer(self: *Self, size: Size) ?*Buffer {
for (self.buffers) |*buffer| {
if (buffer.*) |*buf| {
if (!buf.in_use.load(.Monotonic)) {
if (buf.size.width != size.width or buf.size.height != size.height) {
buf.deinit();
buf.* = .{};
buf.init(self.state.shm, size) catch {
buffer.* = null;
continue;
};
}
return buf;
}
}
}
for (self.buffers) |*buffer| {
if (buffer.* == null) {
buffer.* = .{};
buffer.*.?.init(self.state.shm, size) catch {
buffer.* = null;
continue;
};
return &buffer.*.?;
}
}
return null;
}
};
test "buffers" {
std.testing.refAllDecls(Buffers);
}
const Box = struct {
const Self = @This();
position: Point = .{.x = 0, .y = 0,},
extents: Size = .{.width = 0, .height = 0,},
fn default() Self {
return Self {
.position = .{.x = 0, .y = 0,},
.extents = .{.width = 0, .height = 0,},
};
}
fn intersects(self: *const Self, other: *const Self) bool {
const x1 = self.position.x;
const y1 = self.position.y;
const w1 = @intCast(isize, self.extents.width);
const h1 = @intCast(isize, self.extents.height);
const x2 = other.position.x;
const y2 = other.position.y;
const w2 = @intCast(isize, other.extents.width);
const h2 = @intCast(isize, 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 fromCorners(a: Point, b: Point) Self {
const top = @min(a.y, b.y);
const bot = @max(a.y, b.y);
const left = @min(a.x, b.x);
const right = @max(a.x, b.x);
const pos = Point{.x = left, .y = top,};
const extents = Size{
.width = @intCast(u32, right - left),
.height = @intCast(u32, bot - top),
};
return Self {
.position = pos,
.extents = extents,
};
}
fn getInner(comptime T: anytype, v: anytype) T {
switch (@typeInfo(T)) {
.Float => {
return @intToFloat(T, v);
},
.Int => {
return @intCast(T, v);
},
else => {
@compileError("can only cast positional scalar to integer or floating point types");
},
}
}
fn getX(self: *const Self, comptime T: anytype) T {
return getInner(T, self.position.x);
}
fn getY(self: *const Self, comptime T: anytype) T {
return getInner(T, self.position.y);
}
fn getWidth(self: *const Self, comptime T: anytype) T {
return getInner(T, self.extents.width);
}
fn getHeight(self: *const Self, comptime T: anytype) T {
return getInner(T, self.extents.height);
}
fn translate(self: *Self, delta: Point) void {
self.position.add(delta);
}
fn translated(self: Self, delta: Point) Self {
var new = self;
new.translate(delta);
return new;
}
fn contains(self: *const Self, point: Point) bool {
const x1 = self.position.x;
const y1 = self.position.y;
const w1 = @intCast(isize, self.extents.width);
const h1 = @intCast(isize, 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();
}
};
test "box" {
const a = Box.default();
const a2 = Box{.extents = .{.width = 1, .height = 1,}};
const b = Box.default();
const b2 = Box{.extents = .{.width = 1, .height = 1,}};
std.debug.assert(!a.contains(.{.x = 0, .y = 0}));
std.debug.assert(!a.intersects(&b));
std.debug.assert(a2.contains(.{.x = 0, .y = 0}));
std.debug.assert(a2.intersects(&b2));
const c1 = Box{
.position = .{.x = 1, .y = 1,},
.extents = .{.width = 2, .height = 2,},
};
const c2 = Box{
.position = .{.x = 1, .y = 1,},
.extents = .{.width = 1, .height = 1,},
};
std.debug.assert(!c1.intersects(&a2));
std.debug.assert(c1.intersects(&c2));
}
fn ListItem(comptime T: type) type {
return struct {
const Self = @This();
link: list.Link,
value: T = undefined,
fn create(ally: std.mem.Allocator, value: T) !*Self {
const self = try ally.create(Self);
self.init(value);
return self;
}
fn init(self: *Self, value: T) void {
self.value = value;
}
fn Head() type {
return list.Head(Self, "link");
}
fn fromLink(link: *list.Link) *Self {
return @fieldParentPtr(Self, "link", link);
}
fn fromValue(value: *T) *Self {
return @fieldParentPtr(Self, "value", value);
}
/// automatically calls `deinit()` on `data` if a function with that name exists
fn remove(self: *Self) void {
if (@hasDecl(T, "deinit")) {
self.value.deinit();
}
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.Init = undefined,
output: *wl.Output,
surface: *wl.Surface = undefined,
layer_surface: *zwlr.LayerSurfaceV1 = undefined,
xdg_output: *zxdg.OutputV1 = undefined,
geometry: Box = Box.default(),
logical_geometry: Box = Box.default(),
scale: i32 = 1,
size: Size = Size{},
configured: bool = false,
needs_redraw: bool = false,
buffers: Buffers = undefined,
frame_callback: ?*wl.Callback = null,
fn init(self: *Self, state: *State.Init) !void {
std.debug.print("output.init()\n", .{});
self.state = state;
try self.buffers.init(state);
self.output.setListener(*Self, outputListener, self);
self.surface = try state.compositor.createSurface();
self.layer_surface = try state.layer_shell.getLayerSurface(
self.surface,
self.output,
.overlay,
"selection",
);
self.layer_surface.setListener(*Self, layerSurfaceListener, self);
self.xdg_output = try state.xdg_output_manager.getXdgOutput(self.output);
self.xdg_output.setListener(*Self, xdgOutputListener, self);
self.layer_surface.setAnchor(.{.top = true, .left = true, .right = true, .bottom = true,});
self.layer_surface.setKeyboardInteractivity(1);
self.layer_surface.setExclusiveZone(-1);
self.surface.commit();
}
fn deinit(self: *Self) void {
self.output.destroy();
}
fn setSourceU32(c: *Cairo.cairo_t, color: u32) void {
Cairo.cairo_set_source_rgba(
c,
@intToFloat(f32, color >> (3 * 8) & 0xff) / 255.0,
@intToFloat(f32, color >> (2 * 8) & 0xff) / 255.0,
@intToFloat(f32, color >> (1 * 8) & 0xff) / 255.0,
@intToFloat(f32, color >> (0 * 8) & 0xff) / 255.0,
);
}
fn drawRect(buffer: *Buffer, box: Box, color: u32) void {
const cairo = buffer.cairo.ctx;
setSourceU32(cairo, color);
Cairo.cairo_rectangle(
cairo,
box.getX(f64),
box.getY(f64),
box.getWidth(f64),
box.getHeight(f64),
);
}
fn render(self: *Self, buffer: *Buffer) void {
const cairo = &buffer.cairo;
Cairo.cairo_set_operator(cairo.ctx, Cairo.CAIRO_OPERATOR_SOURCE);
setSourceU32(cairo.ctx, 0xFFFFFF40);
Cairo.cairo_paint(cairo.ctx);
var seat_iter = self.state.seats.iterator(.forward);
while (seat_iter.next()) |link| {
const seat = link.get();
if (seat.pointer == null) {
continue;
}
if (seat.pointer.?.selection.getSelectionBox()) |box| {
// draw inner box
const corrected_box = box.translated(self.logical_geometry.position.inverted());
std.debug.print("selection: {}\n", .{corrected_box});
drawRect(buffer, corrected_box, 0x0);
Cairo.cairo_fill(cairo.ctx);
Cairo.cairo_set_line_width(cairo.ctx, 2.0);
drawRect(buffer, corrected_box, 0x000000FF);
Cairo.cairo_stroke(cairo.ctx);
// TODO: draw dimensions
}
}
}
fn commitFrame(self: *Self) void {
const size = Size{
.width = self.size.width * @intCast(u32, self.scale),
.height = self.size.height * @intCast(u32, self.scale),
};
// FIXME: maybe somehow make this retry to get another buffer if the one
// that was returned has just become in-use?
if (self.buffers.getAvailableBuffer(size)) |buffer| {
if (buffer.in_use.compareAndSwap(false, true, .Release, .Monotonic) == null) {
Cairo.cairo_identity_matrix(buffer.cairo.ctx);
Cairo.cairo_scale(
buffer.cairo.ctx,
@intToFloat(f64, self.scale),
@intToFloat(f64, self.scale),
);
// RENDER
self.render(buffer);
if (self.frame_callback == null) {
self.frame_callback = self.surface.frame() catch null;
if (self.frame_callback) |frame| {
frame.setListener(*Self, frameCallbackListener, self);
}
}
self.surface.attach(buffer.buffer, 0, 0);
self.surface.damage(0, 0,
@intCast(i32, self.size.width),
@intCast(i32, self.size.height),
);
self.surface.setBufferScale(self.scale);
self.surface.commit();
self.needs_redraw = false;
} else {
std.debug.print("failed to CAS in_use.\n", .{});
}
} else {
std.debug.print("No buffer available!\n", .{});
}
}
fn requestRedraw(self: *Self) !void {
self.needs_redraw = true;
if (self.frame_callback == null) {
self.frame_callback = try self.surface.frame();
self.frame_callback.?.setListener(*Self, frameCallbackListener, self);
self.surface.commit();
}
}
fn frameCallbackListener(frame: *wl.Callback, event: wl.Callback.Event, self: *Self) void {
switch (event) {
.done => {
frame.destroy();
self.frame_callback = null;
if (self.needs_redraw) {
self.commitFrame();
}
std.debug.print("frame_callback done\n", .{});
},
}
}
fn xdgOutputListener(output: *zxdg.OutputV1, event: zxdg.OutputV1.Event, self: *Self) void {
_ = output;
std.debug.print("zxdg_output listener: {}\n", .{event});
switch (event) {
.logical_position => |pos| {
self.logical_geometry.position = .{.x = pos.x, .y = pos.y,};
},
.logical_size => |size| {
self.logical_geometry.extents = .{
.width = @intCast(u32, size.width),
.height = @intCast(u32, size.height),
};
},
else => {},
}
}
fn layerSurfaceListener(
layer: *zwlr.LayerSurfaceV1,
event: zwlr.LayerSurfaceV1.Event,
self: *Self,
) void {
std.debug.print("zwlr_layer_surface listener: {}\n", .{event});
switch (event) {
.configure => |cfg|{
self.configured = true;
self.size = .{
.width = cfg.width,
.height = cfg.height,
};
layer.ackConfigure(cfg.serial);
self.commitFrame();
},
.closed => {
self.state.removeOutput(self);
},
}
}
fn outputListener(output: *wl.Output, event: wl.Output.Event, self: *Self) void {
_ = output;
std.debug.print("wl_output listener: {}\n", .{event});
switch (event) {
.geometry => |geom| {
self.geometry.position = .{.x = geom.x, .y = geom.y};
},
.mode => |mode| {
if (!mode.flags.current) {
self.geometry.extents = .{
.width = @intCast(u32, mode.width),
.height = @intCast(u32, mode.height),
};
}
},
.scale => |scale| {
self.scale = scale.factor;
},
else => {
std.debug.print("wl_output listener: unhandled\n", .{});
},
}
}
};
const Seat = struct {
const Self = @This();
state: *State.Init = undefined,
seat: *wl.Seat,
keyboard: ?struct {
keyboard: *wl.Keyboard,
xkb: ?struct {
keymap: *xkb.xkb_keymap,
state: *xkb.xkb_state,
} = null,
} = null,
pointer: ?struct {
pointer: *wl.Pointer,
current_surface_offset: ?Point = null,
stage: SelectionStage = .Edit,
selection: SelectionState = SelectionState.None(),
} = null,
// awful naming but thats life innit
// this encodes whether the selection is actively being edited or moved as a whole
const SelectionStage = enum {
Edit,
Move,
};
const SelectionTag = enum {
none,
pre,
updating,
post,
};
const SelectionState = union(SelectionTag) {
none: void,
pre: Point,
updating: Selection,
post: Box,
fn None() SelectionState {
return SelectionState.none;
}
fn translate(self: *SelectionState, delta: Point) void {
switch (self.*) {
.pre => |*point| {
point.add(delta);
},
.updating => |*selection| {
selection.start.add(delta);
selection.end.add(delta);
},
.post => |*box| {
box.translate(delta);
},
else => {},
}
}
fn getSelectionBox(self: SelectionState) ?Box {
switch (self) {
.updating => |selection| {
return selection.asBox();
},
.post => |box| {
return box;
},
else => {return null;},
}
}
};
fn init(self: *Self, state: *State.Init) void {
self.state = state;
std.debug.print("seat.init()\n", .{});
self.seat.setListener(*Self, seatListener, self);
}
fn calculateRedraws(self: *Self) void {
if (self.pointer) |ptr| {
const box = ptr.selection.getSelectionBox();
var output_iter = self.state.outputs.iterator(.forward);
while (output_iter.next()) |link| {
const output = link.get();
if (box == null or output.logical_geometry.intersects(&box.?)) {
output.requestRedraw() catch {};
}
}
}
}
fn pointerListener(pointer: *wl.Pointer, event: wl.Pointer.Event, self: *Self) void {
_ = pointer;
switch (event) {
.enter => |enter| {
if (self.state.getOutputForSurface(enter.surface.?)) |output| {
self.pointer.?.current_surface_offset = output.logical_geometry.position;
}
std.debug.print("surface offset: {?}\n", .{self.pointer.?.current_surface_offset});
},
.leave => {
self.pointer.?.current_surface_offset = null;
},
.motion => |motion| {
const x = motion.surface_x.toInt();
const y = motion.surface_y.toInt();
const xy = Point{.x = x, .y = y,};
if (self.pointer.?.current_surface_offset) |offset| {
const point = xy.added(offset);
switch (self.pointer.?.stage) {
.Edit => {
switch (self.pointer.?.selection) {
.pre => {
self.pointer.?.selection = .{.updating = .{.start = point, .end = point}};
return self.calculateRedraws();
},
.updating => |*selection| {
selection.end = point;
return self.calculateRedraws();
},
else => {},
}
},
.Move => {
const delta = blk: {
switch (self.pointer.?.selection) {
.pre => |end| {
break :blk point.added(end.inverted());
},
.updating => |selection| {
break :blk point.added(selection.end.inverted());
},
else => {break :blk null;},
}
};
if (delta != null) {
self.pointer.?.selection.translate(delta.?);
return self.calculateRedraws();
}
},
}
}
},
.button => |button| {
switch (button.state) {
.pressed => {
self.pointer.?.selection = SelectionState{.pre = .{}};
},
.released => {
switch (self.pointer.?.selection) {
.updating => |selection| {
const box = selection.asBox();
self.pointer.?.selection = .{.post = box};
return self.calculateRedraws();
},
else => {},
}
},
else => {},
}
},
else => {},
}
}
fn keyboardListener(keyboard: *wl.Keyboard, event: wl.Keyboard.Event, self: *Self) void {
_ = keyboard;
switch (event) {
.keymap => |keymap_event| {
const keymap = blk: {
switch (keymap_event.format) {
.no_keymap => {
break :blk xkb.xkb_keymap_new_from_names(
self.state.xkb_context,
null,
xkb.XKB_KEYMAP_COMPILE_NO_FLAGS,
);
},
.xkb_v1 => {
const buffer = std.os.mmap(
null,
keymap_event.size,
std.os.PROT.READ,
std.os.MAP.PRIVATE,
keymap_event.fd,
0,
) catch break :blk null;
defer std.os.munmap(buffer);
defer std.os.close(keymap_event.fd);
break :blk xkb.xkb_keymap_new_from_buffer(
self.state.xkb_context,
@ptrCast([*c]const u8, buffer),
keymap_event.size - 1,
xkb.XKB_KEYMAP_FORMAT_TEXT_V1,
xkb.XKB_KEYMAP_COMPILE_NO_FLAGS,
);
},
else => unreachable,
}
};
if (keymap) |map| {
const state = xkb.xkb_state_new(map);
if (state) |state_| {
// SAFETY: keyboard cant be null because we are in the keyboard listener
self.keyboard.?.xkb = .{
.keymap = map,
.state = state_,
};
}
}
},
.key => |key| {
if (self.keyboard.?.xkb) |xkb_| {
const keysym = xkb.xkb_state_key_get_one_sym(xkb_.state, key.key + 8);
// const buf_len = 32;
// const buf = self.state.ally.alloc(u8, buf_len) catch null;
// if (buf) |buffer| {
// _ = xkb.xkb_keysym_get_name(keysym, @ptrCast([*c]u8, buffer), buf_len);
// std.debug.print("key: {s} {s}\n", .{buffer, if (key.state == .pressed) "pressed" else "released"});
// }
switch (key.state) {
.pressed => {
switch (keysym) {
xkb.XKB_KEY_Escape, xkb.XKB_KEY_q => {
if (self.pointer) |*ptr| {
switch (ptr.selection) {
.none => {
self.state.running = false;
return;
},
else => {
ptr.selection = .none;
return self.calculateRedraws();
}
}
}
},
xkb.XKB_KEY_space => {
if (self.pointer) |*ptr| {
ptr.stage = .Move;
}
},
else => {},
}
},
.released => {
switch (keysym) {
xkb.XKB_KEY_space => {
if (self.pointer) |*ptr| {
ptr.stage = .Edit;
}
},
else => {},
}
},
else => {},
}
}
},
else => {},
}
}
fn seatListener(seat: *wl.Seat, event: wl.Seat.Event, self: *Self) void {
switch (event) {
.capabilities => |value| {
std.debug.print("seat capabilities: {}\n", .{value.capabilities});
const capabilities = value.capabilities;
if (capabilities.keyboard) {
const keyboard = seat.getKeyboard() catch return;
self.keyboard = .{
.keyboard = keyboard,
};
keyboard.setListener(*Self, keyboardListener, self);
}
if (capabilities.pointer) {
const pointer = seat.getPointer() catch return;
self.pointer = .{
.pointer = pointer,
};
pointer.setListener(*Self, pointerListener, self);
}
},
else => {},
}
}
fn deinit(self: *Self) void {
if (self.keyboard) |keyboard| {
keyboard.keyboard.destroy();
if (keyboard.xkb) |kb| {
xkb.xkb_state_unref(kb.state);
xkb.xkb_keymap_unref(kb.keymap);
}
}
if (self.pointer) |pointer| {
pointer.pointer.destroy();
}
self.seat.destroy();
}
};
/// 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: Listener(T, D2).Fn,
) *Listener(T, D2) {
const other = @ptrCast(*Listener(T, D2), self);
other.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,
seats: *ListItem(Seat).Head() = undefined,
outputs: *ListItem(Output).Head() = undefined,
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 = try ally.create(Listener(wl.Registry, Self));
listener.* = .{
.data = self,
.callback = registryListener,
};
self.* = .{
.ally = ally,
.dpy = dpy,
.registry = registry,
.registry_listener = listener,
.seats = try ally.create(ListItem(Seat).Head()),
.outputs = try ally.create(ListItem(Output).Head()),
};
self.seats.init();
self.outputs.init();
self.registry_listener.set(self.registry);
if (dpy.roundtrip() != .SUCCESS) return error.RoundtripFailed;
return self;
}
fn deinit(self: *Self) void {
self.ally.destroy(self.seats);
self.ally.destroy(self.outputs);
self.ally.destroy(self);
// TODO: clean up wayland state
}
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;
if (ListItem(Seat).create(self.ally, Seat{.seat = seat}) catch null) |ele| {
self.seats.append(ele);
}
} else if (std.cstr.cmp(global.interface, wl.Output.getInterface().name) == 0) {
const output = registry.bind(global.name, wl.Output, 3) catch return;
if (ListItem(Output).create(self.ally, Output{.output = output}) catch null) |ele| {
self.outputs.append(ele);
}
}
},
.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);
try init.init(self);
return init;
}
};
const Init = struct {
const Self = @This();
ally: std.mem.Allocator = std.heap.c_allocator,
running: bool = true,
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,
cursor_theme: []const u8 = undefined,
cursor_size: u32 = undefined,
seats: *ListItem(Seat).Head() = undefined,
outputs: *ListItem(Output).Head() = undefined,
fn init(self: *Self, uninit: *Uninit) !void {
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(uninit.ally, "XCURSOR_THEME") catch "";
// only free cursor_theme if bailing
errdefer uninit.ally.free(cursor_theme);
const buf = try std.process.getEnvVarOwned(uninit.ally, "XCURSOR_SIZE");
defer uninit.ally.free(buf);
const cursor_size = std.fmt.parseInt(u32, buf, 10) catch return error.InvalidXCursorSize;
self.* = Self{
.ally = uninit.ally,
.dpy = uninit.dpy,
.registry = uninit.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 = uninit.shm orelse return error.NoWlShm,
.compositor = uninit.compositor orelse return error.NoWlCompositor,
.layer_shell = uninit.layer_shell orelse return error.NoWlrLayerShell,
.xdg_output_manager = uninit.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,
.seats = uninit.seats,
.outputs = uninit.outputs,
};
// i hope this is fine but appears to be requried to reset the listener on the registry
self.registry_listener = uninit.registry_listener.cast(Self, self, Self.registryListener);
// init seats
var seats_iter = self.seats.safeIterator(.forward);
while (seats_iter.next()) |item| {
const seat = item.get();
seat.init(self);
}
// init outputs
var output_iter = self.outputs.safeIterator(.forward);
while (output_iter.next()) |item| {
const output = item.get();
try output.init(self);
}
if (self.dpy.roundtrip() != .SUCCESS) return error.RoundtripFailed;
}
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 stuff(self: *Self) !void {
while (self.running) {
if (self.dpy.dispatch() != .SUCCESS) return error.DispatchFailed;
}
}
fn finish(self: *Self, box: Box) void {
_ = self;
_ = box;
}
fn removeOutput(self: *Self, output: *Output) void {
const link = ListItem(Output).fromValue(output);
link.remove();
self.ally.destroy(link);
}
fn getOutputForSurface(self: *Self, surface: *wl.Surface) ?*Output {
var output_iter = self.outputs.iterator(.forward);
while (output_iter.next()) |next| {
const output = next.get();
if (output.surface == surface) {
return output;
}
}
return null;
}
fn deinit(self: *Self) void {
defer self.ally.destroy(self);
var seats_iter = self.seats.safeIterator(.forward);
while (seats_iter.next()) |next| {
next.remove();
self.ally.destroy(next);
}
self.ally.destroy(self.seats);
var outputs_iter = self.outputs.safeIterator(.forward);
while (outputs_iter.next()) |next| {
next.remove();
self.ally.destroy(next);
}
self.ally.destroy(self.outputs);
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();
defer init.deinit();
try init.stuff();
}