Fix: extract the actual battery icon

This commit is contained in:
Thomas Avé 2026-07-20 15:46:54 +02:00
parent 353fbd99ec
commit 0b6cc49cec
Signed by: thomasave
SSH Key Fingerprint: SHA256:bvIbWy6TO9+PdMTPzWy6dqkRlVQ3eSky+vQcc9aRIiE
1 changed files with 40 additions and 20 deletions

View File

@ -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 <box />;
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<Gio.Icon>(batteryGicon(), 10000, batteryGicon);
return (
<button
class="battery-item"
onClicked={() => execAsync(["gnome-power-statistics"])}
>
<box>
<With value={batteryPercentage}>
{(percentage) => {
const thresholds = [...Array(11).keys()].map((i) => i * 10);
const icon = thresholds.find(
(threshold) => threshold >= percentage * 100,
);
const charging_name =
battery.percentage >= 0.99 ? "charged" : "charging";
return (
<image
iconName={
battery.charging
? `battery-level-${icon}-${charging_name}-symbolic`
: `battery-level-${icon}-symbolic`
}
/>
);
}}
</With>
<image gicon={gicon} />
</box>
</button>
);