import QtQuick import Quickshell import Quickshell.Io import "." as M M.HoverPanel { id: menuWindow popupMode: true contentWidth: 250 property var _networks: [] property bool _wifiEnabled: true property Process _scanner: Process { id: scanner running: true command: ["sh", "-c", "echo '---RADIO---';" + "nmcli radio wifi 2>/dev/null;" + "echo '---CONNS---';" + "nmcli -t -f NAME,UUID,TYPE,ACTIVE connection show 2>/dev/null;" + "echo '---WIFI---';" + "nmcli -t -f SSID,SIGNAL device wifi list --rescan no 2>/dev/null"] stdout: StdioCollector { onStreamFinished: { const radioSection = text.split("---CONNS---")[0].split("---RADIO---")[1] || ""; menuWindow._wifiEnabled = radioSection.trim() === "enabled"; const sections = text.split("---WIFI---"); const connLines = (sections[0] || "").split("---CONNS---")[1] || ""; const wifiLines = sections[1] || ""; const visible = {}; for (const l of wifiLines.trim().split("\n")) { if (!l) continue; const parts = l.split(":"); const ssid = parts[0]; if (ssid) visible[ssid] = parseInt(parts[1]) || 0; } const nets = []; for (const l of connLines.trim().split("\n")) { if (!l) continue; const parts = l.split(":"); const name = parts[0]; const uuid = parts[1]; const type = parts[2] || ""; const active = parts[3] === "yes"; const isWifi = type.includes("wireless"); if (isWifi && !(name in visible)) continue; nets.push({ "name": name, "uuid": uuid, "isWifi": isWifi, "active": active, "signal": isWifi ? (visible[name] || 0) : -1 }); } nets.sort((a, b) => { if (a.active !== b.active) return a.active ? -1 : 1; if (a.signal >= 0 && b.signal >= 0) return b.signal - a.signal; return a.name.localeCompare(b.name); }); menuWindow._networks = nets; } } } property Process _radioProc: Process { id: radioProc property string _state: "" command: ["nmcli", "radio", "wifi", _state] onRunningChanged: if (!running) scanner.running = true } property Process _connectProc: Process { id: connectProc property string uuid: "" command: ["nmcli", "connection", "up", uuid] onRunningChanged: if (!running) scanner.running = true } property Process _disconnectProc: Process { id: disconnectProc property string uuid: "" command: ["nmcli", "connection", "down", uuid] onRunningChanged: if (!running) scanner.running = true } // Wi-Fi radio toggle header Item { width: menuWindow.contentWidth height: 36 Rectangle { anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 color: headerArea.containsMouse ? M.Theme.base02 : "transparent" radius: M.Theme.radius } Text { id: wifiHeaderIcon anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "\uF1EB" color: menuWindow._wifiEnabled ? menuWindow.accentColor : M.Theme.base04 font.pixelSize: M.Theme.fontSize + 1 font.family: M.Theme.iconFontFamily } Text { anchors.left: wifiHeaderIcon.right anchors.leftMargin: 8 anchors.verticalCenter: parent.verticalCenter text: "Wi-Fi" color: menuWindow._wifiEnabled ? M.Theme.base05 : M.Theme.base04 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: true } Text { anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: "\uF011" color: menuWindow._wifiEnabled ? menuWindow.accentColor : M.Theme.base04 font.pixelSize: M.Theme.fontSize font.family: M.Theme.iconFontFamily } MouseArea { id: headerArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { radioProc._state = menuWindow._wifiEnabled ? "off" : "on"; radioProc.running = true; } } } Rectangle { width: menuWindow.contentWidth - 16 height: 1 anchors.horizontalCenter: parent.horizontalCenter color: M.Theme.base03 } Repeater { model: menuWindow._networks delegate: Item { id: entry required property var modelData required property int index width: menuWindow.contentWidth height: 32 Rectangle { anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 color: entryArea.containsMouse ? M.Theme.base02 : "transparent" radius: M.Theme.radius } Text { id: netIcon anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: entry.modelData.isWifi ? "\uF1EB" : "\uDB80\uDE00" color: entry.modelData.active ? menuWindow.accentColor : M.Theme.base05 font.pixelSize: M.Theme.fontSize + 1 font.family: M.Theme.iconFontFamily } Text { anchors.left: netIcon.right anchors.leftMargin: 8 anchors.right: sigLabel.left anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter text: entry.modelData.name color: entry.modelData.active ? menuWindow.accentColor : M.Theme.base05 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily font.bold: entry.modelData.active elide: Text.ElideRight } Text { id: sigLabel anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: entry.modelData.signal >= 0 ? entry.modelData.signal + "%" : "" color: M.Theme.base04 font.pixelSize: M.Theme.fontSize - 1 font.family: M.Theme.fontFamily width: entry.modelData.signal >= 0 ? implicitWidth : 0 } MouseArea { id: entryArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { if (entry.modelData.active) { disconnectProc.uuid = entry.modelData.uuid; disconnectProc.running = true; } else { connectProc.uuid = entry.modelData.uuid; connectProc.running = true; } menuWindow.dismiss(); } } } } Text { visible: menuWindow._networks.length === 0 width: menuWindow.contentWidth height: 32 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: menuWindow._wifiEnabled ? "No networks available" : "Wi-Fi is off" color: M.Theme.base04 font.pixelSize: M.Theme.fontSize font.family: M.Theme.fontFamily } }