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:
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),
}
}