diff --git a/home/ags/files/Bar.tsx b/home/ags/files/Bar.tsx index 607491a..2c97fcf 100644 --- a/home/ags/files/Bar.tsx +++ b/home/ags/files/Bar.tsx @@ -9,6 +9,7 @@ import { getIconName } from "./utils"; import Wp from "gi://AstalWp"; import Battery from "gi://AstalBattery"; import GLib from "gi://GLib"; +import Gio from "gi://Gio"; const battery = Battery.get_default(); const sensorsAvailable = await execAsync(["sensors"]) @@ -182,34 +183,53 @@ function Right() { ); } +// Papirus encodes the battery level as a 35%-opacity overlay inside its +// *-symbolic icons. GTK4's symbolic recoloring flattens that opacity, so every +// level renders as a solid, full battery. The Papirus *-symbolic icons are just +// symlinks to non-symbolic panel SVGs which keep the opacity when rendered +// normally — so we resolve each name to that underlying file and load it +// directly (as a non-symbolic file icon), which renders the correct level. +function batteryGicon(): Gio.Icon { + const percentage = battery.percentage; + const thresholds = [...Array(11).keys()].map((i) => i * 10); + const icon = thresholds.find((threshold) => threshold >= percentage * 100); + const name = battery.charging + ? `battery-level-${icon}-${percentage >= 0.99 ? "charged" : "charging"}-symbolic` + : `battery-level-${icon}-symbolic`; + const symbolic = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!) + .lookup_icon(name, null, 16, 1, Gtk.TextDirection.NONE, 0) + .get_file(); + if (!symbolic) return Gio.ThemedIcon.new(name); + try { + const target = symbolic + .query_info( + "standard::symlink-target", + Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, + null, + ) + .get_symlink_target(); + if (target) { + return new Gio.FileIcon({ + file: symbolic.get_parent()!.resolve_relative_path(target), + }); + } + } catch (e) {} + return new Gio.FileIcon({ file: symbolic }); +} + function BatteryIcon(): JSX.Element { if (battery.get_state() == 0) return ; - let batteryPercentage = createBinding(battery, "percentage"); + // AstalBattery does not emit notify::percentage/charging/state on this + // system, so a signal binding stays frozen at its initial value (e.g. + // showing "charged" forever). Poll the live values instead. + const gicon = createPoll(batteryGicon(), 10000, batteryGicon); return ( );