Files

106 lines
3.2 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package speech
import (
"fmt"
"net"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
)
type serverAddress struct {
network string
address string
}
// default_socket_path follows Python speechd, retaining Fenrir's sudo-user
// runtime directory selection for console sessions.
func default_socket_path() string {
if uid := os.Getenv("SUDO_UID"); uid != "" {
if parsed, err := strconv.ParseUint(uid, 10, 32); err == nil {
runtimeDirectory := os.Getenv("XDG_RUNTIME_DIR")
if !owned_directory(runtimeDirectory, uint32(parsed)) {
runtimeDirectory = filepath.Join("/run/user", uid)
}
if !owned_directory(runtimeDirectory, uint32(parsed)) {
if account, err := user.LookupId(uid); err == nil {
runtimeDirectory = filepath.Join(account.HomeDir, ".cache")
}
}
return filepath.Join(runtimeDirectory, "speech-dispatcher/speechd.sock")
}
}
runtimeDirectory := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDirectory == "" {
runtimeDirectory = os.Getenv("XDG_CACHE_HOME")
}
if runtimeDirectory == "" {
cacheDirectory, err := os.UserCacheDir()
if err == nil {
runtimeDirectory = cacheDirectory
}
}
return filepath.Join(runtimeDirectory, "speech-dispatcher", "speechd.sock")
}
// resolve_server_address gives an internal socket override priority over speechd's
// standard address variables. SPEECHD_SOCKET is the older Unix-only setting.
func resolve_server_address(socket string) (serverAddress, error) {
if socket != "" {
return serverAddress{network: "unix", address: socket}, nil
}
if address := os.Getenv("SPEECHD_ADDRESS"); address != "" {
return parse_server_address(address)
}
if socket := os.Getenv("SPEECHD_SOCKET"); socket != "" {
return serverAddress{network: "unix", address: socket}, nil
}
return serverAddress{network: "unix", address: default_socket_path()}, nil
}
func parse_server_address(address string) (serverAddress, error) {
method, value, _ := strings.Cut(address, ":")
switch method {
case "unix_socket":
if value == "" {
value = default_socket_path()
}
if !filepath.IsAbs(value) {
return serverAddress{}, fmt.Errorf("Speech Dispatcher Unix socket must be an absolute path: %q", value)
}
return serverAddress{network: "unix", address: value}, nil
case "inet_socket":
host, port := "127.0.0.1", 6560
if value != "" {
parts := strings.Split(value, ":")
if len(parts) > 2 {
return serverAddress{}, fmt.Errorf("invalid Speech Dispatcher address %q", address)
}
if parts[0] != "" {
host = parts[0]
}
if len(parts) == 2 {
parsed, err := strconv.Atoi(parts[1])
if err != nil || parsed < 1 || parsed > 65535 {
return serverAddress{}, fmt.Errorf("invalid Speech Dispatcher port in %q", address)
}
port = parsed
}
}
return serverAddress{network: "tcp4", address: net.JoinHostPort(host, strconv.Itoa(port))}, nil
default:
return serverAddress{}, fmt.Errorf("unsupported Speech Dispatcher address %q", address)
}
}
// Explicit settings select exactly one server; only defaults use fallback.
func automatic_server_address(socket string) bool {
return socket == "" && os.Getenv("SPEECHD_ADDRESS") == "" && os.Getenv("SPEECHD_SOCKET") == ""
}