2020-06-22 20:30:16 -04:00
|
|
|
package tea
|
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
import "strconv"
|
|
|
|
|
2022-10-21 11:00:39 -04:00
|
|
|
// MouseMsg contains information about a mouse event and are sent to a programs
|
2021-09-07 15:28:08 -04:00
|
|
|
// update function when mouse activity occurs. Note that the mouse must first
|
2023-02-18 23:30:17 -05:00
|
|
|
// be enabled in order for the mouse events to be received.
|
2020-06-22 20:30:16 -04:00
|
|
|
type MouseMsg MouseEvent
|
|
|
|
|
2022-10-21 11:00:39 -04:00
|
|
|
// String returns a string representation of a mouse event.
|
|
|
|
func (m MouseMsg) String() string {
|
|
|
|
return MouseEvent(m).String()
|
|
|
|
}
|
|
|
|
|
2020-07-13 11:26:57 -04:00
|
|
|
// MouseEvent represents a mouse event, which could be a click, a scroll wheel
|
|
|
|
// movement, a cursor movement, or a combination.
|
2020-06-22 20:30:16 -04:00
|
|
|
type MouseEvent struct {
|
2023-12-04 11:50:59 -05:00
|
|
|
X int
|
|
|
|
Y int
|
|
|
|
Shift bool
|
|
|
|
Alt bool
|
|
|
|
Ctrl bool
|
|
|
|
Action MouseAction
|
|
|
|
Button MouseButton
|
|
|
|
|
|
|
|
// Deprecated: Use MouseAction & MouseButton instead.
|
2020-09-28 16:42:39 -04:00
|
|
|
Type MouseEventType
|
2023-12-04 11:50:59 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsWheel returns true if the mouse event is a wheel event.
|
|
|
|
func (m MouseEvent) IsWheel() bool {
|
|
|
|
return m.Button == MouseButtonWheelUp || m.Button == MouseButtonWheelDown ||
|
|
|
|
m.Button == MouseButtonWheelLeft || m.Button == MouseButtonWheelRight
|
2020-06-22 20:30:16 -04:00
|
|
|
}
|
|
|
|
|
2020-07-13 11:26:57 -04:00
|
|
|
// String returns a string representation of a mouse event.
|
2020-06-22 20:30:16 -04:00
|
|
|
func (m MouseEvent) String() (s string) {
|
|
|
|
if m.Ctrl {
|
|
|
|
s += "ctrl+"
|
|
|
|
}
|
|
|
|
if m.Alt {
|
|
|
|
s += "alt+"
|
|
|
|
}
|
2023-12-04 11:50:59 -05:00
|
|
|
if m.Shift {
|
|
|
|
s += "shift+"
|
|
|
|
}
|
|
|
|
|
|
|
|
if m.Button == MouseButtonNone {
|
|
|
|
if m.Action == MouseActionMotion || m.Action == MouseActionRelease {
|
|
|
|
s += mouseActions[m.Action]
|
|
|
|
} else {
|
|
|
|
s += "unknown"
|
|
|
|
}
|
|
|
|
} else if m.IsWheel() {
|
|
|
|
s += mouseButtons[m.Button]
|
|
|
|
} else {
|
|
|
|
btn := mouseButtons[m.Button]
|
|
|
|
if btn != "" {
|
|
|
|
s += btn
|
|
|
|
}
|
|
|
|
act := mouseActions[m.Action]
|
|
|
|
if act != "" {
|
|
|
|
s += " " + act
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-22 20:30:16 -04:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
// MouseAction represents the action that occurred during a mouse event.
|
|
|
|
type MouseAction int
|
|
|
|
|
|
|
|
// Mouse event actions.
|
|
|
|
const (
|
|
|
|
MouseActionPress MouseAction = iota
|
|
|
|
MouseActionRelease
|
|
|
|
MouseActionMotion
|
|
|
|
)
|
|
|
|
|
|
|
|
var mouseActions = map[MouseAction]string{
|
|
|
|
MouseActionPress: "press",
|
|
|
|
MouseActionRelease: "release",
|
|
|
|
MouseActionMotion: "motion",
|
|
|
|
}
|
|
|
|
|
|
|
|
// MouseButton represents the button that was pressed during a mouse event.
|
|
|
|
type MouseButton int
|
|
|
|
|
|
|
|
// Mouse event buttons
|
|
|
|
//
|
|
|
|
// This is based on X11 mouse button codes.
|
|
|
|
//
|
|
|
|
// 1 = left button
|
|
|
|
// 2 = middle button (pressing the scroll wheel)
|
|
|
|
// 3 = right button
|
|
|
|
// 4 = turn scroll wheel up
|
|
|
|
// 5 = turn scroll wheel down
|
|
|
|
// 6 = push scroll wheel left
|
|
|
|
// 7 = push scroll wheel right
|
|
|
|
// 8 = 4th button (aka browser backward button)
|
|
|
|
// 9 = 5th button (aka browser forward button)
|
|
|
|
// 10
|
|
|
|
// 11
|
|
|
|
//
|
|
|
|
// Other buttons are not supported.
|
|
|
|
const (
|
|
|
|
MouseButtonNone MouseButton = iota
|
|
|
|
MouseButtonLeft
|
|
|
|
MouseButtonMiddle
|
|
|
|
MouseButtonRight
|
|
|
|
MouseButtonWheelUp
|
|
|
|
MouseButtonWheelDown
|
|
|
|
MouseButtonWheelLeft
|
|
|
|
MouseButtonWheelRight
|
|
|
|
MouseButtonBackward
|
|
|
|
MouseButtonForward
|
|
|
|
MouseButton10
|
|
|
|
MouseButton11
|
|
|
|
)
|
|
|
|
|
|
|
|
var mouseButtons = map[MouseButton]string{
|
|
|
|
MouseButtonNone: "none",
|
|
|
|
MouseButtonLeft: "left",
|
|
|
|
MouseButtonMiddle: "middle",
|
|
|
|
MouseButtonRight: "right",
|
|
|
|
MouseButtonWheelUp: "wheel up",
|
|
|
|
MouseButtonWheelDown: "wheel down",
|
|
|
|
MouseButtonWheelLeft: "wheel left",
|
|
|
|
MouseButtonWheelRight: "wheel right",
|
|
|
|
MouseButtonBackward: "backward",
|
|
|
|
MouseButtonForward: "forward",
|
|
|
|
MouseButton10: "button 10",
|
|
|
|
MouseButton11: "button 11",
|
|
|
|
}
|
|
|
|
|
2020-09-28 16:42:39 -04:00
|
|
|
// MouseEventType indicates the type of mouse event occurring.
|
2023-12-04 11:50:59 -05:00
|
|
|
//
|
|
|
|
// Deprecated: Use MouseAction & MouseButton instead.
|
2020-09-28 16:42:39 -04:00
|
|
|
type MouseEventType int
|
2020-06-22 20:30:16 -04:00
|
|
|
|
2021-09-07 15:28:08 -04:00
|
|
|
// Mouse event types.
|
2023-12-04 11:50:59 -05:00
|
|
|
//
|
|
|
|
// Deprecated: Use MouseAction & MouseButton instead.
|
2020-06-22 20:30:16 -04:00
|
|
|
const (
|
2020-09-28 16:42:39 -04:00
|
|
|
MouseUnknown MouseEventType = iota
|
2020-06-23 10:34:46 -04:00
|
|
|
MouseLeft
|
2020-06-22 20:30:16 -04:00
|
|
|
MouseRight
|
|
|
|
MouseMiddle
|
2023-12-04 11:50:59 -05:00
|
|
|
MouseRelease // mouse button release (X10 only)
|
2020-06-22 20:30:16 -04:00
|
|
|
MouseWheelUp
|
|
|
|
MouseWheelDown
|
2023-12-04 11:50:59 -05:00
|
|
|
MouseWheelLeft
|
|
|
|
MouseWheelRight
|
|
|
|
MouseBackward
|
|
|
|
MouseForward
|
2020-06-22 20:30:16 -04:00
|
|
|
MouseMotion
|
|
|
|
)
|
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
// Parse SGR-encoded mouse events; SGR extended mouse events. SGR mouse events
|
|
|
|
// look like:
|
|
|
|
//
|
|
|
|
// ESC [ < Cb ; Cx ; Cy (M or m)
|
|
|
|
//
|
|
|
|
// where:
|
|
|
|
//
|
|
|
|
// Cb is the encoded button code
|
|
|
|
// Cx is the x-coordinate of the mouse
|
|
|
|
// Cy is the y-coordinate of the mouse
|
|
|
|
// M is for button press, m is for button release
|
|
|
|
//
|
|
|
|
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
|
|
|
|
func parseSGRMouseEvent(buf []byte) MouseEvent {
|
|
|
|
str := string(buf[3:])
|
|
|
|
matches := mouseSGRRegex.FindStringSubmatch(str)
|
|
|
|
if len(matches) != 5 {
|
|
|
|
// Unreachable, we already checked the regex in `detectOneMsg`.
|
|
|
|
panic("invalid mouse event")
|
|
|
|
}
|
|
|
|
|
|
|
|
b, _ := strconv.Atoi(matches[1])
|
|
|
|
px := matches[2]
|
|
|
|
py := matches[3]
|
|
|
|
release := matches[4] == "m"
|
|
|
|
m := parseMouseButton(b, true)
|
|
|
|
|
|
|
|
// Wheel buttons don't have release events
|
|
|
|
// Motion can be reported as a release event in some terminals (Windows Terminal)
|
|
|
|
if m.Action != MouseActionMotion && !m.IsWheel() && release {
|
|
|
|
m.Action = MouseActionRelease
|
|
|
|
m.Type = MouseRelease
|
|
|
|
}
|
|
|
|
|
|
|
|
x, _ := strconv.Atoi(px)
|
|
|
|
y, _ := strconv.Atoi(py)
|
|
|
|
|
|
|
|
// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
|
|
|
|
m.X = x - 1
|
|
|
|
m.Y = y - 1
|
|
|
|
|
|
|
|
return m
|
2020-06-22 20:30:16 -04:00
|
|
|
}
|
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
const x10MouseByteOffset = 32
|
|
|
|
|
2022-02-02 22:14:49 -05:00
|
|
|
// Parse X10-encoded mouse events; the simplest kind. The last release of X10
|
2023-12-04 11:50:59 -05:00
|
|
|
// was December 1986, by the way. The original X10 mouse protocol limits the Cx
|
|
|
|
// and Cy coordinates to 223 (=255-032).
|
2020-06-23 10:34:46 -04:00
|
|
|
//
|
|
|
|
// X10 mouse events look like:
|
|
|
|
//
|
2022-08-15 05:58:40 -04:00
|
|
|
// ESC [M Cb Cx Cy
|
2020-06-23 10:34:46 -04:00
|
|
|
//
|
2020-11-07 00:43:12 -05:00
|
|
|
// See: http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
|
2022-10-21 11:00:39 -04:00
|
|
|
func parseX10MouseEvent(buf []byte) MouseEvent {
|
|
|
|
v := buf[3:6]
|
2023-12-04 11:50:59 -05:00
|
|
|
m := parseMouseButton(int(v[0]), false)
|
|
|
|
|
|
|
|
// (1,1) is the upper left. We subtract 1 to normalize it to (0,0).
|
|
|
|
m.X = int(v[1]) - x10MouseByteOffset - 1
|
|
|
|
m.Y = int(v[2]) - x10MouseByteOffset - 1
|
|
|
|
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
|
|
|
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Extended-coordinates
|
|
|
|
func parseMouseButton(b int, isSGR bool) MouseEvent {
|
2022-10-21 11:00:39 -04:00
|
|
|
var m MouseEvent
|
2023-12-04 11:50:59 -05:00
|
|
|
e := b
|
|
|
|
if !isSGR {
|
|
|
|
e -= x10MouseByteOffset
|
|
|
|
}
|
2022-10-21 11:00:39 -04:00
|
|
|
|
|
|
|
const (
|
|
|
|
bitShift = 0b0000_0100
|
|
|
|
bitAlt = 0b0000_1000
|
|
|
|
bitCtrl = 0b0001_0000
|
|
|
|
bitMotion = 0b0010_0000
|
|
|
|
bitWheel = 0b0100_0000
|
2023-12-04 11:50:59 -05:00
|
|
|
bitAdd = 0b1000_0000 // additional buttons 8-11
|
2022-10-21 11:00:39 -04:00
|
|
|
|
|
|
|
bitsMask = 0b0000_0011
|
|
|
|
)
|
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
if e&bitAdd != 0 {
|
|
|
|
m.Button = MouseButtonBackward + MouseButton(e&bitsMask)
|
|
|
|
} else if e&bitWheel != 0 {
|
|
|
|
m.Button = MouseButtonWheelUp + MouseButton(e&bitsMask)
|
2022-10-21 11:00:39 -04:00
|
|
|
} else {
|
2023-12-04 11:50:59 -05:00
|
|
|
m.Button = MouseButtonLeft + MouseButton(e&bitsMask)
|
|
|
|
// X10 reports a button release as 0b0000_0011 (3)
|
|
|
|
if e&bitsMask == bitsMask {
|
|
|
|
m.Action = MouseActionRelease
|
|
|
|
m.Button = MouseButtonNone
|
2022-02-02 22:14:49 -05:00
|
|
|
}
|
2022-10-21 11:00:39 -04:00
|
|
|
}
|
2020-11-07 00:43:12 -05:00
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
// Motion bit doesn't get reported for wheel events.
|
|
|
|
if e&bitMotion != 0 && !m.IsWheel() {
|
|
|
|
m.Action = MouseActionMotion
|
2022-10-21 11:00:39 -04:00
|
|
|
}
|
|
|
|
|
2023-12-04 11:50:59 -05:00
|
|
|
// Modifiers
|
|
|
|
m.Alt = e&bitAlt != 0
|
|
|
|
m.Ctrl = e&bitCtrl != 0
|
|
|
|
m.Shift = e&bitShift != 0
|
|
|
|
|
|
|
|
// backward compatibility
|
|
|
|
switch {
|
|
|
|
case m.Button == MouseButtonLeft && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseLeft
|
|
|
|
case m.Button == MouseButtonMiddle && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseMiddle
|
|
|
|
case m.Button == MouseButtonRight && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseRight
|
|
|
|
case m.Button == MouseButtonNone && m.Action == MouseActionRelease:
|
|
|
|
m.Type = MouseRelease
|
|
|
|
case m.Button == MouseButtonWheelUp && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseWheelUp
|
|
|
|
case m.Button == MouseButtonWheelDown && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseWheelDown
|
|
|
|
case m.Button == MouseButtonWheelLeft && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseWheelLeft
|
|
|
|
case m.Button == MouseButtonWheelRight && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseWheelRight
|
|
|
|
case m.Button == MouseButtonBackward && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseBackward
|
|
|
|
case m.Button == MouseButtonForward && m.Action == MouseActionPress:
|
|
|
|
m.Type = MouseForward
|
|
|
|
case m.Action == MouseActionMotion:
|
|
|
|
m.Type = MouseMotion
|
|
|
|
switch m.Button {
|
|
|
|
case MouseButtonLeft:
|
|
|
|
m.Type = MouseLeft
|
|
|
|
case MouseButtonMiddle:
|
|
|
|
m.Type = MouseMiddle
|
|
|
|
case MouseButtonRight:
|
|
|
|
m.Type = MouseRight
|
|
|
|
case MouseButtonBackward:
|
|
|
|
m.Type = MouseBackward
|
|
|
|
case MouseButtonForward:
|
|
|
|
m.Type = MouseForward
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
m.Type = MouseUnknown
|
|
|
|
}
|
2020-06-22 20:30:16 -04:00
|
|
|
|
2022-10-21 11:00:39 -04:00
|
|
|
return m
|
2020-06-22 20:30:16 -04:00
|
|
|
}
|