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 (
);