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 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-26 16:01:55 +01:00
parent 074e298f32
commit 4a0399c6d3

View file

@ -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