import Gtk from "gi://Gtk?version=3.0" import Gio from "gi://Gio" import AstalBluetooth from "gi://AstalBluetooth" import { bind, Variable } from "astal" import { ArrowToggleButton, Menu } from "./ToggleButton" const bt = AstalBluetooth.get_default() function power(on: boolean) { if (bt.adapter) bt.adapter.powered = on } function setDiscovery(on: boolean) { if (!bt.adapter) return try { if (on) bt.adapter.start_discovery() else bt.adapter.stop_discovery() } catch { // expected when already in the requested state: bluez stops // discovery itself on connect, auto-starts it, etc. } } // bluez only registers devices that are paired or seen during discovery; // the list is mirrored into a Variable refreshed on device add/remove const devices = Variable(bt.get_devices()) const refreshDevices = () => devices.set(bt.get_devices()) bt.connect("notify::devices", refreshDevices) bt.connect("device_added", refreshDevices) bt.connect("device_removed", refreshDevices) const btIcon = bind(bt, "is_powered").as((p) => p ? "bluetooth-active-symbolic" : "bluetooth-disabled-symbolic", ) const connectedCount = devices().as( (ds) => ds.filter((d) => d.connected).length, ) const pillLabel = Variable.derive( [bind(bt, "is_powered"), devices], (powered, ds) => { if (!powered) return "Disabled" const connected = ds.filter((d) => d.connected) if (connected.length === 1) return connected[0].alias return `${connected.length} Connected` }, ) // ported from AlfheimOS: icon, name, battery, busy icon while connecting, // switch reflecting the bluez connected state function DeviceItem(device: AstalBluetooth.Device) { const battery = bind(device, "battery_percentage") const connecting = bind(device, "connecting") // bluez reports br-connection-busy when attempts pile up, so debounce let lastAttempt = 0 return ( ) } export function BluetoothSection({ open }: { open: Variable }) { if (!bt.adapter) return null const expanded = Variable(false) open.subscribe((v) => { if (!v) { expanded.set(false) setDiscovery(false) } }) const toggle = ( { if (bt.is_powered) { power(false) expanded.set(false) setDiscovery(false) } else { power(true) } }} onArrowClick={() => { const next = !expanded.get() expanded.set(next) if (next) { if (!bt.is_powered) power(true) setDiscovery(true) } else { setDiscovery(false) } }} /> ) const menu = ( ) return { toggle, menu } } // bar cluster indicator: shown while bluetooth is powered, with a count // badge when devices are connected export const bluetoothIndicator: JSX.Element | null = bt.adapter ? ( ) : null