import AstalBluetooth from "gi://AstalBluetooth"; import AstalNetwork from "gi://AstalNetwork"; import AstalWp from "gi://AstalWp"; import { bind, Binding, Variable } from "astal"; import { Gtk, Widget } from "astal/gtk3"; import { Wireplumber } from "../../scripts/volume"; import { Notifications } from "../../scripts/notifications"; import { Windows } from "../../windows"; import { Recording } from "../../scripts/recording"; import { getDateTime } from "../../scripts/time"; import { tr } from "../../i18n/intl"; export function Status(): Gtk.Widget { return new Widget.EventBox({ className: bind(Windows, "openWindows").as((openWins) => Object.hasOwn(openWins, "control-center") ? "open status" : "status"), onClick: () => Windows.toggle("control-center"), child: new Widget.Box({ spacing: 5, children: [ volumeStatus({ className: "sink", endpoint: Wireplumber.getDefault().getDefaultSink(), icon: bind(Wireplumber.getDefault().getDefaultSink(), "volumeIcon").as(icon => !Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? icon : "audio-volume-muted-symbolic"), }), volumeStatus({ className: "source", endpoint: Wireplumber.getDefault().getDefaultSource(), icon: bind(Wireplumber.getDefault().getDefaultSource(), "volumeIcon").as(icon => !Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? icon : "microphone-sensitivity-muted-symbolic"), }), StatusIcons() ] } as Widget.BoxProps) } as Widget.EventBoxProps); } function volumeStatus(props: { className?: string, endpoint: AstalWp.Endpoint, icon?: (string|Binding) }): Gtk.Widget { return new Widget.EventBox({ className: props.className, onScroll: (_, event) => event.delta_y > 0 ? Wireplumber.getDefault().decreaseEndpointVolume(props.endpoint, 5) : Wireplumber.getDefault().increaseEndpointVolume(props.endpoint, 5), child: new Widget.Box({ spacing: 2, children: [ new Widget.Icon({ visible: props.icon, icon: props.icon, } as Widget.IconProps), new Widget.Label({ className: "volume", label: bind(props.endpoint, "volume").as((volume: number) => Math.floor(volume * 100) + "%") } as Widget.LabelProps), ] } as Widget.BoxProps) } as Widget.EventBoxProps) } function StatusIcons(): Gtk.Widget { const bluetoothIcon: Variable = Variable.derive([ bind(AstalBluetooth.get_default(), "isPowered"), bind(AstalBluetooth.get_default(), "isConnected") ], (powered, connected) => { return powered ? ( connected ? "bluetooth-active-symbolic" : "bluetooth-symbolic" ) : "bluetooth-disabled-symbolic" }); const networkIcon: Variable = Variable.derive([ bind(AstalNetwork.get_default(), "primary"), bind(AstalNetwork.get_default(), "wired"), bind(AstalNetwork.get_default(), "wifi") ], (primary, wired, wifi) => { switch(primary) { case AstalNetwork.Primary.WIRED: return wired ? "network-wired-symbolic" : "network-wired-no-route-symbolic"; case AstalNetwork.Primary.WIFI: return wifi ? "network-wireless-signal-excellent-symbolic" : "network-wireless-offline-symbolic"; } return "network-no-route-symbolic"; }); const recordingTimer: Variable = Variable.derive([ bind(Recording.getDefault(), "recording"), getDateTime() ], (recording, dateTime) => { if(!recording || !Recording.getDefault().startedAt) return "..."; const startedAtSeconds = dateTime.to_unix() - Recording.getDefault().startedAt!.to_unix(); if(startedAtSeconds <= 0) return "00:00"; const minutes = Math.floor(startedAtSeconds / 60); const seconds = Math.floor(startedAtSeconds % 60); return `${ minutes < 10 ? `0${minutes}` : minutes }:${ seconds < 10 ? `0${seconds}` : seconds }`; }); return new Widget.Box({ className: "status-icons", spacing: 8, children: [ new Widget.Revealer({ revealChild: bind(Recording.getDefault(), "recording"), transitionDuration: 500, transitionType: Gtk.RevealerTransitionType.SLIDE_LEFT, onDestroy: () => recordingTimer.drop(), child: new Widget.EventBox({ onClick: () => Recording.getDefault().recording && Recording.getDefault().stopRecording(), tooltipText: tr("control_center.tiles.recording.enabled_desc"), child: new Widget.Box({ children: [ new Widget.Icon({ className: "recording state", icon: "media-record-symbolic", css: "margin-right: 4px;" } as Widget.IconProps), new Widget.Label({ className: "rec-time", label: recordingTimer() } as Widget.LabelProps) ] } as Widget.BoxProps) } as Widget.EventBoxProps) } as Widget.RevealerProps), new Widget.Icon({ className: "bluetooth state", visible: bind(AstalBluetooth.get_default(), "adapter").as(Boolean), icon: bluetoothIcon(), onDestroy: () => bluetoothIcon.drop() } as Widget.IconProps), new Widget.Icon({ className: "network state", icon: networkIcon(), onDestroy: () => networkIcon.drop() } as Widget.IconProps), new Widget.Box({ children: [ new Widget.Icon({ className: "bell state", icon: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as((dnd) => dnd ? "minus-circle-filled-symbolic" : "preferences-system-notifications-symbolic") } as Widget.IconProps), new Widget.Icon({ className: "notification-count", visible: bind(Notifications.getDefault(), "history").as(history => history.length > 0), icon: "circle-filled-symbolic" } as Widget.IconProps) ] } as Widget.BoxProps) ] } as Widget.BoxProps); }