diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-03-26 20:22:51 +1100 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-03-26 20:22:51 +1100 |
| commit | 5a1ea91e35487dc877a4422c31c2e225b4ec2aef (patch) | |
| tree | 5f95a8e3a9717569164d7059217d3fd3e1a2dffe /src/modules | |
| parent | sidebar: fix negative content width warnings (diff) | |
| download | caelestia-shell-5a1ea91e35487dc877a4422c31c2e225b4ec2aef.tar.gz caelestia-shell-5a1ea91e35487dc877a4422c31c2e225b4ec2aef.tar.bz2 caelestia-shell-5a1ea91e35487dc877a4422c31c2e225b4ec2aef.zip | |
sidebar: networks module
Diffstat (limited to 'src/modules')
| -rw-r--r-- | src/modules/sidebar/connectivity.tsx | 3 | ||||
| -rw-r--r-- | src/modules/sidebar/modules/networks.tsx | 139 |
2 files changed, 142 insertions, 0 deletions
diff --git a/src/modules/sidebar/connectivity.tsx b/src/modules/sidebar/connectivity.tsx index 437e513..2962b56 100644 --- a/src/modules/sidebar/connectivity.tsx +++ b/src/modules/sidebar/connectivity.tsx @@ -1,7 +1,10 @@ import Bluetooth from "./modules/bluetooth"; +import Networks from "./modules/networks"; export default () => ( <box vertical className="pane connectivity" name="connectivity"> + <Networks /> + <box className="separator" /> <Bluetooth /> </box> ); diff --git a/src/modules/sidebar/modules/networks.tsx b/src/modules/sidebar/modules/networks.tsx new file mode 100644 index 0000000..34ec8fd --- /dev/null +++ b/src/modules/sidebar/modules/networks.tsx @@ -0,0 +1,139 @@ +import { bind, execAsync, Variable, type Binding } from "astal"; +import { Gtk } from "astal/gtk3"; +import AstalNetwork from "gi://AstalNetwork"; + +const sortAPs = (saved: string[], a: AstalNetwork.AccessPoint, b: AstalNetwork.AccessPoint) => { + const { wifi } = AstalNetwork.get_default(); + if (a === wifi.activeAccessPoint || b === wifi.activeAccessPoint) return a === wifi.activeAccessPoint ? -1 : 1; + if (saved.includes(a.ssid) || saved.includes(b.ssid)) return saved.includes(a.ssid) ? -1 : 1; + return b.strength - a.strength; +}; + +const Network = (accessPoint: AstalNetwork.AccessPoint) => ( + <box + className={bind(AstalNetwork.get_default().wifi, "activeAccessPoint").as( + a => `network ${a === accessPoint ? "connected" : ""}` + )} + > + <icon className="icon" icon={bind(accessPoint, "iconName")} /> + <box vertical hexpand> + <label truncate xalign={0} label={bind(accessPoint, "ssid").as(s => s ?? "Unknown")} /> + <label + truncate + xalign={0} + className="sublabel" + label={bind(accessPoint, "strength").as(s => `${accessPoint.frequency > 5000 ? 5 : 2.4}GHz • ${s}/100`)} + /> + </box> + <box hexpand /> + <button + valign={Gtk.Align.CENTER} + visible={false} + cursor="pointer" + onClicked={() => execAsync(`nmcli c delete id '${accessPoint.ssid}'`).catch(() => {})} + label="delete_forever" + setup={self => { + let destroyed = false; + execAsync(`fish -c "nmcli -t -f name,type c show | sed -nE 's/(.*)\\:.*wireless/\\1/p'"`) + .then(out => !destroyed && (self.visible = out.split("\n").includes(accessPoint.ssid))) + .catch(console.error); + self.connect("destroy", () => (destroyed = true)); + }} + /> + <button + valign={Gtk.Align.CENTER} + cursor="pointer" + onClicked={self => { + const cmd = + AstalNetwork.get_default().wifi.activeAccessPoint === accessPoint ? "c down id" : "d wifi connect"; + execAsync(`nmcli ${cmd} '${accessPoint.ssid}'`) + .then(() => (self.sensitive = true)) + .catch(console.error); + self.sensitive = false; + }} + label={bind(AstalNetwork.get_default().wifi, "activeAccessPoint").as(a => + a === accessPoint ? "wifi_off" : "wifi" + )} + /> + </box> +); + +const List = () => { + const { wifi } = AstalNetwork.get_default(); + const children = Variable<JSX.Element[]>([]); + + const update = async () => { + const out = await execAsync(`fish -c "nmcli -t -f name,type c show | sed -nE 's/(.*)\\:.*wireless/\\1/p'"`); + const saved = out.split("\n"); + const aps = wifi.accessPoints + .filter(a => a.ssid) + .sort((a, b) => sortAPs(saved, a, b)) + .map(Network); + children.set(aps); + }; + + wifi.connect("notify::active-access-point", () => update().catch(console.error)); + wifi.connect("notify::access-points", () => update().catch(console.error)); + update().catch(console.error); + + return ( + <box vertical valign={Gtk.Align.START} className="list" onDestroy={() => children.drop()}> + {bind(children)} + </box> + ); +}; + +const NoNetworks = ({ label }: { label: Binding<string> | string }) => ( + <box homogeneous name="empty"> + <box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="empty"> + <label className="icon" label="wifi_off" /> + <label label={label} /> + </box> + </box> +); + +export default () => { + const network = AstalNetwork.get_default(); + const label = Variable(""); + + const update = () => { + if (network.primary === AstalNetwork.Primary.WIFI) label.set(network.wifi.ssid ?? "Disconnected"); + else if (network.primary === AstalNetwork.Primary.WIRED) label.set(`Ethernet (${network.wired.speed})`); + else label.set("No Wifi"); + }; + network.connect("notify::primary", update); + network.get_wifi()?.connect("notify::ssid", update); + network.get_wired()?.connect("notify::speed", update); + update(); + + return ( + <box vertical className="networks"> + <box className="header-bar"> + <label label={bind(label)} /> + <box hexpand /> + <button + className={bind(network.wifi, "scanning").as(s => (s ? "enabled" : ""))} + cursor="pointer" + onClicked={() => network.wifi.scan()} + label=" Scan" + /> + </box> + {network.get_wifi() ? ( + <stack + transitionType={Gtk.StackTransitionType.CROSSFADE} + transitionDuration={200} + shown={bind(network.wifi, "accessPoints").as(a => (a.length > 0 ? "list" : "empty"))} + > + <NoNetworks + label={bind(network.wifi, "enabled").as(p => (p ? "No available networks" : "Wifi is off"))} + /> + <scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list"> + <List /> + </scrollable> + </stack> + ) : ( + <NoNetworks label="Wifi not available" /> + )} + </box> + ); +}; |