From a9a64d2f18adc40548a60676741ff355c98596fd Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 17 Dec 2022 15:12:27 +0100 Subject: [PATCH] selections! --- build.zig | 1 + main.zig | 307 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 290 insertions(+), 18 deletions(-) diff --git a/build.zig b/build.zig index 8141a54..5a35abd 100644 --- a/build.zig +++ b/build.zig @@ -70,6 +70,7 @@ pub fn build(b: *std.build.Builder) void { const exe_tests = b.addTest("main.zig"); exe_tests.setTarget(target); exe_tests.setBuildMode(mode); + exe_tests.linkLibC(); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&exe_tests.step); diff --git a/main.zig b/main.zig index 991c2cb..9a60299 100644 --- a/main.zig +++ b/main.zig @@ -16,8 +16,17 @@ const Cairo = @cImport({ }); 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, + }; + } }; const Size = struct { @@ -58,6 +67,7 @@ const Buffer = struct { } = .{}, 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'}; @@ -118,6 +128,8 @@ const Buffer = struct { .ctx = cairo_ctx, }, }; + + self.buffer.setListener(*Self, bufferListener, self); } fn deinit(self: *Self) void { @@ -127,8 +139,70 @@ const Buffer = struct { 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(); @@ -183,6 +257,48 @@ const Box = struct { }; } + 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.x += delta.x; + self.position.y += delta.y; + } + + 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; @@ -352,10 +468,16 @@ const Output = struct { 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); @@ -382,8 +504,126 @@ const Output = struct { 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 { - _ = self; + 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 { @@ -494,6 +734,18 @@ const Seat = struct { fn None() SelectionState { return SelectionState.none; } + + 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 { @@ -503,11 +755,23 @@ const Seat = struct { 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; - std.debug.print("selection: {}\n", .{self.pointer.?.selection}); - switch (event) { .enter => {}, .leave => {}, @@ -520,9 +784,11 @@ const Seat = struct { switch (self.pointer.?.selection) { .pre => { self.pointer.?.selection = .{.updating = .{.start = end, .end = end}}; + return self.calculateRedraws(); }, .updating => |*selection| { selection.end = end; + return self.calculateRedraws(); }, else => {}, } @@ -537,6 +803,7 @@ const Seat = struct { .updating => |selection| { const box = selection.asBox(); self.pointer.?.selection = .{.post = box}; + return self.calculateRedraws(); }, else => {}, } @@ -614,7 +881,20 @@ const Seat = struct { .pressed => { switch (keysym) { xkb.XKB_KEY_Escape, xkb.XKB_KEY_q => { - self.state.running = false; + + if (self.pointer) |*ptr| { + switch (ptr.selection) { + .none => { + self.state.running = false; + return; + }, + else => { + ptr.selection = .none; + return self.calculateRedraws(); + } + } + } + }, else => {}, } @@ -914,25 +1194,16 @@ const State = struct { } fn stuff(self: *Self) !void { - var it = self.outputs.iterator(.forward); - if (it.next()) |link| { - const output = link.get(); - - var buffer: Buffer = undefined; - const width = output.size.width * @intCast(u32, output.scale); - const height = output.size.height * @intCast(u32, output.scale); - try buffer.init(self.shm, .{.width = width, .height = height}); - std.mem.copy(u8, buffer.data, @embedFile("cat.bgra")); - - output.surface.attach(buffer.buffer, 0, 0); - output.surface.commit(); - } - 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();