nosignal-shell/services/Nmcli.qml
2025-11-13 18:59:48 -05:00

1246 lines
52 KiB
QML

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);
}
}