main
1package tea
2
3import "strconv"
4
5// MouseMsg contains information about a mouse event and are sent to a programs
6// update function when mouse activity occurs. Note that the mouse must first
7// be enabled in order for the mouse events to be received.
8type MouseMsg MouseEvent
9
10// String returns a string representation of a mouse event.
11func (m MouseMsg) String() string {
12 return MouseEvent(m).String()
13}
14
15// MouseEvent represents a mouse event, which could be a click, a scroll wheel
16// movement, a cursor movement, or a combination.
17type MouseEvent struct {
18 X int
19 Y int
20 Shift bool
21 Alt bool
22 Ctrl bool
23 Action MouseAction
24 Button MouseButton
25
26 // Deprecated: Use MouseAction & MouseButton instead.
27 Type MouseEventType
28}
29
30// IsWheel returns true if the mouse event is a wheel event.
31func (m MouseEvent) IsWheel() bool {
32 return m.Button == MouseButtonWheelUp || m.Button == MouseButtonWheelDown ||
33 m.Button == MouseButtonWheelLeft || m.Button == MouseButtonWheelRight
34}
35
36// String returns a string representation of a mouse event.
37func (m MouseEvent) String() (s string) {
38 if m.Ctrl {
39 s += "ctrl+"
40 }
41 if m.Alt {
42 s += "alt+"
43 }
44 if m.Shift {
45 s += "shift+"
46 }
47
48 if m.Button == MouseButtonNone { //nolint:nestif
49 if m.Action == MouseActionMotion || m.Action == MouseActionRelease {
50 s += mouseActions[m.Action]
51 } else {
52 s += "unknown"
53 }
54 } else if m.IsWheel() {
55 s += mouseButtons[m.Button]
56 } else {
57 btn := mouseButtons[m.Button]
58 if btn != "" {
59 s += btn
60 }
61 act := mouseActions[m.Action]
62 if act != "" {
63 s += " " + act
64 }
65 }
66
67 return s
68}
69
70// MouseAction represents the action that occurred during a mouse event.
71type MouseAction int
72
73// Mouse event actions.
74const (
75 MouseActionPress MouseAction = iota
76 MouseActionRelease
77 MouseActionMotion
78)
79
80var mouseActions = map[MouseAction]string{
81 MouseActionPress: "press",
82 MouseActionRelease: "release",
83 MouseActionMotion: "motion",
84}
85
86// MouseButton represents the button that was pressed during a mouse event.
87type MouseButton int
88
89// Mouse event buttons
90//
91// This is based on X11 mouse button codes.
92//
93// 1 = left button
94// 2 = middle button (pressing the scroll wheel)
95// 3 = right button
96// 4 = turn scroll wheel up
97// 5 = turn scroll wheel down
98// 6 = push scroll wheel left
99// 7 = push scroll wheel right
100// 8 = 4th button (aka browser backward button)
101// 9 = 5th button (aka browser forward button)
102// 10
103// 11
104//
105// Other buttons are not supported.
106const (
107 MouseButtonNone MouseButton = iota
108 MouseButtonLeft
109 MouseButtonMiddle
110 MouseButtonRight
111 MouseButtonWheelUp
112 MouseButtonWheelDown
113 MouseButtonWheelLeft
114 MouseButtonWheelRight
115 MouseButtonBackward
116 MouseButtonForward
117 MouseButton10
118 MouseButton11
119)
120
121var mouseButtons = map[MouseButton]string{
122 MouseButtonNone: "none",
123 MouseButtonLeft: "left",
124 MouseButtonMiddle: "middle",
125 MouseButtonRight: "right",
126 MouseButtonWheelUp: "wheel up",
127 MouseButtonWheelDown: "wheel down",
128 MouseButtonWheelLeft: "wheel left",
129 MouseButtonWheelRight: "wheel right",
130 MouseButtonBackward: "backward",
131 MouseButtonForward: "forward",
132 MouseButton10: "button 10",
133 MouseButton11: "button 11",
134}
135
136// MouseEventType indicates the type of mouse event occurring.
137//
138// Deprecated: Use MouseAction & MouseButton instead.
139type MouseEventType int
140
141// Mouse event types.
142//
143// Deprecated: Use MouseAction & MouseButton instead.
144const (
145 MouseUnknown MouseEventType = iota
146 MouseLeft
147 MouseRight
148 MouseMiddle
149 MouseRelease // mouse button release (X10 only)
150 MouseWheelUp
151 MouseWheelDown
152 MouseWheelLeft
153 MouseWheelRight
154 MouseBackward
155 MouseForward
156 MouseMotion
157)
158
159// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
160// look like:
161//
162// ESC [ < Cb ; Cx ; Cy (M or m)
163//
164// where:
165//
166// Cb is the encoded button code
167// Cx is the x-coordinate of the mouse
168// Cy is the y-coordinate of the mouse
169// M is for button press, m is for button release
170//
171// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
172func parseSGRMouseEvent(buf []byte) MouseEvent {
173 str := string(buf[3:])
174 matches := mouseSGRRegex.FindStringSubmatch(str)
175 if len(matches) != 5 { //nolint:gomnd
176 // Unreachable, we already checked the regex in `detectOneMsg`.
177 panic("invalid mouse event")
178 }
179
180 b, _ := strconv.Atoi(matches[1])
181 px := matches[2]
182 py := matches[3]
183 release := matches[4] == "m"
184 m := parseMouseButton(b, true)
185
186 // Wheel buttons don't have release events
187 // Motion can be reported as a release event in some terminals (Windows Terminal)
188 if m.Action != MouseActionMotion && !m.IsWheel() && release {
189 m.Action = MouseActionRelease
190 m.Type = MouseRelease
191 }
192
193 x, _ := strconv.Atoi(px)
194 y, _ := strconv.Atoi(py)
195
196 // (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
197 m.X = x - 1
198 m.Y = y - 1
199
200 return m
201}
202
203const x10MouseByteOffset = 32
204
205// Parse X10-encoded mouse events; the simplest kind. The last release of X10
206// was December 1986, by the way. The original X10 mouse protocol limits the Cx
207// and Cy coordinates to 223 (=255-032).
208//
209// X10 mouse events look like:
210//
211// ESC [M Cb Cx Cy
212//
213// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
214func parseX10MouseEvent(buf []byte) MouseEvent {
215 v := buf[3:6]
216 m := parseMouseButton(int(v[0]), false)
217
218 // (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
219 m.X = int(v[1]) - x10MouseByteOffset - 1
220 m.Y = int(v[2]) - x10MouseByteOffset - 1
221
222 return m
223}
224
225// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
226func parseMouseButton(b int, isSGR bool) MouseEvent {
227 var m MouseEvent
228 e := b
229 if !isSGR {
230 e -= x10MouseByteOffset
231 }
232
233 const (
234 bitShift = 0b0000_0100
235 bitAlt = 0b0000_1000
236 bitCtrl = 0b0001_0000
237 bitMotion = 0b0010_0000
238 bitWheel = 0b0100_0000
239 bitAdd = 0b1000_0000 // additional buttons 8-11
240
241 bitsMask = 0b0000_0011
242 )
243
244 if e&bitAdd != 0 {
245 m.Button = MouseButtonBackward + MouseButton(e&bitsMask)
246 } else if e&bitWheel != 0 {
247 m.Button = MouseButtonWheelUp + MouseButton(e&bitsMask)
248 } else {
249 m.Button = MouseButtonLeft + MouseButton(e&bitsMask)
250 // X10 reports a button release as 0b0000_0011 (3)
251 if e&bitsMask == bitsMask {
252 m.Action = MouseActionRelease
253 m.Button = MouseButtonNone
254 }
255 }
256
257 // Motion bit doesn't get reported for wheel events.
258 if e&bitMotion != 0 && !m.IsWheel() {
259 m.Action = MouseActionMotion
260 }
261
262 // Modifiers
263 m.Alt = e&bitAlt != 0
264 m.Ctrl = e&bitCtrl != 0
265 m.Shift = e&bitShift != 0
266
267 // backward compatibility
268 switch {
269 case m.Button == MouseButtonLeft && m.Action == MouseActionPress:
270 m.Type = MouseLeft
271 case m.Button == MouseButtonMiddle && m.Action == MouseActionPress:
272 m.Type = MouseMiddle
273 case m.Button == MouseButtonRight && m.Action == MouseActionPress:
274 m.Type = MouseRight
275 case m.Button == MouseButtonNone && m.Action == MouseActionRelease:
276 m.Type = MouseRelease
277 case m.Button == MouseButtonWheelUp && m.Action == MouseActionPress:
278 m.Type = MouseWheelUp
279 case m.Button == MouseButtonWheelDown && m.Action == MouseActionPress:
280 m.Type = MouseWheelDown
281 case m.Button == MouseButtonWheelLeft && m.Action == MouseActionPress:
282 m.Type = MouseWheelLeft
283 case m.Button == MouseButtonWheelRight && m.Action == MouseActionPress:
284 m.Type = MouseWheelRight
285 case m.Button == MouseButtonBackward && m.Action == MouseActionPress:
286 m.Type = MouseBackward
287 case m.Button == MouseButtonForward && m.Action == MouseActionPress:
288 m.Type = MouseForward
289 case m.Action == MouseActionMotion:
290 m.Type = MouseMotion
291 switch m.Button { //nolint:exhaustive
292 case MouseButtonLeft:
293 m.Type = MouseLeft
294 case MouseButtonMiddle:
295 m.Type = MouseMiddle
296 case MouseButtonRight:
297 m.Type = MouseRight
298 case MouseButtonBackward:
299 m.Type = MouseBackward
300 case MouseButtonForward:
301 m.Type = MouseForward
302 }
303 default:
304 m.Type = MouseUnknown
305 }
306
307 return m
308}