...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package tcell
19
20 import (
21 "errors"
22 "fmt"
23 "os"
24 "os/signal"
25 "strconv"
26 "sync"
27 "syscall"
28 "time"
29
30 "golang.org/x/term"
31 )
32
33
34 type stdIoTty struct {
35 fd int
36 in *os.File
37 out *os.File
38 saved *term.State
39 sig chan os.Signal
40 cb func()
41 stopQ chan struct{}
42 dev string
43 wg sync.WaitGroup
44 l sync.Mutex
45 }
46
47 func (tty *stdIoTty) Read(b []byte) (int, error) {
48 return tty.in.Read(b)
49 }
50
51 func (tty *stdIoTty) Write(b []byte) (int, error) {
52 return tty.out.Write(b)
53 }
54
55 func (tty *stdIoTty) Close() error {
56 return nil
57 }
58
59 func (tty *stdIoTty) Start() error {
60 tty.l.Lock()
61 defer tty.l.Unlock()
62
63
64
65
66
67
68
69
70
71
72 var err error
73 tty.in = os.Stdin
74 tty.out = os.Stdout
75 tty.fd = int(tty.in.Fd())
76
77 if !term.IsTerminal(tty.fd) {
78 return errors.New("device is not a terminal")
79 }
80
81 _ = tty.in.SetReadDeadline(time.Time{})
82 saved, err := term.MakeRaw(tty.fd)
83 if err != nil {
84 return err
85 }
86 tty.saved = saved
87
88 tty.stopQ = make(chan struct{})
89 tty.wg.Add(1)
90 go func(stopQ chan struct{}) {
91 defer tty.wg.Done()
92 for {
93 select {
94 case <-tty.sig:
95 tty.l.Lock()
96 cb := tty.cb
97 tty.l.Unlock()
98 if cb != nil {
99 cb()
100 }
101 case <-stopQ:
102 return
103 }
104 }
105 }(tty.stopQ)
106
107 signal.Notify(tty.sig, syscall.SIGWINCH)
108 return nil
109 }
110
111 func (tty *stdIoTty) Drain() error {
112 _ = tty.in.SetReadDeadline(time.Now())
113 if err := tcSetBufParams(tty.fd, 0, 0); err != nil {
114 return err
115 }
116 return nil
117 }
118
119 func (tty *stdIoTty) Stop() error {
120 tty.l.Lock()
121 if err := term.Restore(tty.fd, tty.saved); err != nil {
122 tty.l.Unlock()
123 return err
124 }
125 _ = tty.in.SetReadDeadline(time.Now())
126
127 signal.Stop(tty.sig)
128 close(tty.stopQ)
129 tty.l.Unlock()
130
131 tty.wg.Wait()
132
133 return nil
134 }
135
136 func (tty *stdIoTty) WindowSize() (int, int, error) {
137 w, h, err := term.GetSize(tty.fd)
138 if err != nil {
139 return 0, 0, err
140 }
141 if w == 0 {
142 w, _ = strconv.Atoi(os.Getenv("COLUMNS"))
143 }
144 if w == 0 {
145 w = 80
146 }
147 if h == 0 {
148 h, _ = strconv.Atoi(os.Getenv("LINES"))
149 }
150 if h == 0 {
151 h = 25
152 }
153 return w, h, nil
154 }
155
156 func (tty *stdIoTty) NotifyResize(cb func()) {
157 tty.l.Lock()
158 tty.cb = cb
159 tty.l.Unlock()
160 }
161
162
163 func NewStdIoTty() (Tty, error) {
164 tty := &stdIoTty{
165 sig: make(chan os.Signal),
166 in: os.Stdin,
167 out: os.Stdout,
168 }
169 var err error
170 tty.fd = int(tty.in.Fd())
171 if !term.IsTerminal(tty.fd) {
172 return nil, errors.New("not a terminal")
173 }
174 if tty.saved, err = term.GetState(tty.fd); err != nil {
175 return nil, fmt.Errorf("failed to get state: %w", err)
176 }
177 return tty, nil
178 }
179
View as plain text