forked from owenthereal/upterm
/
event.go
147 lines (122 loc) · 3.1 KB
/
event.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
137
138
139
140
141
142
143
144
145
146
147
package internal
import (
"context"
"fmt"
"io"
"strings"
"github.com/olebedev/emitter"
"github.com/owenthereal/upterm/upterm"
log "github.com/sirupsen/logrus"
)
const (
errBadFileDescriptor = "bad file descriptor"
)
type terminal struct {
ID string
Pty *pty
Window window
}
type window struct {
Width int
Height int
}
type terminalEventEmitter struct {
eventEmitter *emitter.Emitter
}
func (t terminalEventEmitter) TerminalWindowChanged(id string, pty *pty, w, h int) {
tt := terminal{
ID: id,
Pty: pty,
Window: window{
Width: w,
Height: h,
},
}
t.eventEmitter.Emit(upterm.EventTerminalWindowChanged, tt)
}
func (t terminalEventEmitter) TerminalDetached(id string, pty *pty) {
tt := terminal{
ID: id,
Pty: pty,
}
t.eventEmitter.Emit(upterm.EventTerminalDetached, tt)
}
type terminalEventHandler struct {
eventEmitter *emitter.Emitter
logger log.FieldLogger
}
func (t terminalEventHandler) Handle(ctx context.Context) error {
winCh := t.eventEmitter.On(upterm.EventTerminalWindowChanged, emitter.Sync, emitter.Skip)
dtCh := t.eventEmitter.On(upterm.EventTerminalDetached, emitter.Sync, emitter.Skip)
defer func() {
t.eventEmitter.Off(upterm.EventTerminalWindowChanged, winCh)
t.eventEmitter.Off(upterm.EventTerminalDetached, dtCh)
}()
m := make(map[io.ReadWriteCloser]map[string]terminal)
for {
select {
case evt := <-winCh:
if err := t.handleWindowChanged(evt, m); err != nil {
t.logger.WithError(err).Error("error handling window changed")
}
case evt := <-dtCh:
if err := t.handleTerminalDetached(evt, m); err != nil {
t.logger.WithError(err).Error("error handling terminal detached")
}
case <-ctx.Done():
return ctx.Err()
}
}
}
func (t terminalEventHandler) handleWindowChanged(evt emitter.Event, m map[io.ReadWriteCloser]map[string]terminal) error {
args := evt.Args
if len(args) == 0 {
return fmt.Errorf("expect terminal window change event to have at least one argument")
}
tt, ok := args[0].(terminal)
if !ok {
return fmt.Errorf("expect terminal window change event to receive a terminal")
}
pty := tt.Pty
ts, ok := m[pty]
if !ok {
ts = make(map[string]terminal)
m[pty] = ts
}
ts[tt.ID] = tt
if err := resizeWindow(pty, ts); err != nil && !strings.Contains(err.Error(), errBadFileDescriptor) {
return fmt.Errorf("error resizing window: %w", err)
}
return nil
}
func (t terminalEventHandler) handleTerminalDetached(evt emitter.Event, m map[io.ReadWriteCloser]map[string]terminal) error {
args := evt.Args
if len(args) == 0 {
return fmt.Errorf("expect terminal window change event to have at least one argument")
}
tt, ok := args[0].(terminal)
if !ok {
return fmt.Errorf("expect terminal window change event to receive a terminal")
}
pty := tt.Pty
ts, ok := m[pty]
if ok {
delete(ts, tt.ID)
}
if len(ts) == 0 {
delete(m, pty)
}
return nil
}
func resizeWindow(ptmx *pty, ts map[string]terminal) error {
var w, h int
for _, t := range ts {
if w == 0 || w > t.Window.Width {
w = t.Window.Width
}
if h == 0 || h > t.Window.Height {
h = t.Window.Height
}
}
return ptmx.Setsize(h, w)
}