noctalia-shell/Services/Network.qml
2025-08-05 17:41:08 +02:00

348 lines
No EOL
11 KiB
QML

import QtQuick
import Quickshell.Io
QtObject {
id: root
property var networks: ({})
property string connectingSsid: ""
property string connectStatus: ""
property string connectStatusSsid: ""
property string connectError: ""
property string detectedInterface: ""
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 "wifi_0_bar";
}
function isSecured(security) {
return security && security.trim() !== "" && security.trim() !== "--";
}
function refreshNetworks() {
existingNetwork.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
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
running: root.hasActiveConnection
repeat: true
onTriggered: root.refreshNetworks()
}
// Force a refresh when menu is opened
function onMenuOpened() {
refreshNetworks();
}
function onMenuClosed() {
// No need to do anything special on close
}
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 (let 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 (let 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;
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.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 : "";
root.connectError = "";
root.pendingConnect = null;
root.refreshNetworks();
}
}
stderr: StdioCollector {
onStreamFinished: {
root.connectingSsid = "";
root.connectStatus = "error";
root.connectStatusSsid = root.pendingConnect ? root.pendingConnect.ssid : "";
root.connectError = text;
root.pendingConnect = null;
}
}
}
Component.onCompleted: {
refreshNetworks();
}
}