Drive Wi-Fi widget from iwctl with scrollable network list
This commit is contained in:
@@ -367,22 +367,32 @@ Item {
|
||||
|
||||
function networkCommand() {
|
||||
return [
|
||||
"route_device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == \"dev\") { print $(i + 1); exit } }')",
|
||||
"connection_name() { command -v nmcli >/dev/null 2>&1 && nmcli -t -f GENERAL.CONNECTION device show \"$1\" 2>/dev/null | awk -F: '$1 == \"GENERAL.CONNECTION\" { print $2; exit }'; }",
|
||||
"wifi_details() { command -v nmcli >/dev/null 2>&1 && nmcli -t -f IN-USE,SIGNAL,FREQ device wifi list --rescan no 2>/dev/null | awk -F: '$1 == \"*\" { print $2 \"\\t\" $3; exit }'; }",
|
||||
"if [[ -n $route_device && ! -d /sys/class/net/$route_device/wireless ]]; then",
|
||||
" label=$(connection_name \"$route_device\"); label=${label:-$route_device}",
|
||||
" printf 'ethernet\\t%s\\t\\t\\n' \"$label\"",
|
||||
"elif [[ -n $route_device ]]; then",
|
||||
" label=$(connection_name \"$route_device\"); label=${label:-$route_device}",
|
||||
" details=$(wifi_details)",
|
||||
" signal=${details%%$'\\t'*}",
|
||||
" frequency=${details#*$'\\t'}",
|
||||
" [[ $frequency == \"$details\" ]] && frequency=\"\"",
|
||||
" printf 'wifi\\t%s\\t%s\\t%s\\n' \"$label\" \"$signal\" \"$frequency\"",
|
||||
"else",
|
||||
"device=$(ip route get 1.1.1.1 2>/dev/null | awk '{ for (i = 1; i <= NF; i++) if ($i == \"dev\") { print $(i + 1); exit } }')",
|
||||
"if [[ -z $device ]]; then",
|
||||
" printf 'disconnected\\t\\t\\t\\n'",
|
||||
"fi"
|
||||
" exit 0",
|
||||
"fi",
|
||||
"if [[ ! -d /sys/class/net/$device/wireless ]]; then",
|
||||
" printf 'ethernet\\t%s\\t\\t\\n' \"$device\"",
|
||||
" exit 0",
|
||||
"fi",
|
||||
"show=$(iwctl station \"$device\" show 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g')",
|
||||
"state=$(awk '/^[[:space:]]*State[[:space:]]/ { sub(/.*State[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")",
|
||||
"ssid=$(awk '/^[[:space:]]*Connected network[[:space:]]/ { sub(/.*Connected network[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")",
|
||||
"freq=$(awk '/^[[:space:]]*Frequency[[:space:]]/ { sub(/.*Frequency[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")",
|
||||
"rssi=$(awk '/^[[:space:]]*RSSI[[:space:]]/ { sub(/.*RSSI[[:space:]]+/, \"\"); sub(/[[:space:]]+$/, \"\"); print; exit }' <<<\"$show\")",
|
||||
"dbm=${rssi%% *}",
|
||||
"signal=\"\"",
|
||||
"if [[ -n $dbm ]]; then",
|
||||
" if (( dbm >= -50 )); then signal=100",
|
||||
" elif (( dbm <= -100 )); then signal=0",
|
||||
" else signal=$(( 2 * (dbm + 100) )); fi",
|
||||
"fi",
|
||||
"if [[ -n $state && $state != connected ]]; then",
|
||||
" printf 'disconnected\\t\\t\\t\\n'",
|
||||
" exit 0",
|
||||
"fi",
|
||||
"printf 'wifi\\t%s\\t%s\\t%s\\n' \"${ssid:-$device}\" \"$signal\" \"$freq\""
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ Rectangle {
|
||||
property real horizontalPadding: 10
|
||||
property real verticalPadding: 6
|
||||
property bool active: false
|
||||
property bool leftAlign: false
|
||||
property color activeBackground: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18)
|
||||
|
||||
ToolTip {
|
||||
@@ -61,7 +62,10 @@ Rectangle {
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: root.leftAlign ? parent.left : undefined
|
||||
anchors.leftMargin: root.leftAlign ? root.horizontalPadding : 0
|
||||
anchors.horizontalCenter: root.leftAlign ? undefined : parent.horizontalCenter
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../common" as Common
|
||||
@@ -143,36 +144,33 @@ fi
|
||||
}
|
||||
}
|
||||
|
||||
// Wi-Fi scan via nmcli OR iwctl, whichever is available. Emits TSV plus
|
||||
// a STATION_AVAILABLE marker so the panel knows whether to render the list.
|
||||
// Wi-Fi scan via iwctl. Emits a STATION_AVAILABLE marker followed by TSV
|
||||
// rows (connected, ssid, signal_pct, security). Strip ANSI escapes early —
|
||||
// iwctl's table layout starts each row with one, which breaks naive parsing.
|
||||
Process {
|
||||
id: wifiProc
|
||||
command: ["bash", "-c", `
|
||||
if command -v nmcli >/dev/null; then
|
||||
echo STATION_AVAILABLE
|
||||
nmcli -t -f IN-USE,SSID,SIGNAL,SECURITY device wifi list --rescan no 2>/dev/null \
|
||||
| awk -F: '{ printf "%s\\t%s\\t%s\\t%s\\n", ($1=="*" ? "1" : "0"), $2, $3, $4 }'
|
||||
elif command -v iwctl >/dev/null; then
|
||||
station=$(iwctl station list 2>/dev/null | sed -n 's/^[[:space:]]*\\(wl[a-z0-9]*\\)[[:space:]].*/\\1/p' | head -1)
|
||||
[[ -n $station ]] && {
|
||||
echo STATION_AVAILABLE
|
||||
iwctl station "$station" get-networks 2>/dev/null \
|
||||
| sed -e 's/\\x1b\\[[0-9;]*m//g' \
|
||||
| awk 'NR > 4 && NF >= 2 {
|
||||
connected = ($0 ~ /^>/) ? 1 : 0
|
||||
line = $0
|
||||
sub(/^[ >]+/, "", line)
|
||||
# Last two columns are security and signal indicator (asterisks)
|
||||
n = split(line, fields, /[ \\t]{2,}/)
|
||||
ssid = fields[1]
|
||||
security = (n >= 2) ? fields[2] : ""
|
||||
signal_marks = (n >= 3) ? fields[3] : ""
|
||||
gsub(/[^*]/, "", signal_marks)
|
||||
signal_pct = length(signal_marks) * 25
|
||||
printf "%d\\t%s\\t%d\\t%s\\n", connected, ssid, signal_pct, security
|
||||
}'
|
||||
}
|
||||
fi
|
||||
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
||||
[[ -z $station ]] && exit 0
|
||||
echo STATION_AVAILABLE
|
||||
iwctl station "$station" get-networks rssi-dbms 2>/dev/null \\
|
||||
| sed -e 's/\\x1b\\[[0-9;]*m//g' \\
|
||||
| awk '
|
||||
NR <= 4 { next }
|
||||
/^[[:space:]]*$/ { next }
|
||||
{
|
||||
connected = (substr($0, 1, 4) ~ />/) ? 1 : 0
|
||||
line = $0
|
||||
sub(/^[[:space:]]*>?[[:space:]]+/, "", line)
|
||||
sub(/[[:space:]]+$/, "", line)
|
||||
n = split(line, parts, /[[:space:]]{2,}/)
|
||||
if (n < 3) next
|
||||
ssid = parts[1]
|
||||
security = parts[n-1]
|
||||
dbm = parts[n] / 100
|
||||
signal = (dbm >= -50) ? 100 : (dbm <= -100) ? 0 : int(2 * (dbm + 100))
|
||||
printf "%d\\t%s\\t%d\\t%s\\n", connected, ssid, signal, security
|
||||
}'
|
||||
`]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
@@ -390,34 +388,69 @@ fi
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.wifiStationAvailable ? root.wifiNetworks.slice(0, 6) : []
|
||||
// Scrollable network list — cap the height so a busy neighbourhood
|
||||
// doesn't push the popup off-screen. Bluetooth panel uses the same
|
||||
// pattern.
|
||||
Flickable {
|
||||
visible: root.wifiStationAvailable
|
||||
width: parent.width
|
||||
height: Math.min(networkList.implicitHeight, 240)
|
||||
contentWidth: width
|
||||
contentHeight: networkList.implicitHeight
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Common.PillButton {
|
||||
required property var modelData
|
||||
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
|
||||
|
||||
Column {
|
||||
id: networkList
|
||||
width: parent.width
|
||||
iconText: root.wifiIconFor(modelData.signal)
|
||||
text: (modelData.ssid || "Hidden") + (modelData.security ? " · " + modelData.security : "")
|
||||
foreground: root.bar.foreground
|
||||
horizontalPadding: 10
|
||||
verticalPadding: 6
|
||||
active: modelData.connected
|
||||
spacing: 6
|
||||
|
||||
onClicked: {
|
||||
if (modelData.connected) return
|
||||
if (actionProc.running) return
|
||||
if (Qt.platform.os) { /* keep linter happy */ }
|
||||
// Prefer nmcli; fall back to iwctl. Both work as one-shot connects
|
||||
// for open or already-known networks; secured networks pop the TUI.
|
||||
actionProc.command = ["bash", "-c",
|
||||
"if command -v nmcli >/dev/null; then" +
|
||||
" nmcli device wifi connect " + root.bar.shellQuote(modelData.ssid) +
|
||||
"; else" +
|
||||
" " + root.bar.omarchyPath + "/bin/omarchy-launch-wifi" +
|
||||
"; fi"]
|
||||
actionProc.running = true
|
||||
root.popupOpen = false
|
||||
Repeater {
|
||||
model: root.wifiStationAvailable ? root.wifiNetworks : []
|
||||
|
||||
Common.PillButton {
|
||||
required property var modelData
|
||||
|
||||
width: networkList.width
|
||||
leftAlign: true
|
||||
iconText: root.wifiIconFor(modelData.signal)
|
||||
text: (modelData.ssid || "Hidden") + (modelData.security ? " · " + modelData.security : "")
|
||||
foreground: root.bar.foreground
|
||||
horizontalPadding: 10
|
||||
verticalPadding: 6
|
||||
active: modelData.connected
|
||||
|
||||
onClicked: {
|
||||
if (modelData.connected) return
|
||||
if (actionProc.running) return
|
||||
// Known networks (and open networks) can connect non-interactively
|
||||
// via iwctl. Anything else needs Impala for passphrase entry.
|
||||
var ssid = root.bar.shellQuote(modelData.ssid)
|
||||
var security = root.bar.shellQuote(modelData.security || "")
|
||||
var omarchyPath = root.bar.omarchyPath
|
||||
actionProc.command = ["bash", "-c", `
|
||||
ssid=${ssid}
|
||||
security=${security}
|
||||
station=$(iwctl station list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk '/^[[:space:]]*wl/ { print $1; exit }')
|
||||
known=$(iwctl known-networks list 2>/dev/null | sed -e 's/\\x1b\\[[0-9;]*m//g' | awk -v s="$ssid" 'NR > 3 {
|
||||
line = $0
|
||||
sub(/^[[:space:]]+/, "", line)
|
||||
sub(/[[:space:]]+$/, "", line)
|
||||
n = split(line, p, /[[:space:]]{2,}/)
|
||||
if (n >= 1 && p[1] == s) { print "yes"; exit }
|
||||
}')
|
||||
if [[ -n $station && ( -n $known || $security == "open" ) ]]; then
|
||||
iwctl --dont-ask station "$station" connect "$ssid"
|
||||
else
|
||||
${omarchyPath}/bin/omarchy-launch-wifi
|
||||
fi
|
||||
`]
|
||||
actionProc.running = true
|
||||
root.popupOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user