mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
288 lines
7.3 KiB
QML
288 lines
7.3 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
import qs.Services.UI
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property var connections: ({})
|
|
property bool refreshing: false
|
|
property bool connecting: false
|
|
property bool disconnecting: false
|
|
property string connectingUuid: ""
|
|
property string disconnectingUuid: ""
|
|
property string lastError: ""
|
|
property bool refreshPending: false
|
|
|
|
readonly property var activeConnections: {
|
|
const result = [];
|
|
const map = connections;
|
|
for (const key in map) {
|
|
const conn = map[key];
|
|
if (conn && conn.active) {
|
|
result.push(conn);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
readonly property var inactiveConnections: {
|
|
const result = [];
|
|
const map = connections;
|
|
for (const key in map) {
|
|
const conn = map[key];
|
|
if (conn && !conn.active) {
|
|
result.push(conn);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
readonly property bool hasActiveConnection: activeConnections.length > 0
|
|
|
|
Timer {
|
|
id: refreshTimer
|
|
interval: 5000
|
|
running: true
|
|
repeat: true
|
|
onTriggered: refresh()
|
|
}
|
|
|
|
Timer {
|
|
id: delayedRefreshTimer
|
|
interval: 1000
|
|
repeat: false
|
|
onTriggered: refresh()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
Logger.i("VPN", "Service started");
|
|
refresh();
|
|
}
|
|
|
|
function refresh() {
|
|
if (refreshing) {
|
|
refreshPending = true;
|
|
return;
|
|
}
|
|
refreshing = true;
|
|
lastError = "";
|
|
refreshProcess.running = true;
|
|
}
|
|
|
|
function connect(uuid) {
|
|
if (connecting || !uuid) {
|
|
return;
|
|
}
|
|
const conn = connections[uuid];
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
connecting = true;
|
|
connectingUuid = uuid;
|
|
lastError = "";
|
|
connectProcess.uuid = uuid;
|
|
connectProcess.name = conn.name;
|
|
connectProcess.running = true;
|
|
}
|
|
|
|
function disconnect(uuid) {
|
|
if (disconnecting || !uuid) {
|
|
return;
|
|
}
|
|
const conn = connections[uuid];
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
disconnecting = true;
|
|
disconnectingUuid = uuid;
|
|
lastError = "";
|
|
disconnectProcess.uuid = uuid;
|
|
disconnectProcess.name = conn.name;
|
|
disconnectProcess.running = true;
|
|
}
|
|
|
|
function toggle(uuid) {
|
|
const conn = connections[uuid];
|
|
if (!conn) {
|
|
return;
|
|
}
|
|
if (conn.active) {
|
|
disconnect(uuid);
|
|
} else {
|
|
connect(uuid);
|
|
}
|
|
}
|
|
|
|
function setConnection(uuid, data) {
|
|
if (!uuid) {
|
|
return;
|
|
}
|
|
const map = Object.assign({}, connections);
|
|
if (map[uuid]) {
|
|
map[uuid] = Object.assign({}, map[uuid], data);
|
|
connections = map;
|
|
}
|
|
}
|
|
|
|
function scheduleRefresh(interval) {
|
|
delayedRefreshTimer.interval = interval;
|
|
delayedRefreshTimer.restart();
|
|
}
|
|
|
|
Process {
|
|
id: refreshProcess
|
|
running: false
|
|
command: ["nmcli", "-t", "-f", "NAME,UUID,TYPE,DEVICE", "connection", "show"]
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const lines = text.split("\n");
|
|
const map = {};
|
|
for (let i = 0; i < lines.length; ++i) {
|
|
const line = lines[i].trim();
|
|
if (!line) {
|
|
continue;
|
|
}
|
|
const lastColonIdx = line.lastIndexOf(":");
|
|
if (lastColonIdx === -1) {
|
|
continue;
|
|
}
|
|
const device = line.substring(lastColonIdx + 1);
|
|
const remaining = line.substring(0, lastColonIdx);
|
|
const secondLastColonIdx = remaining.lastIndexOf(":");
|
|
if (secondLastColonIdx === -1) {
|
|
continue;
|
|
}
|
|
const type = remaining.substring(secondLastColonIdx + 1);
|
|
if (type !== "vpn" && type !== "wireguard") {
|
|
continue;
|
|
}
|
|
const remaining2 = remaining.substring(0, secondLastColonIdx);
|
|
const thirdLastColonIdx = remaining2.lastIndexOf(":");
|
|
if (thirdLastColonIdx === -1) {
|
|
continue;
|
|
}
|
|
const uuid = remaining2.substring(thirdLastColonIdx + 1);
|
|
const name = remaining2.substring(0, thirdLastColonIdx);
|
|
if (!uuid || !name) {
|
|
continue;
|
|
}
|
|
const active = device && device !== "--";
|
|
map[uuid] = {
|
|
"uuid": uuid,
|
|
"name": name,
|
|
"device": device,
|
|
"active": active
|
|
};
|
|
}
|
|
connections = map;
|
|
const pending = refreshPending;
|
|
refreshing = false;
|
|
refreshPending = false;
|
|
if (pending) {
|
|
scheduleRefresh(200);
|
|
}
|
|
}
|
|
}
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: {
|
|
const pending = refreshPending;
|
|
refreshing = false;
|
|
refreshPending = false;
|
|
if (text.trim()) {
|
|
lastError = text.split("\n")[0].trim();
|
|
Logger.w("VPN", "Refresh error: " + text);
|
|
}
|
|
if (pending) {
|
|
scheduleRefresh(2000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: connectProcess
|
|
property string uuid: ""
|
|
property string name: ""
|
|
running: false
|
|
command: ["nmcli", "connection", "up", "uuid", uuid]
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const output = text.trim();
|
|
if (!output || (!output.includes("successfully activated") && !output.includes("Connection successfully"))) {
|
|
return;
|
|
}
|
|
setConnection(connectProcess.uuid, {
|
|
"active": true
|
|
});
|
|
connecting = false;
|
|
connectingUuid = "";
|
|
lastError = "";
|
|
Logger.i("VPN", "Connected to " + connectProcess.name);
|
|
ToastService.showNotice(connectProcess.name, I18n.tr("toast.vpn.connected", {
|
|
"name": connectProcess.name
|
|
}), "shield-lock");
|
|
scheduleRefresh(1000);
|
|
}
|
|
}
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: {
|
|
const trimmed = text.trim();
|
|
if (trimmed) {
|
|
lastError = trimmed.split("\n")[0].trim();
|
|
Logger.w("VPN", "Connect error: " + trimmed);
|
|
ToastService.showWarning(connectProcess.name, lastError);
|
|
}
|
|
connecting = false;
|
|
connectingUuid = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: disconnectProcess
|
|
property string uuid: ""
|
|
property string name: ""
|
|
running: false
|
|
command: ["nmcli", "connection", "down", "uuid", uuid]
|
|
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
Logger.i("VPN", "Disconnected from " + disconnectProcess.name);
|
|
setConnection(disconnectProcess.uuid, {
|
|
"active": false,
|
|
"device": ""
|
|
});
|
|
disconnecting = false;
|
|
disconnectingUuid = "";
|
|
lastError = "";
|
|
ToastService.showNotice(disconnectProcess.name, I18n.tr("toast.vpn.disconnected", {
|
|
"name": disconnectProcess.name
|
|
}), "shield-off");
|
|
scheduleRefresh(1000);
|
|
}
|
|
}
|
|
|
|
stderr: StdioCollector {
|
|
onStreamFinished: {
|
|
const trimmed = text.trim();
|
|
if (trimmed) {
|
|
lastError = trimmed.split("\n")[0].trim();
|
|
Logger.w("VPN", "Disconnect error: " + trimmed);
|
|
ToastService.showWarning(disconnectProcess.name, lastError);
|
|
}
|
|
disconnecting = false;
|
|
disconnectingUuid = "";
|
|
}
|
|
}
|
|
}
|
|
}
|