stevee

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

commit 27e73da715bc9ace58bef0d1082cc364a8a35064
parent 7c8a37dde0fda81570d11f35bdb48965cd292876
Author: Andrea Feletto <andrea@andreafeletto.com>
Date:   Thu,  7 Apr 2022 19:54:47 +0200

major refactor and add alsa module

Diffstat:
MREADME.md | 1-
Mbuild.zig | 38+++++++++++++++-----------------------
Asrc/Buffer.zig | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Loop.zig | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Surface.zig | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Tags.zig | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/c.zig | 1-
Dsrc/event.zig | 135-------------------------------------------------------------------------------
Msrc/main.zig | 33++++++++++++++++++++++-----------
Msrc/modules.zig | 237++++---------------------------------------------------------------------------
Asrc/modules/Alsa.zig | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/Backlight.zig | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/Battery.zig | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/render.zig | 63++++++++++++++++++++++++++++++++-------------------------------
Dsrc/shm.zig | 69---------------------------------------------------------------------
Dsrc/tags.zig | 93-------------------------------------------------------------------------------
Asrc/utils.zig | 15+++++++++++++++
Msrc/wayland.zig | 402++++++++++++++++++++++++++++++++-----------------------------------------------
18 files changed, 1126 insertions(+), 833 deletions(-)

diff --git a/README.md b/README.md @@ -9,7 +9,6 @@ Some important things are not implemented yet: * configuration via cli flags * configuration via config file -* volume information ## Build diff --git a/build.zig b/build.zig @@ -7,12 +7,19 @@ pub fn build(b: *std.build.Builder) void { const target = b.standardTargetOptions(.{}); const mode = b.standardReleaseOptions(); + const exe = b.addExecutable("levee", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + const scanner = ScanProtocolsStep.create(b); scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml"); scanner.addProtocolPath("protocol/wlr-layer-shell-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-status-unstable-v1.xml"); scanner.addProtocolPath("protocol/river-control-unstable-v1.xml"); + exe.step.dependOn(&scanner.step); + scanner.addCSource(exe); + const wayland = Pkg{ .name = "wayland", .path = .{ .generated = &scanner.result }, @@ -31,25 +38,17 @@ pub fn build(b: *std.build.Builder) void { .path = .{ .path = "deps/zig-udev/udev.zig" }, }; - const exe = b.addExecutable("levee", "src/main.zig"); - exe.setTarget(target); - exe.setBuildMode(mode); - - exe.linkLibC(); - - exe.addPackage(wayland); - exe.linkSystemLibrary("wayland-client"); - exe.step.dependOn(&scanner.step); - scanner.addCSource(exe); - + exe.addPackage(fcft); exe.addPackage(pixman); - exe.linkSystemLibrary("pixman-1"); + exe.addPackage(udev); + exe.addPackage(wayland); - exe.addPackage(fcft); + exe.linkLibC(); + exe.linkSystemLibrary("alsa"); exe.linkSystemLibrary("fcft"); - - exe.addPackage(udev); exe.linkSystemLibrary("libudev"); + exe.linkSystemLibrary("pixman-1"); + exe.linkSystemLibrary("wayland-client"); exe.install(); @@ -59,13 +58,6 @@ pub fn build(b: *std.build.Builder) void { run_cmd.addArgs(args); } - const run_step = b.step("run", "Run the app"); + const run_step = b.step("run", "Run levee"); run_step.dependOn(&run_cmd.step); - - const exe_tests = b.addTest("src/main.zig"); - exe_tests.setTarget(target); - exe_tests.setBuildMode(mode); - - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&exe_tests.step); } diff --git a/src/Buffer.zig b/src/Buffer.zig @@ -0,0 +1,66 @@ +const std = @import("std"); +const mem = std.mem; +const os = std.os; + +const pixman = @import("pixman"); +const wl = @import("wayland").client.wl; + +const Buffer = @This(); + +data: ?[]align(4096) u8, +buffer: ?*wl.Buffer, +pix: ?*pixman.Image, +busy: bool, +width: u31, +height: u31, +size: u31, + +pub fn init(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { + self.busy = true; + self.width = width; + self.height = height; + + const fd = try os.memfd_create("levee-wayland-shm-buffer-pool", 0); + defer os.close(fd); + + const stride = width * 4; + self.size = stride * height; + try os.ftruncate(fd, self.size); + + self.data = try os.mmap(null, self.size, os.PROT.READ | os.PROT.WRITE, os.MAP.SHARED, fd, 0); + errdefer os.munmap(self.data.?); + + const pool = try shm.createPool(fd, self.size); + defer pool.destroy(); + + self.buffer = try pool.createBuffer(0, width, height, stride, .argb8888); + errdefer self.buffer.?.destroy(); + self.buffer.?.setListener(*Buffer, listener, self); + + self.pix = pixman.Image.createBitsNoClear(.a8r8g8b8, width, height, @ptrCast([*c]u32, self.data.?), stride); +} + +pub fn deinit(self: *Buffer) void { + if (self.pix) |pix| _ = pix.unref(); + if (self.buffer) |buf| buf.destroy(); + if (self.data) |data| os.munmap(data); +} + +fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void { + switch (event) { + .release => buffer.busy = false, + } +} + +pub fn nextBuffer(pool: *[2]Buffer, shm: *wl.Shm, width: u16, height: u16) !*Buffer { + if (pool[0].busy and pool[1].busy) { + return error.NoAvailableBuffers; + } + const buffer = if (!pool[0].busy) &pool[0] else &pool[1]; + + if (buffer.width != width or buffer.height != height) { + buffer.deinit(); + try buffer.init(shm, width, height); + } + return buffer; +} diff --git a/src/Loop.zig b/src/Loop.zig @@ -0,0 +1,85 @@ +const std = @import("std"); +const fmt = std.fmt; +const mem = std.mem; +const os = std.os; + +const wl = @import("wayland").client.wl; + +const Module = @import("modules.zig").Module; +const State = @import("main.zig").State; +const utils = @import("utils.zig"); +const Loop = @This(); + +state: *State, +sfd: os.fd_t, + +pub const Event = struct { + fd: os.pollfd, + data: *anyopaque, + callbackIn: Callback, + callbackOut: Callback, + + pub const Callback = fn (*anyopaque) error{Terminate}!void; + + pub fn terminate(_: *anyopaque) error{Terminate}!void { + return error.Terminate; + } + + pub fn noop(_: *anyopaque) error{Terminate}!void { + return; + } +}; + +pub fn init(state: *State) !Loop { + var mask = mem.zeroes(os.linux.sigset_t); + os.linux.sigaddset(&mask, os.linux.SIG.INT); + os.linux.sigaddset(&mask, os.linux.SIG.TERM); + os.linux.sigaddset(&mask, os.linux.SIG.QUIT); + _ = os.linux.sigprocmask(os.linux.SIG.BLOCK, &mask, null); + const sfd = os.linux.signalfd(-1, &mask, os.linux.SFD.NONBLOCK); + + return Loop{ .state = state, .sfd = @intCast(os.fd_t, sfd) }; +} + +pub fn run(self: *Loop) !void { + const gpa = self.state.gpa; + const display = self.state.wayland.display; + + var events: std.MultiArrayList(Event) = .{}; + defer events.deinit(gpa); + + try events.append(gpa, .{ + .fd = .{ .fd = self.sfd, .events = os.POLL.IN, .revents = 0 }, + .data = undefined, + .callbackIn = Event.terminate, + .callbackOut = Event.noop, + }); + try events.append(gpa, try self.state.wayland.getEvent()); + for (self.state.modules.items) |*module| { + try events.append(gpa, try module.getEvent()); + } + + const fds = events.items(.fd); + while (true) { + while (true) { + const ret = try display.dispatchPending(); + _ = try display.flush(); + if (ret <= 0) break; + } + _ = try os.poll(fds, -1); + + for (fds) |fd, i| { + if (fd.revents & os.POLL.HUP != 0) return; + if (fd.revents & os.POLL.ERR != 0) return; + + if (fd.revents & os.POLL.IN != 0) { + const event = events.get(i); + event.callbackIn(event.data) catch return; + } + if (fd.revents & os.POLL.OUT != 0) { + const event = events.get(i); + event.callbackOut(event.data) catch return; + } + } + } +} diff --git a/src/Surface.zig b/src/Surface.zig @@ -0,0 +1,146 @@ +const std = @import("std"); +const mem = std.mem; + +const wl = @import("wayland").client.wl; +const zwlr = @import("wayland").client.zwlr; + +const Buffer = @import("Buffer.zig"); +const Monitor = @import("wayland.zig").Monitor; +const render = @import("render.zig"); +const Surface = @This(); + +monitor: *Monitor, + +backgroundSurface: *wl.Surface, +layerSurface: *zwlr.LayerSurfaceV1, +backgroundBuffers: [2]Buffer, + +tagsSurface: *wl.Surface, +tagsSubsurface: *wl.Subsurface, +tagsBuffers: [2]Buffer, + +clockSurface: *wl.Surface, +clockSubsurface: *wl.Subsurface, +clockBuffers: [2]Buffer, + +modulesSurface: *wl.Surface, +modulesSubsurface: *wl.Subsurface, +modulesBuffers: [2]Buffer, + +configured: bool, +width: u16, +height: u16, + +pub fn create(monitor: *Monitor) !*Surface { + const state = monitor.state; + const globals = &state.wayland.globals; + + const self = try state.gpa.create(Surface); + self.monitor = monitor; + self.configured = false; + + self.backgroundSurface = try globals.compositor.createSurface(); + self.layerSurface = try globals.layerShell.getLayerSurface( + self.backgroundSurface, + monitor.output, + .top, + "levee", + ); + self.backgroundBuffers = mem.zeroes([2]Buffer); + + self.tagsSurface = try globals.compositor.createSurface(); + self.tagsSubsurface = try globals.subcompositor.getSubsurface( + self.tagsSurface, + self.backgroundSurface, + ); + self.tagsBuffers = mem.zeroes([2]Buffer); + + self.clockSurface = try globals.compositor.createSurface(); + self.clockSubsurface = try globals.subcompositor.getSubsurface( + self.clockSurface, + self.backgroundSurface, + ); + self.clockBuffers = mem.zeroes([2]Buffer); + + self.modulesSurface = try globals.compositor.createSurface(); + self.modulesSubsurface = try globals.subcompositor.getSubsurface( + self.modulesSurface, + self.backgroundSurface, + ); + self.modulesBuffers = mem.zeroes([2]Buffer); + + // setup layer surface + self.layerSurface.setSize(0, state.config.height); + self.layerSurface.setAnchor( + .{ .top = true, .left = true, .right = true, .bottom = false }, + ); + 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); + self.clockSubsurface.setPosition(0, 0); + self.modulesSubsurface.setPosition(0, 0); + + self.tagsSurface.commit(); + self.clockSurface.commit(); + self.backgroundSurface.commit(); + + return self; +} + +pub fn destroy(self: *Surface) void { + self.monitor.surface = null; + + self.backgroundSurface.destroy(); + self.layerSurface.destroy(); + self.backgroundBuffers[0].deinit(); + self.backgroundBuffers[1].deinit(); + + self.tagsSurface.destroy(); + self.tagsSubsurface.destroy(); + self.tagsBuffers[0].deinit(); + self.tagsBuffers[1].deinit(); + + self.clockSurface.destroy(); + self.clockSubsurface.destroy(); + self.clockBuffers[0].deinit(); + self.clockBuffers[1].deinit(); + + self.modulesSurface.destroy(); + self.modulesSubsurface.destroy(); + self.modulesBuffers[0].deinit(); + self.modulesBuffers[1].deinit(); + + self.monitor.state.gpa.destroy(self); +} + +fn layerSurfaceListener( + layerSurface: *zwlr.LayerSurfaceV1, + event: zwlr.LayerSurfaceV1.Event, + surface: *Surface, +) void { + switch (event) { + .configure => |data| { + surface.configured = true; + surface.width = @intCast(u16, data.width); + surface.height = @intCast(u16, data.height); + + layerSurface.ackConfigure(data.serial); + + render.renderBackground(surface) catch return; + render.renderTags(surface) catch return; + render.renderClock(surface) catch return; + render.renderModules(surface) catch return; + + surface.tagsSurface.commit(); + surface.clockSurface.commit(); + surface.modulesSurface.commit(); + surface.backgroundSurface.commit(); + }, + .closed => { + surface.destroy(); + }, + } +} diff --git a/src/Tags.zig b/src/Tags.zig @@ -0,0 +1,93 @@ +const std = @import("std"); + +const zriver = @import("wayland").client.zriver; + +const Monitor = @import("wayland.zig").Monitor; +const render = @import("render.zig"); +const Input = @import("wayland.zig").Input; +const State = @import("main.zig").State; +const Tags = @This(); + +monitor: *Monitor, +outputStatus: *zriver.OutputStatusV1, +tags: [9]Tag, + +pub const Tag = struct { + label: u8, + focused: bool = false, + occupied: bool = false, +}; + +pub fn create(state: *State, monitor: *Monitor) !*Tags { + const self = try state.gpa.create(Tags); + const globals = &state.wayland.globals; + + self.monitor = monitor; + self.outputStatus = try globals.statusManager.getRiverOutputStatus( + monitor.output, + ); + 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(); + self.monitor.state.gpa.destroy(self); +} + +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.monitor.surface) |surface| { + if (surface.configured) { + render.renderTags(surface) catch return; + surface.tagsSurface.commit(); + surface.backgroundSurface.commit(); + } + } +} + +pub fn handleClick(self: *Tags, x: u32, input: *Input) !void { + const state = self.monitor.state; + const control = state.wayland.globals.control; + + if (self.monitor.surface) |surface| { + const index = x / surface.height; + const payload = try std.fmt.allocPrintZ( + state.gpa, + "{d}", + .{@as(u32, 1) << @intCast(u5, index)}, + ); + defer state.gpa.free(payload); + + control.addArgument("set-focused-tags"); + control.addArgument(payload); + const callback = try control.runCommand(input.seat); + _ = callback; + } +} diff --git a/src/c.zig b/src/c.zig @@ -1 +0,0 @@ -pub const time = @cImport(@cInclude("time.h")); diff --git a/src/event.zig b/src/event.zig @@ -1,135 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const os = std.os; -const ArrayList = std.ArrayList; - -const wl = @import("wayland").client.wl; -const udev = @import("udev"); - -const render = @import("render.zig"); -const State = @import("main.zig").State; - -pub const Loop = struct { - state: *State, - fds: [4]os.pollfd, - monitor: *udev.Monitor, - - pub fn init(state: *State) !Loop { - // signals - var mask = mem.zeroes(os.linux.sigset_t); - os.linux.sigaddset(&mask, os.linux.SIG.INT); - os.linux.sigaddset(&mask, os.linux.SIG.TERM); - os.linux.sigaddset(&mask, os.linux.SIG.QUIT); - _ = os.linux.sigprocmask(os.linux.SIG.BLOCK, &mask, null); - const sfd = os.linux.signalfd(-1, &mask, os.linux.SFD.NONBLOCK); - - // wayland - const wfd = state.wayland.display.getFd(); - - // timer - const tfd = os.linux.timerfd_create( - os.CLOCK.MONOTONIC, - os.linux.TFD.CLOEXEC, - ); - const interval: os.linux.itimerspec = .{ - .it_interval = .{ .tv_sec = 10, .tv_nsec = 0 }, - .it_value = .{ .tv_sec = 10, .tv_nsec = 0 }, - }; - _ = os.linux.timerfd_settime(@intCast(i32, tfd), 0, &interval, null); - - // udev - const context = try udev.Udev.new(); - const monitor = try udev.Monitor.newFromNetlink(context, "udev"); - try monitor.filterAddMatchSubsystemDevType("backlight", null); - try monitor.filterAddMatchSubsystemDevType("power_supply", null); - try monitor.enableReceiving(); - const ufd = try monitor.getFd(); - - // poll fds - const fds: [4]os.fd_t = .{ - @intCast(os.fd_t, sfd), - @intCast(os.fd_t, wfd), - @intCast(os.fd_t, tfd), - @intCast(os.fd_t, ufd), - }; - var pfds: [4]os.pollfd = undefined; - for (fds) |fd, i| { - pfds[i] = .{ .fd = fd, .events = os.POLL.IN, .revents = 0 }; - } - - return Loop{ - .state = state, - .fds = pfds, - .monitor = monitor, - }; - } - - pub fn run(self: *Loop) !void { - const display = self.state.wayland.display; - - while (true) { - while (true) { - const ret = try display.dispatchPending(); - _ = try display.flush(); - if (ret <= 0) break; - } - _ = try os.poll(&self.fds, -1); - - for (self.fds) |fd| { - if (fd.revents & os.POLL.HUP != 0) { - return; - } - if (fd.revents & os.POLL.ERR != 0) { - return; - } - } - - // signals - if (self.fds[0].revents & os.POLL.IN != 0) { - return; - } - - // wayland - if (self.fds[1].revents & os.POLL.IN != 0) { - _ = try display.dispatch(); - } - if (self.fds[1].revents & os.POLL.OUT != 0) { - _ = try display.flush(); - } - - // timer - if (self.fds[2].revents & os.POLL.IN != 0) { - const tfd = self.fds[2].fd; - var expirations = mem.zeroes([8]u8); - _ = try os.read(tfd, &expirations); - - for (self.state.wayland.outputs.items) |output| { - if (output.surface) |surface| { - if (surface.configured) { - render.renderClock(surface) catch continue; - render.renderModules(surface) catch continue; - surface.clockSurface.commit(); - surface.modulesSurface.commit(); - surface.backgroundSurface.commit(); - } - } - } - } - - // udev - if (self.fds[3].revents & os.POLL.IN != 0) { - _ = try self.monitor.receiveDevice(); - - for (self.state.wayland.outputs.items) |output| { - if (output.surface) |surface| { - if (surface.configured) { - render.renderModules(surface) catch continue; - surface.modulesSurface.commit(); - surface.backgroundSurface.commit(); - } - } - } - } - } - } -}; diff --git a/src/main.zig b/src/main.zig @@ -1,46 +1,57 @@ const std = @import("std"); +const heap = std.heap; +const mem = std.mem; const fcft = @import("fcft"); const Config = @import("config.zig").Config; -const Loop = @import("event.zig").Loop; +const Loop = @import("Loop.zig"); const modules = @import("modules.zig"); const Wayland = @import("wayland.zig").Wayland; pub const State = struct { - allocator: std.mem.Allocator, + gpa: mem.Allocator, config: Config, wayland: Wayland, loop: Loop, - battery: modules.Battery, + alsa: modules.Alsa, backlight: modules.Backlight, + battery: modules.Battery, modules: std.ArrayList(modules.Module), }; pub fn main() anyerror!void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); + var gpa: heap.GeneralPurposeAllocator(.{}) = .{}; + defer _ = gpa.deinit(); fcft.init(.auto, false, .info); // initialization var state: State = undefined; - state.allocator = arena.allocator(); + state.gpa = gpa.allocator(); state.config = try Config.init(); state.wayland = try Wayland.init(&state); + defer state.wayland.deinit(); state.loop = try Loop.init(&state); // modules - state.modules = std.ArrayList(modules.Module).init(state.allocator); + state.modules = std.ArrayList(modules.Module).init(state.gpa); + defer state.modules.deinit(); + + state.alsa = try modules.Alsa.init(&state); state.backlight = try modules.Backlight.init(&state); - try state.modules.append(state.backlight.module()); + defer state.backlight.deinit(); state.battery = try modules.Battery.init(&state); - try state.modules.append(state.battery.module()); + defer state.battery.deinit(); - // wayland - try state.wayland.registerGlobals(); + try state.modules.appendSlice(&.{ + try state.backlight.module(), + state.battery.module(), + state.alsa.module(), + }); // event loop + try state.wayland.registerGlobals(); try state.loop.run(); } diff --git a/src/modules.zig b/src/modules.zig @@ -1,241 +1,24 @@ const std = @import("std"); -const fmt = std.fmt; -const fs = std.fs; -const io = std.io; -const mem = std.mem; const os = std.os; -const udev = @import("udev"); +const Event = @import("Loop.zig").Event; -const State = @import("main.zig").State; - -const StringWriter = std.ArrayList(u8).Writer; +pub const Alsa = @import("modules/Alsa.zig"); +pub const Backlight = @import("modules/Backlight.zig"); +pub const Battery = @import("modules/Battery.zig"); pub const Module = struct { impl: *anyopaque, - + eventFn: fn (*anyopaque) anyerror!Event, printFn: fn (*anyopaque, StringWriter) anyerror!void, - pub fn print(self: *Module, writer: StringWriter) !void { - try self.printFn(self.impl, writer); - } - - pub fn cast(comptime to: type) fn (*anyopaque) *to { - return (struct { - pub fn cast(module: *anyopaque) *to { - return @ptrCast(*to, @alignCast(@alignOf(to), module)); - } - }).cast; - } -}; - -pub const Battery = struct { - state: *State, - context: *udev.Udev, - devices: DeviceList, - - const Device = struct { - name: []const u8, - status: []const u8, - capacity: u8, - }; - const DeviceList = std.ArrayList(Device); - - pub fn init(state: *State) !Battery { - const context = try udev.Udev.new(); - - var devices = DeviceList.init(state.allocator); - try updateDevices(state.allocator, context, &devices); - if (devices.items.len == 0) return error.NoDevicesFound; - - return Battery{ - .state = state, - .context = context, - .devices = devices, - }; - } - - pub fn module(self: *Battery) Module { - return .{ .impl = @ptrCast(*anyopaque, self), .printFn = print }; - } - - pub fn print(self_opaque: *anyopaque, writer: StringWriter) !void { - const self = Module.cast(Battery)(self_opaque); - - try updateDevices(self.state.allocator, self.context, &self.devices); - const device = self.devices.items[0]; - - var icon: []const u8 = "❓"; - if (mem.eql(u8, device.status, "Discharging")) { - icon = "🔋"; - } else if (mem.eql(u8, device.status, "Charging")) { - icon = "🔌"; - } else if (mem.eql(u8, device.status, "Full")) { - icon = "⚡"; - } - - try fmt.format(writer, "{s} {d}%", .{ icon, device.capacity }); - } - - fn updateDevices( - allocator: mem.Allocator, - context: *udev.Udev, - devices: *DeviceList, - ) !void { - const enumerate = try udev.Enumerate.new(context); - try enumerate.addMatchSubsystem("power_supply"); - try enumerate.addMatchSysattr("type", "Battery"); - try enumerate.scanDevices(); - const entries = enumerate.getListEntry(); - - var maybe_entry = entries; - while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { - const path = entry.getName(); - const device = try udev.Device.newFromSyspath(context, path); - try updateOrAppend(allocator, devices, device); - } - } - - fn updateOrAppend( - allocator: mem.Allocator, - devices: *DeviceList, - dev: *udev.Device, - ) !void { - const name = dev.getSysname() catch return; - const status = dev.getSysattrValue("status") catch return; - const capacity = getCapacity(dev) catch return; - - const device = blk: { - for (devices.items) |*device| { - if (mem.eql(u8, device.name, name)) { - break :blk device; - } - } else { - const device = try devices.addOne(); - device.name = try allocator.dupe(u8, name); - break :blk device; - } - }; - - device.status = try allocator.dupe(u8, status); - device.capacity = capacity; - } - - fn getCapacity(dev: *udev.Device) !u8 { - const capacity_str = dev.getSysattrValue("capacity") catch { - return computeCapacityFromCharge(dev) catch { - return computeCapacityFromEnergy(dev); - }; - }; - - const capacity = try fmt.parseInt(u8, capacity_str, 10); - return capacity; - } - - fn computeCapacityFromEnergy(dev: *udev.Device) !u8 { - const energy_str = try dev.getSysattrValue("energy_now"); - const energy_full_str = try dev.getSysattrValue("energy_full"); - - const energy = try fmt.parseFloat(f64, energy_str); - const energy_full = try fmt.parseFloat(f64, energy_full_str); - - const capacity = energy * 100.0 / energy_full; - return @floatToInt(u8, @round(capacity)); - } - - fn computeCapacityFromCharge(dev: *udev.Device) !u8 { - const charge_str = try dev.getSysattrValue("charge_now"); - const charge_full_str = try dev.getSysattrValue("charge_full"); - - const charge = try fmt.parseFloat(f64, charge_str); - const charge_full = try fmt.parseFloat(f64, charge_full_str); + pub const StringWriter = std.ArrayList(u8).Writer; - const capacity = charge * 100.0 / charge_full; - return @floatToInt(u8, @round(capacity)); + pub fn getEvent(self: *Module) !Event { + return self.eventFn(self.impl); } -}; - -pub const Backlight = struct { - state: *State, - context: *udev.Udev, - devices: DeviceList, - - const Device = struct { - name: []const u8, - value: u64, - max: u64, - }; - const DeviceList = std.ArrayList(Device); - - pub fn init(state: *State) !Backlight { - const context = try udev.Udev.new(); - - var devices = DeviceList.init(state.allocator); - try updateDevices(state.allocator, context, &devices); - if (devices.items.len == 0) return error.NoDevicesFound; - return Backlight{ - .state = state, - .context = context, - .devices = devices, - }; - } - - pub fn module(self: *Backlight) Module { - return .{ .impl = @ptrCast(*anyopaque, self), .printFn = print }; - } - - pub fn print(self_opaque: *anyopaque, writer: StringWriter) !void { - const self = Module.cast(Backlight)(self_opaque); - - try updateDevices(self.state.allocator, self.context, &self.devices); - const device = self.devices.items[0]; - var percent = @intToFloat(f64, device.value) * 100.0; - percent /= @intToFloat(f64, device.max); - const value = @floatToInt(u8, @round(percent)); - - try writer.print("💡 {d}%", .{value}); - } - - fn updateDevices( - allocator: mem.Allocator, - context: *udev.Udev, - devices: *DeviceList, - ) !void { - const enumerate = try udev.Enumerate.new(context); - try enumerate.addMatchSubsystem("backlight"); - try enumerate.scanDevices(); - const entries = enumerate.getListEntry(); - - var maybe_entry = entries; - while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { - const path = entry.getName(); - const device = try udev.Device.newFromSyspath(context, path); - try updateOrAppend(allocator, devices, device); - } - } - - fn updateOrAppend( - allocator: mem.Allocator, - devices: *DeviceList, - dev: *udev.Device, - ) !void { - const value = try dev.getSysattrValue("actual_brightness"); - const max = try dev.getSysattrValue("max_brightness"); - const name = try dev.getSysname(); - - const device = blk: { - for (devices.items) |*device| { - if (mem.eql(u8, device.name, name)) { - break :blk device; - } - } else { - const device = try devices.addOne(); - device.name = try allocator.dupe(u8, name); - break :blk device; - } - }; - device.value = try fmt.parseInt(u64, value, 10); - device.max = try fmt.parseInt(u64, max, 10); + pub fn print(self: *Module, writer: StringWriter) !void { + return self.printFn(self.impl, writer); } }; diff --git a/src/modules/Alsa.zig b/src/modules/Alsa.zig @@ -0,0 +1,127 @@ +const std = @import("std"); +const fmt = std.fmt; +const math = std.math; +const mem = std.mem; +const os = std.os; + +const alsa = @cImport(@cInclude("alsa/asoundlib.h")); + +const Module = @import("../modules.zig").Module; +const Event = @import("../Loop.zig").Event; +const render = @import("../render.zig"); +const State = @import("../main.zig").State; +const utils = @import("../utils.zig"); +const Alsa = @This(); + +state: *State, +context: *alsa.snd_ctl_t, + +pub fn init(state: *State) !Alsa { + return Alsa{ + .state = state, + .context = try getAlsaCtl(state.gpa), + }; +} + +pub fn module(self: *Alsa) Module { + return .{ + .impl = @ptrCast(*anyopaque, self), + .eventFn = getEvent, + .printFn = print, + }; +} + +fn getEvent(self_opaque: *anyopaque) !Event { + const self = utils.cast(Alsa)(self_opaque); + + var fd = mem.zeroes(alsa.pollfd); + _ = alsa.snd_ctl_poll_descriptors(self.context, &fd, 1); + + return Event{ + .fd = @bitCast(os.pollfd, fd), + .data = self_opaque, + .callbackIn = callbackIn, + .callbackOut = Event.noop, + }; +} + +fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void { + const self = utils.cast(Alsa)(self_opaque); + _ = self; + + var handle: ?*alsa.snd_mixer_t = null; + _ = alsa.snd_mixer_open(&handle, 0); + _ = alsa.snd_mixer_attach(handle, "default"); + _ = alsa.snd_mixer_selem_register(handle, null, null); + _ = alsa.snd_mixer_load(handle); + + var sid: ?*alsa.snd_mixer_selem_id_t = null; + _ = alsa.snd_mixer_selem_id_malloc(&sid); + defer alsa.snd_mixer_selem_id_free(sid); + alsa.snd_mixer_selem_id_set_index(sid, 0); + alsa.snd_mixer_selem_id_set_name(sid, "Master"); + const elem = alsa.snd_mixer_find_selem(handle, sid); + _ = elem; + + var unmuted: i32 = 0; + _ = alsa.snd_mixer_selem_get_playback_switch( + elem, + alsa.SND_MIXER_SCHN_MONO, + &unmuted, + ); + if (unmuted == 0) { + return writer.print(" 🔇 ", .{}); + } + + var min: i64 = 0; + var max: i64 = 0; + _ = alsa.snd_mixer_selem_get_playback_volume_range(elem, &min, &max); + + var volume: i64 = 0; + _ = alsa.snd_mixer_selem_get_playback_volume( + elem, + alsa.SND_MIXER_SCHN_MONO, + &volume, + ); + + const percent = percent: { + var x = @intToFloat(f64, volume) / @intToFloat(f64, max); + x = math.tanh(math.sqrt(x) * 0.65) * 180.0; + break :percent @floatToInt(u8, @round(x)); + }; + return writer.print("🔊 {d}%", .{ percent }); +} + +fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void { + const self = utils.cast(Alsa)(self_opaque); + + var event: ?*alsa.snd_ctl_event_t = null; + _ = alsa.snd_ctl_event_malloc(&event); + defer alsa.snd_ctl_event_free(event); + _ = alsa.snd_ctl_read(self.context, event); + + for (self.state.wayland.monitors.items) |monitor| { + if (monitor.surface) |surface| { + if (surface.configured) { + render.renderClock(surface) catch continue; + render.renderModules(surface) catch continue; + surface.clockSurface.commit(); + surface.modulesSurface.commit(); + surface.backgroundSurface.commit(); + } + } + } +} + +fn getAlsaCtl(gpa: mem.Allocator) !*alsa.snd_ctl_t { + var card: i32 = -1; + _ = alsa.snd_card_next(&card); + const name = try fmt.allocPrintZ(gpa, "hw:{d}", .{ card }); + defer gpa.free(name); + + var ctl: ?*alsa.snd_ctl_t = null; + _ = alsa.snd_ctl_open(&ctl, name.ptr, alsa.SND_CTL_READONLY); + _ = alsa.snd_ctl_subscribe_events(ctl, 1); + + return ctl.?; +} diff --git a/src/modules/Backlight.zig b/src/modules/Backlight.zig @@ -0,0 +1,150 @@ +const std = @import("std"); +const fmt = std.fmt; +const mem = std.mem; +const os = std.os; + +const udev = @import("udev"); + +const Module = @import("../modules.zig").Module; +const Event = @import("../Loop.zig").Event; +const render = @import("../render.zig"); +const State = @import("../main.zig").State; +const utils = @import("../utils.zig"); +const Backlight = @This(); + +state: *State, +context: *udev.Udev, +monitor: *udev.Monitor, +devices: DeviceList, + +const Device = struct { + name: []const u8, + value: u64, + max: u64, + + pub fn deinit(self: *Device, gpa: mem.Allocator) void { + gpa.free(self.name); + } +}; + +const DeviceList = std.ArrayList(Device); + +pub fn init(state: *State) !Backlight { + const context = try udev.Udev.new(); + + const monitor = try udev.Monitor.newFromNetlink(context, "udev"); + try monitor.filterAddMatchSubsystemDevType("backlight", null); + try monitor.filterAddMatchSubsystemDevType("power_supply", null); + try monitor.enableReceiving(); + + var devices = DeviceList.init(state.gpa); + try updateDevices(state.gpa, context, &devices); + if (devices.items.len == 0) return error.NoDevicesFound; + + return Backlight{ + .state = state, + .context = context, + .monitor = monitor, + .devices = devices, + }; +} + +pub fn deinit(self: *Backlight) void { + _ = self.context.unref(); + for (self.devices.items) |*device| { + device.deinit(self.state.gpa); + } + self.devices.deinit(); +} + +pub fn module(self: *Backlight) !Module { + return Module{ + .impl = @ptrCast(*anyopaque, self), + .eventFn = getEvent, + .printFn = print, + }; +} + +pub fn getEvent(self_opaque: *anyopaque) !Event { + const self = utils.cast(Backlight)(self_opaque); + + return Event{ + .fd = .{ + .fd = try self.monitor.getFd(), + .events = os.POLL.IN, + .revents = undefined, + }, + .data = self_opaque, + .callbackIn = callbackIn, + .callbackOut = Event.noop, + }; +} + +fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void { + const self = utils.cast(Backlight)(self_opaque); + + _ = self.monitor.receiveDevice() catch return; + for (self.state.wayland.monitors.items) |monitor| { + if (monitor.surface) |surface| { + if (surface.configured) { + render.renderModules(surface) catch continue; + surface.modulesSurface.commit(); + surface.backgroundSurface.commit(); + } + } + } +} + +pub fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void { + const self = utils.cast(Backlight)(self_opaque); + + try updateDevices(self.state.gpa, self.context, &self.devices); + const device = self.devices.items[0]; + var percent = @intToFloat(f64, device.value) * 100.0; + percent /= @intToFloat(f64, device.max); + const value = @floatToInt(u8, @round(percent)); + + try writer.print("💡 {d}%", .{value}); +} + +fn updateDevices( + gpa: mem.Allocator, + context: *udev.Udev, + devices: *DeviceList, +) !void { + const enumerate = try udev.Enumerate.new(context); + try enumerate.addMatchSubsystem("backlight"); + try enumerate.scanDevices(); + const entries = enumerate.getListEntry(); + + var maybe_entry = entries; + while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { + const path = entry.getName(); + const device = try udev.Device.newFromSyspath(context, path); + try updateOrAppend(gpa, devices, device); + } +} + +fn updateOrAppend( + gpa: mem.Allocator, + devices: *DeviceList, + dev: *udev.Device, +) !void { + const value = try dev.getSysattrValue("actual_brightness"); + const max = try dev.getSysattrValue("max_brightness"); + const name = try dev.getSysname(); + + const device = blk: { + for (devices.items) |*device| { + if (mem.eql(u8, device.name, name)) { + break :blk device; + } + } else { + const device = try devices.addOne(); + device.name = try gpa.dupe(u8, name); + break :blk device; + } + }; + device.value = try fmt.parseInt(u64, value, 10); + device.max = try fmt.parseInt(u64, max, 10); +} diff --git a/src/modules/Battery.zig b/src/modules/Battery.zig @@ -0,0 +1,205 @@ +const std = @import("std"); +const fmt = std.fmt; +const mem = std.mem; +const os = std.os; + +const udev = @import("udev"); + +const Module = @import("../modules.zig").Module; +const Event = @import("../Loop.zig").Event; +const render = @import("../render.zig"); +const State = @import("../main.zig").State; +const utils = @import("../utils.zig"); +const Battery = @This(); + +state: *State, +context: *udev.Udev, +timerFd: os.fd_t, +devices: DeviceList, + +const Device = struct { + name: []const u8, + status: []const u8, + capacity: u8, + + pub fn deinit(self: *Device, gpa: mem.Allocator) void { + gpa.free(self.name); + gpa.free(self.status); + } +}; + +const DeviceList = std.ArrayList(Device); + +pub fn init(state: *State) !Battery { + const tfd = os.linux.timerfd_create( + os.CLOCK.MONOTONIC, + os.linux.TFD.CLOEXEC, + ); + const interval: os.linux.itimerspec = .{ + .it_interval = .{ .tv_sec = 10, .tv_nsec = 0 }, + .it_value = .{ .tv_sec = 10, .tv_nsec = 0 }, + }; + _ = os.linux.timerfd_settime(@intCast(i32, tfd), 0, &interval, null); + + const context = try udev.Udev.new(); + + var devices = DeviceList.init(state.gpa); + try updateDevices(state.gpa, context, &devices); + if (devices.items.len == 0) return error.NoDevicesFound; + + return Battery{ + .state = state, + .context = context, + .timerFd = @intCast(os.fd_t, tfd), + .devices = devices, + }; +} + +pub fn deinit(self: *Battery) void { + _ = self.context.unref(); + for (self.devices.items) |*device| { + device.deinit(self.state.gpa); + } + self.devices.deinit(); +} + +pub fn module(self: *Battery) Module { + return .{ + .impl = @ptrCast(*anyopaque, self), + .eventFn = getEvent, + .printFn = print, + }; +} + +pub fn getEvent(self_opaque: *anyopaque) !Event { + const self = utils.cast(Battery)(self_opaque); + + return Event{ + .fd = .{ + .fd = self.timerFd, + .events = os.POLL.IN, + .revents = undefined, + }, + .data = self_opaque, + .callbackIn = callbackIn, + .callbackOut = Event.noop, + }; +} + +fn callbackIn(self_opaque: *anyopaque) error{Terminate}!void { + const self = utils.cast(Battery)(self_opaque); + + var expirations = mem.zeroes([8]u8); + _ = os.read(self.timerFd, &expirations) catch return; + + for (self.state.wayland.monitors.items) |monitor| { + if (monitor.surface) |surface| { + if (surface.configured) { + render.renderClock(surface) catch continue; + render.renderModules(surface) catch continue; + surface.clockSurface.commit(); + surface.modulesSurface.commit(); + surface.backgroundSurface.commit(); + } + } + } +} + +pub fn print(self_opaque: *anyopaque, writer: Module.StringWriter) !void { + const self = utils.cast(Battery)(self_opaque); + + try updateDevices(self.state.gpa, self.context, &self.devices); + const device = self.devices.items[0]; + + var icon: []const u8 = "❓"; + if (mem.eql(u8, device.status, "Discharging")) { + icon = "🔋"; + } else if (mem.eql(u8, device.status, "Charging")) { + icon = "🔌"; + } else if (mem.eql(u8, device.status, "Full")) { + icon = "⚡"; + } + + try fmt.format(writer, "{s} {d}%", .{ icon, device.capacity }); +} + +fn updateDevices( + gpa: mem.Allocator, + context: *udev.Udev, + devices: *DeviceList, +) !void { + const enumerate = try udev.Enumerate.new(context); + defer _ = enumerate.unref(); + + try enumerate.addMatchSubsystem("power_supply"); + try enumerate.addMatchSysattr("type", "Battery"); + try enumerate.scanDevices(); + + const entries = enumerate.getListEntry(); + + var maybe_entry = entries; + while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { + const path = entry.getName(); + const device = try udev.Device.newFromSyspath(context, path); + try updateOrAppend(gpa, devices, device); + } +} + +fn updateOrAppend( + gpa: mem.Allocator, + devices: *DeviceList, + dev: *udev.Device, +) !void { + const name = dev.getSysname() catch return; + const status = dev.getSysattrValue("status") catch return; + const capacity = getCapacity(dev) catch return; + + const device = blk: { + for (devices.items) |*device| { + if (mem.eql(u8, device.name, name)) { + gpa.free(device.status); + break :blk device; + } + } else { + const device = try devices.addOne(); + device.name = try gpa.dupe(u8, name); + break :blk device; + } + }; + + device.status = try gpa.dupe(u8, status); + device.capacity = capacity; +} + +fn getCapacity(dev: *udev.Device) !u8 { + const capacity_str = dev.getSysattrValue("capacity") catch { + return computeCapacityFromCharge(dev) catch { + return computeCapacityFromEnergy(dev); + }; + }; + + const capacity = try fmt.parseInt(u8, capacity_str, 10); + return capacity; +} + +fn computeCapacityFromEnergy(dev: *udev.Device) !u8 { + const energy_str = try dev.getSysattrValue("energy_now"); + const energy_full_str = try dev.getSysattrValue("energy_full"); + + const energy = try fmt.parseFloat(f64, energy_str); + const energy_full = try fmt.parseFloat(f64, energy_full_str); + + const capacity = energy * 100.0 / energy_full; + return @floatToInt(u8, @round(capacity)); +} + +fn computeCapacityFromCharge(dev: *udev.Device) !u8 { + const charge_str = try dev.getSysattrValue("charge_now"); + const charge_full_str = try dev.getSysattrValue("charge_full"); + + const charge = try fmt.parseFloat(f64, charge_str); + const charge_full = try fmt.parseFloat(f64, charge_full_str); + + const capacity = charge * 100.0 / charge_full; + return @floatToInt(u8, @round(capacity)); +} diff --git a/src/render.zig b/src/render.zig @@ -3,23 +3,22 @@ const mem = std.mem; const fcft = @import("fcft"); const pixman = @import("pixman"); +const time = @cImport(@cInclude("time.h")); -const Buffer = @import("shm.zig").Buffer; -const c = @import("c.zig"); +const Buffer = @import("Buffer.zig"); const State = @import("main.zig").State; -const Surface = @import("wayland.zig").Surface; -const Tag = @import("tags.zig").Tag; -const Tags = @import("tags.zig").Tags; +const Surface = @import("Surface.zig"); +const Tag = @import("Tags.zig").Tag; pub const RenderFn = fn (*Surface) anyerror!void; pub fn renderBackground(surface: *Surface) !void { - const state = surface.output.state; + const state = surface.monitor.state; const wlSurface = surface.backgroundSurface; const buffer = try Buffer.nextBuffer( &surface.backgroundBuffers, - state.wayland.shm, + state.wayland.globals.shm, surface.width, surface.height, ); @@ -31,19 +30,19 @@ pub fn renderBackground(surface: *Surface) !void { const color = &state.config.backgroundColor; _ = pixman.Image.fillRectangles(.src, buffer.pix.?, color, 1, &area); - wlSurface.setBufferScale(surface.output.scale); + wlSurface.setBufferScale(surface.monitor.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 state = surface.monitor.state; const wlSurface = surface.tagsSurface; - const tags = surface.output.tags.tags; + const tags = surface.monitor.tags.tags; const buffer = try Buffer.nextBuffer( &surface.tagsBuffers, - surface.output.state.wayland.shm, + surface.monitor.state.wayland.globals.shm, surface.width, surface.height, ); @@ -54,18 +53,18 @@ pub fn renderTags(surface: *Surface) !void { try renderTag(buffer.pix.?, tag, surface.height, offset, state); } - wlSurface.setBufferScale(surface.output.scale); + wlSurface.setBufferScale(surface.monitor.scale); wlSurface.damageBuffer(0, 0, surface.width, surface.height); wlSurface.attach(buffer.buffer, 0, 0); } pub fn renderClock(surface: *Surface) !void { - const state = surface.output.state; + const state = surface.monitor.state; const wlSurface = surface.clockSurface; const buffer = try Buffer.nextBuffer( &surface.clockBuffers, - surface.output.state.wayland.shm, + surface.monitor.state.wayland.globals.shm, surface.width, surface.height, ); @@ -80,20 +79,20 @@ pub fn renderClock(surface: *Surface) !void { // get formatted datetime const str = try formatDatetime(state); - defer state.allocator.free(str); + defer state.gpa.free(str); // ut8 encoding const utf8 = try std.unicode.Utf8View.init(str); var utf8_iter = utf8.iterator(); - var runes = try state.allocator.alloc(u32, str.len); - defer state.allocator.free(runes); + var runes = try state.gpa.alloc(u32, str.len); + defer state.gpa.free(runes); var i: usize = 0; while (utf8_iter.nextCodepoint()) |rune| : (i += 1) { runes[i] = rune; } - runes = state.allocator.resize(runes, i).?; + runes = state.gpa.resize(runes, i).?; const run = try fcft.TextRun.rasterizeUtf32( state.config.font, @@ -122,25 +121,27 @@ pub fn renderClock(surface: *Surface) !void { x_offset += glyph.advance.x; } - wlSurface.setBufferScale(surface.output.scale); + wlSurface.setBufferScale(surface.monitor.scale); wlSurface.damageBuffer(0, 0, surface.width, surface.height); wlSurface.attach(buffer.buffer, 0, 0); } pub fn renderModules(surface: *Surface) !void { - const state = surface.output.state; + const state = surface.monitor.state; const wlSurface = surface.modulesSurface; const buffer = try Buffer.nextBuffer( &surface.modulesBuffers, - surface.output.state.wayland.shm, + surface.monitor.state.wayland.globals.shm, surface.width, surface.height, ); buffer.busy = true; // compose string - var string = std.ArrayList(u8).init(state.allocator); + var string = std.ArrayList(u8).init(state.gpa); + defer string.deinit(); + const writer = string.writer(); for (state.modules.items) |*module| { try std.fmt.format(writer, " | ", .{}); @@ -151,14 +152,14 @@ pub fn renderModules(surface: *Surface) !void { const utf8 = try std.unicode.Utf8View.init(string.items); var utf8_iter = utf8.iterator(); - var runes = try state.allocator.alloc(u32, string.items.len); - defer state.allocator.free(runes); + var runes = try state.gpa.alloc(u32, string.items.len); + defer state.gpa.free(runes); var i: usize = 0; while (utf8_iter.nextCodepoint()) |rune| : (i += 1) { runes[i] = rune; } - runes = state.allocator.resize(runes, i).?; + runes = state.gpa.resize(runes, i).?; // clear the buffer const bg_area = [_]pixman.Rectangle16{ @@ -196,7 +197,7 @@ pub fn renderModules(surface: *Surface) !void { x_offset += glyph.advance.x; } - wlSurface.setBufferScale(surface.output.scale); + wlSurface.setBufferScale(surface.monitor.scale); wlSurface.damageBuffer(0, 0, surface.width, surface.height); wlSurface.attach(buffer.buffer, 0, 0); } @@ -248,14 +249,14 @@ fn renderTag( } fn formatDatetime(state: *State) ![]const u8 { - var buf = try state.allocator.alloc(u8, 256); - const now = c.time.time(null); - const local = c.time.localtime(&now); - const len = c.time.strftime( + var buf = try state.gpa.alloc(u8, 256); + const now = time.time(null); + const local = time.localtime(&now); + const len = time.strftime( buf.ptr, buf.len, state.config.clockFormat, local, ); - return state.allocator.resize(buf, len).?; + return state.gpa.resize(buf, len).?; } diff --git a/src/shm.zig b/src/shm.zig @@ -1,69 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const os = std.os; - -const wayland = @import("wayland").client; -const wl = wayland.wl; - -const pixman = @import("pixman"); - -pub const Buffer = struct { - data: ?[]align(4096) u8, - buffer: ?*wl.Buffer, - pix: ?*pixman.Image, - - busy: bool, - width: u31, - height: u31, - size: u31, - - pub fn init(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { - self.busy = true; - self.width = width; - self.height = height; - - const fd = try os.memfd_create("levee-wayland-shm-buffer-pool", 0); - defer os.close(fd); - - const stride = width * 4; - self.size = stride * height; - try os.ftruncate(fd, self.size); - - self.data = try os.mmap(null, self.size, os.PROT.READ | os.PROT.WRITE, os.MAP.SHARED, fd, 0); - errdefer os.munmap(self.data.?); - - const pool = try shm.createPool(fd, self.size); - defer pool.destroy(); - - self.buffer = try pool.createBuffer(0, width, height, stride, .argb8888); - errdefer self.buffer.?.destroy(); - self.buffer.?.setListener(*Buffer, listener, self); - - self.pix = pixman.Image.createBitsNoClear(.a8r8g8b8, width, height, @ptrCast([*c]u32, self.data.?), stride); - } - - pub fn deinit(self: *Buffer) void { - if (self.pix) |pix| _ = pix.unref(); - if (self.buffer) |buf| buf.destroy(); - if (self.data) |data| os.munmap(data); - } - - fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void { - switch (event) { - .release => buffer.busy = false, - } - } - - pub fn nextBuffer(pool: *[2]Buffer, shm: *wl.Shm, width: u16, height: u16) !*Buffer { - if (pool[0].busy and pool[1].busy) { - return error.NoAvailableBuffers; - } - const buffer = if (!pool[0].busy) &pool[0] else &pool[1]; - - if (buffer.width != width or buffer.height != height) { - buffer.deinit(); - try buffer.init(shm, width, height); - } - return buffer; - } -}; diff --git a/src/tags.zig b/src/tags.zig @@ -1,93 +0,0 @@ -const std = @import("std"); - -const zriver = @import("wayland").client.zriver; - -const Output = @import("wayland.zig").Output; -const render = @import("render.zig"); -const Seat = @import("wayland.zig").Seat; -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); - const wayland = state.wayland; - - self.output = output; - self.outputStatus = try 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(); - } - } - } - - pub fn handleClick(self: *Tags, x: u32, seat: *Seat) !void { - const state = self.output.state; - const control = state.wayland.control; - - if (self.output.surface) |surface| { - const index = x / surface.height; - const payload = try std.fmt.allocPrintZ( - state.allocator, - "{d}", - .{@as(u32, 1) << @intCast(u5, index)}, - ); - defer state.allocator.free(payload); - - control.addArgument("set-focused-tags"); - control.addArgument(payload); - const callback = try control.runCommand(seat.wlSeat); - _ = callback; - } - } -}; diff --git a/src/utils.zig b/src/utils.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const meta = std.meta; + +pub fn cast(comptime to: type) fn (*anyopaque) *to { + return (struct { + pub fn cast(module: *anyopaque) *to { + return @ptrCast(*to, @alignCast(@alignOf(to), module)); + } + }).cast; +} + +pub fn Mask(comptime container: type) type { + const len = meta.fields(container).len; + return [len]bool; +} diff --git a/src/wayland.zig b/src/wayland.zig @@ -1,5 +1,7 @@ const std = @import("std"); const mem = std.mem; +const meta = std.meta; +const os = std.os; const strcmp = std.cstr.cmp; const ArrayList = std.ArrayList; @@ -7,27 +9,33 @@ const wl = @import("wayland").client.wl; const zwlr = @import("wayland").client.zwlr; const zriver = @import("wayland").client.zriver; -const Buffer = @import("shm.zig").Buffer; +const Buffer = @import("Buffer.zig"); +const Event = @import("Loop.zig").Event; const render = @import("render.zig"); const State = @import("main.zig").State; -const Tags = @import("tags.zig").Tags; +const Surface = @import("Surface.zig"); +const Tags = @import("Tags.zig"); +const utils = @import("utils.zig"); pub const Wayland = struct { state: *State, display: *wl.Display, registry: *wl.Registry, - outputs: ArrayList(*Output), - seats: ArrayList(*Seat), - - compositor: *wl.Compositor, - subcompositor: *wl.Subcompositor, - shm: *wl.Shm, - layerShell: *zwlr.LayerShellV1, - statusManager: *zriver.StatusManagerV1, - control: *zriver.ControlV1, - - globalsRegistered: [6]bool, + monitors: ArrayList(*Monitor), + inputs: ArrayList(*Input), + globals: Globals, + globalsMask: GlobalsMask, + + const Globals = struct { + compositor: *wl.Compositor, + subcompositor: *wl.Subcompositor, + shm: *wl.Shm, + layerShell: *zwlr.LayerShellV1, + statusManager: *zriver.StatusManagerV1, + control: *zriver.ControlV1, + }; + const GlobalsMask = utils.Mask(Globals); pub fn init(state: *State) !Wayland { const display = try wl.Display.connect(null); @@ -36,73 +44,78 @@ pub const Wayland = struct { .state = state, .display = display, .registry = try display.getRegistry(), - .outputs = ArrayList(*Output).init(state.allocator), - .seats = ArrayList(*Seat).init(state.allocator), - .compositor = undefined, - .subcompositor = undefined, - .shm = undefined, - .layerShell = undefined, - .statusManager = undefined, - .control = undefined, - .globalsRegistered = mem.zeroes([6]bool), + .monitors = ArrayList(*Monitor).init(state.gpa), + .inputs = ArrayList(*Input).init(state.gpa), + .globals = undefined, + .globalsMask = mem.zeroes(GlobalsMask), }; } + pub fn deinit(self: *Wayland) void { + for (self.monitors.items) |monitor| monitor.destroy(); + for (self.inputs.items) |input| input.destroy(); + + self.monitors.deinit(); + self.inputs.deinit(); + } + pub fn registerGlobals(self: *Wayland) !void { self.registry.setListener(*State, registryListener, self.state); _ = try self.display.roundtrip(); - for (self.globalsRegistered) |globalRegistered| { - if (!globalRegistered) return error.UnsupportedGlobal; + for (self.globalsMask) |is_registered| { + if (!is_registered) return error.UnsupportedGlobal; } } - fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, state: *State) void { - const wayland = &state.wayland; + pub fn getEvent(self: *Wayland) !Event { + const fd = self.display.getFd(); + + return Event{ + .fd = .{ + .fd = @intCast(os.fd_t, fd), + .events = os.POLL.IN, + .revents = undefined, + }, + .data = @ptrCast(*anyopaque, self), + .callbackIn = dispatch, + .callbackOut = flush, + }; + } + + fn dispatch(self_opaque: *anyopaque) error{Terminate}!void { + const self = utils.cast(Wayland)(self_opaque); + _ = self.display.dispatch() catch return; + } + + fn flush(self_opaque: *anyopaque) error{Terminate}!void { + const self = utils.cast(Wayland)(self_opaque); + _ = self.display.flush() catch return; + } + + fn registryListener( + registry: *wl.Registry, + event: wl.Registry.Event, + state: *State, + ) void { + const self = &state.wayland; switch (event) { - .global => |global| { - const interface = global.interface; - const name = global.name; - - 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[2] = true; - } else if (strcmp(interface, zwlr.LayerShellV1.getInterface().name) == 0) { - wayland.layerShell = registry.bind(name, zwlr.LayerShellV1, 1) catch return; - 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[4] = true; - } else if (strcmp(interface, zriver.ControlV1.getInterface().name) == 0) { - wayland.control = registry.bind(name, zriver.ControlV1, 1) catch return; - wayland.globalsRegistered[5] = 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; - } else if (strcmp(interface, wl.Seat.getInterface().name) == 0) { - const seat = Seat.create(state, registry, name) catch return; - wayland.seats.append(seat) catch return; - } + .global => |g| { + self.bindGlobal(registry, g.interface, g.name) catch return; }, .global_remove => |data| { - for (wayland.outputs.items) |output, i| { - if (output.globalName == data.name) { - output.destroy(); - _ = wayland.outputs.swapRemove(i); + for (self.monitors.items) |monitor, i| { + if (monitor.globalName == data.name) { + monitor.destroy(); + _ = self.monitors.swapRemove(i); break; } } - for (wayland.seats.items) |seat, i| { - if (seat.globalName == data.name) { - seat.destroy(); - _ = wayland.seats.swapRemove(i); + for (self.inputs.items) |input, i| { + if (input.globalName == data.name) { + input.destroy(); + _ = self.inputs.swapRemove(i); break; } } @@ -110,12 +123,55 @@ pub const Wayland = struct { } } + fn bindGlobal( + self: *Wayland, + registry: *wl.Registry, + iface: [*:0]const u8, + name: u32, + ) !void { + if (strcmp(iface, wl.Compositor.getInterface().name) == 0) { + const global = try registry.bind(name, wl.Compositor, 4); + self.setGlobal(global); + } else if (strcmp(iface, wl.Subcompositor.getInterface().name) == 0) { + const global = try registry.bind(name, wl.Subcompositor, 1); + self.setGlobal(global); + } else if (strcmp(iface, wl.Shm.getInterface().name) == 0) { + const global = try registry.bind(name, wl.Shm, 1); + self.setGlobal(global); + } else if (strcmp(iface, zwlr.LayerShellV1.getInterface().name) == 0) { + const global = try registry.bind(name, zwlr.LayerShellV1, 1); + self.setGlobal(global); + } else if (strcmp(iface, zriver.StatusManagerV1.getInterface().name) == 0) { + const global = try registry.bind(name, zriver.StatusManagerV1, 1); + self.setGlobal(global); + } else if (strcmp(iface, zriver.ControlV1.getInterface().name) == 0) { + const global = try registry.bind(name, zriver.ControlV1, 1); + self.setGlobal(global); + } else if (strcmp(iface, wl.Output.getInterface().name) == 0) { + const monitor = try Monitor.create(self.state, registry, name); + try self.monitors.append(monitor); + } else if (strcmp(iface, wl.Seat.getInterface().name) == 0) { + const input = try Input.create(self.state, registry, name); + try self.inputs.append(input); + } + } + + pub fn setGlobal(self: *Wayland, global: anytype) void { + inline for (meta.fields(Globals)) |field, i| { + if (field.field_type == @TypeOf(global)) { + @field(self.globals, field.name) = global; + self.globalsMask[i] = true; + break; + } + } + } + pub fn findSurface(self: *Wayland, wlSurface: ?*wl.Surface) ?*Surface { if (wlSurface == null) { return null; } - for (self.outputs.items) |output| { - if (output.surface) |surface| { + for (self.monitors.items) |monitor| { + if (monitor.surface) |surface| { if (surface.backgroundSurface == wlSurface or surface.tagsSurface == wlSurface or surface.clockSurface == wlSurface or @@ -129,58 +185,58 @@ pub const Wayland = struct { } }; -pub const Output = struct { +pub const Monitor = struct { state: *State, - wlOutput: *wl.Output, + output: *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); + pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Monitor { + const self = try state.gpa.create(Monitor); self.state = state; - self.wlOutput = try registry.bind(name, wl.Output, 3); + self.output = 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); + self.output.setListener(*Monitor, listener, self); return self; } - pub fn destroy(self: *Output) void { + pub fn destroy(self: *Monitor) void { if (self.surface) |surface| { surface.destroy(); } self.tags.destroy(); - self.state.allocator.destroy(self); + self.state.gpa.destroy(self); } - fn listener(_: *wl.Output, event: wl.Output.Event, output: *Output) void { + fn listener(_: *wl.Output, event: wl.Output.Event, monitor: *Monitor) void { switch (event) { .scale => |scale| { - output.scale = scale.factor; + monitor.scale = scale.factor; }, .geometry => {}, .mode => {}, .name => {}, .description => {}, .done => { - if (output.surface) |_| {} else { - output.surface = Surface.create(output) catch return; + if (monitor.surface) |_| {} else { + monitor.surface = Surface.create(monitor) catch return; } }, } } }; -pub const Seat = struct { +pub const Input = struct { state: *State, - wlSeat: *wl.Seat, + seat: *wl.Seat, globalName: u32, pointer: struct { @@ -190,40 +246,40 @@ pub const Seat = struct { surface: ?*Surface, }, - pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Seat { - const self = try state.allocator.create(Seat); + pub fn create(state: *State, registry: *wl.Registry, name: u32) !*Input { + const self = try state.gpa.create(Input); self.state = state; - self.wlSeat = try registry.bind(name, wl.Seat, 3); + self.seat = try registry.bind(name, wl.Seat, 3); self.globalName = name; self.pointer.wlPointer = null; self.pointer.surface = null; - self.wlSeat.setListener(*Seat, listener, self); + self.seat.setListener(*Input, listener, self); return self; } - pub fn destroy(self: *Seat) void { + pub fn destroy(self: *Input) void { if (self.pointer.wlPointer) |wlPointer| { wlPointer.release(); } - self.wlSeat.release(); - self.state.allocator.destroy(self); + self.seat.release(); + self.state.gpa.destroy(self); } - fn listener(wlSeat: *wl.Seat, event: wl.Seat.Event, seat: *Seat) void { + fn listener(seat: *wl.Seat, event: wl.Seat.Event, input: *Input) void { switch (event) { .capabilities => |data| { - if (seat.pointer.wlPointer) |wlPointer| { + if (input.pointer.wlPointer) |wlPointer| { wlPointer.release(); - seat.pointer.wlPointer = null; + input.pointer.wlPointer = null; } if (data.capabilities.pointer) { - seat.pointer.wlPointer = wlSeat.getPointer() catch return; - seat.pointer.wlPointer.?.setListener( - *Seat, + input.pointer.wlPointer = seat.getPointer() catch return; + input.pointer.wlPointer.?.setListener( + *Input, pointerListener, - seat, + input, ); } }, @@ -234,30 +290,30 @@ pub const Seat = struct { fn pointerListener( _: *wl.Pointer, event: wl.Pointer.Event, - seat: *Seat, + input: *Input, ) void { switch (event) { .enter => |data| { - seat.pointer.x = data.surface_x.toInt(); - seat.pointer.y = data.surface_y.toInt(); - const surface = seat.state.wayland.findSurface(data.surface); - seat.pointer.surface = surface; + input.pointer.x = data.surface_x.toInt(); + input.pointer.y = data.surface_y.toInt(); + const surface = input.state.wayland.findSurface(data.surface); + input.pointer.surface = surface; }, .leave => |_| { - seat.pointer.surface = null; + input.pointer.surface = null; }, .motion => |data| { - seat.pointer.x = data.surface_x.toInt(); - seat.pointer.y = data.surface_y.toInt(); + input.pointer.x = data.surface_x.toInt(); + input.pointer.y = data.surface_y.toInt(); }, .button => |data| { if (data.state != .pressed) return; - if (seat.pointer.surface) |surface| { + if (input.pointer.surface) |surface| { if (!surface.configured) return; - const x = @intCast(u32, seat.pointer.x); + const x = @intCast(u32, input.pointer.x); if (x < surface.height * 9) { - surface.output.tags.handleClick(x, seat) catch return; + surface.monitor.tags.handleClick(x, input) catch return; } } }, @@ -265,141 +321,3 @@ pub const Seat = struct { } } }; - -pub const Surface = struct { - output: *Output, - - backgroundSurface: *wl.Surface, - layerSurface: *zwlr.LayerSurfaceV1, - backgroundBuffers: [2]Buffer, - - tagsSurface: *wl.Surface, - tagsSubsurface: *wl.Subsurface, - tagsBuffers: [2]Buffer, - - clockSurface: *wl.Surface, - clockSubsurface: *wl.Subsurface, - clockBuffers: [2]Buffer, - - modulesSurface: *wl.Surface, - modulesSubsurface: *wl.Subsurface, - modulesBuffers: [2]Buffer, - - configured: bool, - width: u16, - height: u16, - - pub fn create(output: *Output) !*Surface { - const state = output.state; - const wayland = state.wayland; - - const self = try state.allocator.create(Surface); - self.output = output; - self.configured = false; - - self.backgroundSurface = try wayland.compositor.createSurface(); - self.layerSurface = try wayland.layerShell.getLayerSurface( - self.backgroundSurface, - output.wlOutput, - .top, - "levee", - ); - self.backgroundBuffers = mem.zeroes([2]Buffer); - - self.tagsSurface = try wayland.compositor.createSurface(); - self.tagsSubsurface = try wayland.subcompositor.getSubsurface( - self.tagsSurface, - self.backgroundSurface, - ); - self.tagsBuffers = mem.zeroes([2]Buffer); - - self.clockSurface = try wayland.compositor.createSurface(); - self.clockSubsurface = try wayland.subcompositor.getSubsurface( - self.clockSurface, - self.backgroundSurface, - ); - self.clockBuffers = mem.zeroes([2]Buffer); - - self.modulesSurface = try wayland.compositor.createSurface(); - self.modulesSubsurface = try wayland.subcompositor.getSubsurface( - self.modulesSurface, - self.backgroundSurface, - ); - self.modulesBuffers = mem.zeroes([2]Buffer); - - // setup layer surface - self.layerSurface.setSize(0, state.config.height); - self.layerSurface.setAnchor( - .{ .top = true, .left = true, .right = true, .bottom = false }, - ); - 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); - self.clockSubsurface.setPosition(0, 0); - self.modulesSubsurface.setPosition(0, 0); - - self.tagsSurface.commit(); - self.clockSurface.commit(); - self.backgroundSurface.commit(); - - return self; - } - - pub fn destroy(self: *Surface) void { - self.output.surface = null; - - self.backgroundSurface.destroy(); - self.layerSurface.destroy(); - self.backgroundBuffers[0].deinit(); - self.backgroundBuffers[1].deinit(); - - self.tagsSurface.destroy(); - self.tagsSubsurface.destroy(); - self.tagsBuffers[0].deinit(); - self.tagsBuffers[1].deinit(); - - self.clockSurface.destroy(); - self.clockSubsurface.destroy(); - self.clockBuffers[0].deinit(); - self.clockBuffers[1].deinit(); - - self.modulesSurface.destroy(); - self.modulesSubsurface.destroy(); - self.modulesBuffers[0].deinit(); - self.modulesBuffers[1].deinit(); - - self.output.state.allocator.destroy(self); - } - - fn layerSurfaceListener( - layerSurface: *zwlr.LayerSurfaceV1, - event: zwlr.LayerSurfaceV1.Event, - surface: *Surface, - ) void { - switch (event) { - .configure => |data| { - surface.configured = true; - surface.width = @intCast(u16, data.width); - surface.height = @intCast(u16, data.height); - - layerSurface.ackConfigure(data.serial); - - render.renderBackground(surface) catch return; - render.renderTags(surface) catch return; - render.renderClock(surface) catch return; - render.renderModules(surface) catch return; - - surface.tagsSurface.commit(); - surface.clockSurface.commit(); - surface.modulesSurface.commit(); - surface.backgroundSurface.commit(); - }, - .closed => { - surface.destroy(); - }, - } - } -};