diff --git a/.config/ags/modules/sideright/centermodules/wifinetworks.js b/.config/ags/modules/sideright/centermodules/wifinetworks.js index ca0e7da0c..90be10b9f 100644 --- a/.config/ags/modules/sideright/centermodules/wifinetworks.js +++ b/.config/ags/modules/sideright/centermodules/wifinetworks.js @@ -17,6 +17,8 @@ const MATERIAL_SYMBOL_SIGNAL_STRENGTH = { } let connectAttempt = ''; +let networkAuth = null; +let networkAuthSSID = null; const WifiNetwork = (accessPoint) => { const networkStrength = MaterialIcon(MATERIAL_SYMBOL_SIGNAL_STRENGTH[accessPoint.iconName], 'hugerass') @@ -35,15 +37,31 @@ const WifiNetwork = (accessPoint) => { ] }); return Button({ - onClicked: accessPoint.active ? () => { } : () => execAsync(`nmcli device wifi connect ${accessPoint.bssid}`) - // .catch(e => { - // Utils.notify({ - // summary: "Network", - // body: e, - // actions: { "Open network manager": () => execAsync("nm-connection-editor").catch(print) } - // }); - // }) - .catch(print), + onClicked: accessPoint.active ? () => { } : () => { + connectAttempt = accessPoint.ssid; + networkAuthSSID.label = `Connecting to: ${connectAttempt}`; + + // Check if the SSID is stored + execAsync(['nmcli', '-g', 'NAME', 'connection', 'show']) + .then((savedConnections) => { + const savedSSIDs = savedConnections.split('\n'); + + if (!savedSSIDs.includes(connectAttempt)) { // SSID not saved: show password input + if (networkAuth) { + networkAuth.revealChild = true; + } + } else { // If SSID is saved, hide password input + if (networkAuth) { + networkAuth.revealChild = false; + } + // Connect + execAsync(['nmcli', 'device', 'wifi', 'connect', connectAttempt]) + .catch(print); + } + }) + .catch(print); + + }, child: Box({ className: 'sidebar-wifinetworks-network spacing-h-10', children: [ @@ -81,7 +99,8 @@ const NetResource = (icon, command) => { const CurrentNetwork = () => { let authLock = false; - // console.log(Network.wifi); + let timeoutId = null; + const bottomSeparator = Box({ className: 'separator-line', }); @@ -107,7 +126,7 @@ const CurrentNetwork = () => { const networkBandwidth = Box({ vertical: true, hexpand: true, - hpack: 'center', + hpack: 'end', className: 'sidebar-wifinetworks-bandwidth', children: [ NetResource('arrow_warm_up', `${App.configDir}/scripts/network_scripts/network_bandwidth.py sent`), @@ -123,37 +142,159 @@ const CurrentNetwork = () => { self.label = Network.wifi.state; }), })] + }); + networkAuthSSID = Label({ + className: 'margin-left-5', + hpack: 'start', + hexpand: true, + label: '', + }); + const cancelAuthButton = Button({ + className: 'txt sidebar-wifinetworks-network-button', + label: 'Cancel', + hpack: 'end', + onClicked: () => { + networkAuth.revealChild = false; + authFailed.revealChild = false; + networkAuthSSID.label = ''; + networkName.children[1].label = Network.wifi?.ssid; + authEntry.text = ''; + }, + setup: setupCursorHover, + }); + const authHeader = Box({ + vertical: false, + hpack: 'fill', + spacing: 10, + children: [ + networkAuthSSID, + cancelAuthButton + ] + }); + const authFailed = Revealer({ + revealChild: false, + child: Label({ + className: 'txt txt-italic txt-subtext', + label: 'Authentication failed', + }), }) - const networkAuth = Revealer({ + const authEntry = Entry({ + className: 'sidebar-wifinetworks-auth-entry', + visibility: false, + onAccept: (self) => { + authLock = false; + // Delete SSID connection before attempting to reconnect + execAsync(['nmcli', 'connection', 'delete', connectAttempt]) + .catch(() => { }); // Ignore error if SSID not found + + execAsync(['nmcli', 'device', 'wifi', 'connect', connectAttempt, 'password', self.text]) + .then(() => { + connectAttempt = ''; // Reset SSID after successful connection + networkAuth.revealChild = false; // Hide input if successful + authFailed.revealChild = false; // Hide failed message if successful + self.text = ''; // Empty input for retry + }) + .catch(() => { + // Connection failed, show password input again + networkAuth.revealChild = true; + authFailed.revealChild = true; + }); + }, + placeholderText: 'Enter network password', + }); + const forgetButton = Button({ + label: 'Forget', + hexpand: true, + className: 'txt sidebar-wifinetworks-network-button', + onClicked: () => { + execAsync(['nmcli', '-t', '-f', 'ACTIVE,NAME', 'connection', 'show']) + .then(output => { + const activeSSID = output + .split('\n') + .find(line => line.startsWith('yes:')) + ?.split(':')[1]; + + if (activeSSID) { + execAsync(['nmcli', 'connection', 'delete', activeSSID]) + .catch(err => Utils.execAsync(['notify-send', + "Network", + `Failed to forget network - Hold to copy\n${err}`, + '-a', 'ags', + ]).catch(print)); + } + }) + .catch(); + }, + setup: setupCursorHover, + }); + const propertiesButton = Button({ + label: 'Properties', + className: 'txt sidebar-wifinetworks-network-button', + hexpand: true, + onClicked: () => { + Utils.execAsync('nmcli -t -f uuid connection show --active').then(uuid => { + if (uuid.trim()) { + Utils.execAsync(`nm-connection-editor --edit ${uuid.trim()}`); + } + closeEverything(); + }).catch(err => Utils.execAsync(['notify-send', + "Network", + `Failed to get connection UUID - Hold to copy\n${err}`, + '-a', 'ags', + ]).catch(print)); + }, + setup: setupCursorHover, + }); + const networkProp = Revealer({ + transition: 'slide_down', + transitionDuration: userOptions.animations.durationLarge, + child: Box({ + className: 'spacing-h-10', + homogeneous: true, + children: [ + propertiesButton, + forgetButton, + ], + setup: setupCursorHover, + }), + setup: (self) => self.hook(Network, (self) => { + if (Network.wifi?.ssid === '') self.revealChild = false; + else self.revealChild = true; + }), + }); + networkAuth = Revealer({ transition: 'slide_down', transitionDuration: userOptions.animations.durationLarge, child: Box({ className: 'margin-top-10 spacing-v-5', vertical: true, children: [ - Label({ - className: 'margin-left-5', - hpack: 'start', - label: getString("Authentication"), - }), - Entry({ - className: 'sidebar-wifinetworks-auth-entry', - visibility: false, // Password dots - onAccept: (self) => { - authLock = false; - networkAuth.revealChild = false; - execAsync(`nmcli device wifi connect '${connectAttempt}' password '${self.text}'`) - .catch(print); - } - }) + authHeader, + authEntry, + authFailed, ] }), setup: (self) => self.hook(Network, (self) => { - if (Network.wifi.state == 'failed' || Network.wifi.state == 'need_auth') { - authLock = true; - connectAttempt = Network.wifi.ssid; - self.revealChild = true; - } + execAsync(['nmcli', '-g', 'NAME', 'connection', 'show']) + .then((savedConnections) => { + const savedSSIDs = savedConnections.split('\n'); + if (Network.wifi.state == 'failed' || + (Network.wifi.state == 'need_auth' && !savedSSIDs.includes(Network.wifi.ssid))) { + authLock = true; + connectAttempt = Network.wifi.ssid; + self.revealChild = true; + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + authLock = false; + self.revealChild = false; + authFailed.revealChild = false; + Network.wifi.state = 'activated'; + }, 20000); // 20 seconds timeout + } + } + ).catch(print); }), }); const actualContent = Box({ @@ -165,16 +306,16 @@ const CurrentNetwork = () => { vertical: true, children: [ Box({ - className: 'spacing-h-10', + className: 'spacing-h-10 margin-bottom-10', children: [ MaterialIcon('language', 'hugerass'), networkName, networkBandwidth, - networkStatus, - + // networkStatus, ] }), - networkAuth, + networkProp, + networkAuth ] }), bottomSeparator, @@ -217,7 +358,7 @@ export default (props) => { vertical: true, className: 'spacing-v-5 margin-bottom-15', setup: (self) => self.hook(Network, self.attribute.updateNetworks), - }) + }), }), overlays: [Box({ className: 'sidebar-centermodules-scrollgradient-bottom' @@ -247,4 +388,4 @@ export default (props) => { bottomBar, ] }); -} +} \ No newline at end of file diff --git a/.config/ags/scss/_sidebars.scss b/.config/ags/scss/_sidebars.scss index d9206f9d5..b02b88152 100644 --- a/.config/ags/scss/_sidebars.scss +++ b/.config/ags/scss/_sidebars.scss @@ -1011,6 +1011,7 @@ $waifu_image_overlay_transparency: 0.7; background-color: $layer1; color: $onLayer1; padding: 0.682rem; + caret-color: $onLayer2; } .sidebar-wifinetworks-bandwidth { @@ -1039,3 +1040,17 @@ $waifu_image_overlay_transparency: 0.7; .sidebar-centermodules-scrollgradient-bottom { background: linear-gradient(to top, $layer1 0%, transparentize($layer1, 1) 1.023rem); } + +.sidebar-wifinetworks-network-button { + @include full-rounding; + @include element_decel; + min-width: 6.818rem; + min-height: 2.25rem; + background-color: $layer2Hover; + color: $onLayer2; +} + +.sidebar-wifinetworks-network-button:hover, +.sidebar-wifinetworks-network-button:focus { + background-color: $layer2Active; +} \ No newline at end of file diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 3387ec4a7..5a107eb33 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -7,16 +7,30 @@ windowrulev2 = noblur, class:.* # Uncomment to apply global transparency to all windows: # windowrulev2 = opacity 0.89 override 0.89 override, class:.* -# Specific floating windows. +# Floating windowrulev2 = float, class:^(blueberry\.py)$ windowrulev2 = float, class:^(steam)$ windowrulev2 = float, class:^(guifetch)$ # FlafyDev/guifetch +windowrulev2 = float, class:^(pavucontrol)$ +windowrulev2 = size 45%, class:^(pavucontrol)$ +windowrulev2 = center, class:^(pavucontrol)$ +windowrulev2 = float, class:^(org.pulseaudio.pavucontrol)$ +windowrulev2 = size 45%, class:^(org.pulseaudio.pavucontrol)$ +windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$ +windowrulev2 = float, class:^(nm-connection-editor)$ +windowrulev2 = size 45%, class:^(nm-connection-editor)$ +windowrulev2 = center, class:^(nm-connection-editor)$ -# Tiling rule for a specific app. +# Tiling windowrulev2 = tile, class:^dev\.warp\.Warp$ -# Picture-in-Picture window (matched by title). +# Picture-in-Picture windowrulev2 = float, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ +windowrulev2 = keepaspectratio, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ +windowrulev2 = move 73% 72%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ +windowrulev2 = size 25%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ +windowrulev2 = float, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ +windowrulev2 = pin, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ # Dialog windows – float+center these windows. windowrulev2 = center, title:^(Open File)(.*)$ @@ -34,12 +48,6 @@ windowrulev2 = float, title:^(Save As)(.*)$ windowrulev2 = float, title:^(Library)(.*)$ windowrulev2 = float, title:^(File Upload)(.*)$ -# --- Picture-in-Picture enhancements --- -windowrulev2 = keepaspectratio, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = move 73% 72%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = size 25%, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = float, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ -windowrulev2 = pin, title:^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$ # --- Tearing --- windowrulev2 = immediate, title:.*\.exe diff --git a/arch-packages/illogical-impulse-widgets/PKGBUILD b/arch-packages/illogical-impulse-widgets/PKGBUILD index 37a018c71..447c4b5aa 100644 --- a/arch-packages/illogical-impulse-widgets/PKGBUILD +++ b/arch-packages/illogical-impulse-widgets/PKGBUILD @@ -13,4 +13,5 @@ depends=( wl-clipboard hyprpicker anyrun-git + nm-connection-editor )