diff options
| author | ATMDA <atdma2600@gmail.com> | 2025-11-13 18:59:48 -0500 |
|---|---|---|
| committer | ATMDA <atdma2600@gmail.com> | 2025-11-13 18:59:48 -0500 |
| commit | 617e686238d3c7155112196043f0883ccf6a7012 (patch) | |
| tree | aa18b05efd2e088a37f9099fb225f93658d07127 /services/Nmcli.qml | |
| parent | controlcenter: created dev panel for wireless testing (diff) | |
| download | caelestia-shell-617e686238d3c7155112196043f0883ccf6a7012.tar.gz caelestia-shell-617e686238d3c7155112196043f0883ccf6a7012.tar.bz2 caelestia-shell-617e686238d3c7155112196043f0883ccf6a7012.zip | |
service: Nmcli.qml
Diffstat (limited to 'services/Nmcli.qml')
| -rw-r--r-- | services/Nmcli.qml | 1246 |
1 files changed, 1246 insertions, 0 deletions
diff --git a/services/Nmcli.qml b/services/Nmcli.qml new file mode 100644 index 0000000..4e45b41 --- /dev/null +++ b/services/Nmcli.qml @@ -0,0 +1,1246 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io +import QtQuick + +Singleton { + id: root + + property var deviceStatus: null + property var wirelessInterfaces: [] + property var ethernetInterfaces: [] + property bool isConnected: false + property string activeInterface: "" + property string activeConnection: "" + property bool wifiEnabled: true + readonly property list<AccessPoint> networks: [] + readonly property AccessPoint active: networks.find(n => n.active) ?? null + property list<string> savedConnections: [] + property list<string> savedConnectionSsids: [] + + property var wifiConnectionQueue: [] + property int currentSsidQueryIndex: 0 + property var pendingConnection: null + signal connectionFailed(string ssid) + property var wirelessDeviceDetails: null + property var ethernetDeviceDetails: null + property list<var> ethernetDevices: [] + readonly property var activeEthernet: ethernetDevices.find(d => d.connected) ?? null + + property list<var> activeProcesses: [] + property var debugLogger: null + + function setDebugLogger(logger: var): void { + root.debugLogger = logger; + } + + function log(message: string): void { + if (root.debugLogger) { + root.debugLogger(message); + } else { + console.log("[Nmcli]", message); + } + } + + function appendLog(message: string): void { + log(message); + } + + function executeCommand(args: list<string>, callback: var): void { + const proc = commandProc.createObject(root); + proc.command = ["nmcli", ...args]; + proc.callback = callback; + + activeProcesses.push(proc); + + proc.processFinished.connect(() => { + const index = activeProcesses.indexOf(proc); + if (index >= 0) { + activeProcesses.splice(index, 1); + } + }); + + Qt.callLater(() => { + proc.exec(proc.command); + }); + } + + function getDeviceStatus(callback: var): void { + executeCommand(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device", "status"], (result) => { + if (callback) callback(result.output); + }); + } + + function getWirelessInterfaces(callback: var): void { + executeCommand(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device", "status"], (result) => { + const interfaces = []; + const lines = result.output.trim().split("\n"); + for (const line of lines) { + const parts = line.split(":"); + if (parts.length >= 2 && parts[1] === "wifi") { + interfaces.push({ + device: parts[0] || "", + type: parts[1] || "", + state: parts[2] || "", + connection: parts[3] || "" + }); + } + } + root.wirelessInterfaces = interfaces; + if (callback) callback(interfaces); + }); + } + + function getEthernetInterfaces(callback: var): void { + executeCommand(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device", "status"], (result) => { + const interfaces = []; + const devices = []; + const lines = result.output.trim().split("\n"); + for (const line of lines) { + const parts = line.split(":"); + if (parts.length >= 2 && parts[1] === "ethernet") { + const device = parts[0] || ""; + const type = parts[1] || ""; + const state = parts[2] || ""; + const connection = parts[3] || ""; + + const connected = state === "100 (connected)" || state === "connected" || state.startsWith("connected"); + + interfaces.push({ + device: device, + type: type, + state: state, + connection: connection + }); + + devices.push({ + interface: device, + type: type, + state: state, + connection: connection, + connected: connected, + ipAddress: "", + gateway: "", + dns: [], + subnet: "", + macAddress: "", + speed: "" + }); + } + } + root.ethernetInterfaces = interfaces; + root.ethernetDevices = devices; + if (callback) callback(interfaces); + }); + } + + function connectEthernet(connectionName: string, interfaceName: string, callback: var): void { + if (connectionName && connectionName.length > 0) { + executeCommand(["connection", "up", connectionName], (result) => { + if (result.success) { + Qt.callLater(() => { + getEthernetInterfaces(() => {}); + if (interfaceName && interfaceName.length > 0) { + Qt.callLater(() => { + getEthernetDeviceDetails(interfaceName, () => {}); + }, 1000); + } + }, 500); + } + if (callback) callback(result); + }); + } else if (interfaceName && interfaceName.length > 0) { + executeCommand(["device", "connect", interfaceName], (result) => { + if (result.success) { + Qt.callLater(() => { + getEthernetInterfaces(() => {}); + Qt.callLater(() => { + getEthernetDeviceDetails(interfaceName, () => {}); + }, 1000); + }, 500); + } + if (callback) callback(result); + }); + } else { + if (callback) callback({ success: false, output: "", error: "No connection name or interface specified", exitCode: -1 }); + } + } + + function disconnectEthernet(connectionName: string, callback: var): void { + if (!connectionName || connectionName.length === 0) { + if (callback) callback({ success: false, output: "", error: "No connection name specified", exitCode: -1 }); + return; + } + + executeCommand(["connection", "down", connectionName], (result) => { + if (result.success) { + root.ethernetDeviceDetails = null; + Qt.callLater(() => { + getEthernetInterfaces(() => {}); + }, 500); + } + if (callback) callback(result); + }); + } + + function getAllInterfaces(callback: var): void { + executeCommand(["-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device", "status"], (result) => { + const interfaces = []; + const lines = result.output.trim().split("\n"); + for (const line of lines) { + const parts = line.split(":"); + if (parts.length >= 2 && (parts[1] === "wifi" || parts[1] === "ethernet")) { + interfaces.push({ + device: parts[0] || "", + type: parts[1] || "", + state: parts[2] || "", + connection: parts[3] || "" + }); + } + } + if (callback) callback(interfaces); + }); + } + + function isInterfaceConnected(interfaceName: string, callback: var): void { + executeCommand(["device", "status"], (result) => { + const lines = result.output.trim().split("\n"); + for (const line of lines) { + const parts = line.split(/\s+/); + if (parts.length >= 3 && parts[0] === interfaceName) { + const connected = parts[2] === "connected" || parts[2].startsWith("connected"); + if (callback) callback(connected); + return; + } + } + if (callback) callback(false); + }); + } + + function connectToNetworkWithPasswordCheck(ssid: string, isSecure: bool, callback: var, bssid: string): void { + if (isSecure) { + const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0; + connectWireless(ssid, "", bssid, (result) => { + if (result.success) { + if (callback) callback({ success: true, usedSavedPassword: true, output: result.output, error: "", exitCode: 0 }); + } else if (result.needsPassword) { + if (callback) callback({ success: false, needsPassword: true, output: result.output, error: result.error, exitCode: result.exitCode }); + } else { + if (callback) callback(result); + } + }); + } else { + connectWireless(ssid, "", bssid, callback); + } + } + + function connectWireless(ssid: string, password: string, bssid: string, callback: var, retryCount: int): void { + const hasBssid = bssid !== undefined && bssid !== null && bssid.length > 0; + const retries = retryCount !== undefined ? retryCount : 0; + const maxRetries = 2; + + if (callback) { + root.pendingConnection = { ssid: ssid, bssid: hasBssid ? bssid : "", callback: callback, retryCount: retries }; + connectionCheckTimer.start(); + immediateCheckTimer.checkCount = 0; + immediateCheckTimer.start(); + } + + if (password && password.length > 0 && hasBssid) { + const bssidUpper = bssid.toUpperCase(); + createConnectionWithPassword(ssid, bssidUpper, password, callback); + return; + } + + let cmd = ["device", "wifi", "connect", ssid]; + if (password && password.length > 0) { + cmd.push("password", password); + } + executeCommand(cmd, (result) => { + if (result.needsPassword && callback) { + if (callback) callback(result); + return; + } + + if (!result.success && root.pendingConnection && retries < maxRetries) { + log("Connection failed, retrying... (attempt " + (retries + 1) + "/" + maxRetries + ")"); + Qt.callLater(() => { + connectWireless(ssid, password, bssid, callback, retries + 1); + }, 1000); + } else if (!result.success && root.pendingConnection) { + } else if (result.success && callback) { + } else if (!result.success && !root.pendingConnection) { + if (callback) callback(result); + } + }); + } + + function createConnectionWithPassword(ssid: string, bssidUpper: string, password: string, callback: var): void { + checkAndDeleteConnection(ssid, () => { + const cmd = ["connection", "add", + "type", "wifi", + "con-name", ssid, + "ifname", "*", + "ssid", ssid, + "802-11-wireless.bssid", bssidUpper, + "802-11-wireless-security.key-mgmt", "wpa-psk", + "802-11-wireless-security.psk", password]; + + executeCommand(cmd, (result) => { + if (result.success) { + loadSavedConnections(() => {}); + activateConnection(ssid, callback); + } else { + const hasDuplicateWarning = result.error && ( + result.error.includes("another connection with the name") || + result.error.includes("Reference the connection by its uuid") + ); + + if (hasDuplicateWarning || (result.exitCode > 0 && result.exitCode < 10)) { + loadSavedConnections(() => {}); + activateConnection(ssid, callback); + } else { + log("Connection profile creation failed, trying fallback..."); + let fallbackCmd = ["device", "wifi", "connect", ssid, "password", password]; + executeCommand(fallbackCmd, (fallbackResult) => { + if (callback) callback(fallbackResult); + }); + } + } + }); + }); + } + + function checkAndDeleteConnection(ssid: string, callback: var): void { + executeCommand(["connection", "show", ssid], (result) => { + if (result.success) { + executeCommand(["connection", "delete", ssid], (deleteResult) => { + Qt.callLater(() => { + if (callback) callback(); + }, 300); + }); + } else { + if (callback) callback(); + } + }); + } + + function activateConnection(connectionName: string, callback: var): void { + executeCommand(["connection", "up", connectionName], (result) => { + if (callback) callback(result); + }); + } + + function loadSavedConnections(callback: var): void { + executeCommand(["-t", "-f", "NAME,TYPE", "connection", "show"], (result) => { + if (!result.success) { + root.savedConnections = []; + root.savedConnectionSsids = []; + if (callback) callback([]); + return; + } + + parseConnectionList(result.output, callback); + }); + } + + function parseConnectionList(output: string, callback: var): void { + const lines = output.trim().split("\n").filter(line => line.length > 0); + const wifiConnections = []; + const connections = []; + + for (const line of lines) { + const parts = line.split(":"); + if (parts.length >= 2) { + const name = parts[0]; + const type = parts[1]; + connections.push(name); + + if (type === "802-11-wireless") { + wifiConnections.push(name); + } + } + } + + root.savedConnections = connections; + + if (wifiConnections.length > 0) { + root.wifiConnectionQueue = wifiConnections; + root.currentSsidQueryIndex = 0; + root.savedConnectionSsids = []; + queryNextSsid(callback); + } else { + root.savedConnectionSsids = []; + root.wifiConnectionQueue = []; + if (callback) callback(root.savedConnectionSsids); + } + } + + function queryNextSsid(callback: var): void { + if (root.currentSsidQueryIndex < root.wifiConnectionQueue.length) { + const connectionName = root.wifiConnectionQueue[root.currentSsidQueryIndex]; + root.currentSsidQueryIndex++; + + executeCommand(["-t", "-f", "802-11-wireless.ssid", "connection", "show", connectionName], (result) => { + if (result.success) { + processSsidOutput(result.output); + } + queryNextSsid(callback); + }); + } else { + root.wifiConnectionQueue = []; + root.currentSsidQueryIndex = 0; + if (callback) callback(root.savedConnectionSsids); + } + } + + function processSsidOutput(output: string): void { + const lines = output.trim().split("\n"); + for (const line of lines) { + if (line.startsWith("802-11-wireless.ssid:")) { + const ssid = line.substring("802-11-wireless.ssid:".length).trim(); + if (ssid && ssid.length > 0) { + const ssidLower = ssid.toLowerCase(); + const exists = root.savedConnectionSsids.some(s => s && s.toLowerCase() === ssidLower); + if (!exists) { + const newList = root.savedConnectionSsids.slice(); + newList.push(ssid); + root.savedConnectionSsids = newList; + } + } + } + } + } + + function hasSavedProfile(ssid: string): bool { + if (!ssid || ssid.length === 0) { + return false; + } + const ssidLower = ssid.toLowerCase().trim(); + + if (root.active && root.active.ssid) { + const activeSsidLower = root.active.ssid.toLowerCase().trim(); + if (activeSsidLower === ssidLower) { + return true; + } + } + + const hasSsid = root.savedConnectionSsids.some(savedSsid => + savedSsid && savedSsid.toLowerCase().trim() === ssidLower + ); + + if (hasSsid) { + return true; + } + + const hasConnectionName = root.savedConnections.some(connName => + connName && connName.toLowerCase().trim() === ssidLower + ); + + return hasConnectionName; + } + + function forgetNetwork(ssid: string, callback: var): void { + if (!ssid || ssid.length === 0) { + if (callback) callback({ success: false, output: "", error: "No SSID specified", exitCode: -1 }); + return; + } + + const connectionName = root.savedConnections.find(conn => + conn && conn.toLowerCase().trim() === ssid.toLowerCase().trim() + ) || ssid; + + executeCommand(["connection", "delete", connectionName], (result) => { + if (result.success) { + Qt.callLater(() => { + loadSavedConnections(() => {}); + }, 500); + } + if (callback) callback(result); + }); + } + + function disconnect(interfaceName: string, callback: var): void { + if (interfaceName && interfaceName.length > 0) { + executeCommand(["device", "disconnect", interfaceName], (result) => { + if (callback) callback(result.success ? result.output : ""); + }); + } else { + executeCommand(["device", "disconnect", "wifi"], (result) => { + if (callback) callback(result.success ? result.output : ""); + }); + } + } + + function getDeviceDetails(interfaceName: string, callback: var): void { + executeCommand(["device", "show", interfaceName], (result) => { + if (callback) callback(result.output); + }); + } + + function refreshStatus(callback: var): void { + getDeviceStatus((output) => { + const lines = output.trim().split("\n"); + let connected = false; + let activeIf = ""; + let activeConn = ""; + + for (const line of lines) { + const parts = line.split(":"); + if (parts.length >= 4) { + const state = parts[2] || ""; + if (state === "connected" || state.startsWith("connected")) { + connected = true; + activeIf = parts[0] || ""; + activeConn = parts[3] || ""; + break; + } + } + } + + root.isConnected = connected; + root.activeInterface = activeIf; + root.activeConnection = activeConn; + + if (callback) callback({ connected, interface: activeIf, connection: activeConn }); + }); + } + + function bringInterfaceUp(interfaceName: string, callback: var): void { + if (interfaceName && interfaceName.length > 0) { + executeCommand(["device", "connect", interfaceName], (result) => { + if (callback) { + callback(result); + } + }); + } else { + if (callback) callback({ success: false, output: "", error: "No interface specified", exitCode: -1 }); + } + } + + function bringInterfaceDown(interfaceName: string, callback: var): void { + if (interfaceName && interfaceName.length > 0) { + executeCommand(["device", "disconnect", interfaceName], (result) => { + if (callback) { + callback(result); + } + }); + } else { + if (callback) callback({ success: false, output: "", error: "No interface specified", exitCode: -1 }); + } + } + + function scanWirelessNetworks(interfaceName: string, callback: var): void { + let cmd = ["device", "wifi", "rescan"]; + if (interfaceName && interfaceName.length > 0) { + cmd.push("ifname", interfaceName); + } + executeCommand(cmd, (result) => { + if (callback) { + callback(result); + } + }); + } + + function enableWifi(enabled: bool, callback: var): void { + const cmd = enabled ? "on" : "off"; + executeCommand(["radio", "wifi", cmd], (result) => { + if (result.success) { + getWifiStatus((status) => { + root.wifiEnabled = status; + if (callback) callback(result); + }); + } else { + if (callback) callback(result); + } + }); + } + + function toggleWifi(callback: var): void { + const newState = !root.wifiEnabled; + enableWifi(newState, callback); + } + + function getWifiStatus(callback: var): void { + executeCommand(["radio", "wifi"], (result) => { + if (result.success) { + const enabled = result.output.trim() === "enabled"; + root.wifiEnabled = enabled; + if (callback) callback(enabled); + } else { + if (callback) callback(root.wifiEnabled); + } + }); + } + + function getNetworks(callback: var): void { + executeCommand(["-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID,SECURITY", "d", "w"], (result) => { + if (!result.success) { + if (callback) callback([]); + return; + } + + const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; + const rep = new RegExp("\\\\:", "g"); + const rep2 = new RegExp(PLACEHOLDER, "g"); + + const allNetworks = result.output.trim().split("\n") + .filter(line => line && line.length > 0) + .map(n => { + const net = n.replace(rep, PLACEHOLDER).split(":"); + return { + active: net[0] === "yes", + strength: parseInt(net[1] || "0", 10) || 0, + frequency: parseInt(net[2] || "0", 10) || 0, + ssid: (net[3]?.replace(rep2, ":") ?? "").trim(), + bssid: (net[4]?.replace(rep2, ":") ?? "").trim(), + security: (net[5] ?? "").trim() + }; + }) + .filter(n => n.ssid && n.ssid.length > 0); + + const networkMap = new Map(); + for (const network of allNetworks) { + const existing = networkMap.get(network.ssid); + if (!existing) { + networkMap.set(network.ssid, network); + } else { + if (network.active && !existing.active) { + networkMap.set(network.ssid, network); + } else if (!network.active && !existing.active) { + if (network.strength > existing.strength) { + networkMap.set(network.ssid, network); + } + } + } + } + + const networks = Array.from(networkMap.values()); + const rNetworks = root.networks; + + const destroyed = rNetworks.filter(rn => !networks.find(n => + n.frequency === rn.frequency && + n.ssid === rn.ssid && + n.bssid === rn.bssid + )); + for (const network of destroyed) { + const index = rNetworks.indexOf(network); + if (index >= 0) { + rNetworks.splice(index, 1); + network.destroy(); + } + } + + for (const network of networks) { + const match = rNetworks.find(n => + n.frequency === network.frequency && + n.ssid === network.ssid && + n.bssid === network.bssid + ); + if (match) { + match.lastIpcObject = network; + } else { + rNetworks.push(apComp.createObject(root, { + lastIpcObject: network + })); + } + } + + if (callback) callback(root.networks); + checkPendingConnection(); + }); + } + + function getWirelessSSIDs(interfaceName: string, callback: var): void { + let cmd = ["-t", "-f", "SSID,SIGNAL,SECURITY", "device", "wifi", "list"]; + if (interfaceName && interfaceName.length > 0) { + cmd.push("ifname", interfaceName); + } + executeCommand(cmd, (result) => { + if (!result.success) { + if (callback) callback([]); + return; + } + + const ssids = []; + const lines = result.output.trim().split("\n"); + const seenSSIDs = new Set(); + + for (const line of lines) { + if (!line || line.length === 0) continue; + + const parts = line.split(":"); + if (parts.length >= 1) { + const ssid = parts[0].trim(); + if (ssid && ssid.length > 0 && !seenSSIDs.has(ssid)) { + seenSSIDs.add(ssid); + const signalStr = parts.length >= 2 ? parts[1].trim() : ""; + const signal = signalStr ? parseInt(signalStr, 10) : 0; + const security = parts.length >= 3 ? parts[2].trim() : ""; + ssids.push({ + ssid: ssid, + signal: signalStr, + signalValue: isNaN(signal) ? 0 : signal, + security: security + }); + } + } + } + + ssids.sort((a, b) => { + return b.signalValue - a.signalValue; + }); + + if (callback) callback(ssids); + }); + } + + component CommandProcess: Process { + id: proc + property var callback: null + property list<string> command: [] + property bool callbackCalled: false + property int exitCode: 0 + signal processFinished + + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) + + stdout: StdioCollector { + id: stdoutCollector + onStreamFinished: { + } + } + + stderr: StdioCollector { + id: stderrCollector + onStreamFinished: { + const error = text.trim(); + if (error && error.length > 0) { + const isConnectionCommand = proc.command && proc.command.length > 0 && + (proc.command.includes("wifi") || proc.command.includes("connection")); + + if (isConnectionCommand && root.pendingConnection && root.pendingConnection.callback) { + const needsPassword = (error.includes("Secrets were required") || + error.includes("Secrets were required, but not provided") || + error.includes("No secrets provided") || + error.includes("802-11-wireless-security.psk") || + error.includes("password for") || + (error.includes("password") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("Secrets") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("802.11") && !error.includes("Connection activated") && !error.includes("successfully"))) && + !error.includes("Connection activated") && + !error.includes("successfully"); + + if (needsPassword && !proc.callbackCalled && root.pendingConnection) { + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + const pending = root.pendingConnection; + root.pendingConnection = null; + proc.callbackCalled = true; + const result = { + success: false, + output: (stdoutCollector && stdoutCollector.text) ? stdoutCollector.text : "", + error: error, + exitCode: -1, + needsPassword: true + }; + if (pending.callback) { + pending.callback(result); + } + if (proc.callback && proc.callback !== pending.callback) { + proc.callback(result); + } + } + } + } + } + } + + onExited: { + proc.exitCode = exitCode; + Qt.callLater(() => { + if (proc.callbackCalled) { + proc.processFinished(); + return; + } + + if (proc.callback) { + const output = (stdoutCollector && stdoutCollector.text) ? stdoutCollector.text : ""; + const error = (stderrCollector && stderrCollector.text) ? stderrCollector.text : ""; + const success = proc.exitCode === 0; + + const isConnectionCommand = proc.command && proc.command.length > 0 && + (proc.command.includes("wifi") || proc.command.includes("connection")); + const needsPassword = isConnectionCommand && error && error.length > 0 && + (error.includes("Secrets were required") || + error.includes("Secrets were required, but not provided") || + error.includes("No secrets provided") || + error.includes("802-11-wireless-security.psk") || + error.includes("password for") || + (error.includes("password") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("Secrets") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("802.11") && !error.includes("Connection activated") && !error.includes("successfully"))) && + !error.includes("Connection activated") && + !error.includes("successfully"); + + if (needsPassword && root.pendingConnection && root.pendingConnection.callback) { + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + const pending = root.pendingConnection; + root.pendingConnection = null; + proc.callbackCalled = true; + const result = { + success: false, + output: output, + error: error, + exitCode: proc.exitCode, + needsPassword: true + }; + if (pending.callback) { + pending.callback(result); + } + if (proc.callback && proc.callback !== pending.callback) { + proc.callback(result); + } + proc.processFinished(); + return; + } else if (!success && isConnectionCommand && root.pendingConnection) { + const failedSsid = root.pendingConnection.ssid; + root.connectionFailed(failedSsid); + } + + proc.callbackCalled = true; + proc.callback({ + success: success, + output: output, + error: error, + exitCode: proc.exitCode, + needsPassword: needsPassword || false + }); + proc.processFinished(); + } else { + proc.processFinished(); + } + }); + } + } + + Component { + id: commandProc + CommandProcess {} + } + + component AccessPoint: QtObject { + required property var lastIpcObject + readonly property string ssid: lastIpcObject.ssid + readonly property string bssid: lastIpcObject.bssid + readonly property int strength: lastIpcObject.strength + readonly property int frequency: lastIpcObject.frequency + readonly property bool active: lastIpcObject.active + readonly property string security: lastIpcObject.security + readonly property bool isSecure: security.length > 0 + } + + Component { + id: apComp + AccessPoint {} + } + + Timer { + id: connectionCheckTimer + interval: 4000 + onTriggered: { + if (root.pendingConnection) { + const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + + if (!connected && root.pendingConnection.callback) { + let foundPasswordError = false; + for (let i = 0; i < root.activeProcesses.length; i++) { + const proc = root.activeProcesses[i]; + if (proc && proc.stderr && proc.stderr.text) { + const error = proc.stderr.text.trim(); + if (error && error.length > 0) { + const isConnectionCommand = proc.command && proc.command.length > 0 && + (proc.command.includes("wifi") || proc.command.includes("connection")); + + if (isConnectionCommand) { + const needsPassword = (error.includes("Secrets were required") || + error.includes("Secrets were required, but not provided") || + error.includes("No secrets provided") || + error.includes("802-11-wireless-security.psk") || + error.includes("password for") || + (error.includes("password") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("Secrets") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("802.11") && !error.includes("Connection activated") && !error.includes("successfully"))) && + !error.includes("Connection activated") && + !error.includes("successfully"); + + if (needsPassword && !proc.callbackCalled && root.pendingConnection) { + const pending = root.pendingConnection; + root.pendingConnection = null; + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + proc.callbackCalled = true; + const result = { + success: false, + output: (proc.stdout && proc.stdout.text) ? proc.stdout.text : "", + error: error, + exitCode: -1, + needsPassword: true + }; + if (pending.callback) { + pending.callback(result); + } + if (proc.callback && proc.callback !== pending.callback) { + proc.callback(result); + } + foundPasswordError = true; + break; + } + } + } + } + } + + if (!foundPasswordError) { + const pending = root.pendingConnection; + const failedSsid = pending.ssid; + root.pendingConnection = null; + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + root.connectionFailed(failedSsid); + pending.callback({ + success: false, + output: "", + error: "Connection timeout", + exitCode: -1, + needsPassword: false + }); + } + } else if (connected) { + root.pendingConnection = null; + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + } + } + } + } + + Timer { + id: immediateCheckTimer + interval: 500 + repeat: true + triggeredOnStart: false + property int checkCount: 0 + + onTriggered: { + if (root.pendingConnection) { + checkCount++; + const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + + if (connected) { + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + if (root.pendingConnection.callback) { + root.pendingConnection.callback({ success: true, output: "Connected", error: "", exitCode: 0 }); + } + root.pendingConnection = null; + } else { + for (let i = 0; i < root.activeProcesses.length; i++) { + const proc = root.activeProcesses[i]; + if (proc && proc.stderr && proc.stderr.text) { + const error = proc.stderr.text.trim(); + if (error && error.length > 0) { + const isConnectionCommand = proc.command && proc.command.length > 0 && + (proc.command.includes("wifi") || proc.command.includes("connection")); + + if (isConnectionCommand) { + const needsPassword = (error.includes("Secrets were required") || + error.includes("Secrets were required, but not provided") || + error.includes("No secrets provided") || + error.includes("802-11-wireless-security.psk") || + error.includes("password for") || + (error.includes("password") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("Secrets") && !error.includes("Connection activated") && !error.includes("successfully")) || + (error.includes("802.11") && !error.includes("Connection activated") && !error.includes("successfully"))) && + !error.includes("Connection activated") && + !error.includes("successfully"); + + if (needsPassword && !proc.callbackCalled && root.pendingConnection && root.pendingConnection.callback) { + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + const pending = root.pendingConnection; + root.pendingConnection = null; + proc.callbackCalled = true; + const result = { + success: false, + output: (proc.stdout && proc.stdout.text) ? proc.stdout.text : "", + error: error, + exitCode: -1, + needsPassword: true + }; + if (pending.callback) { + pending.callback(result); + } + if (proc.callback && proc.callback !== pending.callback) { + proc.callback(result); + } + return; + } + } + } + } + } + + if (checkCount >= 6) { + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + } + } + } else { + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + } + } + } + + function checkPendingConnection(): void { + if (root.pendingConnection) { + Qt.callLater(() => { + const connected = root.active && root.active.ssid === root.pendingConnection.ssid; + if (connected) { + connectionCheckTimer.stop(); + immediateCheckTimer.stop(); + immediateCheckTimer.checkCount = 0; + if (root.pendingConnection.callback) { + root.pendingConnection.callback({ success: true, output: "Connected", error: "", exitCode: 0 }); + } + root.pendingConnection = null; + } else { + if (!immediateCheckTimer.running) { + immediateCheckTimer.start(); + } + } + }); + } + } + + function cidrToSubnetMask(cidr: string): string { + const cidrNum = parseInt(cidr, 10); + if (isNaN(cidrNum) || cidrNum < 0 || cidrNum > 32) { + return ""; + } + + const mask = (0xffffffff << (32 - cidrNum)) >>> 0; + const octet1 = (mask >>> 24) & 0xff; + const octet2 = (mask >>> 16) & 0xff; + const octet3 = (mask >>> 8) & 0xff; + const octet4 = mask & 0xff; + + return `${octet1}.${octet2}.${octet3}.${octet4}`; + } + + function getWirelessDeviceDetails(interfaceName: string, callback: var): void { + if (!interfaceName || interfaceName.length === 0) { + const activeInterface = root.wirelessInterfaces.find(iface => { + return iface.state === "connected" || iface.state.startsWith("connected"); + }); + if (activeInterface && activeInterface.device) { + interfaceName = activeInterface.device; + } else { + if (callback) callback(null); + return; + } + } + + executeCommand(["device", "show", interfaceName], (result) => { + if (!result.success || !result.output) { + root.wirelessDeviceDetails = null; + if (callback) callback(null); + return; + } + + const details = parseDeviceDetails(result.output, false); + root.wirelessDeviceDetails = details; + if (callback) callback(details); + }); + } + + function getEthernetDeviceDetails(interfaceName: string, callback: var): void { + if (!interfaceName || interfaceName.length === 0) { + const activeInterface = root.ethernetInterfaces.find(iface => { + return iface.state === "connected" || iface.state.startsWith("connected"); + }); + if (activeInterface && activeInterface.device) { + interfaceName = activeInterface.device; + } else { + if (callback) callback(null); + return; + } + } + + executeCommand(["device", "show", interfaceName], (result) => { + if (!result.success || !result.output) { + root.ethernetDeviceDetails = null; + if (callback) callback(null); + return; + } + + const details = parseDeviceDetails(result.output, true); + root.ethernetDeviceDetails = details; + if (callback) callback(details); + }); + } + + function parseDeviceDetails(output: string, isEthernet: bool): var { + const details = { + ipAddress: "", + gateway: "", + dns: [], + subnet: "", + macAddress: "", + speed: "" + }; + + if (!output || output.length === 0) { + return details; + } + + const lines = output.trim().split("\n"); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const parts = line.split(":"); + if (parts.length >= 2) { + const key = parts[0].trim(); + const value = parts.slice(1).join(":").trim(); + + if (key.startsWith("IP4.ADDRESS")) { + const ipParts = value.split("/"); + details.ipAddress = ipParts[0] || ""; + if (ipParts[1]) { + details.subnet = cidrToSubnetMask(ipParts[1]); + } else { + details.subnet = ""; + } + } else if (key === "IP4.GATEWAY") { + if (value !== "--") { + details.gateway = value; + } + } else if (key.startsWith("IP4.DNS")) { + if (value !== "--" && value.length > 0) { + details.dns.push(value); + } + } else if (isEthernet && key === "WIRED-PROPERTIES.MAC") { + details.macAddress = value; + } else if (isEthernet && key === "WIRED-PROPERTIES.SPEED") { + details.speed = value; + } else if (!isEthernet && key === "GENERAL.HWADDR") { + details.macAddress = value; + } + } + } + + return details; + } + + Process { + id: monitorProc + running: true + command: ["nmcli", "monitor"] + environment: ({ + LANG: "C.UTF-8", + LC_ALL: "C.UTF-8" + }) + + stdout: SplitParser { + onRead: { + log("Connection state change detected, refreshing..."); + root.refreshOnConnectionChange(); + } + } + + onExited: { + log("Monitor process exited, restarting..."); + Qt.callLater(() => { + monitorProc.running = true; + }, 2000); + } + } + + function refreshOnConnectionChange(): void { + getNetworks((networks) => { + const newActive = root.active; + + if (newActive && newActive.active) { + Qt.callLater(() => { + if (root.wirelessInterfaces.length > 0) { + const activeWireless = root.wirelessInterfaces.find(iface => { + return iface.state === "connected" || iface.state.startsWith("connected"); + }); + if (activeWireless && activeWireless.device) { + getWirelessDeviceDetails(activeWireless.device, () => {}); + } + } + + if (root.ethernetInterfaces.length > 0) { + const activeEthernet = root.ethernetInterfaces.find(iface => { + return iface.state === "connected" || iface.state.startsWith("connected"); + }); + if (activeEthernet && activeEthernet.device) { + getEthernetDeviceDetails(activeEthernet.device, () => {}); + } + } + }, 500); + } else { + root.wirelessDeviceDetails = null; + root.ethernetDeviceDetails = null; + } + + getWirelessInterfaces(() => {}); + getEthernetInterfaces(() => { + if (root.activeEthernet && root.activeEthernet.connected) { + Qt.callLater(() => { + getEthernetDeviceDetails(root.activeEthernet.interface, () => {}); + }, 500); + } + }); + }); + } + + Component.onCompleted: { + getWifiStatus(() => {}); + getNetworks(() => {}); + loadSavedConnections(() => {}); + getEthernetInterfaces(() => {}); + + Qt.callLater(() => { + if (root.wirelessInterfaces.length > 0) { + const activeWireless = root.wirelessInterfaces.find(iface => { + return iface.state === "connected" || iface.state.startsWith("connected"); + }); + if (activeWireless && activeWireless.device) { + getWirelessDeviceDetails(activeWireless.device, () => {}); + } + } + + if (root.ethernetInterfaces.length > 0) { + const activeEthernet = root.ethernetInterfaces.find(iface => { + return iface.state === "connected" || iface.state.startsWith("connected"); + }); + if (activeEthernet && activeEthernet.device) { + getEthernetDeviceDetails(activeEthernet.device, () => {}); + } + } + }, 2000); + } +} + |