-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathtui_cmd.go
More file actions
149 lines (131 loc) · 3.34 KB
/
tui_cmd.go
File metadata and controls
149 lines (131 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package main
import (
"fmt"
"io"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
"github.com/richiejp/VoxInput/internal/ipc"
"github.com/richiejp/VoxInput/internal/tui"
)
func tuiCommand(args []string) {
var connectPath string
var listenArgs []string
for i := 0; i < len(args); i++ {
switch args[i] {
case "--connect":
if i+1 < len(args) {
connectPath = args[i+1]
i++
} else {
log.Fatalln("tui: --connect requires a socket path argument")
}
default:
listenArgs = append(listenArgs, args[i])
}
}
if connectPath != "" {
connectAndRun(connectPath)
return
}
socketPath := ipc.SocketPath()
// Launch listen as subprocess
cmdArgs := []string{"listen", "--socket", socketPath}
cmdArgs = append(cmdArgs, listenArgs...)
executable, err := os.Executable()
if err != nil {
log.Fatalln("tui: failed to find own executable:", err)
}
cmd := exec.Command(executable, cmdArgs...)
cmd.Env = os.Environ()
// Create pipes manually instead of using cmd.StdoutPipe/StderrPipe
// so that cmd.Wait() doesn't close our read ends. This lets us
// read stderr after the process exits for early-crash diagnostics.
stdoutRead, stdoutWrite, err := os.Pipe()
if err != nil {
log.Fatalln("tui: failed to create stdout pipe:", err)
}
stderrRead, stderrWrite, err := os.Pipe()
if err != nil {
log.Fatalln("tui: failed to create stderr pipe:", err)
}
cmd.Stdout = stdoutWrite
cmd.Stderr = stderrWrite
if err := cmd.Start(); err != nil {
log.Fatalln("tui: failed to start listen subprocess:", err)
}
stdoutWrite.Close()
stderrWrite.Close()
exited := make(chan error, 1)
go func() { exited <- cmd.Wait() }()
// Wait for socket to appear, checking for early subprocess exit.
var client *ipc.Client
for i := 0; i < 50; i++ {
select {
case waitErr := <-exited:
remaining, _ := io.ReadAll(stderrRead)
if len(remaining) > 0 {
fmt.Fprintf(os.Stderr, "%s", remaining)
}
if waitErr != nil {
log.Fatalln("tui: listen subprocess exited early:", waitErr)
}
log.Fatalln("tui: listen subprocess exited before creating socket")
default:
}
time.Sleep(100 * time.Millisecond)
client, err = ipc.Connect(socketPath)
if err == nil {
break
}
}
if client == nil {
cmd.Process.Kill()
<-exited
remaining, _ := io.ReadAll(stderrRead)
if len(remaining) > 0 {
fmt.Fprintf(os.Stderr, "%s", remaining)
}
log.Fatalln("tui: failed to connect to listen subprocess:", err)
}
// Run TUI
tuiErr := tui.Run(client, stdoutRead, stderrRead)
client.Close()
stdoutRead.Close()
stderrRead.Close()
// Clean up subprocess
cmd.Process.Signal(syscall.SIGTERM)
select {
case <-exited:
case <-time.After(5 * time.Second):
cmd.Process.Kill()
<-exited
}
if tuiErr != nil {
fmt.Fprintf(os.Stderr, "tui: %v\n", tuiErr)
os.Exit(1)
}
}
func connectAndRun(path string) {
client, err := ipc.Connect(path)
if err != nil {
log.Fatalln("tui: failed to connect to", path, ":", err)
}
defer client.Close()
// Handle interrupt gracefully by closing the client,
// which causes tui.Run to receive a disconnect and exit.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
client.SendCommand(ipc.Command{Kind: ipc.CommandQuit})
client.Close()
}()
if err := tui.Run(client); err != nil {
fmt.Fprintf(os.Stderr, "tui: %v\n", err)
os.Exit(1)
}
}