Files
Domipoke 955801c666 noctalia
2026-06-25 19:58:23 +02:00

232 lines
6.9 KiB
QML

import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Services.UI
QtObject {
id: root
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property var toast: root.pluginSettings?.disableToastNotifications ? null : ToastService
property var pluginApi: null
property var vpnList: []
property real connectedCount: 0
readonly property bool isLoading: Object.keys(root._pending).length > 0
property var _pending: ({})
// Needed only to detect disconnection not initiated by the user
property var _pollTimer: Timer {
interval: 5000
running: true
repeat: true
onTriggered: root.refresh()
}
property var _lines: []
property var _listProc: Process {
command: ["nmcli", "-t", "-f", "NAME,TYPE,STATE,UUID", "connection", "show"]
running: true
stdout: SplitParser {
onRead: (line) => {
if (line.trim() !== "")
root._lines.push(line)
}
}
onExited: (exitCode) => {
if (exitCode === 0) {
const parsed = []
const newPending = Object.assign({}, root._pending)
for (const line of root._lines) {
const parts = line.split(":")
if (parts.length >= 4) {
const name = parts[0]
const type = parts[1]
const state = parts[2]
const uuid = parts[3]
if (type === "vpn" || type === "wireguard") {
if (newPending[uuid]) {
const wasConnecting = newPending[uuid] === "connect"
if (wasConnecting && state === "activated")
delete newPending[uuid]
else if (!wasConnecting && state !== "activated")
delete newPending[uuid]
}
parsed.push({
name,
type,
connected: state === "activated",
isLoading: !!newPending[uuid],
uuid
})
}
}
}
root._pending = newPending
root.vpnList = parsed
root.connectedCount = parsed.filter(v => v.connected).length
}
root._lines = []
}
}
property var _connectProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "up", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.connectedTo", { name: targetName }))
else {
root.stopLoading(targetUuid)
toast?.showError(t("toast.connectionError", { name: targetName }))
}
root.refresh()
}
}
property var _disconnectProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "down", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.disconnectedFrom", { name: targetName }))
else {
root.stopLoading(targetUuid)
toast?.showError(t("toast.disconnectionError", { name: targetName }))
}
root.refresh()
}
}
property var _addProc: Process {
property string targetType: ""
command: ["nm-connection-editor", "--create", "--type", targetType]
onExited: (exitCode) => {
root.refresh();
}
}
property var _editProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nm-connection-editor", "--edit", targetUuid]
onExited: (exitCode) => {
root.refresh();
}
}
property var _removeProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "delete", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.vpnRemoved", { "name": targetName }));
else
toast?.showError(t("toast.vpnRemoveError", { "name": targetName }));
root.refresh();
}
}
function t(key: string, data) {
if (!pluginApi)
return null;
return pluginApi.tr(key, data);
}
function refresh() {
_listProc.running = true
}
function stopLoading(uuid) {
if (uuid && _pending[uuid]) {
const p = Object.assign({}, _pending)
delete p[uuid]
_pending = p
}
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
return Object.assign({}, v, { isLoading: false })
})
}
function connectTo(uuid) {
const p = Object.assign({}, _pending)
p[uuid] = "connect"
_pending = p
let name = ""
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
name = v.name
return Object.assign({}, v, { isLoading: true })
})
_connectProc.targetName = name
_connectProc.targetUuid = uuid
_connectProc.running = true
}
function disconnectFrom(uuid) {
const p = Object.assign({}, _pending)
p[uuid] = "disconnect"
_pending = p
let name = ""
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
name = v.name
return Object.assign({}, v, { isLoading: true })
})
_disconnectProc.targetName = name
_disconnectProc.targetUuid = uuid
_disconnectProc.running = true
}
function addConnection(type) {
_addProc.targetType = type || "vpn";
_addProc.running = true;
}
function editConnection(uuid) {
const vpn = vpnList.find((v) => {
return v.uuid === uuid;
});
_editProc.targetName = vpn ? vpn.name : uuid;
_editProc.targetUuid = uuid;
_editProc.running = true;
}
function removeConnection(uuid) {
const vpn = vpnList.find((v) => {
return v.uuid === uuid;
});
_removeProc.targetName = vpn ? vpn.name : uuid;
_removeProc.targetUuid = uuid;
_removeProc.running = true;
}
Component.onCompleted: {
Logger.i("NetworkManagerVPN", "Started")
}
}