From 2398961473b156c169a0d6a8f129afc8a2d26396 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Sat, 6 Sep 2025 00:36:29 -0400 Subject: [PATCH] Wifi: more clean ups and improvements --- Modules/WiFiPanel/WiFiPanel.qml | 134 ++++++++++++++++---------- Services/NetworkService.qml | 163 +++++++++++++++++++------------- 2 files changed, 181 insertions(+), 116 deletions(-) diff --git a/Modules/WiFiPanel/WiFiPanel.qml b/Modules/WiFiPanel/WiFiPanel.qml index 98fde74..4b8b904 100644 --- a/Modules/WiFiPanel/WiFiPanel.qml +++ b/Modules/WiFiPanel/WiFiPanel.qml @@ -51,7 +51,7 @@ NPanel { id: wifiSwitch checked: Settings.data.network.wifiEnabled onToggled: checked => NetworkService.setWifiEnabled(checked) - baseSize: Style.baseWidgetSize * 0.7 * scaling + baseSize: Style.baseWidgetSize * 0.65 * scaling } NIconButton { @@ -122,9 +122,11 @@ NPanel { ColumnLayout { visible: !Settings.data.network.wifiEnabled anchors.fill: parent - spacing: Style.marginL * scaling + spacing: Style.marginM * scaling - Item { Layout.fillHeight: true } + Item { + Layout.fillHeight: true + } NIcon { text: "wifi_off" @@ -141,22 +143,27 @@ NPanel { } NText { - text: "Enable Wi-Fi to see available networks" + text: "Enable Wi-Fi to see available networks." font.pointSize: Style.fontSizeS * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } - Item { Layout.fillHeight: true } + Item { + Layout.fillHeight: true + } } // Scanning state ColumnLayout { - visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0 + visible: Settings.data.network.wifiEnabled && NetworkService.scanning && Object.keys( + NetworkService.networks).length === 0 anchors.fill: parent spacing: Style.marginL * scaling - Item { Layout.fillHeight: true } + Item { + Layout.fillHeight: true + } NBusyIndicator { running: true @@ -172,12 +179,15 @@ NPanel { Layout.alignment: Qt.AlignHCenter } - Item { Layout.fillHeight: true } + Item { + Layout.fillHeight: true + } } // Networks list container ScrollView { - visible: Settings.data.network.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0) + visible: Settings.data.network.wifiEnabled && (!NetworkService.scanning || Object.keys( + NetworkService.networks).length > 0) anchors.fill: parent ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: ScrollBar.AsNeeded @@ -190,20 +200,23 @@ NPanel { // Network list Repeater { model: { - if (!Settings.data.network.wifiEnabled) return [] - + if (!Settings.data.network.wifiEnabled) + return [] + const nets = Object.values(NetworkService.networks) return nets.sort((a, b) => { - if (a.connected !== b.connected) return b.connected - a.connected - return b.signal - a.signal - }) + if (a.connected !== b.connected) + return b.connected - a.connected + return b.signal - a.signal + }) } Rectangle { Layout.fillWidth: true implicitHeight: netColumn.implicitHeight + (Style.marginM * scaling * 2) radius: Style.radiusM * scaling - color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.05) : Color.mSurface + color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, + 0.05) : Color.mSurface border.width: Math.max(1, Style.borderS * scaling) border.color: modelData.connected ? Color.mPrimary : Color.mOutline @@ -262,7 +275,6 @@ NPanel { Item { Layout.preferredWidth: Style.marginXXS * scaling } - Rectangle { visible: modelData.connected @@ -302,7 +314,7 @@ NPanel { // Action area RowLayout { - spacing: Style.marginXS * scaling + spacing: Style.marginS * scaling NBusyIndicator { visible: NetworkService.connectingTo === modelData.ssid @@ -311,11 +323,23 @@ NPanel { size: Style.baseWidgetSize * 0.5 * scaling } + NIconButton { + visible: (modelData.existing || modelData.cached) && !modelData.connected + && NetworkService.connectingTo !== modelData.ssid + icon: "delete" + tooltipText: "Forget network" + sizeRatio: 0.7 + onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid + } + NButton { - visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid + visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid + && passwordSsid !== modelData.ssid text: { - if (modelData.existing || modelData.cached) return "Connect" - if (!NetworkService.isSecured(modelData.security)) return "Connect" + if (modelData.existing || modelData.cached) + return "Connect" + if (!NetworkService.isSecured(modelData.security)) + return "Connect" return "Password" } outlined: !hovered @@ -339,13 +363,6 @@ NPanel { backgroundColor: Color.mError onClicked: NetworkService.disconnect(modelData.ssid) } - - NIconButton { - visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid - icon: "more_vert" - sizeRatio: 0.7 - onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid - } } } @@ -353,21 +370,24 @@ NPanel { Rectangle { visible: passwordSsid === modelData.ssid Layout.fillWidth: true - height: 40 * scaling + height: passwordRow.implicitHeight + Style.marginS * scaling * 2 color: Color.mSurfaceVariant + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) radius: Style.radiusS * scaling RowLayout { + id: passwordRow anchors.fill: parent - anchors.margins: Style.marginXS * scaling - spacing: Style.marginXS * scaling + anchors.margins: Style.marginS * scaling + spacing: Style.marginM * scaling Rectangle { Layout.fillWidth: true Layout.fillHeight: true radius: Style.radiusXS * scaling color: Color.mSurface - border.color: pwdInput.activeFocus ? Color.mPrimary : Color.mOutline + border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline border.width: Math.max(1, Style.borderS * scaling) TextInput { @@ -382,7 +402,10 @@ NPanel { echoMode: TextInput.Password selectByMouse: true focus: visible + passwordCharacter: "●" onTextChanged: passwordInput = text + onVisibleChanged: if (visible) + forceActiveFocus() onAccepted: { if (text) { NetworkService.connect(passwordSsid, text) @@ -405,6 +428,7 @@ NPanel { text: "Connect" fontSize: Style.fontSizeXXS * scaling enabled: passwordInput.length > 0 + outlined: true onClicked: { NetworkService.connect(passwordSsid, passwordInput) passwordSsid = "" @@ -414,7 +438,7 @@ NPanel { NIconButton { icon: "close" - sizeRatio: 0.6 + sizeRatio: 0.8 onClicked: { passwordSsid = "" passwordInput = "" @@ -423,38 +447,43 @@ NPanel { } } - // Options menu + // Forget network Rectangle { visible: expandedSsid === modelData.ssid Layout.fillWidth: true - height: forgetRow.implicitHeight + Style.marginS * 2 + height: forgetRow.implicitHeight + Style.marginS * 2 * scaling color: Color.mSurfaceVariant radius: Style.radiusS * scaling border.width: Math.max(1, Style.borderS * scaling) - border.color: Color.mError + border.color: Color.mOutline RowLayout { id: forgetRow anchors.fill: parent anchors.margins: Style.marginS * scaling + spacing: Style.marginM * scaling - NIcon { - text: "delete_outline" - font.pointSize: Style.fontSizeM * scaling - color: Color.mError - } + RowLayout { + NIcon { + text: "delete_outline" + font.pointSize: Style.fontSizeL * scaling + color: Color.mError + } - NText { - text: "Forget this network?" - font.pointSize: Style.fontSizeS * scaling - color: Color.mError - Layout.fillWidth: true + NText { + text: "Forget this network?" + font.pointSize: Style.fontSizeS * scaling + color: Color.mError + Layout.fillWidth: true + } } NButton { + id: forgetButton text: "Forget" fontSize: Style.fontSizeXXS * scaling backgroundColor: Color.mError + outlined: forgetButton.hovered ? false : true onClicked: { NetworkService.forget(modelData.ssid) expandedSsid = "" @@ -463,7 +492,7 @@ NPanel { NIconButton { icon: "close" - sizeRatio: 0.6 + sizeRatio: 0.8 onClicked: expandedSsid = "" } } @@ -476,11 +505,14 @@ NPanel { // Empty state when no networks ColumnLayout { - visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0 + visible: Settings.data.network.wifiEnabled && !NetworkService.scanning && Object.keys( + NetworkService.networks).length === 0 anchors.fill: parent spacing: Style.marginL * scaling - Item { Layout.fillHeight: true } + Item { + Layout.fillHeight: true + } NIcon { text: "wifi_find" @@ -503,9 +535,11 @@ NPanel { onClicked: NetworkService.scan() } - Item { Layout.fillHeight: true } + Item { + Layout.fillHeight: true + } } } } } -} \ No newline at end of file +} diff --git a/Services/NetworkService.qml b/Services/NetworkService.qml index 4e23c8d..9eb04eb 100644 --- a/Services/NetworkService.qml +++ b/Services/NetworkService.qml @@ -14,7 +14,8 @@ Singleton { property bool connecting: false property string connectingTo: "" property string lastError: "" - + property bool ethernet: false + // Persistent cache property string cacheFile: Settings.cacheDir + "network.json" readonly property string cachedLastConnected: cacheAdapter.lastConnected @@ -24,13 +25,13 @@ Singleton { FileView { id: cacheFileView path: root.cacheFile - + JsonAdapter { id: cacheAdapter property var knownNetworks: ({}) property string lastConnected: "" } - + onLoadFailed: { cacheAdapter.knownNetworks = ({}) cacheAdapter.lastConnected = "" @@ -79,26 +80,29 @@ Singleton { function setWifiEnabled(enabled) { Settings.data.network.wifiEnabled = enabled - + wifiToggleProcess.action = enabled ? "on" : "off" wifiToggleProcess.running = true } function scan() { - if (scanning) return - + if (scanning) + return + scanning = true lastError = "" scanProcess.running = true + ethernetStateProcess.running = true } function connect(ssid, password = "") { - if (connecting) return - + if (connecting) + return + connecting = true connectingTo = ssid lastError = "" - + // Check if we have a saved connection if (networks[ssid]?.existing || cachedNetworks[ssid]) { connectProcess.mode = "saved" @@ -109,7 +113,7 @@ Singleton { connectProcess.ssid = ssid connectProcess.password = password } - + connectProcess.running = true } @@ -123,13 +127,13 @@ Singleton { let known = cacheAdapter.knownNetworks delete known[ssid] cacheAdapter.knownNetworks = known - + if (cacheAdapter.lastConnected === ssid) { cacheAdapter.lastConnected = "" } - + saveCache() - + // Remove from system forgetProcess.ssid = ssid forgetProcess.running = true @@ -137,10 +141,14 @@ Singleton { // Helper functions function signalIcon(signal) { - if (signal >= 80) return "network_wifi" - if (signal >= 60) return "network_wifi_3_bar" - if (signal >= 40) return "network_wifi_2_bar" - if (signal >= 20) return "network_wifi_1_bar" + if (signal >= 80) + return "network_wifi" + if (signal >= 60) + return "network_wifi_3_bar" + if (signal >= 40) + return "network_wifi_2_bar" + if (signal >= 20) + return "network_wifi_1_bar" return "signal_wifi_0_bar" } @@ -149,14 +157,32 @@ Singleton { } // Processes + Process { + id: ethernetStateProcess + running: false + command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"] + + stdout: StdioCollector { + onStreamFinished: { + + root.ethernet = text.split("\n").some(line => { + const parts = line.split(":") + return parts[1] === "ethernet" && parts[2] === "connected" + }) + Logger.log("Network", "Ethernet connected:", root.ethernet) + } + } + } + Process { id: wifiStateProcess running: false command: ["nmcli", "radio", "wifi"] - + stdout: StdioCollector { onStreamFinished: { const enabled = text.trim() === "enabled" + Logger.log("Network", "Wifi enabled:", enabled) if (Settings.data.network.wifiEnabled !== enabled) { Settings.data.network.wifiEnabled = enabled } @@ -169,19 +195,20 @@ Singleton { property string action: "on" running: false command: ["nmcli", "radio", "wifi", action] - + onRunningChanged: { if (!running) { if (action === "on") { // Clear networks immediately and start delayed scan root.networks = ({}) + delayedScanTimer.interval = 8000 delayedScanTimer.restart() } else { root.networks = ({}) } } } - + stderr: StdioCollector { onStreamFinished: { if (text.trim()) { @@ -197,66 +224,69 @@ Singleton { command: ["sh", "-c", ` # Get existing profiles profiles=$(nmcli -t -f NAME,TYPE connection show | grep ':802-11-wireless' | cut -d: -f1) - + # Get WiFi networks nmcli -t -f SSID,SECURITY,SIGNAL,IN-USE device wifi list | while read line; do - ssid=$(echo "$line" | cut -d: -f1) - security=$(echo "$line" | cut -d: -f2) - signal=$(echo "$line" | cut -d: -f3) - in_use=$(echo "$line" | cut -d: -f4) - - # Skip empty SSIDs - if [ -z "$ssid" ]; then - continue - fi - - existing=false - if echo "$profiles" | grep -q "^$ssid$"; then - existing=true - fi - - echo "$ssid|$security|$signal|$in_use|$existing" + ssid=$(echo "$line" | cut -d: -f1) + security=$(echo "$line" | cut -d: -f2) + signal=$(echo "$line" | cut -d: -f3) + in_use=$(echo "$line" | cut -d: -f4) + + # Skip empty SSIDs + if [ -z "$ssid" ]; then + continue + fi + + existing=false + if echo "$profiles" | grep -q "^$ssid$"; then + existing=true + fi + + echo "$ssid|$security|$signal|$in_use|$existing" done - `] - + `] + stdout: StdioCollector { onStreamFinished: { const nets = {} const lines = text.split("\n").filter(l => l.trim()) - + for (const line of lines) { const parts = line.split("|") - if (parts.length < 5) continue - + if (parts.length < 5) + continue + const ssid = parts[0] - if (!ssid || ssid.trim() === "") continue - + if (!ssid || ssid.trim() === "") + continue + const network = { - ssid: ssid, - security: parts[1] || "--", - signal: parseInt(parts[2]) || 0, - connected: parts[3] === "*", - existing: parts[4] === "true", - cached: ssid in cacheAdapter.knownNetworks + "ssid": ssid, + "security": parts[1] || "--", + "signal": parseInt(parts[2]) || 0, + "connected": parts[3] === "*", + "existing": parts[4] === "true", + "cached": ssid in cacheAdapter.knownNetworks } - + // Track connected network if (network.connected && cacheAdapter.lastConnected !== ssid) { cacheAdapter.lastConnected = ssid saveCache() } - + // Keep best signal for duplicate SSIDs if (!nets[ssid] || network.signal > nets[ssid].signal) { nets[ssid] = network } } - + root.networks = nets root.scanning = false + Logger.log("Network", "Discovered", Object.keys(root.networks).length, "Wi-Fi networks") } } - + stderr: StdioCollector { onStreamFinished: { root.scanning = false @@ -278,7 +308,7 @@ Singleton { property string ssid: "" property string password: "" running: false - + command: { if (mode === "saved") { return ["nmcli", "connection", "up", "id", ssid] @@ -290,38 +320,39 @@ Singleton { return cmd } } - + stdout: StdioCollector { onStreamFinished: { // Success - update cache let known = cacheAdapter.knownNetworks known[connectProcess.ssid] = { - profileName: connectProcess.ssid, - lastConnected: Date.now() + "profileName": connectProcess.ssid, + "lastConnected": Date.now() } cacheAdapter.knownNetworks = known cacheAdapter.lastConnected = connectProcess.ssid saveCache() - + root.connecting = false root.connectingTo = "" Logger.log("Network", "Connected to " + connectProcess.ssid) - + // Rescan to update status delayedScanTimer.interval = 1000 delayedScanTimer.restart() } } - + stderr: StdioCollector { onStreamFinished: { root.connecting = false root.connectingTo = "" - + if (text.trim()) { // Parse common errors if (text.includes("Secrets were required") || text.includes("no secrets provided")) { root.lastError = "Incorrect password" + forget(connectProcess.ssid) } else if (text.includes("No network with SSID")) { root.lastError = "Network not found" } else if (text.includes("Timeout")) { @@ -329,7 +360,7 @@ Singleton { } else { root.lastError = text.split("\n")[0].trim() } - + Logger.warn("Network", "Connect error: " + text) } } @@ -341,7 +372,7 @@ Singleton { property string ssid: "" running: false command: ["nmcli", "connection", "down", "id", ssid] - + onRunningChanged: { if (!running) { delayedScanTimer.interval = 1000 @@ -355,7 +386,7 @@ Singleton { property string ssid: "" running: false command: ["nmcli", "connection", "delete", "id", ssid] - + onRunningChanged: { if (!running) { delayedScanTimer.interval = 1000 @@ -363,4 +394,4 @@ Singleton { } } } -} \ No newline at end of file +}