Battery.zig (5059B)
1 const std = @import("std"); 2 3 const udev = @import("udev"); 4 5 const Module = @import("../Modules.zig").Module; 6 const Event = @import("../Loop.zig").Event; 7 const render = @import("../render.zig"); 8 const utils = @import("../utils.zig"); 9 const Battery = @This(); 10 11 const state = &@import("root").state; 12 13 context: *udev.Udev, 14 fd: std.posix.fd_t, 15 devices: DeviceList, 16 17 const Device = struct { 18 name: []const u8, 19 status: []const u8, 20 capacity: u8, 21 }; 22 23 const DeviceList = std.ArrayList(Device); 24 25 pub fn init() !Battery { 26 const tfd = tfd: { 27 const fd = try std.posix.timerfd_create( 28 std.os.linux.TIMERFD_CLOCK.MONOTONIC, 29 .{ .CLOEXEC = true }, 30 ); 31 const interval: std.os.linux.itimerspec = .{ 32 .it_interval = .{ .sec = 10, .nsec = 0 }, 33 .it_value = .{ .sec = 10, .nsec = 0 }, 34 }; 35 try std.posix.timerfd_settime(@intCast(fd), .{}, &interval, null); 36 break :tfd @as(std.posix.fd_t, @intCast(fd)); 37 }; 38 39 const context = try udev.Udev.new(); 40 41 var devices = DeviceList.init(state.gpa); 42 try updateDevices(state.gpa, context, &devices); 43 44 return Battery{ 45 .context = context, 46 .fd = tfd, 47 .devices = devices, 48 }; 49 } 50 51 pub fn deinit(self: *Battery) void { 52 _ = self.context.unref(); 53 for (self.devices.items) |*device| { 54 state.gpa.free(device.name); 55 state.gpa.free(device.status); 56 } 57 self.devices.deinit(); 58 } 59 60 pub fn print(self: *Battery, writer: anytype) !void { 61 try updateDevices(state.gpa, self.context, &self.devices); 62 const device = self.devices.items[0]; 63 64 var icon: []const u8 = "❓"; 65 if (std.mem.eql(u8, device.status, "Discharging")) { 66 icon = ""; 67 } else if (std.mem.eql(u8, device.status, "Charging")) { 68 icon = ""; 69 } else if (std.mem.eql(u8, device.status, "Full")) { 70 icon = "⚡"; 71 } 72 73 try std.fmt.format(writer, "{s} {d}%", .{ icon, device.capacity }); 74 } 75 76 pub fn refresh(self: *Battery) !void { 77 var expirations = std.mem.zeroes([8]u8); 78 _ = try std.posix.read(self.fd, &expirations); 79 80 for (state.wayland.monitors.items) |monitor| { 81 if (monitor.bar) |bar| { 82 if (bar.configured) { 83 render.renderClock(bar) catch continue; 84 render.renderModules(bar) catch continue; 85 bar.clock.surface.commit(); 86 bar.modules.surface.commit(); 87 bar.background.surface.commit(); 88 } 89 } 90 } 91 } 92 93 fn updateDevices( 94 gpa: std.mem.Allocator, 95 context: *udev.Udev, 96 devices: *DeviceList, 97 ) !void { 98 const enumerate = try udev.Enumerate.new(context); 99 defer _ = enumerate.unref(); 100 101 try enumerate.addMatchSubsystem("power_supply"); 102 try enumerate.addMatchSysattr("type", "Battery"); 103 try enumerate.scanDevices(); 104 105 const entries = enumerate.getListEntry(); 106 107 var maybe_entry = entries; 108 while (maybe_entry) |entry| : (maybe_entry = entry.getNext()) { 109 const path = entry.getName(); 110 const device = try udev.Device.newFromSyspath(context, path); 111 try updateOrAppend(gpa, devices, device); 112 } 113 } 114 115 fn updateOrAppend( 116 gpa: std.mem.Allocator, 117 devices: *DeviceList, 118 dev: *udev.Device, 119 ) !void { 120 const name = dev.getSysname() catch return; 121 const status = dev.getSysattrValue("status") catch return; 122 const capacity = getCapacity(dev) catch return; 123 124 const device = blk: { 125 for (devices.items) |*device| { 126 if (std.mem.eql(u8, device.name, name)) { 127 gpa.free(device.status); 128 break :blk device; 129 } 130 } else { 131 const device = try devices.addOne(); 132 device.name = try gpa.dupe(u8, name); 133 break :blk device; 134 } 135 }; 136 137 device.status = try gpa.dupe(u8, status); 138 device.capacity = capacity; 139 } 140 141 fn getCapacity(dev: *udev.Device) !u8 { 142 const capacity_str = dev.getSysattrValue("capacity") catch { 143 return computeCapacityFromCharge(dev) catch { 144 return computeCapacityFromEnergy(dev); 145 }; 146 }; 147 148 const capacity = try std.fmt.parseInt(u8, capacity_str, 10); 149 return capacity; 150 } 151 152 fn computeCapacityFromEnergy(dev: *udev.Device) !u8 { 153 const energy_str = try dev.getSysattrValue("energy_now"); 154 const energy_full_str = try dev.getSysattrValue("energy_full"); 155 156 const energy = try std.fmt.parseFloat(f64, energy_str); 157 const energy_full = try std.fmt.parseFloat(f64, energy_full_str); 158 159 const capacity = energy * 100.0 / energy_full; 160 return @as(u8, @intFromFloat(@round(capacity))); 161 } 162 163 fn computeCapacityFromCharge(dev: *udev.Device) !u8 { 164 const charge_str = try dev.getSysattrValue("charge_now"); 165 const charge_full_str = try dev.getSysattrValue("charge_full"); 166 167 const charge = try std.fmt.parseFloat(f64, charge_str); 168 const charge_full = try std.fmt.parseFloat(f64, charge_full_str); 169 170 const capacity = charge * 100.0 / charge_full; 171 return @as(u8, @intFromFloat(@round(capacity))); 172 }