From 4a0399c6d3502c6605567381a2d7212c3200ee06 Mon Sep 17 00:00:00 2001 From: rope Date: Tue, 26 May 2026 16:01:55 +0100 Subject: [PATCH] quickshell: add wifi network selector dropdown Click network icon to see available wifi networks with signal strength. Click to connect, disconnect button for active network. Uses BarDropdown component for consistent styling. Co-Authored-By: Claude Opus 4.6 --- settings/hyprland.nix | 192 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 3 deletions(-) diff --git a/settings/hyprland.nix b/settings/hyprland.nix index ed072d4..7da3b7e 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -595,14 +595,56 @@ in font.pixelSize: 14 } + property var wifiNetworks: [] + Process { - id: nmEditorProc - command: ["${pkgs.networkmanagerapplet}/bin/nm-connection-editor"] + id: wifiScanProc + command: ["${pkgs.networkmanager}/bin/nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY,IN-USE", "device", "wifi", "list", "--rescan", "auto"] + stdout: SplitParser { + onRead: data => { + let fields = data.split(":"); + if (fields.length < 4 || fields[0] === "") return; + let nets = netWidget.wifiNetworks; + // Avoid duplicates + for (let i = 0; i < nets.length; i++) { + if (nets[i].ssid === fields[0]) return; + } + nets.push({ + ssid: fields[0], + signal: parseInt(fields[1]) || 0, + security: fields[2], + active: fields[3] === "*" + }); + netWidget.wifiNetworks = nets; + } + } + } + + Process { + id: wifiConnectProc + property string targetSsid: "" + command: ["${pkgs.networkmanager}/bin/nmcli", "device", "wifi", "connect", targetSsid] + } + + Process { + id: netDisconnectProc + command: ["${pkgs.networkmanager}/bin/nmcli", "device", "disconnect", "wlan0"] } MouseArea { anchors.fill: parent - onClicked: nmEditorProc.running = true + onClicked: { + if (netDropdown.justDismissed) return; + if (netDropdown.visible) { + netDropdown.visible = false; + } else { + netWidget.wifiNetworks = []; + wifiScanProc.running = true; + let pos = netWidget.mapToItem(bar.contentItem, netWidget.width / 2, 0); + netDropdown.dropdownX = pos.x; + netDropdown.visible = true; + } + } } } @@ -917,6 +959,150 @@ in } } + // Network dropdown + BarDropdown { + id: netDropdown + fullWidth: netDropdownCol.width + 24 + fullHeight: netDropdownCol.height + 16 + + Column { + id: netDropdownCol + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 8 + width: 220 + spacing: 4 + + // Current connection header + Text { + width: parent.width + text: netWidget.netState === "connected" + ? "\u{f05a9} " + netWidget.netConn + : "\u{f05aa} Not connected" + color: "#${c.base05}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 13 + font.weight: Font.Medium + elide: Text.ElideRight + } + + // Disconnect button (when connected) + Rectangle { + visible: netWidget.netState === "connected" + width: parent.width + height: 28 + color: disconnectMouse.containsMouse ? "#${c.base02}" : "transparent" + radius: 4 + + Text { + anchors.centerIn: parent + text: "Disconnect" + color: "#${c.base08}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 12 + } + + MouseArea { + id: disconnectMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + netDisconnectProc.running = true; + netDropdown.visible = false; + } + } + } + + // Separator + Rectangle { + width: parent.width - 20 + anchors.horizontalCenter: parent.horizontalCenter + height: 1 + color: "#${c.base03}" + } + + // Available networks header + Text { + text: "Available networks" + color: "#${c.base03}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 11 + topPadding: 2 + } + + // Network list + Repeater { + model: netWidget.wifiNetworks + + Rectangle { + required property var modelData + width: 220 + height: 32 + color: netItemMouse.containsMouse ? "#${c.base02}" : "transparent" + radius: 4 + + Row { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 8 + anchors.right: parent.right + anchors.rightMargin: 8 + spacing: 8 + + // Signal icon + Text { + text: { + let s = modelData.signal; + if (s >= 75) return "\u{f05a9}"; + if (s >= 50) return "\u{f05a9}"; + if (s >= 25) return "\u{f05a9}"; + return "\u{f05aa}"; + } + color: modelData.active ? "#${c.base0B}" : "#${c.base04}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 13 + anchors.verticalCenter: parent.verticalCenter + } + + // SSID + Text { + text: modelData.ssid + color: modelData.active ? "#${c.base0B}" : "#${c.base05}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 12 + elide: Text.ElideRight + width: 140 + anchors.verticalCenter: parent.verticalCenter + } + + // Lock icon for secured networks + Text { + visible: modelData.security !== "" && modelData.security !== "--" + text: "\u{f0341}" + color: "#${c.base03}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 10 + anchors.verticalCenter: parent.verticalCenter + } + } + + MouseArea { + id: netItemMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (!modelData.active) { + wifiConnectProc.targetSsid = modelData.ssid; + wifiConnectProc.running = true; + } + netDropdown.visible = false; + } + } + } + } + } + } + // Calendar popup BarDropdown { id: calPopup