mouse.go 7.7 KB

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