-
Notifications
You must be signed in to change notification settings - Fork 29
/
gow_stdio.go
136 lines (108 loc) · 2.54 KB
/
gow_stdio.go
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
package main
import (
"os"
"syscall"
"time"
"github.com/mitranim/gg"
)
const DoubleInputDelay = time.Second
type Stdio struct {
Mained
LastChar byte
LastInst time.Time
}
/*
Doesn't require special cleanup before stopping `gow`. We run only one stdio
loop, without ever replacing it.
*/
func (*Stdio) Deinit() {}
/*
See `(*TermState).Init`. Terminal raw mode allows us to support our own control
codes, but we're also responsible for interpreting common ASCII codes into OS
signals and for echoing other characters to stdout.
*/
func (self *Stdio) Run() {
if !self.Main().Opt.Raw {
return
}
self.LastInst = time.Now()
for {
var buf [1]byte
size, err := os.Stdin.Read(buf[:])
if err != nil || size == 0 {
return
}
self.OnByte(buf[0])
}
}
/*
Interpret known ASCII codes as OS signals.
Otherwise forward the input to the subprocess.
*/
func (self *Stdio) OnByte(char byte) {
defer recLog()
defer self.AfterByte(char)
switch char {
case CODE_INTERRUPT:
self.OnCodeInterrupt()
case CODE_QUIT:
self.OnCodeQuit()
case CODE_PRINT_COMMAND:
self.OnCodePrintCommand()
case CODE_RESTART:
self.OnCodeRestart()
case CODE_STOP:
self.OnCodeStop()
default:
self.OnByteAny(char)
}
}
func (self *Stdio) AfterByte(char byte) {
self.LastChar = char
self.LastInst = time.Now()
}
func (self *Stdio) OnCodeInterrupt() {
self.OnCodeSig(CODE_INTERRUPT, syscall.SIGINT, `^C`)
}
func (self *Stdio) OnCodeQuit() {
self.OnCodeSig(CODE_QUIT, syscall.SIGQUIT, `^\`)
}
func (self *Stdio) OnCodePrintCommand() {
log.Printf(`current command: %q`, os.Args)
}
func (self *Stdio) OnCodeRestart() {
main := self.Main()
if main.Opt.Verb {
log.Println(`received ^R, restarting`)
}
main.Restart()
}
func (self *Stdio) OnCodeStop() {
self.OnCodeSig(CODE_STOP, syscall.SIGTERM, `^T`)
}
func (self *Stdio) OnByteAny(char byte) {
main := self.Main()
main.Cmd.WriteChar(char)
if main.Opt.Echo == EchoModeGow {
self.WriteChar(char)
}
}
func (self *Stdio) WriteChar(char byte) {
buf := [1]byte{char}
gg.Nop2(os.Stdout.Write(buf[:]))
}
func (self *Stdio) OnCodeSig(code byte, sig syscall.Signal, desc string) {
main := self.Main()
if self.IsCodeRepeated(code) {
log.Println(`received ` + desc + desc + `, shutting down`)
main.Kill(sig)
return
}
if main.Opt.Verb {
log.Println(`broadcasting ` + desc + ` to subprocesses; repeat within ` + DoubleInputDelay.String() + ` to kill gow`)
}
main.Cmd.Broadcast(sig)
}
func (self *Stdio) IsCodeRepeated(char byte) bool {
return self.LastChar == char && time.Since(self.LastInst) < DoubleInputDelay
}