168 lines
6.1 KiB
TypeScript
168 lines
6.1 KiB
TypeScript
|
import { bind, execAsync, Binding, Variable } from "astal";
|
||
|
import { App, Astal, Gdk, Gtk } from "astal/gtk4";
|
||
|
|
||
|
import Bluetooth from "gi://AstalBluetooth";
|
||
|
import Hyprland from "gi://AstalHyprland";
|
||
|
import Network from "gi://AstalNetwork";
|
||
|
|
||
|
import { SHELL, IDLE_INHIBIT_SCRIPT, RANDOM_WALLPAPER_SCRIPT } from "@/settings.json";
|
||
|
|
||
|
import { hideWindow, openOnButton } from "@lib/utils";
|
||
|
|
||
|
type ButtonProps = {
|
||
|
icon: string | Binding<string>,
|
||
|
command?: string,
|
||
|
label: string | Binding<string>,
|
||
|
binding?: Binding<boolean>,
|
||
|
bindingMethod?: (binding: boolean) => void
|
||
|
}
|
||
|
|
||
|
const isIdleInhibitorEnabled = async () => {
|
||
|
try {
|
||
|
await execAsync([SHELL, "-c", `pgrep -f ${IDLE_INHIBIT_SCRIPT}`]);
|
||
|
return true;
|
||
|
} catch {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function Preference(props: ButtonProps & JSX.IntrinsicElements["button"]) {
|
||
|
const onClicked = () => {
|
||
|
if (props.command) execAsync([ SHELL, "-c", props.command ]);
|
||
|
else if (typeof props.binding !== 'undefined' && props.bindingMethod)
|
||
|
props.bindingMethod(!props.binding.get())
|
||
|
}
|
||
|
|
||
|
return <button
|
||
|
{...props}
|
||
|
label={undefined}
|
||
|
onClicked={onClicked}
|
||
|
cssClasses={props.binding?.as(active => active ? ["active"] : [])}
|
||
|
>
|
||
|
<box orientation={Gtk.Orientation.HORIZONTAL} spacing={4}>
|
||
|
<image iconName={props.icon} />
|
||
|
<label>{props.label}</label>
|
||
|
</box>
|
||
|
</button>
|
||
|
}
|
||
|
|
||
|
async function toggleIdleInhibitor(state: Variable<boolean>, enable: boolean) {
|
||
|
try {
|
||
|
if (enable) {
|
||
|
execAsync([SHELL, "-c", IDLE_INHIBIT_SCRIPT]);
|
||
|
} else {
|
||
|
const pids = await execAsync([SHELL, "-c", `pgrep -f ${IDLE_INHIBIT_SCRIPT}`]);
|
||
|
if (pids) {
|
||
|
const pidList = pids.trim().split("\n");
|
||
|
for (const pid of pidList) {
|
||
|
await execAsync(["kill", "-9", pid]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
state.set(!state.get());
|
||
|
} catch {
|
||
|
console.error("Failed to change state of idle inhibitor");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default async function QuickSettings(_monitor_id: number) {
|
||
|
const { BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor
|
||
|
const CENTER = Gtk.Align.CENTER;
|
||
|
|
||
|
const hypr = Hyprland.get_default();
|
||
|
const bt = Bluetooth.get_default();
|
||
|
const net = Network.get_default();
|
||
|
|
||
|
const connectedBtDevice = bind(bt, "devices").as(devices => devices.find(device => device.connected));
|
||
|
const idleInhibitorEnabled = Variable(await isIdleInhibitorEnabled());
|
||
|
|
||
|
return <window
|
||
|
visible={false}
|
||
|
name={"quick_settings"}
|
||
|
monitor={bind(hypr, "focusedMonitor").as(monitor => monitor.id)}
|
||
|
cssClasses={["quick_settings"]}
|
||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||
|
exclusivity={Astal.Exclusivity.IGNORE}
|
||
|
anchor={BOTTOM | LEFT | RIGHT}
|
||
|
onKeyPressed={hideWindow}
|
||
|
application={App}>
|
||
|
<box halign={CENTER} cssClasses={["inner"]} vertical>
|
||
|
<box orientation={Gtk.Orientation.HORIZONTAL} halign={CENTER}>
|
||
|
<Preference
|
||
|
icon={"network-wireless-symbolic"}
|
||
|
label={bind(Variable.derive([
|
||
|
bind(net.wifi, "enabled"),
|
||
|
bind(net.wifi, "ssid")
|
||
|
], (enabled, ssid) => enabled && ssid ? ssid : "Wi-Fi"))}
|
||
|
binding={bind(net.wifi, "enabled")}
|
||
|
bindingMethod={enabled => net.wifi.set_enabled(enabled)}
|
||
|
/>
|
||
|
<Preference
|
||
|
icon={bind(bt, "isConnected").as(c => c
|
||
|
? "bluetooth-active-symbolic"
|
||
|
: "bluetooth-disabled-symbolic"
|
||
|
)}
|
||
|
label={connectedBtDevice.as(device => bt.isPowered && device ? device.alias : "Bluetooth")}
|
||
|
binding={bind(bt, "isPowered")}
|
||
|
bindingMethod={() => bt.toggle()}
|
||
|
onButtonPressed={(_self, event) =>
|
||
|
openOnButton(event, Gdk.BUTTON_SECONDARY)
|
||
|
(() => App.toggle_window("qs_bluetooth"))
|
||
|
}
|
||
|
/>
|
||
|
<Preference
|
||
|
icon={"uninterruptible-power-supply-symbolic"}
|
||
|
label={"Idle inhibitor"}
|
||
|
binding={bind(idleInhibitorEnabled)}
|
||
|
bindingMethod={toggleIdleInhibitor.bind(null, idleInhibitorEnabled)}
|
||
|
/>
|
||
|
<Preference
|
||
|
icon="preferences-desktop-wallpaper-symbolic"
|
||
|
command={RANDOM_WALLPAPER_SCRIPT}
|
||
|
label="Random wallpaper"
|
||
|
/>
|
||
|
</box>
|
||
|
<box orientation={Gtk.Orientation.HORIZONTAL} halign={CENTER}>
|
||
|
<Preference
|
||
|
icon="system-shutdown-symbolic"
|
||
|
command="systemctl poweroff"
|
||
|
label="Shutdown"
|
||
|
/>
|
||
|
<Preference
|
||
|
icon="system-reboot-symbolic"
|
||
|
command="systemctl reboot"
|
||
|
label="Reboot"
|
||
|
/>
|
||
|
<Preference
|
||
|
icon="preferences-system-privacy-symbolic"
|
||
|
command="systemctl suspend"
|
||
|
label="Suspend"
|
||
|
/>
|
||
|
<Preference
|
||
|
icon="application-exit-symbolic"
|
||
|
command="systemctl exit"
|
||
|
label="Exit session"
|
||
|
/>
|
||
|
</box>
|
||
|
<box orientation={Gtk.Orientation.HORIZONTAL} halign={CENTER}>
|
||
|
<Preference
|
||
|
icon="system-lock-screen-symbolic"
|
||
|
command="loginctl lock-session"
|
||
|
label="Lock Screen"
|
||
|
/>
|
||
|
<Preference
|
||
|
icon="system-log-out-symbolic"
|
||
|
command="loginctl terminate-user $(whoami)"
|
||
|
label="Log out"
|
||
|
/>
|
||
|
<Preference
|
||
|
icon="system-reboot-symbolic"
|
||
|
command="systemctl reboot --boot-loader-entry=menu"
|
||
|
label="Reboot to bootloader"
|
||
|
/>
|
||
|
</box>
|
||
|
</box>
|
||
|
</window>
|
||
|
}
|