Add AGS WiFi widget
This commit is contained in:
parent
4be533b185
commit
70b7605475
|
|
@ -43,12 +43,15 @@
|
|||
}
|
||||
|
||||
.ws:hover,
|
||||
.volume:hover {
|
||||
.volume:hover,
|
||||
.wifi:hover {
|
||||
background: transparentize($base05, 0.88);
|
||||
}
|
||||
|
||||
.volume icon,
|
||||
.mute icon {
|
||||
.mute icon,
|
||||
.wifi icon,
|
||||
.wifi-header icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
|
@ -84,3 +87,72 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wifi-header {
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.wifi-rescan {
|
||||
padding: 0 2px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
switch {
|
||||
background: $base03;
|
||||
border-radius: 999px;
|
||||
min-width: 34px;
|
||||
min-height: 16px;
|
||||
margin: 1px 0;
|
||||
|
||||
slider {
|
||||
background: $base05;
|
||||
border-radius: 999px;
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
margin: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
switch:checked {
|
||||
background: $base0D;
|
||||
|
||||
slider {
|
||||
background: $base00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wifi-status,
|
||||
.wifi-empty {
|
||||
font-size: 0.85em;
|
||||
opacity: 0.7;
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.wifi-password {
|
||||
margin: 4px 0;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
background: $base02;
|
||||
color: $base05;
|
||||
}
|
||||
|
||||
.wifi-row {
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
margin: 1px 0;
|
||||
|
||||
icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.wifi-row-ssid {
|
||||
margin: 0 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.wifi-row:hover {
|
||||
background: transparentize($base05, 0.88);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,20 @@ import { App, Astal, Gdk, Gtk } from "astal/gtk3"
|
|||
import Workspaces from "./Workspaces"
|
||||
import Clock from "./Clock"
|
||||
import { VolumeWidget } from "./Volume"
|
||||
import { WifiWidget } from "./Wifi"
|
||||
|
||||
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
||||
const volume = VolumeWidget(gdkmonitor)
|
||||
const wifi = WifiWidget(gdkmonitor)
|
||||
|
||||
const right: Array<JSX.Element> = []
|
||||
if (wifi.button) right.push(wifi.button)
|
||||
right.push(volume.button)
|
||||
|
||||
const popups: Array<JSX.Element> = []
|
||||
if (wifi.popup) popups.push(wifi.popup)
|
||||
popups.push(volume.popup)
|
||||
|
||||
return [
|
||||
(
|
||||
|
|
@ -37,11 +47,11 @@ export default function Bar(gdkmonitor: Gdk.Monitor) {
|
|||
halign={Gtk.Align.END}
|
||||
valign={Gtk.Align.CENTER}
|
||||
>
|
||||
{volume.button}
|
||||
{right}
|
||||
</box>
|
||||
</centerbox>
|
||||
</window>
|
||||
),
|
||||
volume.popup,
|
||||
...popups,
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
import Gtk from "gi://Gtk?version=3.0"
|
||||
import AstalNetwork from "gi://AstalNetwork"
|
||||
import { type Binding, Variable, bind } from "astal"
|
||||
import Dropdown from "./Dropdown"
|
||||
|
||||
const CONNECTING = [40, 50, 60, 70, 80, 90]
|
||||
|
||||
function strongest(aps: AstalNetwork.AccessPoint[]) {
|
||||
const bySsid = new Map<string, AstalNetwork.AccessPoint>()
|
||||
for (const ap of aps) {
|
||||
const ssid = ap.ssid ?? ""
|
||||
const best = bySsid.get(ssid)
|
||||
if (!best || ap.strength > best.strength) bySsid.set(ssid, ap)
|
||||
}
|
||||
return [...bySsid.values()].sort((a, b) => b.strength - a.strength)
|
||||
}
|
||||
|
||||
function WifiRow(
|
||||
ap: AstalNetwork.AccessPoint,
|
||||
activeSsid: Binding<string>,
|
||||
connect: (ap: AstalNetwork.AccessPoint) => void,
|
||||
) {
|
||||
return (
|
||||
<button className="wifi-row" onClicked={() => connect(ap)}>
|
||||
<box>
|
||||
<icon icon={bind(ap, "icon-name")} />
|
||||
<label
|
||||
className="wifi-row-ssid"
|
||||
label={ap.ssid ?? "(hidden network)"}
|
||||
halign={Gtk.Align.START}
|
||||
hexpand
|
||||
/>
|
||||
<icon
|
||||
visible={ap.requires_password}
|
||||
icon="network-wireless-encrypted-symbolic"
|
||||
/>
|
||||
<icon
|
||||
visible={activeSsid.as((ssid) => ssid === ap.ssid)}
|
||||
icon="object-select-symbolic"
|
||||
/>
|
||||
</box>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function WifiWidget(gdkmonitor: Gdk.Monitor) {
|
||||
const wifi = AstalNetwork.get_default().wifi
|
||||
|
||||
if (!wifi) return { button: null, popup: null }
|
||||
|
||||
const open = Variable(false)
|
||||
const pendingAP = Variable<AstalNetwork.AccessPoint | null>(null)
|
||||
const failed = Variable("")
|
||||
|
||||
const status = Variable.derive(
|
||||
[bind(wifi, "scanning"), bind(wifi, "state"), bind(wifi, "ssid"), failed],
|
||||
(scanning, state, ssid, fail) => {
|
||||
if (scanning) return "scanning…"
|
||||
if (CONNECTING.includes(state)) return `connecting to ${ssid ?? "network"}…`
|
||||
return fail
|
||||
},
|
||||
)
|
||||
|
||||
const activeSsid = bind(wifi, "active-access-point").as((ap) => ap?.ssid ?? "")
|
||||
|
||||
function connect(ap: AstalNetwork.AccessPoint) {
|
||||
if (ap.requires_password) {
|
||||
failed.set("")
|
||||
pendingAP.set(ap)
|
||||
} else {
|
||||
ap.activate()
|
||||
.then(() => failed.set(""))
|
||||
.catch(() => failed.set(`failed to connect to ${ap.ssid ?? "network"}`))
|
||||
}
|
||||
}
|
||||
|
||||
const passwordEntry = (
|
||||
<entry
|
||||
className="wifi-password"
|
||||
hexpand
|
||||
visible={pendingAP().as((ap) => ap !== null)}
|
||||
placeholderText={pendingAP().as(
|
||||
(ap) => (ap ? `password for ${ap.ssid ?? "network"}` : ""),
|
||||
)}
|
||||
onActivate={(self) => {
|
||||
const ap = pendingAP.get()
|
||||
if (!ap || !self.text) return
|
||||
ap.activate(self.text)
|
||||
.then(() => {
|
||||
pendingAP.set(null)
|
||||
self.text = ""
|
||||
})
|
||||
.catch(() =>
|
||||
failed.set(`failed to connect to ${ap.ssid ?? "network"}`)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
const button = (
|
||||
<button
|
||||
className="wifi"
|
||||
onClicked={() => open.set(!open.get())}
|
||||
>
|
||||
<icon icon={bind(wifi, "icon-name")} />
|
||||
</button>
|
||||
)
|
||||
|
||||
const popup = (
|
||||
<Dropdown gdkmonitor={gdkmonitor} target={button} open={open} width={260}>
|
||||
<box className="wifi-header">
|
||||
<label label="Wi-Fi" hexpand halign={Gtk.Align.START} />
|
||||
<button className="wifi-rescan" onClicked={() => wifi.scan()}>
|
||||
<icon icon="view-refresh-symbolic" />
|
||||
</button>
|
||||
<switch
|
||||
active={bind(wifi, "enabled")}
|
||||
onStateSet={(_, state) => {
|
||||
wifi.enabled = state
|
||||
return true
|
||||
}}
|
||||
/>
|
||||
</box>
|
||||
<label
|
||||
className="wifi-status"
|
||||
label={status()}
|
||||
visible={status().as((text) => text !== "")}
|
||||
/>
|
||||
{passwordEntry}
|
||||
<label
|
||||
className="wifi-empty"
|
||||
label="wi-fi is off"
|
||||
visible={bind(wifi, "enabled").as((enabled) => !enabled)}
|
||||
/>
|
||||
<scrollable
|
||||
hscroll={Gtk.PolicyType.NEVER}
|
||||
vscroll={Gtk.PolicyType.AUTOMATIC}
|
||||
maxContentHeight={360}
|
||||
propagateNaturalHeight
|
||||
visible={bind(wifi, "enabled")}
|
||||
>
|
||||
<box vertical>
|
||||
{bind(wifi, "access-points").as((aps) =>
|
||||
strongest(aps).map((ap) => WifiRow(ap, activeSsid, connect))
|
||||
)}
|
||||
</box>
|
||||
</scrollable>
|
||||
</Dropdown>
|
||||
)
|
||||
|
||||
open.subscribe((v) => {
|
||||
if (v) {
|
||||
wifi.scan()
|
||||
} else {
|
||||
pendingAP.set(null)
|
||||
failed.set("")
|
||||
passwordEntry.text = ""
|
||||
}
|
||||
})
|
||||
|
||||
return { button, popup }
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ let
|
|||
network
|
||||
battery
|
||||
wireplumber
|
||||
];
|
||||
] ++ [ pkgs.networkmanager ];
|
||||
|
||||
ags-bar = pkgs.ags.bundle {
|
||||
pname = "ags-bar";
|
||||
|
|
|
|||
Loading…
Reference in New Issue