Skip to content

Commit

Permalink
refactor(terminal): support mouse click envent SGR 1006, #91
Browse files Browse the repository at this point in the history
  • Loading branch information
ericwq committed Aug 1, 2024
1 parent ae361ef commit 9188c2a
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 0 deletions.
44 changes: 44 additions & 0 deletions terminal/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const (
CSI_XTMODKEYS
CSI_XTWINOPS
CSI_U
CSI_MOUSETRACK
DCS_DECRQSS
DCS_XTGETTCAP
ESC_BI
Expand Down Expand Up @@ -176,6 +177,7 @@ var strHandlerID = [...]string{
"csi_xtmodkeys",
"csi_xtwinops",
"csi_u",
"csi_mousetrack",
"dcs_decrqss",
"dcs_xtgettcap",
"esc_bi",
Expand Down Expand Up @@ -2255,3 +2257,45 @@ func hdl_csi_rep(emu *Emulator, arg int, chs []rune) {
hdl_graphemes(emu, chs...)
}
}

/*
SGR (1006)
The normal mouse response is altered to use
o CSI < followed by semicolon-separated
o encoded button value,
o Px and Py ordinates and
o a final character which is M for button press and m for
button release.
The encoded button value in this case does not add 32 since
that was useful only in the X10 scheme for ensuring that the
byte containing the button value is a printable code.
o The modifiers are encoded in the same way.
o A different final character is used for button release to
resolve the X10 ambiguity regarding which button was
released.
The highlight tracking responses are also modified to an SGR-
like format, using the same SGR-style scheme and button-
encodings.
"\x1b[<0;1;5M"
*/
func hdl_csi_mousetrack(emu *Emulator, press bool, params []int) {
switch emu.mouseTrk.enc {
case MouseTrackingEnc_SGR:
if !press && len(params) == 3 {
hdl_csi_cup(emu, params[2], params[1])
}
default:
util.Logger.Warn("unhandled operation", "encoding", emu.mouseTrk.enc,
"press", press, "params", params, "id", strHandlerID[CSI_MOUSETRACK])
}
}
28 changes: 28 additions & 0 deletions terminal/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
InputState_CSI_Bang
InputState_CSI_SPC
InputState_CSI_GT
InputState_CSI_LT
InputState_DCS
InputState_DCS_Esc
InputState_OSC
Expand All @@ -51,6 +52,7 @@ var strInputState = [...]string{
"CSI_Bang",
"CSI_SPC",
"CSI_GT",
"CSI_LT",
"DCS",
"DCS_Esc",
"OSC",
Expand Down Expand Up @@ -1530,6 +1532,18 @@ func (p *Parser) handle_ID() (hd *Handler) {
return hd
}

func (p *Parser) handle_MouseTrack(press bool) (hd *Handler) {
params := p.copyArgs()

hd = &Handler{id: CSI_MOUSETRACK, ch: p.ch, sequence: p.historyString()}
hd.handle = func(emu *Emulator) {
hdl_csi_mousetrack(emu, press, params)
}

p.setState(InputState_Normal)
return hd
}

// process data stream from outside. for VT mode, character set can be changed
// according to control sequences. for UTF-8 mode, no need to change character set.
// the result is a *Handler list. waiting to be executed later.
Expand Down Expand Up @@ -1937,6 +1951,8 @@ func (p *Parser) ProcessInput(chs ...rune) (hd *Handler) {
p.setState(InputState_CSI_SPC)
case '>':
p.setState(InputState_CSI_GT)
case '<':
p.setState(InputState_CSI_LT)
case '\x07': // BEL is ignored \a in c++
case '\x08': // BS is \b
// undo last character in CSI sequence:
Expand Down Expand Up @@ -2003,6 +2019,18 @@ func (p *Parser) ProcessInput(chs ...rune) (hd *Handler) {
default:
p.unhandledInput()
}
case InputState_CSI_LT:
if p.collectNumericParameters(ch) {
break
}
switch ch {
case 'M':
hd = p.handle_MouseTrack(true)
case 'm':
hd = p.handle_MouseTrack(false)
default:
p.unhandledInput()
}
case InputState_CSI_Priv:
if p.collectNumericParameters(ch) {
break
Expand Down
87 changes: 87 additions & 0 deletions terminal/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5257,3 +5257,90 @@ func TestHandle_DECRQM(t *testing.T) {
})
}
}

func TestHandle_MouseTrack(t *testing.T) {
tc := []struct {
label string
seq string
hdIDs []int
wantY int
wantX int
}{
{
"SGR press/release", "\x1b[?1006h\x1B[<0:14;24M\x1B[<0:14;24m",
[]int{CSI_privSM, CSI_MOUSETRACK, CSI_MOUSETRACK},
23, 13,
},
{
"URXVT press/release", "\x1b[?1015h\x1B[<0:14;24M\x1B[<0:14;24m",
[]int{CSI_privSM, CSI_MOUSETRACK, CSI_MOUSETRACK},
0, 0,
},
}

util.Logger.CreateLogger(io.Discard, true, slog.LevelDebug)

for _, v := range tc {
p := NewParser()
emu := NewEmulator3(80, 40, 0)

t.Run(v.label, func(t *testing.T) {
// parse control sequence
hds := make([]*Handler, 0, 16)
hds = p.processStream(v.seq, hds)

// reset the cursor position
emu.posY = 0
emu.posX = 0

// execute the control sequence
for j, hd := range hds {
hd.handle(emu)
if hd.id != v.hdIDs[j] {
t.Fatalf("%s: seq=%q expect %s, got %s\n",
v.label, v.seq, strHandlerID[v.hdIDs[j]], strHandlerID[hd.id])
}
}
// get the result
gotY := emu.posY
gotX := emu.posX

if gotX != v.wantX || gotY != v.wantY {
t.Errorf("%s expect cursor position (%d,%d), got (%d,%d)\n",
v.label, v.wantX, v.wantY, gotX, gotY)
}
})
}
}

func TestHandle_MouseTrack_break(t *testing.T) {
tc := []struct {
label string
seq string
log string
}{
{"unhandled break", "\x1b[<20p", "Unhandled input"},
}

var place strings.Builder
util.Logger.CreateLogger(&place, false, util.LevelTrace)

for _, v := range tc {
t.Run(v.label, func(t *testing.T) {
p := NewParser()
emu := NewEmulator3(80, 40, 0)

hds := make([]*Handler, 0, 16)
hds = p.processStream(v.seq, hds)

for i := range hds {
hds[i].handle(emu)
}
result := place.String()

if strings.Contains(result, "2026l") {
t.Errorf("%s got warn log \n%s", v.label, result)
}
})
}
}

0 comments on commit 9188c2a

Please sign in to comment.