From e02f96b151f3db3c161397ce6ab80586c75c5365 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 19 Jun 2025 19:10:25 -0300 Subject: [PATCH] :sparkles: feat(control-center/network): add provisory support for wireless connections I call it "provisory" because I don't have a Wi-Fi card to test it, but in theory, everything should work --- ags/scripts/utils.ts | 6 +- ags/style/_wal.scss | 40 +++--- ags/widget/control-center/pages/Network.ts | 148 +++++++++++++++++---- 3 files changed, 146 insertions(+), 48 deletions(-) diff --git a/ags/scripts/utils.ts b/ags/scripts/utils.ts index 4a847a4..35fb02a 100644 --- a/ags/scripts/utils.ts +++ b/ags/scripts/utils.ts @@ -1,9 +1,7 @@ import { exec, execAsync, Gio, GLib } from "astal"; -export function getDecoded(text: (Uint8Array)): string { - const decoder = new TextDecoder('utf-8'); - return decoder.decode(text); -} +export const decoder = new TextDecoder("utf-8"), + encoder = new TextEncoder(); export function getHyprlandInstanceSig(): (string|null) { return GLib.getenv("HYPRLAND_INSTANCE_SIGNATURE"); diff --git a/ags/style/_wal.scss b/ags/style/_wal.scss index d20c79b..869ba1a 100644 --- a/ags/style/_wal.scss +++ b/ags/style/_wal.scss @@ -1,26 +1,26 @@ // SCSS Variables // Generated by 'wal' -$wallpaper: "/home/joaov/wallpapers/Frieren Blue.jpeg"; +$wallpaper: "/home/joaov/wallpapers/Frieren Rain.jpg"; // Special -$background: #22303d; -$foreground: #c7cbce; -$cursor: #c7cbce; +$background: #25301c; +$foreground: #c8cbc6; +$cursor: #c8cbc6; // Colors -$color0: #22303d; -$color1: #9f847e; -$color2: #aa9381; -$color3: #7b8c92; -$color4: #78999c; -$color5: #76a3a9; -$color6: #78a4aa; -$color7: #9ca1a5; -$color8: #6b7883; -$color9: #D5B1A9; -$color10: #E3C5AD; -$color11: #A5BBC3; -$color12: #A1CDD0; -$color13: #9EDAE2; -$color14: #A1DBE3; -$color15: #c7cbce; +$color0: #25301c; +$color1: #6c8251; +$color2: #7c9357; +$color3: #78846e; +$color4: #948a88; +$color5: #899869; +$color6: #98a27b; +$color7: #9ba197; +$color8: #707c66; +$color9: #91AE6D; +$color10: #A6C574; +$color11: #A0B093; +$color12: #C6B9B6; +$color13: #B7CB8D; +$color14: #CBD9A4; +$color15: #c8cbc6; diff --git a/ags/widget/control-center/pages/Network.ts b/ags/widget/control-center/pages/Network.ts index 600d5c5..f653223 100644 --- a/ags/widget/control-center/pages/Network.ts +++ b/ags/widget/control-center/pages/Network.ts @@ -1,11 +1,15 @@ import { Gtk, Widget } from "astal/gtk3"; import { Page, PageButton } from "./Page"; import AstalNetwork from "gi://AstalNetwork"; -import { bind } from "astal"; +import { bind, GLib } from "astal"; import NM from "gi://NM"; import { Windows } from "../../../windows"; import { tr } from "../../../i18n/intl"; import { execApp } from "../../../scripts/apps"; +import { EntryPopup, EntryPopupProps } from "../../EntryPopup"; +import { Notifications } from "../../../scripts/notifications"; +import { AskPopup, AskPopupProps } from "../../AskPopup"; +import { encoder } from "../../../scripts/utils"; export const PageNetwork: (() => Page) = () => new Page({ id: "network", @@ -77,30 +81,126 @@ export const PageNetwork: (() => Page) = () => new Page({ visible: bind(AstalNetwork.get_default(), "primary").as((primary) => primary === AstalNetwork.Primary.WIFI), hexpand: true, orientation: Gtk.Orientation.VERTICAL, - children: AstalNetwork.get_default().wifi ? bind(AstalNetwork.get_default().wifi.get_device(), "accessPoints").as((aps) => - aps.map(ap => new Widget.Button({ - hexpand: true, - onClick: () => console.log("connect to " + ap.get_ssid().toArray().toString()), // TODO I don't have a WiFi board :( - child: new Widget.Box({ - hexpand: true, - children: [ - new Widget.Icon({ - halign: Gtk.Align.START, - className: "icon", - icon: "network-wireless-signal-excellent-symbolic" - } as Widget.IconProps), - new Widget.Label({ - className: "ssid", - halign: Gtk.Align.START, - label: (getDecoded(ap.ssid.get_data()) ?? ap.ssid.get_data().toString()) ?? "Wi-Fi" - } as Widget.LabelProps), - new Widget.Label({ - className: "status", - } as Widget.LabelProps) - ] - } as Widget.BoxProps) - } as Widget.ButtonProps))) : [], + children: AstalNetwork.get_default().wifi ? bind(AstalNetwork.get_default().wifi, "accessPoints").as((aps) => [ + new Widget.Label({ + className: "sub-header", + label: "Wi-Fi" + } as Widget.LabelProps), + ...aps.filter(ap => ap.ssid).map(ap => PageButton({ + className: bind(AstalNetwork.get_default().wifi, "activeAccessPoint").as(activeAP => + activeAP.ssid === ap.ssid ? "active" : ""), + title: bind(ap, "ssid").as(ssid => + ssid ?? "Unknown SSID"), + icon: bind(ap, "iconName"), + endWidget: new Widget.Icon({ + icon: bind(ap, "flags").as(flags => flags & NM.__80211ApFlags.PRIVACY ? + "channel-secure-symbolic" + : "channel-insecure-symbolic"), + css: "font-size: 18px;" + } as Widget.IconProps), + extraButtons: [ + new Widget.Button({ + image: new Widget.Icon({ + icon: "window-close-symbolic", + css: "font-size: 18px;" + } as Widget.IconProps) + } as Widget.ButtonProps) + ], + onClick: () => { + const ssid: string = ap.ssid ?? "Unknown SSID", + ssidBytes = GLib.Bytes.new(encoder.encode(ssid)); + + const connection = new NM.Connection(); + const setting = NM.SettingWireless.new(); + setting.ssid = ssidBytes; + setting.bssid = ap.bssid; + + connection.add_setting(setting); + + // Check if access point has encryption(needs a password) + if(ap.flags & NM.__80211ApFlags.PRIVACY) { + const passwdPopup = EntryPopup({ + isPassword: true, + title: `${tr("connect")}: ${ssid}`, + acceptText: tr("connect"), + closeOnAccept: false, + text: `Input password for ${ssid}`, + onAccept: (input) => { + const pskSetting = NM.SettingWirelessSecurity.new(); + pskSetting.keyMgmt = "wpa-psk"; + if(ap.flags & NM.__80211ApSecurityFlags.KEY_MGMT_SAE) + pskSetting.keyMgmt = "sae"; + + pskSetting.psk = input; + + AstalNetwork.get_default().get_client().add_connection_async( + connection, true, null, (client, asyncRes) => { + const remoteConnection = client!.add_connection_finish(asyncRes); + if(!remoteConnection) { + notifyConnectionError(ssid); + return; + } + + passwdPopup.close(); + saveToDisk(remoteConnection, ssid); + } + ); + }, + } as EntryPopupProps); + + return; + } + + AstalNetwork.get_default().get_client().add_connection_async(connection, false, null, (_, asyncRes) => { + const remoteConnection = AstalNetwork.get_default().get_client().add_connection_finish(asyncRes); + + if(!remoteConnection) { + notifyConnectionError(ssid); + return; + } + + activateWirelessConnection(remoteConnection, ssid); + }); + } + })) + ] + ) : [], } as Widget.BoxProps) ] }); +function activateWirelessConnection(connection: NM.RemoteConnection, ssid: string): void { + AstalNetwork.get_default().get_client().activate_connection_async( + connection, AstalNetwork.get_default().wifi.get_device(), null, null, (_, asyncRes) => { + const activeConnection = AstalNetwork.get_default().get_client().activate_connection_finish(asyncRes); + if(!activeConnection) { + Notifications.getDefault().sendNotification({ + appName: "network", + summary: "Couldn't activate wireless connection", + body: `An error occurred while activating the wireless connection "${ssid}"` + }); + return; + } + } + ); +} + +function notifyConnectionError(ssid: string): void { + Notifications.getDefault().sendNotification({ + appName: "network", + summary: "Coudn't connect Wi-Fi", + body: `An error occurred while trying to connect to the "${ssid}" access point. \nMaybe the password is invalid?` + }); +} +function saveToDisk(remoteConnection: NM.RemoteConnection, ssid: string): void { + AskPopup({ + text: `Save password for connection "${ssid}"?`, + acceptText: "Yes", + onAccept: () => remoteConnection.commit_changes_async(true, null, (_, asyncRes) => + !remoteConnection.commit_changes_finish(asyncRes) && Notifications.getDefault().sendNotification({ + appName: "network", + summary: "Couldn't save Wi-Fi password", + body: `An error occurred while trying to write the password for "${ssid}" to disk` + })) + } as AskPopupProps); +}