nixos/modules/home/ags/app/widget/QuickSettings.tsx

176 lines
6.5 KiB
TypeScript

import Gtk from "gi://Gtk?version=3.0"
import GLib from "gi://GLib"
import AstalWp from "gi://AstalWp"
import Notifd from "gi://AstalNotifd"
import { execAsync, Variable, bind } from "astal"
import Dropdown from "./Dropdown"
import { wifiIcon, WifiSection } from "./Wifi"
import { bluetoothIndicator, BluetoothSection } from "./Bluetooth"
import { BrightnessRow } from "./Brightness"
import { MediaSection } from "./Media"
import { batteryIcon, batteryPercent, BatterySection } from "./Battery"
import { NotificationCard } from "./Notifications"
const speaker = AstalWp.get_default().audio.default_speaker!
const mic = AstalWp.get_default().audio.default_microphone
const notifd = Notifd.get_default()
const notifCount = bind(notifd, "notifications").as((ns) => ns.length)
const speakerIcon = Variable.derive(
[bind(speaker, "volume"), bind(speaker, "mute")],
(volume, mute) => {
if (mute || volume <= 0.01) return "audio-volume-muted-symbolic"
if (volume < 0.33) return "audio-volume-low-symbolic"
if (volume < 0.66) return "audio-volume-medium-symbolic"
return "audio-volume-high-symbolic"
},
)
const micIcon = mic
? Variable.derive([bind(mic, "volume"), bind(mic, "mute")], (volume, mute) =>
mute || volume <= 0.01
? "microphone-sensitivity-muted-symbolic"
: "microphone-sensitivity-high-symbolic",
)
: null
const user = GLib.get_user_name()
const actions: { icon: string; cmd?: string[] }[] = [
{ icon: "emblem-system-symbolic" },
{ icon: "system-lock-screen-symbolic", cmd: ["hyprlock"] },
{ icon: "weather-clear-night-symbolic", cmd: ["systemctl", "suspend"] },
{ icon: "system-reboot-symbolic", cmd: ["systemctl", "reboot"] },
{ icon: "system-log-out-symbolic", cmd: ["hyprctl", "dispatch", "exit"] },
{ icon: "system-shutdown-symbolic", cmd: ["systemctl", "poweroff"] },
]
function Header() {
return (
<box className="header" spacing={8}>
<box className="avatar">
<icon icon="nix-snowflake-white" pixelSize={16} />
</box>
<label className="username" label={user} valign={Gtk.Align.CENTER} />
<box hexpand />
{actions.map((action) => (
<button
className="power-action"
onClicked={() => action.cmd && execAsync(action.cmd)}
>
<icon icon={action.icon} />
</button>
))}
</box>
)
}
export default function QuickSettings(gdkmonitor: Gdk.Monitor) {
const open = Variable(false)
const button = (
<button
className="quicksettings end"
onClicked={() => open.set(!open.get())}
onScroll={(_, e) => {
const volume = Math.min(
1,
Math.max(0, speaker.volume - e.delta_y * 0.02),
)
speaker.volume = volume
}}
>
<box spacing={6}>
<box className="notif-indicator" spacing={3}>
<icon icon="preferences-system-notifications-symbolic" />
<label
className="notif-count"
label={notifCount.as((n) => `${n}`)}
visible={notifCount.as((n) => n > 0)}
/>
</box>
{wifiIcon && <icon icon={wifiIcon} />}
{bluetoothIndicator}
<icon icon={speakerIcon()} />
{micIcon && <icon icon={micIcon()} />}
{batteryIcon && batteryPercent && (
<box spacing={3}>
<icon icon={batteryIcon} />
<label className="battery-level" label={batteryPercent} />
</box>
)}
</box>
</button>
)
const popup = (
<Dropdown gdkmonitor={gdkmonitor} target={button} open={open} width={280}>
<Header />
<BatterySection />
<box className="qs-row">
<button
className="qs-toggle"
onClicked={() => (speaker.mute = !speaker.mute)}
>
<icon icon={speakerIcon()} />
</button>
<slider
className="vol-slider"
hexpand
value={bind(speaker, "volume")}
onDragged={(self) => (speaker.volume = self.value)}
/>
</box>
{mic && micIcon && (
<box className="qs-row">
<button
className="qs-toggle"
onClicked={() => (mic.mute = !mic.mute)}
>
<icon icon={micIcon()} />
</button>
<slider
className="vol-slider"
hexpand
value={bind(mic, "volume")}
onDragged={(self) => (mic.volume = self.value)}
/>
</box>
)}
<BrightnessRow />
<WifiSection open={open} />
<BluetoothSection open={open} />
<MediaSection />
<box className="notif-section" vertical spacing={6}>
<box className="notif-header-row" spacing={6}>
<box className="notif-header" spacing={6}>
<icon icon="preferences-system-notifications-symbolic" />
<label label="Notifications" />
</box>
<box hexpand />
<button
className="notif-clear"
onClicked={() => [...notifd.notifications].forEach((n) => n.dismiss())}
>
<label label="Clear" />
</button>
</box>
<box className="notifications" vertical>
<label
className="notif-empty"
label="no notifications"
visible={notifCount.as((n) => n === 0)}
/>
{bind(notifd, "notifications").as((ns) =>
[...ns]
.sort((a, b) => b.time - a.time)
.map((n) => NotificationCard(n)),
)}
</box>
</box>
</Dropdown>
)
return { button, popup }
}