Files
vmux/client.go
Pierre Martin a79a0e154c feat(02-03): client socket, autostart daemon, switch handler, workspace wiring
- Client struct sends JSON requests to daemon over Unix socket
- EnsureDaemon auto-starts the daemon if not running (retry 50ms x 20)
- Switch handler uses FuzzyMatch + SwitchToWorkspace via i3 IPC
- InitWorkspaceResolver wires BuildTerminalWorkspaceMap + ResolveWorkspace
- sysattr_linux.go for Setsid detach on daemon spawn
2026-03-23 17:51:31 +01:00

85 lines
1.9 KiB
Go

package main
import (
"encoding/json"
"fmt"
"net"
"os"
"os/exec"
"time"
)
// Client communicates with the vmux daemon over a Unix socket.
type Client struct {
sockPath string
}
// NewClient creates a client targeting the given socket path.
func NewClient(sockPath string) *Client {
return &Client{sockPath: sockPath}
}
// Send transmits a Request to the daemon and returns the Response.
func (c *Client) Send(req Request) (*Response, error) {
conn, err := net.DialTimeout("unix", c.sockPath, 2*time.Second)
if err != nil {
return nil, fmt.Errorf("dial daemon: %w", err)
}
defer conn.Close()
conn.SetDeadline(time.Now().Add(5 * time.Second))
if err := json.NewEncoder(conn).Encode(req); err != nil {
return nil, fmt.Errorf("send request: %w", err)
}
var resp Response
if err := json.NewDecoder(conn).Decode(&resp); err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
return &resp, nil
}
// EnsureDaemon checks if a daemon is already running on sockPath.
// If not, it spawns one in the background and waits until it's reachable.
func EnsureDaemon(sockPath string) error {
// Already running?
conn, err := net.DialTimeout("unix", sockPath, 200*time.Millisecond)
if err == nil {
conn.Close()
return nil
}
// Spawn daemon in background
exe, err := os.Executable()
if err != nil {
return fmt.Errorf("resolve executable: %w", err)
}
cmd := exec.Command(exe, "daemon")
cmd.SysProcAttr = newSysProcAttr()
cmd.Stdout = nil
cmd.Stderr = nil
cmd.Stdin = nil
if err := cmd.Start(); err != nil {
return fmt.Errorf("start daemon: %w", err)
}
// Detach: don't wait for the child
go cmd.Wait()
// Retry until daemon is reachable (50ms x 20 = 1s max)
for i := 0; i < 20; i++ {
time.Sleep(50 * time.Millisecond)
conn, err := net.DialTimeout("unix", sockPath, 200*time.Millisecond)
if err == nil {
conn.Close()
return nil
}
}
return fmt.Errorf("daemon did not start within 1s")
}