import QtQuick import Quickshell import Quickshell.Io Singleton { id: root property var networks: ({}) property string connectingSsid: "" property string connectStatus: "" property string connectStatusSsid: "" property string connectError: "" property string detectedInterface: "" property string lastConnectedNetwork: "" property bool isLoading: false 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" return "signal_wifi_0_bar" } function isSecured(security) { return security && security.trim() !== "" && security.trim() !== "--" } function refreshNetworks() { isLoading = true existingNetwork.running = true } function setWifiEnabled(enabled) { if (enabled) { // Enable WiFi radio isLoading = true enableWifiProcess.running = true } else { // Store the currently connected network before disabling for (const ssid in networks) { if (networks[ssid].connected) { lastConnectedNetwork = ssid break } } // Disable WiFi radio disableWifiProcess.running = true } } function connectNetwork(ssid, security) { pendingConnect = { "ssid": ssid, "security": security, "password": "" } doConnect() } function submitPassword(ssid, password) { pendingConnect = { "ssid": ssid, "security": networks[ssid].security, "password": password } doConnect() } function disconnectNetwork(ssid) { disconnectProfileProcess.connectionName = ssid disconnectProfileProcess.running = true } property var pendingConnect: null function doConnect() { const params = pendingConnect if (!params) return connectingSsid = params.ssid connectStatus = "" connectStatusSsid = params.ssid const targetNetwork = networks[params.ssid] if (targetNetwork && targetNetwork.existing) { upConnectionProcess.profileName = params.ssid upConnectionProcess.running = true pendingConnect = null return } if (params.security && params.security !== "--") { getInterfaceProcess.running = true return } connectProcess.security = params.security connectProcess.ssid = params.ssid connectProcess.password = params.password connectProcess.running = true pendingConnect = null } property int refreshInterval: 25000 // Only refresh when we have an active connection and WiFi is enabled property bool hasActiveConnection: { for (const net in networks) { if (networks[net].connected) { return true } } return false } property Timer refreshTimer: Timer { interval: root.refreshInterval // Only run timer when we're connected to a network and WiFi is enabled running: root.hasActiveConnection && Settings.data.network.wifiEnabled repeat: true onTriggered: root.refreshNetworks() } // Force a refresh when menu is opened function onMenuOpened() { if (Settings.data.network.wifiEnabled) { refreshNetworks() } } function onMenuClosed() {// No need to do anything special on close } // Process to enable WiFi radio property Process enableWifiProcess: Process { id: enableWifiProcess running: false command: ["nmcli", "radio", "wifi", "on"] onRunningChanged: { if (!running) { // Wait a moment for the radio to be enabled, then refresh networks enableWifiDelayTimer.start() } } stderr: StdioCollector { onStreamFinished: { if (text.trim() !== "") { console.warn("Error enabling WiFi:", text) } } } } // Timer to delay network refresh after enabling WiFi property Timer enableWifiDelayTimer: Timer { id: enableWifiDelayTimer interval: 2000 // Wait 2 seconds for radio to be ready repeat: false onTriggered: { // Force refresh networks multiple times to ensure UI updates root.refreshNetworks() // Try to auto-reconnect to the last connected network if it exists if (lastConnectedNetwork) { autoReconnectTimer.start() } // Set up additional refresh to ensure UI is populated postEnableRefreshTimer.start() } } // Additional timer to ensure networks are populated after enabling property Timer postEnableRefreshTimer: Timer { id: postEnableRefreshTimer interval: 1000 repeat: false onTriggered: { root.refreshNetworks() } } // Timer to attempt auto-reconnection to the last connected network property Timer autoReconnectTimer: Timer { id: autoReconnectTimer interval: 3000 // Wait 3 seconds after scan for networks to be available repeat: false onTriggered: { if (lastConnectedNetwork && networks[lastConnectedNetwork]) { const network = networks[lastConnectedNetwork] if (network.existing && !network.connected) { upConnectionProcess.profileName = lastConnectedNetwork upConnectionProcess.running = true } } } } // Process to disable WiFi radio property Process disableWifiProcess: Process { id: disableWifiProcess running: false command: ["nmcli", "radio", "wifi", "off"] onRunningChanged: { if (!running) { // Clear networks when WiFi is disabled root.networks = ({}) root.connectingSsid = "" root.connectStatus = "" root.connectStatusSsid = "" root.connectError = "" root.isLoading = false } } stderr: StdioCollector { onStreamFinished: { if (text.trim() !== "") { console.warn("Error disabling WiFi:", text) } } } } property Process disconnectProfileProcess: Process { id: disconnectProfileProcess property string connectionName: "" running: false command: ["nmcli", "connection", "down", connectionName] onRunningChanged: { if (!running) { root.refreshNetworks() } } } property Process existingNetwork: Process { id: existingNetwork running: false command: ["nmcli", "-t", "-f", "NAME,TYPE", "connection", "show"] stdout: StdioCollector { onStreamFinished: { const lines = text.split("\n") const networksMap = {} for (var i = 0; i < lines.length; ++i) { const line = lines[i].trim() if (!line) continue const parts = line.split(":") if (parts.length < 2) { console.warn("Malformed nmcli output line:", line) continue } const ssid = parts[0] const type = parts[1] if (ssid) { networksMap[ssid] = { "ssid": ssid, "type": type } } } scanProcess.existingNetwork = networksMap scanProcess.running = true } } } property Process scanProcess: Process { id: scanProcess running: false command: ["nmcli", "-t", "-f", "SSID,SECURITY,SIGNAL,IN-USE", "device", "wifi", "list"] property var existingNetwork stdout: StdioCollector { onStreamFinished: { const lines = text.split("\n") const networksMap = {} for (var i = 0; i < lines.length; ++i) { const line = lines[i].trim() if (!line) continue const parts = line.split(":") if (parts.length < 4) { console.warn("Malformed nmcli output line:", line) continue } const ssid = parts[0] const security = parts[1] const signal = parseInt(parts[2]) const inUse = parts[3] === "*" if (ssid) { if (!networksMap[ssid]) { networksMap[ssid] = { "ssid": ssid, "security": security, "signal": signal, "connected": inUse, "existing": ssid in scanProcess.existingNetwork } } else { const existingNet = networksMap[ssid] if (inUse) { existingNet.connected = true } if (signal > existingNet.signal) { existingNet.signal = signal existingNet.security = security } } } } root.networks = networksMap root.isLoading = false scanProcess.existingNetwork = {} } } } property Process connectProcess: Process { id: connectProcess property string ssid: "" property string password: "" property string security: "" running: false command: { if (password) { return ["nmcli", "device", "wifi", "connect", `'${ssid}'`, "password", password] } else { return ["nmcli", "device", "wifi", "connect", `'${ssid}'`] } } stdout: StdioCollector { onStreamFinished: { root.connectingSsid = "" root.connectStatus = "success" root.connectStatusSsid = connectProcess.ssid root.connectError = "" root.lastConnectedNetwork = connectProcess.ssid root.refreshNetworks() } } stderr: StdioCollector { onStreamFinished: { root.connectingSsid = "" root.connectStatus = "error" root.connectStatusSsid = connectProcess.ssid root.connectError = text } } } property Process getInterfaceProcess: Process { id: getInterfaceProcess running: false command: ["nmcli", "-t", "-f", "DEVICE,TYPE,STATE", "device"] stdout: StdioCollector { onStreamFinished: { var lines = text.split("\n") for (var i = 0; i < lines.length; ++i) { var parts = lines[i].split(":") if (parts[1] === "wifi" && parts[2] !== "unavailable") { root.detectedInterface = parts[0] break } } if (root.detectedInterface) { var params = root.pendingConnect addConnectionProcess.ifname = root.detectedInterface addConnectionProcess.ssid = params.ssid addConnectionProcess.password = params.password addConnectionProcess.profileName = params.ssid addConnectionProcess.security = params.security addConnectionProcess.running = true } else { root.connectStatus = "error" root.connectStatusSsid = root.pendingConnect.ssid root.connectError = "No Wi-Fi interface found." root.connectingSsid = "" root.pendingConnect = null } } } } property Process addConnectionProcess: Process { id: addConnectionProcess property string ifname: "" property string ssid: "" property string password: "" property string profileName: "" property string security: "" running: false command: { var cmd = ["nmcli", "connection", "add", "type", "wifi", "ifname", ifname, "con-name", profileName, "ssid", ssid] if (security && security !== "--") { cmd.push("wifi-sec.key-mgmt") cmd.push("wpa-psk") cmd.push("wifi-sec.psk") cmd.push(password) } return cmd } stdout: StdioCollector { onStreamFinished: { upConnectionProcess.profileName = addConnectionProcess.profileName upConnectionProcess.running = true } } stderr: StdioCollector { onStreamFinished: { upConnectionProcess.profileName = addConnectionProcess.profileName upConnectionProcess.running = true } } } property Process upConnectionProcess: Process { id: upConnectionProcess property string profileName: "" running: false command: ["nmcli", "connection", "up", "id", profileName] stdout: StdioCollector { onStreamFinished: { root.connectingSsid = "" root.connectStatus = "success" root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : profileName root.connectError = "" root.lastConnectedNetwork = profileName root.pendingConnect = null root.refreshNetworks() } } stderr: StdioCollector { onStreamFinished: { root.connectingSsid = "" root.connectStatus = "error" root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : profileName root.connectError = text root.pendingConnect = null } } } Component.onCompleted: { // Only refresh networks if WiFi is enabled if (Settings.data.network.wifiEnabled) { refreshNetworks() } } }