✨ feat(bar/status): add battery status support
added a battery status module thanks to @Elgemp4 in #20!!
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
import { Accessor, createBinding } from "ags";
|
||||||
|
import AstalBattery from "gi://AstalBattery?version=0.1";
|
||||||
|
|
||||||
|
export class Battery {
|
||||||
|
private static astalBattery: AstalBattery.Device = AstalBattery.get_default();
|
||||||
|
|
||||||
|
private static batteryInst: Battery;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
AstalBattery.get_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDefault(): Battery {
|
||||||
|
if (!this.batteryInst) {
|
||||||
|
this.batteryInst = new Battery();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.batteryInst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getBattery(): AstalBattery.Device {
|
||||||
|
return this.astalBattery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bindHasBattery(): Accessor<boolean> {
|
||||||
|
return createBinding(Battery.getBattery(), "isBattery");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bindPercentage(): Accessor<string> {
|
||||||
|
return createBinding(Battery.getBattery(), "percentage").as(
|
||||||
|
(v) => Math.round(v * 100) + "%"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bindIcon(): Accessor<string> {
|
||||||
|
return createBinding(Battery.getBattery(), "battery_icon_name");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Gtk } from "ags/gtk4";
|
import { Gtk } from "ags/gtk4";
|
||||||
import { Wireplumber } from "../../../modules/volume";
|
import { Wireplumber } from "../../../modules/volume";
|
||||||
|
import { Battery } from "../../../modules/battery";
|
||||||
import { Notifications } from "../../../modules/notifications";
|
import { Notifications } from "../../../modules/notifications";
|
||||||
import { Windows } from "../../../windows";
|
import { Windows } from "../../../windows";
|
||||||
import { Recording } from "../../../modules/recording";
|
import { Recording } from "../../../modules/recording";
|
||||||
@@ -12,90 +13,166 @@ import AstalBluetooth from "gi://AstalBluetooth";
|
|||||||
import AstalNetwork from "gi://AstalNetwork";
|
import AstalNetwork from "gi://AstalNetwork";
|
||||||
import AstalWp from "gi://AstalWp";
|
import AstalWp from "gi://AstalWp";
|
||||||
|
|
||||||
|
|
||||||
export const Status = () =>
|
export const Status = () =>
|
||||||
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
|
(
|
||||||
openWins.includes("control-center") ? "open status" : "status")}
|
<Gtk.Button
|
||||||
onClicked={() => Windows.getDefault().toggle("control-center")}>
|
class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
|
||||||
|
openWins.includes("control-center") ? "open status" : "status"
|
||||||
|
)}
|
||||||
|
onClicked={() => Windows.getDefault().toggle("control-center")}
|
||||||
|
>
|
||||||
<Gtk.Box>
|
<Gtk.Box>
|
||||||
<Gtk.Box class={"volume-indicators"} spacing={5}>
|
<Gtk.Box class={"volume-indicators"} spacing={5}>
|
||||||
<VolumeStatus class="sink" endpoint={Wireplumber.getDefault().getDefaultSink()}
|
<BatteryStatus
|
||||||
icon={createBinding(Wireplumber.getDefault().getDefaultSink(), "volumeIcon").as(icon =>
|
visible={Battery.getDefault().bindHasBattery()}
|
||||||
|
class="battery"
|
||||||
|
icon={Battery.getDefault().bindIcon()}
|
||||||
|
percentage={Battery.getDefault().bindPercentage()}
|
||||||
|
></BatteryStatus>
|
||||||
|
<VolumeStatus
|
||||||
|
class="sink"
|
||||||
|
endpoint={Wireplumber.getDefault().getDefaultSink()}
|
||||||
|
icon={createBinding(
|
||||||
|
Wireplumber.getDefault().getDefaultSink(),
|
||||||
|
"volumeIcon"
|
||||||
|
).as((icon) =>
|
||||||
!Wireplumber.getDefault().isMutedSink() &&
|
!Wireplumber.getDefault().isMutedSink() &&
|
||||||
Wireplumber.getDefault().getSinkVolume() > 0 ?
|
Wireplumber.getDefault().getSinkVolume() > 0
|
||||||
icon
|
? icon
|
||||||
: "audio-volume-muted-symbolic")
|
: "audio-volume-muted-symbolic"
|
||||||
} />
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<VolumeStatus class="source" endpoint={Wireplumber.getDefault().getDefaultSource()}
|
<VolumeStatus
|
||||||
icon={createBinding(Wireplumber.getDefault().getDefaultSource(), "volumeIcon").as(icon =>
|
class="source"
|
||||||
|
endpoint={Wireplumber.getDefault().getDefaultSource()}
|
||||||
|
icon={createBinding(
|
||||||
|
Wireplumber.getDefault().getDefaultSource(),
|
||||||
|
"volumeIcon"
|
||||||
|
).as((icon) =>
|
||||||
!Wireplumber.getDefault().isMutedSource() &&
|
!Wireplumber.getDefault().isMutedSource() &&
|
||||||
Wireplumber.getDefault().getSourceVolume() > 0 ?
|
Wireplumber.getDefault().getSourceVolume() > 0
|
||||||
icon
|
? icon
|
||||||
: "microphone-sensitivity-muted-symbolic")
|
: "microphone-sensitivity-muted-symbolic"
|
||||||
} />
|
)}
|
||||||
|
/>
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
<Gtk.Revealer revealChild={createBinding(Recording.getDefault(), "recording")}
|
<Gtk.Revealer
|
||||||
transitionDuration={500} transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}>
|
revealChild={createBinding(Recording.getDefault(), "recording")}
|
||||||
|
transitionDuration={500}
|
||||||
|
transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}
|
||||||
|
>
|
||||||
<Gtk.Box>
|
<Gtk.Box>
|
||||||
<Gtk.Image class={"recording state"} iconName={"media-record-symbolic"}
|
<Gtk.Image
|
||||||
css={"margin-right: 6px;"} />
|
class={"recording state"}
|
||||||
|
iconName={"media-record-symbolic"}
|
||||||
|
css={"margin-right: 6px;"}
|
||||||
|
/>
|
||||||
|
|
||||||
<Gtk.Label class={"rec-time"} label={
|
<Gtk.Label
|
||||||
createBinding(Recording.getDefault(), "recordingTime")
|
class={"rec-time"}
|
||||||
} />
|
label={createBinding(Recording.getDefault(), "recordingTime")}
|
||||||
|
/>
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
</Gtk.Revealer>
|
</Gtk.Revealer>
|
||||||
<StatusIcons />
|
<StatusIcons />
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
</Gtk.Button> as Gtk.Button;
|
</Gtk.Button>
|
||||||
|
) as Gtk.Button;
|
||||||
|
|
||||||
function VolumeStatus(props: { class?: string, endpoint: AstalWp.Endpoint, icon?: (string|Accessor<string>) }) {
|
function VolumeStatus(props: {
|
||||||
return <Gtk.Box spacing={2} class={props.class} $={(self) => {
|
class?: string;
|
||||||
|
endpoint: AstalWp.Endpoint;
|
||||||
|
icon?: string | Accessor<string>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Gtk.Box
|
||||||
|
spacing={2}
|
||||||
|
class={props.class}
|
||||||
|
$={(self) => {
|
||||||
const conns: Map<GObject.Object, number> = new Map();
|
const conns: Map<GObject.Object, number> = new Map();
|
||||||
const controllerScroll = Gtk.EventControllerScroll.new(Gtk.EventControllerScrollFlags.VERTICAL
|
const controllerScroll = Gtk.EventControllerScroll.new(
|
||||||
| Gtk.EventControllerScrollFlags.KINETIC);
|
Gtk.EventControllerScrollFlags.VERTICAL |
|
||||||
|
Gtk.EventControllerScrollFlags.KINETIC
|
||||||
|
);
|
||||||
|
|
||||||
conns.set(controllerScroll, controllerScroll.connect("scroll", (_, _dx, dy) => {
|
conns.set(
|
||||||
|
controllerScroll,
|
||||||
|
controllerScroll.connect("scroll", (_, _dx, dy) => {
|
||||||
console.log`Scrolled! dx: ${_dx}; dy: ${dy}`;
|
console.log`Scrolled! dx: ${_dx}; dy: ${dy}`;
|
||||||
dy > 0 ?
|
dy > 0
|
||||||
Wireplumber.getDefault().decreaseEndpointVolume(props.endpoint, 5)
|
? Wireplumber.getDefault().decreaseEndpointVolume(
|
||||||
: Wireplumber.getDefault().increaseEndpointVolume(props.endpoint, 5);
|
props.endpoint,
|
||||||
|
5
|
||||||
|
)
|
||||||
|
: Wireplumber.getDefault().increaseEndpointVolume(
|
||||||
|
props.endpoint,
|
||||||
|
5
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
conns.set(self, self.connect("destroy", () => conns.forEach((id, obj) =>
|
|
||||||
obj.disconnect(id))));
|
|
||||||
}}>
|
|
||||||
|
|
||||||
|
conns.set(
|
||||||
|
self,
|
||||||
|
self.connect("destroy", () =>
|
||||||
|
conns.forEach((id, obj) => obj.disconnect(id))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{props.icon && <Gtk.Image iconName={props.icon} />}
|
{props.icon && <Gtk.Image iconName={props.icon} />}
|
||||||
<Gtk.Label class={"volume"} label={createBinding(props.endpoint, "volume").as(vol =>
|
<Gtk.Label
|
||||||
`${Math.floor(vol * 100)}%`)} />
|
class={"volume"}
|
||||||
</Gtk.Box> as Gtk.Box;
|
label={createBinding(props.endpoint, "volume").as(
|
||||||
|
(vol) => `${Math.floor(vol * 100)}%`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Gtk.Box>
|
||||||
|
) as Gtk.Box;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BatteryStatus(props: {
|
||||||
|
visible?: Accessor<boolean>;
|
||||||
|
class?: string;
|
||||||
|
percentage?: Accessor<string>;
|
||||||
|
icon?: string | Accessor<string>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Gtk.Box visible={props.visible} spacing={2} class={props.class}>
|
||||||
|
{props.icon && <Gtk.Image iconName={props.icon} />}
|
||||||
|
<Gtk.Label class={"level"} label={props.percentage} />
|
||||||
|
</Gtk.Box>
|
||||||
|
) as Gtk.Box;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusIcons() {
|
function StatusIcons() {
|
||||||
return <Gtk.Box class={"status-icons"} spacing={8}>
|
return (
|
||||||
<Gtk.Image iconName={createComputed([
|
<Gtk.Box class={"status-icons"} spacing={8}>
|
||||||
|
<Gtk.Image
|
||||||
|
iconName={createComputed(
|
||||||
|
[
|
||||||
createBinding(AstalBluetooth.get_default(), "isPowered"),
|
createBinding(AstalBluetooth.get_default(), "isPowered"),
|
||||||
createBinding(AstalBluetooth.get_default(), "isConnected")
|
createBinding(AstalBluetooth.get_default(), "isConnected"),
|
||||||
], (powered, connected) => {
|
],
|
||||||
return powered ? (
|
(powered, connected) => {
|
||||||
connected ?
|
return powered
|
||||||
"bluetooth-active-symbolic"
|
? connected
|
||||||
|
? "bluetooth-active-symbolic"
|
||||||
: "bluetooth-symbolic"
|
: "bluetooth-symbolic"
|
||||||
) : "bluetooth-disabled-symbolic"
|
: "bluetooth-disabled-symbolic";
|
||||||
})} class={"bluetooth state"} visible={
|
|
||||||
createBinding(Bluetooth.getDefault(), "adapter").as(Boolean)
|
|
||||||
}
|
}
|
||||||
|
)}
|
||||||
|
class={"bluetooth state"}
|
||||||
|
visible={createBinding(Bluetooth.getDefault(), "adapter").as(Boolean)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Gtk.Box visible={createBinding(AstalNetwork.get_default(), "primary").as(primary =>
|
<Gtk.Box
|
||||||
primary !== AstalNetwork.Primary.UNKNOWN)}>
|
visible={createBinding(AstalNetwork.get_default(), "primary").as(
|
||||||
|
(primary) => primary !== AstalNetwork.Primary.UNKNOWN
|
||||||
|
)}
|
||||||
|
>
|
||||||
<With value={createBinding(AstalNetwork.get_default(), "primary")}>
|
<With value={createBinding(AstalNetwork.get_default(), "primary")}>
|
||||||
{(primary: AstalNetwork.Primary) => {
|
{(primary: AstalNetwork.Primary) => {
|
||||||
let device: AstalNetwork.Wifi | AstalNetwork.Wired;
|
let device: AstalNetwork.Wifi | AstalNetwork.Wired;
|
||||||
@@ -117,15 +194,25 @@ function StatusIcons() {
|
|||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
|
|
||||||
<Gtk.Box>
|
<Gtk.Box>
|
||||||
<Gtk.Image class={"bell state"} iconName={createBinding(
|
<Gtk.Image
|
||||||
Notifications.getDefault().getNotifd(), "dontDisturb").as(dnd => dnd ?
|
class={"bell state"}
|
||||||
"minus-circle-filled-symbolic"
|
iconName={createBinding(
|
||||||
: "preferences-system-notifications-symbolic")
|
Notifications.getDefault().getNotifd(),
|
||||||
}
|
"dontDisturb"
|
||||||
|
).as((dnd) =>
|
||||||
|
dnd
|
||||||
|
? "minus-circle-filled-symbolic"
|
||||||
|
: "preferences-system-notifications-symbolic"
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<Gtk.Image iconName={"circle-filled-symbolic"} class={"notification-count"}
|
<Gtk.Image
|
||||||
visible={variableToBoolean(createBinding(Notifications.getDefault(), "history"))}
|
iconName={"circle-filled-symbolic"}
|
||||||
|
class={"notification-count"}
|
||||||
|
visible={variableToBoolean(
|
||||||
|
createBinding(Notifications.getDefault(), "history")
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
</Gtk.Box>
|
</Gtk.Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user