stevee

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

commit a7c0ffdebf456978521b8a94ff738e801b1a8ef6
parent dc7741307dcf774cf5f7c8432d7c091512dbf0cf
Author: Andrea Feletto <andrea@andreafeletto.com>
Date:   Sat, 24 Aug 2024 18:45:29 +0200

update to zig 0.13.0

Diffstat:
M.build.yml | 6+++---
M.gitignore | 5+++--
Mbuild.zig | 31++++++++++++++-----------------
Abuild.zig.zon | 25+++++++++++++++++++++++++
Aflake.lock | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflake.nix | 43+++++++++++++++++++++++++++++++++++++++++++
Anix/deps.nix | 34++++++++++++++++++++++++++++++++++
Anix/package.nix | 36++++++++++++++++++++++++++++++++++++
Msrc/Buffer.zig | 15++++++---------
Msrc/Loop.zig | 59+++++++++++++++++++++++++++++------------------------------
Msrc/Modules.zig | 9++++++++-
Msrc/Wayland.zig | 26+++++++++++---------------
Asrc/modules/Backlight.zig | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/modules/Battery.zig | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/modules/Pulse.zig | 32++++++++++++++------------------
Msrc/render.zig | 1+
16 files changed, 578 insertions(+), 95 deletions(-)

diff --git a/.build.yml b/.build.yml @@ -12,9 +12,9 @@ secrets: - 8a791f75-2c57-4cd6-8cae-710da7d992cc tasks: - zig: | - curl -O https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz - tar xf zig-linux-x86_64-0.11.0.tar.xz - mv zig-linux-x86_64-0.11.0 zig + curl -O https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz + tar xf zig-linux-x86_64-0.13.0.tar.xz + mv zig-linux-x86_64-0.13.0 zig - submodules: | cd levee git submodule update --init diff --git a/.gitignore b/.gitignore @@ -1,2 +1,3 @@ -zig-cache/ -zig-out/ +.zig-cache +zig-out +result diff --git a/build.zig b/build.zig @@ -1,11 +1,18 @@ const std = @import("std"); -const Scanner = @import("deps/zig-wayland/build.zig").Scanner; +const Scanner = @import("wayland").Scanner; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const exe = b.addExecutable(.{ + .name = "stevee", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + const scanner = Scanner.create(b, .{}); const wayland = b.createModule(.{ .root_source_file = scanner.result }); @@ -27,23 +34,13 @@ pub fn build(b: *std.Build) void { scanner.generate("zriver_status_manager_v1", 1); scanner.generate("zriver_control_v1", 1); - const pixman = b.createModule(.{ - .root_source_file = .{ .path = "deps/zig-pixman/pixman.zig" }, - }); - const fcft = b.createModule(.{ - .root_source_file = .{ .path = "deps/zig-fcft/fcft.zig" }, - }); - fcft.addImport("pixman", pixman); - - const exe = b.addExecutable(.{ - .name = "stevee", - .root_source_file = .{ .path = "src/main.zig" }, - .target = target, - .optimize = optimize, - }); + const pixman = b.dependency("pixman", .{}); + const fcft = b.dependency("fcft", .{}); + const udev = b.dependency("udev", .{}); - exe.root_module.addImport("fcft", fcft); - exe.root_module.addImport("pixman", pixman); + exe.root_module.addImport("pixman", pixman.module("pixman")); + exe.root_module.addImport("fcft", fcft.module("fcft")); + exe.root_module.addImport("udev", udev.module("udev")); exe.root_module.addImport("wayland", wayland); exe.linkLibC(); diff --git a/build.zig.zon b/build.zig.zon @@ -0,0 +1,25 @@ +.{ + .name = "levee", + .version = "0.1.4", + .paths = .{ + ".", + }, + .dependencies = .{ + .wayland = .{ + .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.2.0.tar.gz", + .hash = "1220687c8c47a48ba285d26a05600f8700d37fc637e223ced3aa8324f3650bf52242", + }, + .pixman = .{ + .url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.2.0.tar.gz", + .hash = "12209db20ce873af176138b76632931def33a10539387cba745db72933c43d274d56", + }, + .fcft = .{ + .url = "https://git.sr.ht/~novakane/zig-fcft/archive/40691ff2df73ff09724d19791c8da8f966a95c6a.tar.gz", + .hash = "1220a4029ee3ee70d3175c69878e2b70dccd000c4324bc74ba800d8a143b7250fb38", + }, + .udev = .{ + .url = "https://git.sr.ht/~andreafeletto/zig-udev/archive/442a1c2b6c9f1f672c234b9ee977e4d3c2408f9a.tar.gz", + .hash = "122064e2c4230105728ba521c61ad545bfabbe79987d316756fb8a9477b1609df889", + }, + }, +} diff --git a/flake.lock b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1724224976, + "narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c374d94f1536013ca8e92341b540eba4c22f9c62", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix @@ -0,0 +1,43 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/default-linux"; + flake-utils = { + url = "github:numtide/flake-utils"; + inputs.systems.follows = "systems"; + }; + }; + + outputs = { nixpkgs, flake-utils, ... }: + let + overlays = [ + (final: prev: { + levee = prev.callPackage ./nix/package.nix {}; + }) + ]; + in + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit overlays system; }; + in + { + packages.default = pkgs.levee; + + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + zig + pkg-config + + wayland + wayland-protocols + wayland-scanner + + fcft + libpulseaudio + pixman + systemdLibs + ]; + }; + } + ); +} diff --git a/nix/deps.nix b/nix/deps.nix @@ -0,0 +1,34 @@ +# generated by zon2nix (https://github.com/nix-community/zon2nix) + +{ linkFarm, fetchzip }: + +linkFarm "zig-packages" [ + { + name = "122064e2c4230105728ba521c61ad545bfabbe79987d316756fb8a9477b1609df889"; + path = fetchzip { + url = "https://git.sr.ht/~andreafeletto/zig-udev/archive/442a1c2b6c9f1f672c234b9ee977e4d3c2408f9a.tar.gz"; + hash = "sha256-y7qpx2Rs48JVyLcU7bfZNXx7zZxnHiloH6pk1ZkaDGA="; + }; + } + { + name = "1220687c8c47a48ba285d26a05600f8700d37fc637e223ced3aa8324f3650bf52242"; + path = fetchzip { + url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.2.0.tar.gz"; + hash = "sha256-dvit+yvc0MnipqWjxJdfIsA6fJaJZOaIpx4w4woCxbE="; + }; + } + { + name = "12209db20ce873af176138b76632931def33a10539387cba745db72933c43d274d56"; + path = fetchzip { + url = "https://codeberg.org/ifreund/zig-pixman/archive/v0.2.0.tar.gz"; + hash = "sha256-zcfZEMnipWDPuptl9UN0PoaJDjy2EHc7Wwi4GQq3hkY="; + }; + } + { + name = "1220a4029ee3ee70d3175c69878e2b70dccd000c4324bc74ba800d8a143b7250fb38"; + path = fetchzip { + url = "https://git.sr.ht/~novakane/zig-fcft/archive/40691ff2df73ff09724d19791c8da8f966a95c6a.tar.gz"; + hash = "sha256-JAR6Ticav9l/3aemJWGsjXMEUyHjuhUr3L0fznnLoEY="; + }; + } +] diff --git a/nix/package.nix b/nix/package.nix @@ -0,0 +1,36 @@ +{ stdenv +, callPackage +, zig_0_13 +, pkg-config +, wayland +, wayland-protocols +, wayland-scanner +, fcft +, libpulseaudio +, pixman +, systemdLibs +}: +stdenv.mkDerivation { + pname = "levee"; + version = "0.1.4"; + + src = ./..; + + nativeBuildInputs = [ + zig_0_13.hook + pkg-config + + wayland + wayland-protocols + wayland-scanner + + fcft + libpulseaudio + pixman + systemdLibs + ]; + + postPatch = '' + ln -s ${callPackage ./deps.nix {}} $ZIG_GLOBAL_CACHE_DIR/p + ''; +} diff --git a/src/Buffer.zig b/src/Buffer.zig @@ -1,7 +1,4 @@ const std = @import("std"); -const mem = std.mem; -const posix = std.posix; -const linux = std.os.linux; const pixman = @import("pixman"); const wl = @import("wayland").client.wl; @@ -25,15 +22,15 @@ pub fn resize(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { self.width = width; self.height = height; - const fd = try posix.memfd_create("stevee-shm", linux.MFD.CLOEXEC); - defer posix.close(fd); + const fd = try std.posix.memfd_create("levee-shm", std.os.linux.MFD.CLOEXEC); + defer std.posix.close(fd); const stride = width * 4; self.size = stride * height; - try posix.ftruncate(fd, self.size); + try std.posix.ftruncate(fd, self.size); - self.mmap = try posix.mmap(null, self.size, posix.PROT.READ | posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0); - self.data = mem.bytesAsSlice(u32, self.mmap.?); + self.mmap = try std.posix.mmap(null, self.size, std.posix.PROT.READ | std.posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0); + self.data = std.mem.bytesAsSlice(u32, self.mmap.?); const pool = try shm.createPool(fd, self.size); defer pool.destroy(); @@ -48,7 +45,7 @@ pub fn resize(self: *Buffer, shm: *wl.Shm, width: u31, height: u31) !void { pub fn deinit(self: *Buffer) void { if (self.pix) |pix| _ = pix.unref(); if (self.buffer) |buf| buf.destroy(); - if (self.mmap) |mmap| posix.munmap(mmap); + if (self.mmap) |mmap| std.posix.munmap(mmap); } fn listener(_: *wl.Buffer, event: wl.Buffer.Event, buffer: *Buffer) void { diff --git a/src/Loop.zig b/src/Loop.zig @@ -1,8 +1,4 @@ const std = @import("std"); -const log = std.log; -const mem = std.mem; -const posix = std.posix; -const linux = std.os.linux; const utils = @import("utils.zig"); const Loop = @This(); @@ -10,16 +6,16 @@ const Loop = @This(); const state = &@import("root").state; const render = @import("render.zig"); -sfd: posix.fd_t, +sfd: std.posix.fd_t, pub fn init() !Loop { - var mask = posix.empty_sigset; - linux.sigaddset(&mask, linux.SIG.INT); - linux.sigaddset(&mask, linux.SIG.TERM); - linux.sigaddset(&mask, linux.SIG.QUIT); + var mask = std.posix.empty_sigset; + std.os.linux.sigaddset(&mask, std.os.linux.SIG.INT); + std.os.linux.sigaddset(&mask, std.os.linux.SIG.TERM); + std.os.linux.sigaddset(&mask, std.os.linux.SIG.QUIT); - _ = linux.sigprocmask(linux.SIG.BLOCK, &mask, null); - const sfd = linux.signalfd(-1, &mask, linux.SFD.NONBLOCK); + _ = std.os.linux.sigprocmask(std.os.linux.SIG.BLOCK, &mask, null); + const sfd = std.os.linux.signalfd(-1, &mask, std.os.linux.SFD.NONBLOCK); return Loop{ .sfd = @intCast(sfd) }; } @@ -28,20 +24,25 @@ pub fn run(self: *Loop) !void { const wayland = &state.wayland; const modules = &state.modules; - var fds = [_]posix.pollfd{ + var fds = [_]std.posix.pollfd{ .{ .fd = self.sfd, - .events = posix.POLL.IN, + .events = std.posix.POLL.IN, .revents = undefined, }, .{ .fd = wayland.fd, - .events = posix.POLL.IN, + .events = std.posix.POLL.IN, + .revents = undefined, + }, + .{ + .fd = if (modules.battery) |mod| mod.fd else -1, + .events = std.posix.POLL.IN, .revents = undefined, }, .{ .fd = if (modules.pulse) |mod| mod.fd else -1, - .events = posix.POLL.IN, + .events = std.posix.POLL.IN, .revents = undefined, }, }; @@ -59,36 +60,34 @@ pub fn run(self: *Loop) !void { if (ret == .SUCCESS) break; } - const poll_result = posix.poll(&fds, timeout_ms) catch |err| { - log.err("poll failed: {s}", .{@errorName(err)}); + _ = std.posix.poll(&fds, -1) catch |err| { + std.log.err("poll failed: {s}", .{@errorName(err)}); return; }; - if (poll_result > 0) { - for (fds) |fd| { - if (fd.revents & posix.POLL.HUP != 0 or fd.revents & posix.POLL.ERR != 0) { - return; - } - } - - // signals - if (fds[0].revents & posix.POLL.IN != 0) { + for (fds) |fd| { + if (fd.revents & std.posix.POLL.HUP != 0 or fd.revents & std.posix.POLL.ERR != 0) { return; } // wayland - if (fds[1].revents & posix.POLL.IN != 0) { + if (fds[1].revents & std.posix.POLL.IN != 0) { const errno = wayland.display.dispatch(); if (errno != .SUCCESS) return; } - if (fds[1].revents & posix.POLL.OUT != 0) { + if (fds[1].revents & std.posix.POLL.OUT != 0) { const errno = wayland.display.flush(); if (errno != .SUCCESS) return; } // modules - if (modules.pulse) |*mod| if (fds[2].revents & posix.POLL.IN != 0) { - log.info("pulse", .{}); + if (modules.pulse) |*mod| if (fds[2].revents & std.posix.POLL.IN != 0) { + std.log.info("pulse", .{}); + mod.refresh() catch return; + }; + + if (modules.battery) |*mod| if (fds[3].revents & std.posix.POLL.IN != 0) { + std.log.info("battery", .{}); mod.refresh() catch return; }; } diff --git a/src/Modules.zig b/src/Modules.zig @@ -5,10 +5,13 @@ const ArrayList = std.ArrayList; const Modules = @This(); const Pulse = @import("modules/Pulse.zig"); +const Battery = @import("modules/Battery.zig"); var state = &@import("root").state; -const Tag = enum { pulse }; +const Tag = enum { pulse, battery }; + +battery: ?Battery = null, pulse: ?Pulse = null, order: ArrayList(Tag), @@ -19,6 +22,7 @@ pub fn init() Modules { pub fn deinit(self: *Modules) void { if (self.pulse) |*mod| mod.deinit(); + if (self.battery) |*mod| mod.deinit(); self.order.deinit(); } @@ -27,6 +31,9 @@ pub fn register(self: *Modules, name: []const u8) !void { self.pulse = try Pulse.init(); try self.pulse.?.start(); try self.order.append(.pulse); + } else if (mem.eql(u8, name, "battery")) { + self.battery = try Battery.init(); + try self.order.append(.battery); } else { return error.UnknownModule; } diff --git a/src/Wayland.zig b/src/Wayland.zig @@ -1,8 +1,4 @@ const std = @import("std"); -const log = std.log; -const mem = std.mem; -const meta = std.meta; -const posix = std.posix; const wl = @import("wayland").client.wl; const wp = @import("wayland").client.wp; @@ -19,7 +15,7 @@ const Wayland = @This(); const state = &@import("root").state; display: *wl.Display, -fd: posix.fd_t, +fd: std.posix.fd_t, compositor: ?*wl.Compositor = null, subcompositor: ?*wl.Subcompositor = null, @@ -113,26 +109,26 @@ fn registryListener(registry: *wl.Registry, event: wl.Registry.Event, self: *Way } fn bindGlobal(self: *Wayland, registry: *wl.Registry, name: u32, iface: [*:0]const u8) !void { - if (mem.orderZ(u8, iface, wl.Compositor.getInterface().name) == .eq) { + if (std.mem.orderZ(u8, iface, wl.Compositor.getInterface().name) == .eq) { self.compositor = try registry.bind(name, wl.Compositor, 4); - } else if (mem.orderZ(u8, iface, wl.Subcompositor.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, wl.Subcompositor.getInterface().name) == .eq) { self.subcompositor = try registry.bind(name, wl.Subcompositor, 1); - } else if (mem.orderZ(u8, iface, wl.Shm.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, wl.Shm.getInterface().name) == .eq) { self.shm = try registry.bind(name, wl.Shm, 1); - } else if (mem.orderZ(u8, iface, wp.Viewporter.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, wp.Viewporter.getInterface().name) == .eq) { self.viewporter = try registry.bind(name, wp.Viewporter, 1); - } else if (mem.orderZ(u8, iface, wp.SinglePixelBufferManagerV1.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, wp.SinglePixelBufferManagerV1.getInterface().name) == .eq) { self.single_pixel_buffer_manager = try registry.bind(name, wp.SinglePixelBufferManagerV1, 1); - } else if (mem.orderZ(u8, iface, zwlr.LayerShellV1.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, zwlr.LayerShellV1.getInterface().name) == .eq) { self.layer_shell = try registry.bind(name, zwlr.LayerShellV1, 1); - } else if (mem.orderZ(u8, iface, zriver.StatusManagerV1.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, zriver.StatusManagerV1.getInterface().name) == .eq) { self.status_manager = try registry.bind(name, zriver.StatusManagerV1, 1); - } else if (mem.orderZ(u8, iface, zriver.ControlV1.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, zriver.ControlV1.getInterface().name) == .eq) { self.control = try registry.bind(name, zriver.ControlV1, 1); - } else if (mem.orderZ(u8, iface, wl.Output.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, wl.Output.getInterface().name) == .eq) { const monitor = try Monitor.create(registry, name); try self.monitors.append(monitor); - } else if (mem.orderZ(u8, iface, wl.Seat.getInterface().name) == .eq) { + } else if (std.mem.orderZ(u8, iface, wl.Seat.getInterface().name) == .eq) { const input = try Input.create(registry, name); try self.inputs.append(input); } diff --git a/src/modules/Backlight.zig b/src/modules/Backlight.zig @@ -0,0 +1,115 @@ +const std = @import("std"); + +const udev = @import("udev"); + +const render = @import("../render.zig"); +const utils = @import("../utils.zig"); +const Backlight = @This(); + +const state = &@import("root").state; + +context: *udev.Udev, +monitor: *udev.Monitor, +fd: std.posix.fd_t, +devices: DeviceList, + +const Device = struct { + name: []const u8, + value: u64, + max: u64, +}; + +const DeviceList = std.ArrayList(Device); + +pub fn init() !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); + + return Backlight{ + .context = context, + .monitor = monitor, + .fd = try monitor.getFd(), + .devices = devices, + }; +} + +pub fn deinit(self: *Backlight) void { + _ = self.context.unref(); + for (self.devices.items) |*device| { + state.gpa.free(device.name); + } + self.devices.deinit(); +} + +pub fn refresh(self: *Backlight) !void { + _ = try self.monitor.receiveDevice(); + + for (state.wayland.monitors.items) |monitor| { + if (monitor.bar) |bar| { + if (bar.configured) { + render.renderModules(bar) catch continue; + bar.modules.surface.commit(); + bar.background.surface.commit(); + } + } + } +} + +pub fn print(self: *Backlight, writer: anytype) !void { + try updateDevices(state.gpa, self.context, &self.devices); + const device = self.devices.items[0]; + var percent = @as(f64, @floatFromInt(device.value)) * 100.0; + percent /= @as(f64, @floatFromInt(device.max)); + const value = @as(u8, @intFromFloat(@round(percent))); + + try writer.print("💡 {d}%", .{value}); +} + +fn updateDevices( + gpa: std.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: std.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 (std.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 std.fmt.parseInt(u64, value, 10); + device.max = try std.fmt.parseInt(u64, max, 10); +} diff --git a/src/modules/Battery.zig b/src/modules/Battery.zig @@ -0,0 +1,172 @@ +const std = @import("std"); + +const udev = @import("udev"); + +const Module = @import("../Modules.zig").Module; +const Event = @import("../Loop.zig").Event; +const render = @import("../render.zig"); +const utils = @import("../utils.zig"); +const Battery = @This(); + +const state = &@import("root").state; + +context: *udev.Udev, +fd: std.posix.fd_t, +devices: DeviceList, + +const Device = struct { + name: []const u8, + status: []const u8, + capacity: u8, +}; + +const DeviceList = std.ArrayList(Device); + +pub fn init() !Battery { + const tfd = tfd: { + const fd = try std.posix.timerfd_create( + std.posix.CLOCK.MONOTONIC, + .{ .CLOEXEC = true }, + ); + const interval: std.os.linux.itimerspec = .{ + .it_interval = .{ .tv_sec = 10, .tv_nsec = 0 }, + .it_value = .{ .tv_sec = 10, .tv_nsec = 0 }, + }; + try std.posix.timerfd_settime(@intCast(fd), .{}, &interval, null); + break :tfd @as(std.posix.fd_t, @intCast(fd)); + }; + + const context = try udev.Udev.new(); + + var devices = DeviceList.init(state.gpa); + try updateDevices(state.gpa, context, &devices); + + return Battery{ + .context = context, + .fd = tfd, + .devices = devices, + }; +} + +pub fn deinit(self: *Battery) void { + _ = self.context.unref(); + for (self.devices.items) |*device| { + state.gpa.free(device.name); + state.gpa.free(device.status); + } + self.devices.deinit(); +} + +pub fn print(self: *Battery, writer: anytype) !void { + try updateDevices(state.gpa, self.context, &self.devices); + const device = self.devices.items[0]; + + var icon: []const u8 = "❓"; + if (std.mem.eql(u8, device.status, "Discharging")) { + icon = "🔋"; + } else if (std.mem.eql(u8, device.status, "Charging")) { + icon = "🔌"; + } else if (std.mem.eql(u8, device.status, "Full")) { + icon = "⚡"; + } + + try std.fmt.format(writer, "{s} {d}%", .{ icon, device.capacity }); +} + +pub fn refresh(self: *Battery) !void { + var expirations = std.mem.zeroes([8]u8); + _ = try std.posix.read(self.fd, &expirations); + + for (state.wayland.monitors.items) |monitor| { + if (monitor.bar) |bar| { + if (bar.configured) { + render.renderClock(bar) catch continue; + render.renderModules(bar) catch continue; + bar.clock.surface.commit(); + bar.modules.surface.commit(); + bar.background.surface.commit(); + } + } + } +} + +fn updateDevices( + gpa: std.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: std.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 (std.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 std.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 std.fmt.parseFloat(f64, energy_str); + const energy_full = try std.fmt.parseFloat(f64, energy_full_str); + + const capacity = energy * 100.0 / energy_full; + return @as(u8, @intFromFloat(@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 std.fmt.parseFloat(f64, charge_str); + const charge_full = try std.fmt.parseFloat(f64, charge_full_str); + + const capacity = charge * 100.0 / charge_full; + return @as(u8, @intFromFloat(@round(capacity))); +} diff --git a/src/modules/Pulse.zig b/src/modules/Pulse.zig @@ -1,8 +1,4 @@ const std = @import("std"); -const log = std.log; -const mem = std.mem; -const posix = std.posix; -const linux = std.os.linux; const pulse = @cImport(@cInclude("pulse/pulseaudio.h")); @@ -14,7 +10,7 @@ const Pulse = @This(); const state = &@import("root").state; -fd: posix.fd_t, +fd: std.posix.fd_t, mainloop: *pulse.pa_threaded_mainloop, api: *pulse.pa_mainloop_api, context: *pulse.pa_context, @@ -27,8 +23,8 @@ muted: bool, pub fn init() !Pulse { // create descriptor for poll in Loop const efd = efd: { - const fd = try posix.eventfd(0, linux.EFD.NONBLOCK); - break :efd @as(posix.fd_t, @intCast(fd)); + const fd = try std.posix.eventfd(0, std.os.linux.EFD.NONBLOCK); + break :efd @as(std.posix.fd_t, @intCast(fd)); }; // setup pulseaudio api @@ -71,8 +67,8 @@ pub fn start(self: *Pulse) !void { } pub fn refresh(self: *Pulse) !void { - var data = mem.zeroes([8]u8); - _ = try posix.read(self.fd, &data); + var data = std.mem.zeroes([8]u8); + _ = try std.posix.read(self.fd, &data); for (state.wayland.monitors.items) |monitor| { if (monitor.bar) |bar| { @@ -122,10 +118,10 @@ export fn contextStateCallback( _ = pulse.pa_context_subscribe(ctx, mask, null, null); }, pulse.PA_CONTEXT_TERMINATED, pulse.PA_CONTEXT_FAILED => { - log.info("pulse: restarting", .{}); + std.log.info("pulse: restarting", .{}); self.deinit(); self.* = Pulse.init() catch return; - log.info("pulse: restarted", .{}); + std.log.info("pulse: restarted", .{}); }, else => {}, } @@ -138,9 +134,9 @@ export fn serverInfoCallback( ) void { const self = utils.cast(Pulse)(self_opaque.?); - self.sink_name = mem.span(info.?.default_sink_name); + self.sink_name = std.mem.span(info.?.default_sink_name); self.sink_is_running = true; - log.info("pulse: sink set to {s}", .{self.sink_name}); + std.log.info("pulse: sink set to {s}", .{self.sink_name}); _ = pulse.pa_context_get_sink_info_list(ctx, sinkInfoCallback, self_opaque); } @@ -174,8 +170,8 @@ export fn sinkInfoCallback( const self = utils.cast(Pulse)(self_opaque.?); const info = maybe_info orelse return; - const sink_name = mem.span(info.name); - const is_current = mem.eql(u8, self.sink_name, sink_name); + const sink_name = std.mem.span(info.name); + const is_current = std.mem.eql(u8, self.sink_name, sink_name); const is_running = info.state == pulse.PA_SINK_RUNNING; if (is_current) self.sink_is_running = is_running; @@ -183,7 +179,7 @@ export fn sinkInfoCallback( if (!self.sink_is_running and is_running) { self.sink_name = sink_name; self.sink_is_running = true; - log.info("pulse: sink set to {s}", .{sink_name}); + std.log.info("pulse: sink set to {s}", .{sink_name}); } self.volume = volume: { @@ -194,6 +190,6 @@ export fn sinkInfoCallback( }; self.muted = info.mute != 0; - const increment = mem.asBytes(&@as(u64, 1)); - _ = posix.write(self.fd, increment) catch return; + const increment = std.mem.asBytes(&@as(u64, 1)); + _ = std.posix.write(self.fd, increment) catch return; } diff --git a/src/render.zig b/src/render.zig @@ -106,6 +106,7 @@ pub fn renderModules(bar: *Bar) !void { try writer.print(" | ", .{}); switch (tag) { .pulse => try state.modules.pulse.?.print(writer), + .battery => try state.modules.battery.?.print(writer), } }