Add AGS WiFi widget
This commit is contained in:
parent
aeb2b713dc
commit
69a1977e5a
|
|
@ -43,12 +43,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws:hover,
|
.ws:hover,
|
||||||
.volume:hover {
|
.volume:hover,
|
||||||
|
.wifi:hover {
|
||||||
background: transparentize($base05, 0.88);
|
background: transparentize($base05, 0.88);
|
||||||
}
|
}
|
||||||
|
|
||||||
.volume icon,
|
.volume icon,
|
||||||
.mute icon {
|
.mute icon,
|
||||||
|
.wifi icon,
|
||||||
|
.wifi-header icon {
|
||||||
font-size: 16px;
|
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 Workspaces from "./Workspaces"
|
||||||
import Clock from "./Clock"
|
import Clock from "./Clock"
|
||||||
import { VolumeWidget } from "./Volume"
|
import { VolumeWidget } from "./Volume"
|
||||||
|
import { WifiWidget } from "./Wifi"
|
||||||
|
|
||||||
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
export default function Bar(gdkmonitor: Gdk.Monitor) {
|
||||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
|
||||||
const volume = VolumeWidget(gdkmonitor)
|
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 [
|
return [
|
||||||
(
|
(
|
||||||
|
|
@ -37,11 +47,11 @@ export default function Bar(gdkmonitor: Gdk.Monitor) {
|
||||||
halign={Gtk.Align.END}
|
halign={Gtk.Align.END}
|
||||||
valign={Gtk.Align.CENTER}
|
valign={Gtk.Align.CENTER}
|
||||||
>
|
>
|
||||||
{volume.button}
|
{right}
|
||||||
</box>
|
</box>
|
||||||
</centerbox>
|
</centerbox>
|
||||||
</window>
|
</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
|
network
|
||||||
battery
|
battery
|
||||||
wireplumber
|
wireplumber
|
||||||
];
|
] ++ [ pkgs.networkmanager ];
|
||||||
|
|
||||||
ags-bar = pkgs.ags.bundle {
|
ags-bar = pkgs.ags.bundle {
|
||||||
pname = "ags-bar";
|
pname = "ags-bar";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue