@teammates/consolonia 0.6.3 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Terminal environment detection.
3
+ *
4
+ * Probes environment variables and process state to determine which
5
+ * terminal capabilities are available. Used by App to send only the
6
+ * escape sequences the host terminal actually supports.
7
+ */
8
+ // ── Detection ───────────────────────────────────────────────────────
9
+ /**
10
+ * Detect terminal capabilities from the current environment.
11
+ *
12
+ * On Windows the main differentiator is whether we're running under
13
+ * Windows Terminal / ConPTY (full VT support) or legacy conhost
14
+ * (very limited escape handling). On Unix the TERM variable and
15
+ * TERM_PROGRAM give us enough signal.
16
+ */
17
+ export function detectTerminal() {
18
+ const env = process.env;
19
+ const isTTY = !!process.stdout.isTTY;
20
+ if (!isTTY) {
21
+ return {
22
+ isTTY: false,
23
+ alternateScreen: false,
24
+ bracketedPaste: false,
25
+ mouse: false,
26
+ sgrMouse: false,
27
+ truecolor: false,
28
+ color256: false,
29
+ name: "pipe",
30
+ };
31
+ }
32
+ // ── Windows ─────────────────────────────────────────────────────
33
+ if (process.platform === "win32") {
34
+ // Windows Terminal sets WT_SESSION
35
+ if (env.WT_SESSION) {
36
+ return {
37
+ isTTY: true,
38
+ alternateScreen: true,
39
+ bracketedPaste: true,
40
+ mouse: true,
41
+ sgrMouse: true,
42
+ truecolor: true,
43
+ color256: true,
44
+ name: "windows-terminal",
45
+ };
46
+ }
47
+ // VS Code's integrated terminal (xterm.js)
48
+ if (env.TERM_PROGRAM === "vscode") {
49
+ return {
50
+ isTTY: true,
51
+ alternateScreen: true,
52
+ bracketedPaste: true,
53
+ mouse: true,
54
+ sgrMouse: true,
55
+ truecolor: true,
56
+ color256: true,
57
+ name: "vscode",
58
+ };
59
+ }
60
+ // ConEmu / Cmder
61
+ if (env.ConEmuPID) {
62
+ return {
63
+ isTTY: true,
64
+ alternateScreen: true,
65
+ bracketedPaste: true,
66
+ mouse: true,
67
+ sgrMouse: true,
68
+ truecolor: true,
69
+ color256: true,
70
+ name: "conemu",
71
+ };
72
+ }
73
+ // Mintty (Git Bash) — sets TERM=xterm*
74
+ if (env.TERM?.startsWith("xterm") && env.MSYSTEM) {
75
+ return {
76
+ isTTY: true,
77
+ alternateScreen: true,
78
+ bracketedPaste: true,
79
+ mouse: true,
80
+ sgrMouse: true,
81
+ truecolor: true,
82
+ color256: true,
83
+ name: "mintty",
84
+ };
85
+ }
86
+ // Fallback: modern Windows 10+ conhost with ConPTY has decent VT
87
+ // support, but mouse tracking can be unreliable. Enable everything
88
+ // and let the terminal silently ignore what it doesn't support.
89
+ return {
90
+ isTTY: true,
91
+ alternateScreen: true,
92
+ bracketedPaste: true,
93
+ mouse: true,
94
+ sgrMouse: true,
95
+ truecolor: true,
96
+ color256: true,
97
+ name: "conhost",
98
+ };
99
+ }
100
+ // ── Unix / macOS ────────────────────────────────────────────────
101
+ const term = env.TERM ?? "";
102
+ const termProgram = env.TERM_PROGRAM ?? "";
103
+ // tmux — full VT support, passes through SGR mouse
104
+ if (env.TMUX || term.startsWith("tmux") || term === "screen-256color") {
105
+ return {
106
+ isTTY: true,
107
+ alternateScreen: true,
108
+ bracketedPaste: true,
109
+ mouse: true,
110
+ sgrMouse: true,
111
+ truecolor: !!env.COLORTERM || term.includes("256color"),
112
+ color256: true,
113
+ name: "tmux",
114
+ };
115
+ }
116
+ // GNU screen — limited mouse support, no SGR
117
+ if (term === "screen" && !env.TMUX) {
118
+ return {
119
+ isTTY: true,
120
+ alternateScreen: true,
121
+ bracketedPaste: false,
122
+ mouse: true,
123
+ sgrMouse: false,
124
+ truecolor: false,
125
+ color256: false,
126
+ name: "screen",
127
+ };
128
+ }
129
+ // iTerm2
130
+ if (termProgram === "iTerm.app" || env.ITERM_SESSION_ID) {
131
+ return {
132
+ isTTY: true,
133
+ alternateScreen: true,
134
+ bracketedPaste: true,
135
+ mouse: true,
136
+ sgrMouse: true,
137
+ truecolor: true,
138
+ color256: true,
139
+ name: "iterm2",
140
+ };
141
+ }
142
+ // VS Code integrated terminal (macOS/Linux)
143
+ if (termProgram === "vscode") {
144
+ return {
145
+ isTTY: true,
146
+ alternateScreen: true,
147
+ bracketedPaste: true,
148
+ mouse: true,
149
+ sgrMouse: true,
150
+ truecolor: true,
151
+ color256: true,
152
+ name: "vscode",
153
+ };
154
+ }
155
+ // Alacritty
156
+ if (termProgram === "Alacritty" || term === "alacritty") {
157
+ return {
158
+ isTTY: true,
159
+ alternateScreen: true,
160
+ bracketedPaste: true,
161
+ mouse: true,
162
+ sgrMouse: true,
163
+ truecolor: true,
164
+ color256: true,
165
+ name: "alacritty",
166
+ };
167
+ }
168
+ // xterm-compatible (most Linux/macOS terminals)
169
+ if (term.startsWith("xterm") || term.includes("256color")) {
170
+ const hasTruecolor = env.COLORTERM === "truecolor" || env.COLORTERM === "24bit";
171
+ return {
172
+ isTTY: true,
173
+ alternateScreen: true,
174
+ bracketedPaste: true,
175
+ mouse: true,
176
+ sgrMouse: true,
177
+ truecolor: hasTruecolor,
178
+ color256: true,
179
+ name: term,
180
+ };
181
+ }
182
+ // Dumb terminal — absolute minimum
183
+ if (term === "dumb" || !term) {
184
+ return {
185
+ isTTY: true,
186
+ alternateScreen: false,
187
+ bracketedPaste: false,
188
+ mouse: false,
189
+ sgrMouse: false,
190
+ truecolor: false,
191
+ color256: false,
192
+ name: term || "unknown",
193
+ };
194
+ }
195
+ // Unknown but it is a TTY — enable common capabilities
196
+ return {
197
+ isTTY: true,
198
+ alternateScreen: true,
199
+ bracketedPaste: true,
200
+ mouse: true,
201
+ sgrMouse: false,
202
+ truecolor: false,
203
+ color256: true,
204
+ name: term,
205
+ };
206
+ }
package/dist/app.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * RenderTarget, DrawingContext, InputProcessor) and drives the
6
6
  * measure → arrange → render loop in response to input and resize events.
