summaryrefslogtreecommitdiff
path: root/services/VPN.qml
diff options
context:
space:
mode:
Diffstat (limited to 'services/VPN.qml')
-rw-r--r--services/VPN.qml176
1 files changed, 176 insertions, 0 deletions
diff --git a/services/VPN.qml b/services/VPN.qml
new file mode 100644
index 0000000..10e5e7e
--- /dev/null
+++ b/services/VPN.qml
@@ -0,0 +1,176 @@
+pragma Singleton
+
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import qs.config
+import Caelestia
+
+Singleton {
+ id: root
+
+ property bool connected: false
+
+ readonly property bool connecting: connectProc.running || disconnectProc.running
+ readonly property bool enabled: Config.utilities.vpn.enabled
+ readonly property var providerInput: (Config.utilities.vpn.provider && Config.utilities.vpn.provider.length > 0) ? Config.utilities.vpn.provider[0] : "wireguard"
+ readonly property bool isCustomProvider: typeof providerInput === "object"
+ readonly property string providerName: isCustomProvider ? (providerInput.name || "custom") : String(providerInput)
+ readonly property string interfaceName: isCustomProvider ? (providerInput.interface || "") : ""
+ readonly property var currentConfig: {
+ const name = providerName;
+ const iface = interfaceName;
+ const defaults = getBuiltinDefaults(name, iface);
+
+ if (isCustomProvider) {
+ const custom = providerInput;
+ return {
+ connectCmd: custom.connectCmd || defaults.connectCmd,
+ disconnectCmd: custom.disconnectCmd || defaults.disconnectCmd,
+ interface: custom.interface || defaults.interface,
+ displayName: custom.displayName || defaults.displayName
+ };
+ }
+
+ return defaults;
+ }
+
+ function getBuiltinDefaults(name, iface) {
+ const builtins = {
+ "wireguard": {
+ connectCmd: ["pkexec", "wg-quick", "up", iface],
+ disconnectCmd: ["pkexec", "wg-quick", "down", iface],
+ interface: iface,
+ displayName: iface
+ },
+ "warp": {
+ connectCmd: ["warp-cli", "connect"],
+ disconnectCmd: ["warp-cli", "disconnect"],
+ interface: "CloudflareWARP",
+ displayName: "Warp"
+ },
+ "netbird": {
+ connectCmd: ["netbird", "up"],
+ disconnectCmd: ["netbird", "down"],
+ interface: "wt0",
+ displayName: "NetBird"
+ },
+ "tailscale": {
+ connectCmd: ["tailscale", "up"],
+ disconnectCmd: ["tailscale", "down"],
+ interface: "tailscale0",
+ displayName: "Tailscale"
+ }
+ };
+
+ return builtins[name] || {
+ connectCmd: [name, "up"],
+ disconnectCmd: [name, "down"],
+ interface: iface || name,
+ displayName: name
+ };
+ }
+
+ function connect(): void {
+ if (!connected && !connecting && root.currentConfig && root.currentConfig.connectCmd) {
+ connectProc.exec(root.currentConfig.connectCmd);
+ }
+ }
+
+ function disconnect(): void {
+ if (connected && !connecting && root.currentConfig && root.currentConfig.disconnectCmd) {
+ disconnectProc.exec(root.currentConfig.disconnectCmd);
+ }
+ }
+
+ function toggle(): void {
+ if (connected) {
+ disconnect();
+ } else {
+ connect();
+ }
+ }
+
+ function checkStatus(): void {
+ if (root.enabled) {
+ statusProc.running = true;
+ }
+ }
+
+ onConnectedChanged: {
+ if (!Config.utilities.toasts.vpnChanged)
+ return;
+
+ const displayName = root.currentConfig ? (root.currentConfig.displayName || "VPN") : "VPN";
+ if (connected) {
+ Toaster.toast(qsTr("VPN connected"), qsTr("Connected to %1").arg(displayName), "vpn_key");
+ } else {
+ Toaster.toast(qsTr("VPN disconnected"), qsTr("Disconnected from %1").arg(displayName), "vpn_key_off");
+ }
+ }
+
+ Component.onCompleted: root.enabled && statusCheckTimer.start()
+
+ Process {
+ id: nmMonitor
+
+ running: root.enabled
+ command: ["nmcli", "monitor"]
+ stdout: SplitParser {
+ onRead: statusCheckTimer.restart()
+ }
+ }
+
+ Process {
+ id: statusProc
+
+ command: ["ip", "link", "show"]
+ environment: ({
+ LANG: "C.UTF-8",
+ LC_ALL: "C.UTF-8"
+ })
+ stdout: StdioCollector {
+ onStreamFinished: {
+ const iface = root.currentConfig ? root.currentConfig.interface : "";
+ root.connected = iface && text.includes(iface + ":");
+ }
+ }
+ }
+
+ Process {
+ id: connectProc
+
+ onExited: statusCheckTimer.start()
+ stderr: StdioCollector {
+ onStreamFinished: {
+ const error = text.trim();
+ if (error && !error.includes("[#]") && !error.includes("already exists")) {
+ console.warn("VPN connection error:", error);
+ } else if (error.includes("already exists")) {
+ root.connected = true;
+ }
+ }
+ }
+ }
+
+ Process {
+ id: disconnectProc
+
+ onExited: statusCheckTimer.start()
+ stderr: StdioCollector {
+ onStreamFinished: {
+ const error = text.trim();
+ if (error && !error.includes("[#]")) {
+ console.warn("VPN disconnection error:", error);
+ }
+ }
+ }
+ }
+
+ Timer {
+ id: statusCheckTimer
+
+ interval: 500
+ onTriggered: root.checkStatus()
+ }
+}