stevee

My wayland statusbar
git clone git://gtms.dev/stevee
Log | Files | Refs | Submodules | README | LICENSE

commit 808900456e850ace23a37b40ba00cbdd71254236
parent e2a1a91f361fcce9616d73210f877fa65845d821
Author: Andrea Feletto <andrea@andreafeletto.com>
Date:   Wed, 19 Jan 2022 18:23:13 +0100

render tags without labels (yet)

Diffstat:
Asrc/config.zig | 27+++++++++++++++++++++++++++
Msrc/main.zig | 3+++
Msrc/render.zig | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Asrc/tags.zig | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/wayland.zig | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++------------
5 files changed, 216 insertions(+), 20 deletions(-)

diff --git a/src/config.zig b/src/config.zig @@ -0,0 +1,27 @@ +const pixman = @import("pixman"); + +pub const Config = struct { + height: u16, + backgroundColor: pixman.Color, + foregroundColor: pixman.Color, + border: u15, + + pub fn init() Config { + return Config{ + .height = 32, + .backgroundColor = .{ + .red = 0, + .green = 0, + .blue = 0, + .alpha = 0xffff, + }, + .foregroundColor = .{ + .red = 0xffff, + .green = 0xffff, + .blue = 0xffff, + .alpha = 0xffff, + }, + .border = 2, + }; + } +}; diff --git a/src/main.zig b/src/main.zig @@ -1,10 +1,12 @@ const std = @import("std"); +const Config = @import("config.zig").Config; const Loop = @import("event.zig").Loop; const Wayland = @import("wayland.zig").Wayland; pub const State = struct { allocator: std.mem.Allocator, + config: Config, wayland: Wayland, loop: Loop, }; @@ -16,6 +18,7 @@ pub fn main() anyerror!void { std.log.info("initialization", .{}); var state: State = undefined; state.allocator = arena.allocator(); + state.config = Config.init(); state.wayland = try Wayland.init(&state); state.loop = try Loop.init(&state); diff --git a/src/render.zig b/src/render.zig @@ -1,14 +1,18 @@ const pixman = @import("pixman"); const Buffer = @import("shm.zig").Buffer; +const State = @import("main.zig").State; const Surface = @import("wayland.zig").Surface; +const Tag = @import("tags.zig").Tag; +const Tags = @import("tags.zig").Tags; pub fn renderBackground(surface: *Surface) !void { + const state = surface.output.state; const wlSurface = surface.backgroundSurface; const buffer = try Buffer.nextBuffer( &surface.backgroundBuffers, - surface.output.state.wayland.shm, + state.wayland.shm, surface.width, surface.height, ); @@ -17,15 +21,67 @@ pub fn renderBackground(surface: *Surface) !void { const area = [_]pixman.Rectangle16{ .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height }, }; - const color = pixman.Color{ - .red = 0, - .green = 0, - .blue = 0, - .alpha = 0xffff, - }; - _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &color, 1, &area); + const color = &state.config.backgroundColor; + _ = pixman.Image.fillRectangles(.src, buffer.pix.?, color, 1, &area); wlSurface.setBufferScale(surface.output.scale); wlSurface.damageBuffer(0, 0, surface.width, surface.height); wlSurface.attach(buffer.buffer, 0, 0); } + +pub fn renderTags(surface: *Surface) !void { + const state = surface.output.state; + const wlSurface = surface.tagsSurface; + const tags = surface.output.tags.tags; + + const buffer = try Buffer.nextBuffer( + &surface.tagsBuffers, + surface.output.state.wayland.shm, + surface.width, + surface.height, + ); + buffer.busy = true; + + for (tags) |*tag, i| { + const offset = @intCast(i16, surface.height * i); + try renderTag(buffer.pix.?, tag, surface.height, offset, state); + } + + wlSurface.setBufferScale(surface.output.scale); + wlSurface.damageBuffer(0, 0, surface.width, surface.height); + wlSurface.attach(buffer.buffer, 0, 0); +} + +fn renderTag( + pix: *pixman.Image, + tag: *const Tag, + height: u16, + offset: i16, + state: *State, +) !void { + const size = @intCast(u16, height); + + const outer = [_]pixman.Rectangle16{ + .{ .x = offset, .y = 0, .width = size, .height = size }, + }; + const outer_color = if (tag.focused or tag.occupied) blk: { + break :blk &state.config.foregroundColor; + } else blk: { + break :blk &state.config.backgroundColor; + }; + _ = pixman.Image.fillRectangles(.over, pix, outer_color, 1, &outer); + + const border = state.config.border; + const inner = [_]pixman.Rectangle16{ + .{ + .x = offset + border, + .y = border, + .width = size - 2 * border, + .height = size - 2 * border, + }, + }; + const inner_color = &state.config.backgroundColor; + if (!tag.focused and tag.occupied) { + _ = pixman.Image.fillRectangles(.over, pix, inner_color, 1, &inner); + } +} diff --git a/src/tags.zig b/src/tags.zig @@ -0,0 +1,69 @@ +const zriver = @import("wayland").client.zriver; + +const Output = @import("wayland.zig").Output; +const render = @import("render.zig"); +const State = @import("main.zig").State; + +pub const Tag = struct { + label: u8, + focused: bool = false, + occupied: bool = false, +}; + +pub const Tags = struct { + output: *Output, + outputStatus: *zriver.OutputStatusV1, + tags: [9]Tag, + + pub fn create(state: *State, output: *Output) !*Tags { + const self = try state.allocator.create(Tags); + + self.output = output; + self.outputStatus = try state.wayland.statusManager.getRiverOutputStatus( + output.wlOutput, + ); + for (self.tags) |*tag, i| { + tag.label = '1' + @intCast(u8, i); + } + + self.outputStatus.setListener(*Tags, outputStatusListener, self); + return self; + } + + pub fn destroy(self: *Tags) void { + self.outputStatus.destroy(); + } + + fn outputStatusListener( + _: *zriver.OutputStatusV1, + event: zriver.OutputStatusV1.Event, + tags: *Tags, + ) void { + switch (event) { + .focused_tags => |data| { + for (tags.tags) |*tag, i| { + const mask = @as(u32, 1) << @intCast(u5, i); + tag.focused = data.tags & mask != 0; + } + }, + .view_tags => |data| { + for (tags.tags) |*tag| { + tag.occupied = false; + } + for (data.tags.slice(u32)) |view| { + for (tags.tags) |*tag, i| { + const mask = @as(u32, 1) << @intCast(u5, i); + if (view & mask != 0) tag.occupied = true; + } + } + }, + } + if (tags.output.surface) |surface| { + if (surface.configured) { + render.renderTags(surface) catch return; + surface.tagsSurface.commit(); + surface.backgroundSurface.commit(); + } + } + } +}; diff --git a/src/wayland.zig b/src/wayland.zig @@ -10,6 +10,7 @@ const zriver = @import("wayland").client.zriver; const Buffer = @import("shm.zig").Buffer; const render = @import("render.zig"); const State = @import("main.zig").State; +const Tags = @import("tags.zig").Tags; pub const Wayland = struct { state: *State, @@ -19,11 +20,12 @@ pub const Wayland = struct { outputs: ArrayList(*Output), compositor: *wl.Compositor, + subcompositor: *wl.Subcompositor, shm: *wl.Shm, layerShell: *zwlr.LayerShellV1, statusManager: *zriver.StatusManagerV1, - globalsRegistered: [4]bool, + globalsRegistered: [5]bool, pub fn init(state: *State) !Wayland { const display = try wl.Display.connect(null); @@ -34,10 +36,11 @@ pub const Wayland = struct { .registry = try display.getRegistry(), .outputs = ArrayList(*Output).init(state.allocator), .compositor = undefined, + .subcompositor = undefined, .shm = undefined, .layerShell = undefined, .statusManager = undefined, - .globalsRegistered = mem.zeroes([4]bool), + .globalsRegistered = mem.zeroes([5]bool), }; } @@ -61,15 +64,18 @@ pub const Wayland = struct { if (strcmp(interface, wl.Compositor.getInterface().name) == 0) { wayland.compositor = registry.bind(name, wl.Compositor, 4) catch return; wayland.globalsRegistered[0] = true; + } else if (strcmp(interface, wl.Subcompositor.getInterface().name) == 0) { + wayland.subcompositor = registry.bind(name, wl.Subcompositor, 1) catch return; + wayland.globalsRegistered[1] = true; } else if (strcmp(interface, wl.Shm.getInterface().name) == 0) { wayland.shm = registry.bind(name, wl.Shm, 1) catch return; - wayland.globalsRegistered[1] = true; + wayland.globalsRegistered[2] = true; } else if (strcmp(interface, zwlr.LayerShellV1.getInterface().name) == 0) { wayland.layerShell = registry.bind(name, zwlr.LayerShellV1, 1) catch return; - wayland.globalsRegistered[2] = true; + wayland.globalsRegistered[3] = true; } else if (strcmp(interface, zriver.StatusManagerV1.getInterface().name) == 0) { wayland.statusManager = registry.bind(name, zriver.StatusManagerV1, 1) catch return; - wayland.globalsRegistered[3] = true; + wayland.globalsRegistered[4] = true; } else if (strcmp(interface, wl.Output.getInterface().name) == 0) { const output = Output.create(state, registry, name) catch return; wayland.outputs.append(output) catch return; @@ -78,7 +84,7 @@ pub const Wayland = struct { .global_remove => |data| { for (wayland.outputs.items) |output, i| { if (output.globalName == data.name) { - state.allocator.destroy(output); + output.destroy(); _ = wayland.outputs.swapRemove(i); break; } @@ -93,7 +99,9 @@ pub const Output = struct { wlOutput: *wl.Output, globalName: u32, scale: i32, + surface: ?*Surface, + tags: *Tags, pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Output { const self = try state.allocator.create(Output); @@ -101,12 +109,22 @@ pub const Output = struct { self.wlOutput = try registry.bind(name, wl.Output, 3); self.globalName = name; self.scale = 1; + self.surface = null; + self.tags = try Tags.create(state, self); self.wlOutput.setListener(*Output, listener, self); return self; } + pub fn destroy(self: *Output) void { + if (self.surface) |surface| { + surface.destroy(); + } + self.tags.destroy(); + self.state.allocator.destroy(self); + } + fn listener(_: *wl.Output, event: wl.Output.Event, output: *Output) void { switch (event) { .scale => |scale| { @@ -129,9 +147,12 @@ pub const Surface = struct { output: *Output, backgroundSurface: *wl.Surface, + layerSurface: *zwlr.LayerSurfaceV1, backgroundBuffers: [2]Buffer, - layerSurface: *zwlr.LayerSurfaceV1, + tagsSurface: *wl.Surface, + tagsSubsurface: *wl.Subsurface, + tagsBuffers: [2]Buffer, configured: bool, width: u16, @@ -145,24 +166,37 @@ pub const Surface = struct { self.configured = false; self.backgroundSurface = try state.wayland.compositor.createSurface(); - self.backgroundBuffers = mem.zeroes([2]Buffer); - self.layerSurface = try state.wayland.layerShell.getLayerSurface( self.backgroundSurface, output.wlOutput, .overlay, "levee", ); + self.backgroundBuffers = mem.zeroes([2]Buffer); + + self.tagsSurface = try state.wayland.compositor.createSurface(); + self.tagsSubsurface = try state.wayland.subcompositor.getSubsurface( + self.tagsSurface, + self.backgroundSurface, + ); + self.tagsBuffers = mem.zeroes([2]Buffer); - const height = 32; - self.layerSurface.setSize(0, height); + // setup layer surface + self.layerSurface.setSize(0, state.config.height); self.layerSurface.setAnchor( .{ .top = true, .left = true, .right = true, .bottom = false }, ); - self.layerSurface.setExclusiveZone(@intCast(i32, height)); + self.layerSurface.setExclusiveZone(state.config.height); self.layerSurface.setMargin(0, 0, 0, 0); self.layerSurface.setListener(*Surface, layerSurfaceListener, self); + // setup subsurfaces + self.tagsSubsurface.setPosition(0, 0); + const region = try state.wayland.compositor.createRegion(); + self.tagsSurface.setInputRegion(region); + region.destroy(); + + self.tagsSurface.commit(); self.backgroundSurface.commit(); return self; @@ -172,8 +206,12 @@ pub const Surface = struct { self.output.surface = null; self.backgroundSurface.destroy(); self.layerSurface.destroy(); + self.tagsSurface.destroy(); + self.tagsSubsurface.destroy(); self.backgroundBuffers[0].deinit(); self.backgroundBuffers[1].deinit(); + self.tagsBuffers[0].deinit(); + self.tagsBuffers[1].deinit(); self.output.state.allocator.destroy(self); } @@ -191,6 +229,9 @@ pub const Surface = struct { layerSurface.ackConfigure(data.serial); render.renderBackground(surface) catch return; + render.renderTags(surface) catch return; + + surface.tagsSurface.commit(); surface.backgroundSurface.commit(); }, .closed => {