7
7
  */
8
+ import { type TerminalCaps } from "./ansi/terminal-env.js";
8
9
  import type { Control } from "./layout/control.js";
9
10
  export interface AppOptions {
10
11
  /** Root control to render. */
@@ -21,6 +22,7 @@ export declare class App {
21
22
  private readonly _alternateScreen;
22
23
  private readonly _mouse;
23
24
  private readonly _title;
25
+ private readonly _caps;
24
26
  private _output;
25
27
  private _buffer;
26
28
  private _dirtyRegions;
@@ -34,6 +36,8 @@ export declare class App {
34
36
  private _resizeListener;
35
37
  private _sigintListener;
36
38
  private _renderScheduled;
39
+ /** Detected terminal capabilities (read-only, for diagnostics). */
40
+ get caps(): Readonly<TerminalCaps>;
37
41
  constructor(options: AppOptions);
38
42
  /**
39
43
  * Start the app — enters raw mode, sets up terminal, runs the event
package/dist/app.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import * as esc from "./ansi/esc.js";
9
9
  import { AnsiOutput } from "./ansi/output.js";
10
+ import { detectTerminal, } from "./ansi/terminal-env.js";
10
11
  import { DrawingContext } from "./drawing/context.js";
11
12
  import { createInputProcessor } from "./input/processor.js";
12
13
  import { disableRawMode, enableRawMode } from "./input/raw-mode.js";
@@ -19,6 +20,7 @@ export class App {
19
20
  _alternateScreen;
20
21
  _mouse;
21
22
  _title;
23
+ _caps;
22
24
  // Subsystems — created during run()
23
25
  _output;
24
26
  _buffer;
@@ -34,11 +36,16 @@ export class App {
34
36
  _resizeListener = null;
35
37
  _sigintListener = null;
36
38
  _renderScheduled = false;
39
+ /** Detected terminal capabilities (read-only, for diagnostics). */
40
+ get caps() {
41
+ return this._caps;
42
+ }
37
43
  constructor(options) {
38
44
  this.root = options.root;
39
45
  this._alternateScreen = options.alternateScreen ?? true;
40
46
  this._mouse = options.mouse ?? false;
41
47
  this._title = options.title;
48
+ this._caps = detectTerminal();
42
49
  }
43
50
  // ── Public API ───────────────────────────────────────────────────
44
51
  /**
@@ -114,30 +121,21 @@ export class App {
114
121
  }
115
122
  _prepareTerminal() {
116
123
  const stream = process.stdout;
117
- let seq = "";
118
- if (this._alternateScreen) {
119
- seq += esc.alternateScreenOn;
120
- }
121
- seq += esc.hideCursor;
122
- seq += esc.bracketedPasteOn;
123
- if (this._mouse) {
124
- seq += esc.mouseTrackingOn;
125
- }
126
- seq += esc.clearScreen;
127
- stream.write(seq);
124
+ const seq = esc.initSequence(this._caps, {
125
+ alternateScreen: this._alternateScreen,
126
+ mouse: this._mouse,
127
+ });
128
+ if (seq)
129
+ stream.write(seq);
128
130
  }
129
131
  _restoreTerminal() {
130
132
  const stream = process.stdout;
131
- let seq = esc.reset;
132
- if (this._mouse) {
133
- seq += esc.mouseTrackingOff;
134
- }
135
- seq += esc.bracketedPasteOff;
136
- seq += esc.showCursor;
137
- if (this._alternateScreen) {
138
- seq += esc.alternateScreenOff;
139
- }
140
- stream.write(seq);
133
+ const seq = esc.restoreSequence(this._caps, {
134
+ alternateScreen: this._alternateScreen,
135
+ mouse: this._mouse,
136
+ });
137
+ if (seq)
138
+ stream.write(seq);
141
139
  }
142
140
  _createRenderPipeline(cols, rows) {
143
141
  this._buffer = new PixelBuffer(cols, rows);
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export type { Constraint, Point, Rect, Size, } from "./layout/types.js";
16
16
  export * as esc from "./ansi/esc.js";
17
17
  export { AnsiOutput } from "./ansi/output.js";
18
18
  export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
19
+ export { type TerminalCaps, detectTerminal, } from "./ansi/terminal-env.js";
19
20
  export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
20
21
  export { RenderTarget } from "./render/render-target.js";
21
22
  export { EscapeMatcher } from "./input/escape-matcher.js";
@@ -35,13 +36,15 @@ export { Control } from "./layout/control.js";
35
36
  export { Row, type RowOptions } from "./layout/row.js";
36
37
  export { Stack, type StackOptions } from "./layout/stack.js";
37
38
  export { Border, type BorderOptions } from "./widgets/border.js";
38
- export { ChatView, type ChatViewOptions, type DropdownItem, type FeedActionItem, } from "./widgets/chat-view.js";
39
+ export { ChatView, type ChatViewOptions, type DropdownItem, type FeedActionEntry, type FeedActionItem, type FeedItem, } from "./widgets/chat-view.js";
40
+ export { FeedStore } from "./widgets/feed-store.js";
39
41
  export { Interview, type InterviewOptions, type InterviewQuestion, } from "./widgets/interview.js";
40
42
  export { Panel, type PanelOptions } from "./widgets/panel.js";
41
43
  export { ScrollView, type ScrollViewOptions } from "./widgets/scroll-view.js";
42
44
  export { type StyledLine, StyledText, type StyledTextOptions, } from "./widgets/styled-text.js";
43
45
  export { Text, type TextOptions } from "./widgets/text.js";
44
46
  export { type DeleteSizer, type InputColorizer, TextInput, type TextInputOptions, } from "./widgets/text-input.js";
47
+ export { VirtualList, type VirtualListItem, type VirtualListOptions, } from "./widgets/virtual-list.js";
45
48
  export { type MarkdownOptions, type MarkdownTheme, renderMarkdown, } from "./widgets/markdown.js";
46
49
  export { DEFAULT_SYNTAX_THEME, getHighlighter, highlightLine, registerHighlighter, type SyntaxHighlighter, type SyntaxTheme, type SyntaxToken, type SyntaxTokenType, } from "./widgets/syntax.js";
47
50
  export { concat, isStyledSpan, pen, type StyledSegment, type StyledSpan, spanLength, spanText, } from "./styled.js";
package/dist/index.js CHANGED
@@ -23,6 +23,7 @@ export { charWidth, EMPTY_SYMBOL, isZeroWidth, stringDisplayWidth, sym, } from "
23
23
  export * as esc from "./ansi/esc.js";
24
24
  export { AnsiOutput } from "./ansi/output.js";
25
25
  export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
26
+ export { detectTerminal, } from "./ansi/terminal-env.js";
26
27
  // ── Render pipeline ─────────────────────────────────────────────────
27
28
  export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
28
29
  export { RenderTarget } from "./render/render-target.js";
@@ -47,12 +48,14 @@ export { Stack } from "./layout/stack.js";
47
48
  // ── Widgets ─────────────────────────────────────────────────────────
48
49
  export { Border } from "./widgets/border.js";
49
50
  export { ChatView, } from "./widgets/chat-view.js";
51
+ export { FeedStore } from "./widgets/feed-store.js";
50
52
  export { Interview, } from "./widgets/interview.js";
51
53
  export { Panel } from "./widgets/panel.js";
52
54
  export { ScrollView } from "./widgets/scroll-view.js";
53
55
  export { StyledText, } from "./widgets/styled-text.js";
54
56
  export { Text } from "./widgets/text.js";
55
57
  export { TextInput, } from "./widgets/text-input.js";
58
+ export { VirtualList, } from "./widgets/virtual-list.js";
56
59
  // ── Markdown ─────────────────────────────────────────────────────────
57
60
  export { renderMarkdown, } from "./widgets/markdown.js";
58
61
  // ── Syntax highlighting ──────────────────────────────────────────────
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Parses SGR extended mouse tracking sequences.
2
+ * Parses terminal mouse tracking sequences.
3
3
  *
4
- * Format: \x1b[<Cb;Cx;CyM (press/motion)
5
- * \x1b[<Cb;Cx;Cym (release)
4
+ * Supported formats:
5
+ * SGR: \x1b[<Cb;Cx;CyM (press/motion)
6
+ * \x1b[<Cb;Cx;Cym (release)
7
+ * SGR-Pixels: \x1b[<Cb;Cx;CyM (same wire format as SGR, pixel coords)
8
+ * \x1b[<Cb;Cx;Cym
9
+ * X10: \x1b[M Cb Cx Cy (classic xterm byte encoding)
10
+ * UTF-8: \x1b[M Cb Cx Cy (same prefix as X10, UTF-8 encoded coords)
11
+ * URXVT: \x1b[Cb;Cx;CyM (decimal params, no < prefix)
6
12
  *
7
13
  * Cb encodes button and modifiers:
8
14
  * bits 0-1: 0=left, 1=middle, 2=right
@@ -13,15 +19,27 @@
13
19
  * bit 4 (+16): ctrl
14
20
  *
15
21
  * Cx, Cy are 1-based coordinates.
22
+ *
23
+ * Note: UTF-8 mode uses the same \x1b[M prefix as X10 but encodes
24
+ * coordinates as UTF-8 characters for values > 127. Node.js decodes
25
+ * UTF-8 stdin automatically, so the X10 parser handles both formats.
26
+ *
27
+ * Note: SGR-Pixels mode uses the same wire format as SGR but reports
28
+ * pixel coordinates instead of cell coordinates. These are passed
29
+ * through as-is (the caller must convert to cells if needed).
16
30
  */
17
31
  import { type InputEvent } from "./events.js";
18
32
  import { type IMatcher, MatchResult } from "./matcher.js";
19
33
  export declare class MouseMatcher implements IMatcher {
20
34
  private state;
21
35
  private params;
36
+ private x10Bytes;
37
+ private urxvtParams;
22
38
  private result;
23
39
  append(char: string): MatchResult;
24
40
  flush(): InputEvent | null;
25
41
  reset(): void;
26
42
  private finalize;
43
+ private finalizeUrxvt;
44
+ private finalizeX10;
27
45
  }
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Parses SGR extended mouse tracking sequences.
2
+ * Parses terminal mouse tracking sequences.
3
3
  *
4
- * Format: \x1b[<Cb;Cx;CyM (press/motion)
5
- * \x1b[<Cb;Cx;Cym (release)
4
+ * Supported formats:
5
+ * SGR: \x1b[<Cb;Cx;CyM (press/motion)
6
+ * \x1b[<Cb;Cx;Cym (release)
7
+ * SGR-Pixels: \x1b[<Cb;Cx;CyM (same wire format as SGR, pixel coords)
8
+ * \x1b[<Cb;Cx;Cym
9
+ * X10: \x1b[M Cb Cx Cy (classic xterm byte encoding)
10
+ * UTF-8: \x1b[M Cb Cx Cy (same prefix as X10, UTF-8 encoded coords)
11
+ * URXVT: \x1b[Cb;Cx;CyM (decimal params, no < prefix)
6
12
  *
7
13
  * Cb encodes button and modifiers:
8
14
  * bits 0-1: 0=left, 1=middle, 2=right
@@ -13,6 +19,14 @@
13
19
  * bit 4 (+16): ctrl
14
20
  *
15
21
  * Cx, Cy are 1-based coordinates.
22
+ *
23
+ * Note: UTF-8 mode uses the same \x1b[M prefix as X10 but encodes
24
+ * coordinates as UTF-8 characters for values > 127. Node.js decodes
25
+ * UTF-8 stdin automatically, so the X10 parser handles both formats.
26
+ *
27
+ * Note: SGR-Pixels mode uses the same wire format as SGR but reports
28
+ * pixel coordinates instead of cell coordinates. These are passed
29
+ * through as-is (the caller must convert to cells if needed).
16
30
  */
17
31
  import { mouseEvent } from "./events.js";
18
32
  import { MatchResult } from "./matcher.js";
@@ -24,12 +38,18 @@ var State;
24
38
  State[State["GotEsc"] = 1] = "GotEsc";
25
39
  /** Got \x1b[ */
26
40
  State[State["GotBracket"] = 2] = "GotBracket";
27
- /** Got \x1b[< — now reading params until M or m */
41
+ /** Got \x1b[< — now reading SGR/SGR-Pixels params until M or m */
28
42
  State[State["Reading"] = 3] = "Reading";
43
+ /** Got \x1b[M — now reading three encoded bytes (X10 or UTF-8) */
44
+ State[State["ReadingX10"] = 4] = "ReadingX10";
45
+ /** Got \x1b[ followed by a digit — reading URXVT decimal params until M */
46
+ State[State["ReadingUrxvt"] = 5] = "ReadingUrxvt";
29
47
  })(State || (State = {}));
30
48
  export class MouseMatcher {
31
49
  state = State.Idle;
32
50
  params = "";
51
+ x10Bytes = [];
52
+ urxvtParams = "";
33
53
  result = null;
34
54
  append(char) {
35
55
  switch (this.state) {
@@ -52,6 +72,17 @@ export class MouseMatcher {
52
72
  this.params = "";
53
73
  return MatchResult.Partial;
54
74
  }
75
+ if (char === "M") {
76
+ this.state = State.ReadingX10;
77
+ this.x10Bytes = [];
78
+ return MatchResult.Partial;
79
+ }
80
+ // URXVT: \x1b[ followed by a digit starts decimal param reading
81
+ if (char >= "0" && char <= "9") {
82
+ this.state = State.ReadingUrxvt;
83
+ this.urxvtParams = char;
84
+ return MatchResult.Partial;
85
+ }
55
86
  this.state = State.Idle;
56
87
  return MatchResult.NoMatch;
57
88
  case State.Reading: {
@@ -69,6 +100,26 @@ export class MouseMatcher {
69
100
  this.params = "";
70
101
  return MatchResult.NoMatch;
71
102
  }
103
+ case State.ReadingX10:
104
+ this.x10Bytes.push(char);
105
+ if (this.x10Bytes.length < 3) {
106
+ return MatchResult.Partial;
107
+ }
108
+ return this.finalizeX10();
109
+ case State.ReadingUrxvt: {
110
+ if (char === "M") {
111
+ return this.finalizeUrxvt();
112
+ }
113
+ const c = char.charCodeAt(0);
114
+ if ((c >= 0x30 && c <= 0x39) || char === ";") {
115
+ this.urxvtParams += char;
116
+ return MatchResult.Partial;
117
+ }
118
+ // Not a valid URXVT sequence — bail out
119
+ this.state = State.Idle;
120
+ this.urxvtParams = "";
121
+ return MatchResult.NoMatch;
122
+ }
72
123
  default:
73
124
  return MatchResult.NoMatch;
74
125
  }
@@ -81,6 +132,8 @@ export class MouseMatcher {
81
132
  reset() {
82
133
  this.state = State.Idle;
83
134
  this.params = "";
135
+ this.x10Bytes = [];
136
+ this.urxvtParams = "";
84
137
  this.result = null;
85
138
  }
86
139
  finalize(isRelease) {
@@ -96,37 +149,77 @@ export class MouseMatcher {
96
149
  if (Number.isNaN(cb) || Number.isNaN(cx) || Number.isNaN(cy)) {
97
150
  return MatchResult.NoMatch;
98
151
  }
99
- // Decode modifiers from cb
100
- const shift = (cb & 4) !== 0;
101
- const alt = (cb & 8) !== 0;
102
- const ctrl = (cb & 16) !== 0;
103
- const isMotion = (cb & 32) !== 0;
104
- // Decode button from low bits (masking out modifier/motion bits)
105
- const buttonBits = cb & 3;
106
- const highBits = cb & (64 | 128);
107
- let button;
108
- let type;
109
- if (highBits === 64) {
110
- // Wheel events
111
- button = "none";
112
- type = buttonBits === 0 ? "wheelup" : "wheeldown";
152
+ if (isRelease) {
153
+ const shift = (cb & 4) !== 0;
154
+ const alt = (cb & 8) !== 0;
155
+ const ctrl = (cb & 16) !== 0;
156
+ this.result = mouseEvent(cx - 1, cy - 1, decodeButton(cb & 3), "release", shift, ctrl, alt);
157
+ return MatchResult.Complete;
113
158
  }
114
- else if (isRelease) {
115
- button = decodeButton(buttonBits);
116
- type = "release";
159
+ this.result = decodeMouseEvent(cb, cx, cy, true);
160
+ return this.result ? MatchResult.Complete : MatchResult.NoMatch;
161
+ }
162
+ finalizeUrxvt() {
163
+ this.state = State.Idle;
164
+ const parts = this.urxvtParams.split(";");
165
+ this.urxvtParams = "";
166
+ if (parts.length !== 3) {
167
+ return MatchResult.NoMatch;
168
+ }
169
+ const cb = parseInt(parts[0], 10);
170
+ const cx = parseInt(parts[1], 10);
171
+ const cy = parseInt(parts[2], 10);
172
+ if (Number.isNaN(cb) || Number.isNaN(cx) || Number.isNaN(cy)) {
173
+ return MatchResult.NoMatch;
117
174
  }
118
- else if (isMotion) {
119
- button = buttonBits === 3 ? "none" : decodeButton(buttonBits);
120
- type = "move";
175
+ // URXVT uses the same button encoding as X10 (button 3 = release)
176
+ this.result = decodeMouseEvent(cb, cx, cy, true);
177
+ return this.result ? MatchResult.Complete : MatchResult.NoMatch;
178
+ }
179
+ finalizeX10() {
180
+ this.state = State.Idle;
181
+ if (this.x10Bytes.length !== 3) {
182
+ this.x10Bytes = [];
183
+ return MatchResult.NoMatch;
121
184
  }
122
- else {
123
- button = decodeButton(buttonBits);
124
- type = "press";
185
+ const [cbChar, cxChar, cyChar] = this.x10Bytes;
186
+ this.x10Bytes = [];
187
+ const cb = cbChar.charCodeAt(0) - 32;
188
+ const cx = cxChar.charCodeAt(0) - 32;
189
+ const cy = cyChar.charCodeAt(0) - 32;
190
+ if (cb < 0 || cx <= 0 || cy <= 0) {
191
+ return MatchResult.NoMatch;
125
192
  }
126
- // Convert from 1-based to 0-based coordinates
127
- this.result = mouseEvent(cx - 1, cy - 1, button, type, shift, ctrl, alt);
128
- return MatchResult.Complete;
193
+ this.result = decodeMouseEvent(cb, cx, cy, true);
194
+ return this.result ? MatchResult.Complete : MatchResult.NoMatch;
195
+ }
196
+ }
197
+ function decodeMouseEvent(cb, cx, cy, x10ReleaseUsesButton3) {
198
+ const shift = (cb & 4) !== 0;
199
+ const alt = (cb & 8) !== 0;
200
+ const ctrl = (cb & 16) !== 0;
201
+ const isMotion = (cb & 32) !== 0;
202
+ const buttonBits = cb & 3;
203
+ const highBits = cb & (64 | 128);
204
+ let button;
205
+ let type;
206
+ if (highBits === 64) {
207
+ button = "none";
208
+ type = buttonBits === 0 ? "wheelup" : "wheeldown";
209
+ }
210
+ else if (x10ReleaseUsesButton3 && !isMotion && buttonBits === 3) {
211
+ button = "none";
212
+ type = "release";
213
+ }
214
+ else if (isMotion) {
215
+ button = buttonBits === 3 ? "none" : decodeButton(buttonBits);
216
+ type = "move";
217
+ }
218
+ else {
219
+ button = decodeButton(buttonBits);
220
+ type = "press";
129
221
  }
222
+ return mouseEvent(cx - 1, cy - 1, button, type, shift, ctrl, alt);
130
223
  }
131
224
  function decodeButton(bits) {
132
225
  switch (bits) {