1347 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
			
		
		
	
	
			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();
 | |
| }
 |