Fix ags battery icon + always select main monitor
This commit is contained in:
parent
dbec898d65
commit
c4ae794622
|
|
@ -183,6 +183,32 @@ function Right() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Icon names to try for the current battery state, most specific first.
|
||||||
|
// Papirus does not ship every combination, so each entry needs a fallback:
|
||||||
|
// "-charged-symbolic" only exists at level 100, and "-charging-symbolic" only
|
||||||
|
// up to level 90 (above that the panel icon has to be named directly).
|
||||||
|
function batteryIconNames(): string[] {
|
||||||
|
const percentage = battery.percentage;
|
||||||
|
const thresholds = [...Array(11).keys()].map((i) => i * 10);
|
||||||
|
const level = thresholds.find((threshold) => threshold >= percentage * 100);
|
||||||
|
const plain = `battery-level-${level}-symbolic`;
|
||||||
|
|
||||||
|
// Being full is a state, not a 100% reading: with a charge limit set the
|
||||||
|
// battery stops well below 100 and sits there reporting FULLY_CHARGED.
|
||||||
|
if (battery.state === Battery.State.FULLY_CHARGED) {
|
||||||
|
return [`battery-level-${level}-charged-symbolic`, plain];
|
||||||
|
}
|
||||||
|
if (battery.charging) {
|
||||||
|
const padded = String(level).padStart(3, "0");
|
||||||
|
return [
|
||||||
|
`battery-level-${level}-charging-symbolic`,
|
||||||
|
`battery-${padded}-charging`,
|
||||||
|
plain,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [plain];
|
||||||
|
}
|
||||||
|
|
||||||
// Papirus encodes the battery level as a 35%-opacity overlay inside its
|
// Papirus encodes the battery level as a 35%-opacity overlay inside its
|
||||||
// *-symbolic icons. GTK4's symbolic recoloring flattens that opacity, so every
|
// *-symbolic icons. GTK4's symbolic recoloring flattens that opacity, so every
|
||||||
// level renders as a solid, full battery. The Papirus *-symbolic icons are just
|
// level renders as a solid, full battery. The Papirus *-symbolic icons are just
|
||||||
|
|
@ -190,13 +216,12 @@ function Right() {
|
||||||
// normally — so we resolve each name to that underlying file and load it
|
// 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.
|
// directly (as a non-symbolic file icon), which renders the correct level.
|
||||||
function batteryGicon(): Gio.Icon {
|
function batteryGicon(): Gio.Icon {
|
||||||
const percentage = battery.percentage;
|
const theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!);
|
||||||
const thresholds = [...Array(11).keys()].map((i) => i * 10);
|
// lookup_icon() never fails: for an unknown name it hands back the
|
||||||
const icon = thresholds.find((threshold) => threshold >= percentage * 100);
|
// "image-missing" paintable, so check availability before resolving.
|
||||||
const name = battery.charging
|
const names = batteryIconNames();
|
||||||
? `battery-level-${icon}-${percentage >= 0.99 ? "charged" : "charging"}-symbolic`
|
const name = names.find((n) => theme.has_icon(n)) ?? names[names.length - 1];
|
||||||
: `battery-level-${icon}-symbolic`;
|
const symbolic = theme
|
||||||
const symbolic = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!)
|
|
||||||
.lookup_icon(name, null, 16, 1, Gtk.TextDirection.NONE, 0)
|
.lookup_icon(name, null, 16, 1, Gtk.TextDirection.NONE, 0)
|
||||||
.get_file();
|
.get_file();
|
||||||
if (!symbolic) return Gio.ThemedIcon.new(name);
|
if (!symbolic) return Gio.ThemedIcon.new(name);
|
||||||
|
|
@ -217,15 +242,21 @@ function batteryGicon(): Gio.Icon {
|
||||||
return new Gio.FileIcon({ file: symbolic });
|
return new Gio.FileIcon({ file: symbolic });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function batteryTooltip(): string {
|
||||||
|
return `${Math.round(battery.percentage * 100)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
function BatteryIcon(): JSX.Element {
|
function BatteryIcon(): JSX.Element {
|
||||||
if (battery.get_state() == 0) return <box />;
|
if (battery.get_state() == 0) return <box />;
|
||||||
// AstalBattery does not emit notify::percentage/charging/state on this
|
// AstalBattery does not emit notify::percentage/charging/state on this
|
||||||
// system, so a signal binding stays frozen at its initial value (e.g.
|
// system, so a signal binding stays frozen at its initial value (e.g.
|
||||||
// showing "charged" forever). Poll the live values instead.
|
// showing "charged" forever). Poll the live values instead.
|
||||||
const gicon = createPoll<Gio.Icon>(batteryGicon(), 10000, batteryGicon);
|
const gicon = createPoll<Gio.Icon>(batteryGicon(), 10000, batteryGicon);
|
||||||
|
const tooltip = createPoll<string>(batteryTooltip(), 10000, batteryTooltip);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class="battery-item"
|
class="battery-item"
|
||||||
|
tooltipText={tooltip}
|
||||||
onClicked={() => execAsync(["gnome-power-statistics"])}
|
onClicked={() => execAsync(["gnome-power-statistics"])}
|
||||||
>
|
>
|
||||||
<box>
|
<box>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@ import style from "./style.scss";
|
||||||
import Bar from "./Bar";
|
import Bar from "./Bar";
|
||||||
import NotificationPopups from "./notifications/NotificationPopups";
|
import NotificationPopups from "./notifications/NotificationPopups";
|
||||||
|
|
||||||
|
const PREFERRED_MONITOR = "eDP-1";
|
||||||
|
|
||||||
|
function pick_monitor(): Gdk.Monitor | undefined {
|
||||||
|
const monitors = app.get_monitors();
|
||||||
|
return (
|
||||||
|
monitors.find((m: Gdk.Monitor) => m.get_connector() === PREFERRED_MONITOR) ??
|
||||||
|
monitors[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function register_windows(monitor: Gdk.Monitor) {
|
function register_windows(monitor: Gdk.Monitor) {
|
||||||
let scale = (monitor.get_geometry().width >= 3000) ? 1.2 : 1;
|
let scale = (monitor.get_geometry().width >= 3000) ? 1.2 : 1;
|
||||||
Bar(monitor, scale);
|
Bar(monitor, scale);
|
||||||
|
|
@ -14,6 +24,11 @@ app.start({
|
||||||
css: style,
|
css: style,
|
||||||
iconTheme: "Papirus",
|
iconTheme: "Papirus",
|
||||||
main() {
|
main() {
|
||||||
register_windows(app.get_monitors()[0]);
|
const monitor = pick_monitor();
|
||||||
|
if (!monitor) {
|
||||||
|
console.error("No monitors available, not creating the bar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
register_windows(monitor);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,15 @@ const app_icons = new Apps.Apps().list.reduce(
|
||||||
export function getIconName(app_id: string | null | undefined, title: string | null | undefined) {
|
export function getIconName(app_id: string | null | undefined, title: string | null | undefined) {
|
||||||
if (!app_id && !title) return "";
|
if (!app_id && !title) return "";
|
||||||
|
|
||||||
|
// Our foot terminals carry a unique per-window app-id ("footclient-<pid>") so the
|
||||||
|
// "duplicate window" keybind can tell them apart; strip that suffix back to the
|
||||||
|
// base app-id ("footclient") so the icon still resolves.
|
||||||
|
const norm = app_id ? app_id.replace(/-\d+$/, "") : app_id;
|
||||||
|
|
||||||
// try fields matching Niri outputs
|
// try fields matching Niri outputs
|
||||||
const possibleKeys = [
|
const possibleKeys = [
|
||||||
app_id,
|
app_id,
|
||||||
|
norm,
|
||||||
title
|
title
|
||||||
].filter(Boolean) as string[];
|
].filter(Boolean) as string[];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue