Files
RTSP-Streamer/internal/ipc/ipc.go
2026-07-01 18:21:14 -05:00

76 lines
2.1 KiB
Go

// Package ipc defines the tiny control protocol shared by the daemon (server)
// and the CLI/TUI (clients). Messages are newline-delimited JSON over a unix
// socket, so switching layouts or reading status never requires restarting
// the video wall.
package ipc
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"time"
)
// Request is a command sent to the daemon.
type Request struct {
Cmd string `json:"cmd"` // "status" | "reload" | "set-layout"
Name string `json:"name,omitempty"` // layout name for set-layout
}
// SlotStatus reports one grid cell's state.
type SlotStatus struct {
Slot int `json:"slot"`
Camera string `json:"camera"`
PID int `json:"pid"`
Running bool `json:"running"`
Healthy bool `json:"healthy"`
}
// Response is the daemon's reply.
type Response struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
ActiveLayout string `json:"active_layout,omitempty"`
Slots []SlotStatus `json:"slots,omitempty"`
}
// SocketPath returns the control socket path inside the runtime dir.
func SocketPath() string {
return filepath.Join(RunDir(), "control.sock")
}
// RunDir is the per-user runtime directory for sockets, created on demand.
func RunDir() string {
base := os.Getenv("XDG_RUNTIME_DIR")
if base == "" {
base = filepath.Join(os.TempDir(), "rtsp-streamer-"+strconv.Itoa(os.Getuid()))
} else {
base = filepath.Join(base, "rtsp-streamer")
}
return base
}
// Send dials the daemon, sends one request, and returns the reply.
func Send(req Request) (*Response, error) {
conn, err := net.DialTimeout("unix", SocketPath(), 3*time.Second)
if err != nil {
return nil, fmt.Errorf("cannot reach daemon (is it running?): %w", err)
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
enc := json.NewEncoder(conn)
if err := enc.Encode(req); err != nil {
return nil, err
}
var resp Response
if err := json.NewDecoder(bufio.NewReader(conn)).Decode(&resp); err != nil {
return nil, err
}
return &resp, nil
}