commit 06fedf5965f432f21f107955e5d68a7f28abafef
parent 4ece040cbf210783eceaa3baab84c32d36be1d40
Author: Andrea Feletto <andrea@andreafeletto.com>
Date: Mon, 24 Jan 2022 22:59:25 +0100
remove stdin support and add battery module
Diffstat:
8 files changed, 183 insertions(+), 174 deletions(-)
diff --git a/README.md b/README.md
@@ -16,20 +16,8 @@ zig build -Drelease-safe --prefix ~/.local install
## Usage
-Levee renders the last line received through `stdin` at the right of the
-statusbar.
-A sample script is provided in the `contrib` folder which prints brightness,
-volume and battery level.
-
-```
-./contrib/status | levee
-```
-
-The script refreshes automatically every 10 seconds, but can be refreshed
-immediately by running:
-
```
-./contrib/status --refresh
+levee
```
## Dependencies
diff --git a/contrib/status b/contrib/status
@@ -1,128 +0,0 @@
-#!/bin/sh
-
-PrgName=${0##*/}
-
-PrgDir="$HOME/.local/share/$PrgName"
-mkdir -p "$PrgDir"
-PidPath="$PrgDir/$PrgName.pid"
-
-Usage() {
- while read -r Line; do
- printf '%b\n' "$Line"
- done <<-EOF
- \rUsage: $PrgName OPTION
- \rStatusbar widgets for levee.
-
- \rOptions:
- \r -h, --help, -? display this help and exit
- \r -r, --refresh refresh
- \r -s, --stop stop daemon if one exists
- EOF
-}
-
-Die() {
- printf 'error: %s\n' "$1" 1>&2
- exit 1
-}
-
-Battery() {
- CapacityPath='/sys/class/power_supply/BAT1/capacity'
- StatusPath='/sys/class/power_supply/BAT1/status'
- if [ -r "$CapacityPath" ] && [ -r "$StatusPath" ]; then
- read -r Status <"$StatusPath"
- case $Status in
- Discharging)
- Icon="🔋"
- ;;
- Charging)
- Icon="🔌"
- ;;
- Full)
- Icon="⚡"
- ;;
- Unknown)
- Icon="❓"
- ;;
- esac
- read -r Capacity <"$CapacityPath"
- printf '%s %4s%%\n' "$Icon" "$Capacity"
- else
- Die 'unable to access power supply files'
- fi
-}
-
-Volume() {
- if ! command -v pulsemixer >/dev/null; then
- Die 'missing dependency: pulsemixer'
- fi
-
- if [ "$(pulsemixer --get-mute)" -eq 1 ]; then
- printf ' 🔇 \n'
- else
- Value=$(pulsemixer --get-volume)
- printf '🔊 %4s%%\n' "${Value%% *}"
- fi
-}
-
-Backlight() {
- Brightness=$(brightnessctl get)
- MaxBrightness=$(brightnessctl max)
- Value=$((100 * Brightness / MaxBrightness))
- printf '💡 %4d%%\n' "${Value%%.*}"
-}
-
-Refresh() {
- printf '%s' "$$" >"$PidPath"
- printf '%s | %s | %s\n' "$(Backlight)" "$(Volume)" "$(Battery)"
-}
-
-GetDaemonPid() {
- if [ -r "$PidPath" ]; then
- read -r Pid <"$PidPath"
- printf '%s' "$Pid"
- fi
-}
-
-SendRefresh() {
- Pid=$(GetDaemonPid)
- if [ -n "$Pid" ]; then
- kill -USR1 "$Pid"
- else
- Die 'no daemon to refresh'
- fi
-}
-
-StopDaemon() {
- Pid=$(GetDaemonPid)
- if [ -n "$Pid" ] && kill -0 "$Pid" ; then
- kill -15 "$Pid"
- else
- Die 'no daemon to kill'
- fi
-}
-
-if [ $# -gt 1 ]; then
- Usage
- exit 1
-fi
-
-case $1 in
---help | -h | -\?)
- Usage
- exit 0 ;;
---refresh | -r)
- SendRefresh
- exit 0 ;;
---stop | -s)
- StopDaemon
- exit 0 ;;
-esac
-
-trap ':' USR1
-trap 'rm $PidPath; exit 0' INT TERM
-
-while true; do
- Refresh
- sleep 10 &
- wait $!
-done
diff --git a/src/config.zig b/src/config.zig
@@ -8,6 +8,7 @@ pub const Config = struct {
border: u15,
font: *fcft.Font,
clockFormat: [*:0]const u8,
+ batteryDir: []const u8,
pub fn init() !Config {
var font_names = [_][*:0]const u8{"monospace:size=14"};
@@ -29,6 +30,7 @@ pub const Config = struct {
.border = 2,
.font = try fcft.Font.fromName(&font_names, null),
.clockFormat = "%d %b %Y - %R",
+ .batteryDir = "BAT1",
};
}
};
diff --git a/src/event.zig b/src/event.zig
@@ -13,6 +13,7 @@ pub const Loop = struct {
fds: [3]os.pollfd,
pub fn init(state: *State) !Loop {
+ // Timer
const tfd = os.linux.timerfd_create(
os.CLOCK.MONOTONIC,
os.linux.TFD.CLOEXEC,
@@ -23,6 +24,9 @@ pub const Loop = struct {
};
_ = os.linux.timerfd_settime(@intCast(i32, tfd), 0, &interval, null);
+ // inotify
+ const ifd = os.linux.inotify_init1(os.linux.IN.CLOEXEC);
+
return Loop{
.state = state,
.fds = .{
@@ -37,7 +41,7 @@ pub const Loop = struct {
.revents = 0,
},
.{
- .fd = os.linux.STDIN_FILENO,
+ .fd = @intCast(os.fd_t, ifd),
.events = os.POLL.IN,
.revents = 0,
},
@@ -90,17 +94,17 @@ pub const Loop = struct {
}
}
- // stdin
+ // inotify
if (self.fds[2].revents & os.POLL.IN != 0) {
- var buffer = [_]u8{0} ** 256;
- const len = try os.read(self.fds[2].fd, &buffer);
- if (len == 0) continue;
+ const ifd = self.fds[2].fd;
+ var event = mem.zeroes(os.linux.inotify_event);
+ _ = try os.read(ifd, mem.asBytes(&event));
for (self.state.wayland.outputs.items) |output| {
if (output.surface) |surface| {
if (surface.configured) {
- render.renderCustom(surface, buffer[0 .. len - 1]) catch continue;
- surface.customSurface.commit();
+ render.renderModules(surface) catch continue;
+ surface.modulesSurface.commit();
surface.backgroundSurface.commit();
}
}
diff --git a/src/main.zig b/src/main.zig
@@ -2,6 +2,7 @@ const std = @import("std");
const Config = @import("config.zig").Config;
const Loop = @import("event.zig").Loop;
+const modules = @import("modules.zig");
const Wayland = @import("wayland.zig").Wayland;
pub const State = struct {
@@ -9,6 +10,9 @@ pub const State = struct {
config: Config,
wayland: Wayland,
loop: Loop,
+
+ battery: modules.Battery,
+ modules: std.ArrayList(modules.Module),
};
pub fn main() anyerror!void {
@@ -22,6 +26,11 @@ pub fn main() anyerror!void {
state.wayland = try Wayland.init(&state);
state.loop = try Loop.init(&state);
+ std.log.info("modules initialization", .{});
+ state.modules = std.ArrayList(modules.Module).init(state.allocator);
+ state.battery = try modules.Battery.init(&state);
+ try state.modules.append(state.battery.module());
+
std.log.info("wayland globals registration", .{});
try state.wayland.registerGlobals();
diff --git a/src/modules.zig b/src/modules.zig
@@ -0,0 +1,125 @@
+const std = @import("std");
+const fs = std.fs;
+const io = std.io;
+const mem = std.mem;
+const os = std.os;
+
+const State = @import("main.zig").State;
+
+const StringWriter = std.ArrayList(u8).Writer;
+
+pub const Module = struct {
+ impl: *anyopaque,
+
+ 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,
+ path: []const u8,
+ watch: i32,
+
+ pub const Data = struct {
+ capacity: u8,
+ icon: []const u8,
+ };
+
+ pub fn init(state: *State) !Battery {
+ const path = try fs.path.join(
+ state.allocator,
+ &.{ "/sys/class/power_supply", state.config.batteryDir },
+ );
+
+ const uevent_path = try fs.path.joinZ(
+ state.allocator,
+ &.{ path, "uevent" },
+ );
+ defer state.allocator.free(uevent_path);
+
+ const watch = os.linux.inotify_add_watch(
+ state.loop.fds[2].fd,
+ uevent_path,
+ os.linux.IN.ACCESS,
+ );
+
+ return Battery{
+ .state = state,
+ .path = path,
+ .watch = @intCast(i32, watch),
+ };
+ }
+
+ 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);
+
+ const data = try self.readData();
+ try std.fmt.format(writer, "{s} {d}%", .{ data.icon, data.capacity });
+ }
+
+ fn readData(self: *const Battery) !Data {
+ const voltage = try self.readInt("voltage_now");
+ const charge = try self.readInt("charge_now");
+ const charge_full = try self.readInt("charge_full");
+
+ const energy = @as(u64, charge) * @as(u64, voltage) / 1000000;
+ const energy_full = @as(u64, charge_full) * @as(u64, voltage) / 1000000;
+
+ var capacity = @intToFloat(f64, energy) * 100.0;
+ capacity /= @intToFloat(f64, energy_full);
+
+ const status = try self.readValue("status");
+
+ var icon: []const u8 = "❓";
+ if (mem.eql(u8, status, "Discharging")) {
+ icon = "🔋";
+ } else if (mem.eql(u8, status, "Charging")) {
+ icon = "🔌";
+ } else if (mem.eql(u8, status, "Full")) {
+ icon = "⚡";
+ }
+
+ return Data{
+ .capacity = @floatToInt(u8, @round(capacity)),
+ .icon = icon,
+ };
+ }
+
+ fn readInt(self: *const Battery, filename: []const u8) !u32 {
+ const value = try self.readValue(filename);
+ defer self.state.allocator.free(value);
+
+ return std.fmt.parseInt(u32, value, 10);
+ }
+
+ fn readValue(self: *const Battery, filename: []const u8) ![]u8 {
+ const state = self.state;
+
+ const path = try fs.path.join(
+ state.allocator,
+ &.{ self.path, filename },
+ );
+ defer state.allocator.free(path);
+
+ const file = try fs.openFileAbsolute(path, .{});
+ defer file.close();
+
+ var str = try file.readToEndAlloc(state.allocator, 128);
+ return state.allocator.resize(str, str.len - 1).?;
+ }
+};
diff --git a/src/render.zig b/src/render.zig
@@ -121,30 +121,31 @@ pub fn renderClock(surface: *Surface) !void {
wlSurface.attach(buffer.buffer, 0, 0);
}
-pub fn renderCustom(surface: *Surface, str: []const u8) !void {
+pub fn renderModules(surface: *Surface) !void {
const state = surface.output.state;
- const wlSurface = surface.customSurface;
+ const wlSurface = surface.modulesSurface;
const buffer = try Buffer.nextBuffer(
- &surface.customBuffers,
+ &surface.modulesBuffers,
surface.output.state.wayland.shm,
surface.width,
surface.height,
);
buffer.busy = true;
- // clear the buffer
- const bg_area = [_]pixman.Rectangle16{
- .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height },
- };
- const bg_color = mem.zeroes(pixman.Color);
- _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
+ // compose string
+ var string = std.ArrayList(u8).init(state.allocator);
+ const writer = string.writer();
+ for (state.modules.items) |*module| {
+ try std.fmt.format(writer, " | ", .{});
+ try module.print(writer);
+ }
// ut8 encoding
- const utf8 = try std.unicode.Utf8View.init(str);
+ const utf8 = try std.unicode.Utf8View.init(string.items);
var utf8_iter = utf8.iterator();
- var runes = try state.allocator.alloc(c_int, str.len);
+ var runes = try state.allocator.alloc(c_int, string.items.len);
defer state.allocator.free(runes);
var i: usize = 0;
@@ -153,10 +154,17 @@ pub fn renderCustom(surface: *Surface, str: []const u8) !void {
}
runes = state.allocator.resize(runes, i).?;
+ // clear the buffer
+ const bg_area = [_]pixman.Rectangle16{
+ .{ .x = 0, .y = 0, .width = surface.width, .height = surface.height },
+ };
+ const bg_color = mem.zeroes(pixman.Color);
+ _ = pixman.Image.fillRectangles(.src, buffer.pix.?, &bg_color, 1, &bg_area);
+
+ // compute offsets
const run = try fcft.TextRun.rasterize(state.config.font, runes, .default);
defer run.destroy();
- // compute offsets
i = 0;
var text_width: u32 = 0;
while (i < run.count) : (i += 1) {
diff --git a/src/wayland.zig b/src/wayland.zig
@@ -119,7 +119,7 @@ pub const Wayland = struct {
if (surface.backgroundSurface == wlSurface or
surface.tagsSurface == wlSurface or
surface.clockSurface == wlSurface or
- surface.customSurface == wlSurface)
+ surface.modulesSurface == wlSurface)
{
return surface;
}
@@ -281,9 +281,9 @@ pub const Surface = struct {
clockSubsurface: *wl.Subsurface,
clockBuffers: [2]Buffer,
- customSurface: *wl.Surface,
- customSubsurface: *wl.Subsurface,
- customBuffers: [2]Buffer,
+ modulesSurface: *wl.Surface,
+ modulesSubsurface: *wl.Subsurface,
+ modulesBuffers: [2]Buffer,
configured: bool,
width: u16,
@@ -320,12 +320,12 @@ pub const Surface = struct {
);
self.clockBuffers = mem.zeroes([2]Buffer);
- self.customSurface = try wayland.compositor.createSurface();
- self.customSubsurface = try wayland.subcompositor.getSubsurface(
- self.customSurface,
+ self.modulesSurface = try wayland.compositor.createSurface();
+ self.modulesSubsurface = try wayland.subcompositor.getSubsurface(
+ self.modulesSurface,
self.backgroundSurface,
);
- self.customBuffers = mem.zeroes([2]Buffer);
+ self.modulesBuffers = mem.zeroes([2]Buffer);
// setup layer surface
self.layerSurface.setSize(0, state.config.height);
@@ -339,7 +339,7 @@ pub const Surface = struct {
// setup subsurfaces
self.tagsSubsurface.setPosition(0, 0);
self.clockSubsurface.setPosition(0, 0);
- self.customSubsurface.setPosition(0, 0);
+ self.modulesSubsurface.setPosition(0, 0);
self.tagsSurface.commit();
self.clockSurface.commit();
@@ -366,10 +366,10 @@ pub const Surface = struct {
self.clockBuffers[0].deinit();
self.clockBuffers[1].deinit();
- self.customSurface.destroy();
- self.customSubsurface.destroy();
- self.customBuffers[0].deinit();
- self.customBuffers[1].deinit();
+ self.modulesSurface.destroy();
+ self.modulesSubsurface.destroy();
+ self.modulesBuffers[0].deinit();
+ self.modulesBuffers[1].deinit();
self.output.state.allocator.destroy(self);
}
@@ -390,10 +390,11 @@ pub const Surface = struct {
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.customSurface.commit();
+ surface.modulesSurface.commit();
surface.backgroundSurface.commit();
},
.closed => {