1package mouse
2
3import (
4 "bytes"
5 "strconv"
6)
7
8// Type describes the kind of SGR mouse event.
9type Type int
10
11const (
12 Press Type = iota
13 Release
14 Motion
15)
16
17// Button describes the button encoded by an SGR mouse event.
18type Button int
19
20const (
21 Left Button = iota
22 Middle
23 Right
24 WheelUp
25 WheelDown
26 Other
27)
28
29// Event is a decoded SGR mouse report.
30type Event struct {
31 Type Type
32 Button Button
33 Col int
34 Row int
35 Raw []byte
36}
37
38// Scanner decodes SGR mouse reports from a byte stream while preserving the
39// ordering of decoded mouse events and ordinary byte segments.
40type Scanner struct {
41 pending []byte
42}
43
44// Scan decodes SGR mouse reports of the form ESC [ < Cb ; Cx ; Cy M/m.
45// Non-mouse bytes are emitted through onBytes in their original order. Only
46// partial reports after a complete ESC[< introducer are buffered between calls;
47// a bare ESC or ESC[ at the end of data is passed through unchanged.
48func (s *Scanner) Scan(data []byte, onMouse func(Event), onBytes func([]byte)) {
49 if len(s.pending) > 0 {
50 combined := make([]byte, 0, len(s.pending)+len(data))
51 combined = append(combined, s.pending...)
52 combined = append(combined, data...)
53 data = combined
54 s.pending = nil
55 }
56
57 byteStart := 0
58 for i := 0; i < len(data); i++ {
59 if data[i] != '\x1b' {
60 continue
61 }
62
63 remaining := len(data) - i
64 if remaining < 3 {
65 continue
66 }
67 if data[i+1] != '[' || data[i+2] != '<' {
68 continue
69 }
70
71 if byteStart < i {
72 onBytes(data[byteStart:i])
73 }
74
75 raw, ev, complete, ok := parseSGR(data[i:])
76 if !complete {
77 s.pending = append(s.pending, data[i:]...)
78 return
79 }
80 if !ok {
81 onBytes(raw)
82 i += len(raw) - 1
83 byteStart = i + 1
84 continue
85 }
86
87 onMouse(ev)
88 i += len(raw) - 1
89 byteStart = i + 1
90 }
91
92 if byteStart < len(data) {
93 onBytes(data[byteStart:])
94 }
95}
96
97func parseSGR(data []byte) (raw []byte, ev Event, complete bool, ok bool) {
98 end := -1
99 for i := 3; i < len(data); i++ {
100 switch data[i] {
101 case 'M', 'm':
102 end = i
103 goto found
104 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';':
105 continue
106 default:
107 return data[:i+1], Event{}, true, false
108 }
109 }
110found:
111 if end == -1 {
112 return nil, Event{}, false, false
113 }
114
115 raw = data[:end+1]
116 parts := bytes.Split(data[3:end], []byte(";"))
117 if len(parts) != 3 {
118 return raw, Event{}, true, false
119 }
120 cb, ok := atoiBytes(parts[0])
121 if !ok {
122 return raw, Event{}, true, false
123 }
124 cx, ok := atoiBytes(parts[1])
125 if !ok {
126 return raw, Event{}, true, false
127 }
128 cy, ok := atoiBytes(parts[2])
129 if !ok {
130 return raw, Event{}, true, false
131 }
132 if cx < 1 || cy < 1 {
133 return raw, Event{}, true, false
134 }
135
136 ev = Event{
137 Type: eventType(cb, data[end]),
138 Button: eventButton(cb),
139 Col: cx - 1,
140 Row: cy - 1,
141 Raw: append([]byte(nil), raw...),
142 }
143 return raw, ev, true, true
144}
145
146func atoiBytes(b []byte) (int, bool) {
147 if len(b) == 0 {
148 return 0, false
149 }
150 v, err := strconv.Atoi(string(b))
151 return v, err == nil
152}
153
154func eventType(cb int, final byte) Type {
155 if final == 'm' {
156 return Release
157 }
158 if cb&32 != 0 {
159 return Motion
160 }
161 return Press
162}
163
164func eventButton(cb int) Button {
165 if cb&64 != 0 {
166 switch cb {
167 case 64:
168 return WheelUp
169 case 65:
170 return WheelDown
171 default:
172 return Other
173 }
174 }
175
176 switch cb & 3 {
177 case 0:
178 return Left
179 case 1:
180 return Middle
181 case 2:
182 return Right
183 default:
184 return Other
185 }
186}