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
This commit is contained in:
84
client.go
Normal file
84
client.go
Normal file
@@ -0,0 +1,84 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user