565a171797
The tray IPC socket was hardcoded to /run/theta/tray.sock and /tmp/theta-tray.sock, which cannot be bound on Windows (they resolve to C:\run\... and C:\tmp\... and need admin rights). The daemon now binds a Unix socket under the per-user temp dir on Windows, and the tray companion dials the same path. The tray also exited immediately on Windows because it checked DISPLAY/WAYLAND_DISPLAY, which are never set there. That graphical-session guard is now Windows-only; the tray always runs on Windows.
87 lines
3.0 KiB
Go
87 lines
3.0 KiB
Go
package main
|
|
|
|
// IPC protocol between the root daemon (theta-agent) and the desktop tray
|
|
// (theta-agent-tray). Both processes communicate via a Unix domain socket at
|
|
// /run/theta/tray.sock (created by the daemon; the tray connects to it).
|
|
//
|
|
// Protocol: newline-delimited JSON. The daemon streams TrayStatus messages to
|
|
// any connected tray client. The tray sends TrayCommand messages to the daemon.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
// TraySocketPaths are the paths the daemon tries to bind, in order.
|
|
//
|
|
// Windows has no /run or /tmp; a Unix socket in the per-user temp dir works
|
|
// there (AF_UNIX is supported since Windows 10 1803) and needs no admin
|
|
// rights. Linux keeps the original /run/theta path with a /tmp fallback.
|
|
var TraySocketPaths = func() []string {
|
|
if runtime.GOOS == "windows" {
|
|
return []string{filepath.Join(os.TempDir(), "theta-tray.sock")}
|
|
}
|
|
return []string{"/run/theta/tray.sock", "/tmp/theta-tray.sock"}
|
|
}()
|
|
|
|
// TraySocket is the canonical socket path the tray dials. It matches the last
|
|
// entry in TraySocketPaths on each platform.
|
|
var TraySocket = func() string {
|
|
if runtime.GOOS == "windows" {
|
|
return filepath.Join(os.TempDir(), "theta-tray.sock")
|
|
}
|
|
return "/tmp/theta-tray.sock"
|
|
}()
|
|
|
|
|
|
// TrayColor represents the icon color state.
|
|
type TrayColor string
|
|
|
|
const (
|
|
ColorRed TrayColor = "red" // not connected to directory
|
|
ColorYellow TrayColor = "yellow" // connected, but not home
|
|
ColorGreen TrayColor = "green" // connected, on home LAN (public IP matches)
|
|
ColorBlue TrayColor = "blue" // connected + WireGuard tunnel active to home site
|
|
)
|
|
|
|
// TrayStatus is sent from the daemon to the tray on every state change.
|
|
type TrayStatus struct {
|
|
Color TrayColor `json:"color"`
|
|
Connected bool `json:"connected"` // directory WebSocket is up
|
|
IsHome bool `json:"is_home"` // public IP matches home site
|
|
VPNActive bool `json:"vpn_active"` // WireGuard tunnel is up
|
|
AutoVPN bool `json:"auto_vpn"` // auto-connect preference
|
|
SiteName string `json:"site_name"` // configured site name
|
|
AgentPublicIP string `json:"agent_public_ip"` // this agent's detected public IP
|
|
HomePublicIP string `json:"home_public_ip"` // home site's public IP (from directory)
|
|
StatusText string `json:"status_text"` // one-line human description
|
|
}
|
|
|
|
// TrayCommand is sent from the tray to the daemon.
|
|
type TrayCommand struct {
|
|
Command string `json:"command"` // "set_auto_vpn", "vpn_connect", "vpn_disconnect"
|
|
Value bool `json:"value"` // used by set_auto_vpn
|
|
}
|
|
|
|
func encodeTrayStatus(s TrayStatus) ([]byte, error) {
|
|
b, err := json.Marshal(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(b, '\n'), nil
|
|
}
|
|
|
|
func decodeTrayCommand(data []byte) (TrayCommand, error) {
|
|
var cmd TrayCommand
|
|
err := json.Unmarshal(data, &cmd)
|
|
return cmd, err
|
|
}
|
|
|
|
func decodeTrayStatus(data []byte) (TrayStatus, error) {
|
|
var s TrayStatus
|
|
err := json.Unmarshal(data, &s)
|
|
return s, err
|
|
}
|