Files
proton-vpn-tui/internal/backend/status.go
T
2026-08-27 23:14:56 +02:00

104 lines
2.1 KiB
Go

package backend
import (
"bytes"
"context"
"errors"
"fmt"
"os/exec"
"strconv"
"strings"
)
const protonInterface = "proton0"
var ErrNmcliNotFound = errors.New("nmcli: binary not found in PATH")
type ConnectionStatus struct {
Connected bool
Server string
Location string
Load int
Protocol string
}
func Status() (ConnectionStatus, error) {
connected, err := isConnectedViaNetworkManager()
if err != nil {
return ConnectionStatus{}, err
}
if !connected {
return ConnectionStatus{Connected: false}, nil
}
res, err := Run(context.Background(), "status")
if err != nil {
return ConnectionStatus{}, err
}
status := parseStatus(res.Stdout)
status.Connected = true
return status, nil
}
func IsConnected() (bool, error) {
return isConnectedViaNetworkManager()
}
func isConnectedViaNetworkManager() (bool, error) {
if _, err := exec.LookPath("nmcli"); err != nil {
return false, ErrNmcliNotFound
}
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, "nmcli", "-t", "-f", "DEVICE", "connection", "show", "--active")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return false, fmt.Errorf("nmcli: %w (stderr: %q)", err, strings.TrimSpace(stderr.String()))
}
for device := range strings.SplitSeq(strings.TrimSpace(stdout.String()), "\n") {
if device == protonInterface {
return true, nil
}
}
return false, nil
}
func parseStatus(output string) ConnectionStatus {
var status ConnectionStatus
for line := range strings.SplitSeq(output, "\n") {
key, value, found := strings.Cut(strings.TrimSpace(line), ": ")
if !found {
continue
}
switch key {
case "Status":
status.Connected = value == "Connected"
case "Server":
if server, location, ok := strings.Cut(value, " in "); ok {
status.Server = server
status.Location = location
} else {
status.Server = value
}
case "Load":
if load, err := strconv.Atoi(strings.TrimSuffix(value, "%")); err == nil {
status.Load = load
}
case "Protocol":
status.Protocol = value
}
}
return status
}