green-screen-proxy 0.3.0
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.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +32 -0
- package/dist/hp6530/connection.d.ts +51 -0
- package/dist/hp6530/connection.js +258 -0
- package/dist/hp6530/constants.d.ts +64 -0
- package/dist/hp6530/constants.js +135 -0
- package/dist/hp6530/encoder.d.ts +37 -0
- package/dist/hp6530/encoder.js +89 -0
- package/dist/hp6530/parser.d.ts +45 -0
- package/dist/hp6530/parser.js +255 -0
- package/dist/hp6530/screen.d.ts +104 -0
- package/dist/hp6530/screen.js +252 -0
- package/dist/mock/mock-routes.d.ts +2 -0
- package/dist/mock/mock-routes.js +231 -0
- package/dist/protocols/hp6530-handler.d.ts +29 -0
- package/dist/protocols/hp6530-handler.js +64 -0
- package/dist/protocols/index.d.ts +11 -0
- package/dist/protocols/index.js +27 -0
- package/dist/protocols/tn3270-handler.d.ts +26 -0
- package/dist/protocols/tn3270-handler.js +61 -0
- package/dist/protocols/tn5250-handler.d.ts +26 -0
- package/dist/protocols/tn5250-handler.js +62 -0
- package/dist/protocols/types.d.ts +59 -0
- package/dist/protocols/types.js +7 -0
- package/dist/protocols/vt-handler.d.ts +30 -0
- package/dist/protocols/vt-handler.js +67 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +141 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +34 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +88 -0
- package/dist/tn3270/connection.d.ts +31 -0
- package/dist/tn3270/connection.js +266 -0
- package/dist/tn3270/constants.d.ts +262 -0
- package/dist/tn3270/constants.js +261 -0
- package/dist/tn3270/encoder.d.ts +24 -0
- package/dist/tn3270/encoder.js +97 -0
- package/dist/tn3270/parser.d.ts +22 -0
- package/dist/tn3270/parser.js +284 -0
- package/dist/tn3270/screen.d.ts +89 -0
- package/dist/tn3270/screen.js +207 -0
- package/dist/tn5250/connection.d.ts +41 -0
- package/dist/tn5250/connection.js +254 -0
- package/dist/tn5250/constants.d.ts +128 -0
- package/dist/tn5250/constants.js +156 -0
- package/dist/tn5250/ebcdic.d.ts +10 -0
- package/dist/tn5250/ebcdic.js +89 -0
- package/dist/tn5250/encoder.d.ts +30 -0
- package/dist/tn5250/encoder.js +121 -0
- package/dist/tn5250/parser.d.ts +33 -0
- package/dist/tn5250/parser.js +412 -0
- package/dist/tn5250/screen.d.ts +80 -0
- package/dist/tn5250/screen.js +155 -0
- package/dist/vt/connection.d.ts +45 -0
- package/dist/vt/connection.js +229 -0
- package/dist/vt/constants.d.ts +97 -0
- package/dist/vt/constants.js +163 -0
- package/dist/vt/encoder.d.ts +30 -0
- package/dist/vt/encoder.js +55 -0
- package/dist/vt/parser.d.ts +36 -0
- package/dist/vt/parser.js +534 -0
- package/dist/vt/screen.d.ts +101 -0
- package/dist/vt/screen.js +424 -0
- package/dist/websocket.d.ts +6 -0
- package/dist/websocket.js +50 -0
- package/package.json +57 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { VTScreenBuffer } from './screen.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encodes client input into VT terminal wire-format data.
|
|
4
|
+
*
|
|
5
|
+
* VT terminals are character-at-a-time (stream mode): each keystroke
|
|
6
|
+
* is sent immediately to the host. No block-mode buffering.
|
|
7
|
+
*/
|
|
8
|
+
export declare class VTEncoder {
|
|
9
|
+
private screen;
|
|
10
|
+
/** Whether BACKSPACE sends DEL (0x7F) or BS (0x08). Default: DEL */
|
|
11
|
+
backspaceIsDel: boolean;
|
|
12
|
+
/** Whether ENTER sends CR (0x0D) or CRLF. Default: CR only */
|
|
13
|
+
enterIsCR: boolean;
|
|
14
|
+
constructor(screen: VTScreenBuffer);
|
|
15
|
+
/**
|
|
16
|
+
* Encode a key name into the VT escape sequence to send over the wire.
|
|
17
|
+
* Returns null if the key is unknown.
|
|
18
|
+
*/
|
|
19
|
+
encodeKey(keyName: string): Buffer | null;
|
|
20
|
+
/**
|
|
21
|
+
* Encode plain text for sending to the host.
|
|
22
|
+
* Each character is sent as its ASCII byte.
|
|
23
|
+
*/
|
|
24
|
+
encodeText(text: string): Buffer;
|
|
25
|
+
/**
|
|
26
|
+
* Insert text at the current cursor position (local echo + wire output).
|
|
27
|
+
* Returns true if text was encoded successfully.
|
|
28
|
+
*/
|
|
29
|
+
insertText(text: string): boolean;
|
|
30
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { VT_KEYS } from './constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Encodes client input into VT terminal wire-format data.
|
|
4
|
+
*
|
|
5
|
+
* VT terminals are character-at-a-time (stream mode): each keystroke
|
|
6
|
+
* is sent immediately to the host. No block-mode buffering.
|
|
7
|
+
*/
|
|
8
|
+
export class VTEncoder {
|
|
9
|
+
screen;
|
|
10
|
+
/** Whether BACKSPACE sends DEL (0x7F) or BS (0x08). Default: DEL */
|
|
11
|
+
backspaceIsDel = true;
|
|
12
|
+
/** Whether ENTER sends CR (0x0D) or CRLF. Default: CR only */
|
|
13
|
+
enterIsCR = true;
|
|
14
|
+
constructor(screen) {
|
|
15
|
+
this.screen = screen;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encode a key name into the VT escape sequence to send over the wire.
|
|
19
|
+
* Returns null if the key is unknown.
|
|
20
|
+
*/
|
|
21
|
+
encodeKey(keyName) {
|
|
22
|
+
const upper = keyName.toUpperCase();
|
|
23
|
+
const seq = VT_KEYS[upper];
|
|
24
|
+
if (seq) {
|
|
25
|
+
return Buffer.from(seq, 'binary');
|
|
26
|
+
}
|
|
27
|
+
// Ctrl+letter (Ctrl+C = 0x03, Ctrl+A = 0x01, etc.)
|
|
28
|
+
if (upper.startsWith('CTRL+') && upper.length === 6) {
|
|
29
|
+
const letter = upper.charAt(5);
|
|
30
|
+
const code = letter.charCodeAt(0) - 0x40; // A=1, B=2, ...
|
|
31
|
+
if (code >= 1 && code <= 26) {
|
|
32
|
+
return Buffer.from([code]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Encode plain text for sending to the host.
|
|
39
|
+
* Each character is sent as its ASCII byte.
|
|
40
|
+
*/
|
|
41
|
+
encodeText(text) {
|
|
42
|
+
return Buffer.from(text, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Insert text at the current cursor position (local echo + wire output).
|
|
46
|
+
* Returns true if text was encoded successfully.
|
|
47
|
+
*/
|
|
48
|
+
insertText(text) {
|
|
49
|
+
if (!text)
|
|
50
|
+
return false;
|
|
51
|
+
// VT is stream mode — no local screen buffering needed.
|
|
52
|
+
// The host will echo characters back and the parser will render them.
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { VTScreenBuffer } from './screen.js';
|
|
2
|
+
/**
|
|
3
|
+
* VT100/VT220/VT320 escape sequence parser.
|
|
4
|
+
*
|
|
5
|
+
* Processes a stream of bytes from the host, interpreting control characters
|
|
6
|
+
* and ANSI/VT escape sequences, and updating the screen buffer accordingly.
|
|
7
|
+
*/
|
|
8
|
+
export declare class VTParser {
|
|
9
|
+
private screen;
|
|
10
|
+
private state;
|
|
11
|
+
/** Accumulated CSI parameter string */
|
|
12
|
+
private params;
|
|
13
|
+
/** CSI intermediate bytes */
|
|
14
|
+
private intermediates;
|
|
15
|
+
/** Whether the CSI sequence has a '?' prefix (DEC private mode) */
|
|
16
|
+
private decPrivate;
|
|
17
|
+
/** OSC accumulator */
|
|
18
|
+
private oscData;
|
|
19
|
+
constructor(screen: VTScreenBuffer);
|
|
20
|
+
/**
|
|
21
|
+
* Feed a chunk of data from the host into the parser.
|
|
22
|
+
* Returns true if the screen was modified.
|
|
23
|
+
*/
|
|
24
|
+
feed(data: Buffer): boolean;
|
|
25
|
+
private handleNormal;
|
|
26
|
+
private handleControlChar;
|
|
27
|
+
private handleEsc;
|
|
28
|
+
private handleCsi;
|
|
29
|
+
/** Parse CSI parameters as an array of numbers (default values handled per command) */
|
|
30
|
+
private parseParams;
|
|
31
|
+
private executeCsi;
|
|
32
|
+
/** Handle DEC private mode sequences (CSI ? ... h/l) */
|
|
33
|
+
private executeDecPrivate;
|
|
34
|
+
private executeSgr;
|
|
35
|
+
private handleOsc;
|
|
36
|
+
}
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { defaultAttrs } from './screen.js';
|
|
2
|
+
import { ESC, CSI_CHAR, BS, HT, LF, VT, FF, CR, SO, SI, BEL, SGR } from './constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* VT100/VT220/VT320 escape sequence parser.
|
|
5
|
+
*
|
|
6
|
+
* Processes a stream of bytes from the host, interpreting control characters
|
|
7
|
+
* and ANSI/VT escape sequences, and updating the screen buffer accordingly.
|
|
8
|
+
*/
|
|
9
|
+
export class VTParser {
|
|
10
|
+
screen;
|
|
11
|
+
state = 0 /* State.NORMAL */;
|
|
12
|
+
/** Accumulated CSI parameter string */
|
|
13
|
+
params = '';
|
|
14
|
+
/** CSI intermediate bytes */
|
|
15
|
+
intermediates = '';
|
|
16
|
+
/** Whether the CSI sequence has a '?' prefix (DEC private mode) */
|
|
17
|
+
decPrivate = false;
|
|
18
|
+
/** OSC accumulator */
|
|
19
|
+
oscData = '';
|
|
20
|
+
constructor(screen) {
|
|
21
|
+
this.screen = screen;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Feed a chunk of data from the host into the parser.
|
|
25
|
+
* Returns true if the screen was modified.
|
|
26
|
+
*/
|
|
27
|
+
feed(data) {
|
|
28
|
+
let modified = false;
|
|
29
|
+
for (let i = 0; i < data.length; i++) {
|
|
30
|
+
const byte = data[i];
|
|
31
|
+
switch (this.state) {
|
|
32
|
+
case 0 /* State.NORMAL */:
|
|
33
|
+
modified = this.handleNormal(byte) || modified;
|
|
34
|
+
break;
|
|
35
|
+
case 1 /* State.ESC */:
|
|
36
|
+
modified = this.handleEsc(byte) || modified;
|
|
37
|
+
break;
|
|
38
|
+
case 2 /* State.CSI_PARAM */:
|
|
39
|
+
case 3 /* State.CSI_INTER */:
|
|
40
|
+
modified = this.handleCsi(byte) || modified;
|
|
41
|
+
break;
|
|
42
|
+
case 4 /* State.OSC */:
|
|
43
|
+
this.handleOsc(byte);
|
|
44
|
+
break;
|
|
45
|
+
case 5 /* State.DCS */:
|
|
46
|
+
// Consume until ST (ESC \) — simplified: just wait for ESC or BEL
|
|
47
|
+
if (byte === ESC || byte === BEL) {
|
|
48
|
+
this.state = 0 /* State.NORMAL */;
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
case 7 /* State.SS3 */:
|
|
52
|
+
// SS3 sequences: ESC O <final> — used for F1-F4, keypad
|
|
53
|
+
// From the host side these are rare; just consume and ignore
|
|
54
|
+
this.state = 0 /* State.NORMAL */;
|
|
55
|
+
break;
|
|
56
|
+
case 6 /* State.SS2 */:
|
|
57
|
+
this.state = 0 /* State.NORMAL */;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return modified;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// State: NORMAL
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
handleNormal(byte) {
|
|
67
|
+
// Control characters
|
|
68
|
+
if (byte === ESC) {
|
|
69
|
+
this.state = 1 /* State.ESC */;
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (byte < 0x20 || byte === 0x7f) {
|
|
73
|
+
return this.handleControlChar(byte);
|
|
74
|
+
}
|
|
75
|
+
// Printable character — write to screen
|
|
76
|
+
this.screen.writeChar(String.fromCharCode(byte));
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
handleControlChar(byte) {
|
|
80
|
+
switch (byte) {
|
|
81
|
+
case BS: // Backspace
|
|
82
|
+
if (this.screen.cursorCol > 0) {
|
|
83
|
+
this.screen.cursorCol--;
|
|
84
|
+
this.screen.pendingWrap = false;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
case HT: // Horizontal tab
|
|
88
|
+
this.screen.tabForward();
|
|
89
|
+
return false;
|
|
90
|
+
case LF: // Line feed
|
|
91
|
+
case VT: // Vertical tab (treated as LF)
|
|
92
|
+
case FF: // Form feed (treated as LF)
|
|
93
|
+
this.screen.lineFeed();
|
|
94
|
+
return true;
|
|
95
|
+
case CR: // Carriage return
|
|
96
|
+
this.screen.cursorCol = 0;
|
|
97
|
+
this.screen.pendingWrap = false;
|
|
98
|
+
return false;
|
|
99
|
+
case SO: // Shift Out — select G1 charset (simplified: ignore)
|
|
100
|
+
return false;
|
|
101
|
+
case SI: // Shift In — select G0 charset (simplified: ignore)
|
|
102
|
+
return false;
|
|
103
|
+
case BEL: // Bell
|
|
104
|
+
return false;
|
|
105
|
+
default:
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// State: ESC (received ESC byte)
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
handleEsc(byte) {
|
|
113
|
+
switch (byte) {
|
|
114
|
+
case CSI_CHAR: // '[' — Control Sequence Introducer
|
|
115
|
+
this.state = 2 /* State.CSI_PARAM */;
|
|
116
|
+
this.params = '';
|
|
117
|
+
this.intermediates = '';
|
|
118
|
+
this.decPrivate = false;
|
|
119
|
+
return false;
|
|
120
|
+
case 0x5d: // ']' — OSC (Operating System Command)
|
|
121
|
+
this.state = 4 /* State.OSC */;
|
|
122
|
+
this.oscData = '';
|
|
123
|
+
return false;
|
|
124
|
+
case 0x50: // 'P' — DCS (Device Control String)
|
|
125
|
+
this.state = 5 /* State.DCS */;
|
|
126
|
+
return false;
|
|
127
|
+
case 0x4e: // 'N' — SS2 (Single Shift 2)
|
|
128
|
+
this.state = 6 /* State.SS2 */;
|
|
129
|
+
return false;
|
|
130
|
+
case 0x4f: // 'O' — SS3 (Single Shift 3)
|
|
131
|
+
this.state = 7 /* State.SS3 */;
|
|
132
|
+
return false;
|
|
133
|
+
case 0x37: // '7' — DECSC (Save Cursor)
|
|
134
|
+
this.screen.saveCursor();
|
|
135
|
+
this.state = 0 /* State.NORMAL */;
|
|
136
|
+
return false;
|
|
137
|
+
case 0x38: // '8' — DECRC (Restore Cursor)
|
|
138
|
+
this.screen.restoreCursor();
|
|
139
|
+
this.state = 0 /* State.NORMAL */;
|
|
140
|
+
return false;
|
|
141
|
+
case 0x44: // 'D' — IND (Index / line feed)
|
|
142
|
+
this.screen.lineFeed();
|
|
143
|
+
this.state = 0 /* State.NORMAL */;
|
|
144
|
+
return true;
|
|
145
|
+
case 0x4d: // 'M' — RI (Reverse Index / reverse line feed)
|
|
146
|
+
this.screen.reverseLineFeed();
|
|
147
|
+
this.state = 0 /* State.NORMAL */;
|
|
148
|
+
return true;
|
|
149
|
+
case 0x45: // 'E' — NEL (Next Line = CR + LF)
|
|
150
|
+
this.screen.cursorCol = 0;
|
|
151
|
+
this.screen.lineFeed();
|
|
152
|
+
this.state = 0 /* State.NORMAL */;
|
|
153
|
+
return true;
|
|
154
|
+
case 0x48: // 'H' — HTS (Horizontal Tab Set) — simplified: ignore
|
|
155
|
+
this.state = 0 /* State.NORMAL */;
|
|
156
|
+
return false;
|
|
157
|
+
case 0x63: // 'c' — RIS (Reset to Initial State)
|
|
158
|
+
this.screen.reset();
|
|
159
|
+
this.state = 0 /* State.NORMAL */;
|
|
160
|
+
return true;
|
|
161
|
+
case 0x5c: // '\' — ST (String Terminator) — end of DCS/OSC
|
|
162
|
+
this.state = 0 /* State.NORMAL */;
|
|
163
|
+
return false;
|
|
164
|
+
case 0x28: // '(' — Designate G0 character set — consume next byte
|
|
165
|
+
case 0x29: // ')' — Designate G1 character set — consume next byte
|
|
166
|
+
case 0x2a: // '*' — Designate G2 character set
|
|
167
|
+
case 0x2b: // '+' — Designate G3 character set
|
|
168
|
+
// Next byte is the charset designator (B, 0, etc.) — ignore it
|
|
169
|
+
// We stay in a pseudo-state; simplification: just consume one more byte
|
|
170
|
+
// by not changing state. Actually we need to consume the next byte.
|
|
171
|
+
// Use a trick: stay in ESC state for one more byte? No — just go NORMAL
|
|
172
|
+
// and accept that we may misinterpret one character. For correctness,
|
|
173
|
+
// we handle it by noting this is a 3-byte sequence.
|
|
174
|
+
this.state = 0 /* State.NORMAL */; // next byte will be consumed as normal char
|
|
175
|
+
// This is a slight inaccuracy but charset switching is rarely
|
|
176
|
+
// semantically important for screen scraping.
|
|
177
|
+
return false;
|
|
178
|
+
default:
|
|
179
|
+
// Unknown ESC sequence — return to NORMAL
|
|
180
|
+
this.state = 0 /* State.NORMAL */;
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// State: CSI (parsing CSI parameters and executing)
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
handleCsi(byte) {
|
|
188
|
+
// Check for '?' prefix (DEC private mode)
|
|
189
|
+
if (byte === 0x3f && this.params === '' && !this.decPrivate) { // '?'
|
|
190
|
+
this.decPrivate = true;
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
// Parameter bytes: 0x30-0x3F (digits, semicolons, etc.)
|
|
194
|
+
if (byte >= 0x30 && byte <= 0x3f) {
|
|
195
|
+
this.params += String.fromCharCode(byte);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
// Intermediate bytes: 0x20-0x2F
|
|
199
|
+
if (byte >= 0x20 && byte <= 0x2f) {
|
|
200
|
+
this.intermediates += String.fromCharCode(byte);
|
|
201
|
+
this.state = 3 /* State.CSI_INTER */;
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
// Final byte: 0x40-0x7E — execute the sequence
|
|
205
|
+
if (byte >= 0x40 && byte <= 0x7e) {
|
|
206
|
+
const result = this.executeCsi(byte);
|
|
207
|
+
this.state = 0 /* State.NORMAL */;
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
// Invalid byte — abort
|
|
211
|
+
this.state = 0 /* State.NORMAL */;
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
/** Parse CSI parameters as an array of numbers (default values handled per command) */
|
|
215
|
+
parseParams() {
|
|
216
|
+
if (this.params === '')
|
|
217
|
+
return [];
|
|
218
|
+
return this.params.split(';').map((s) => {
|
|
219
|
+
const n = parseInt(s, 10);
|
|
220
|
+
return isNaN(n) ? 0 : n;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
executeCsi(finalByte) {
|
|
224
|
+
const params = this.parseParams();
|
|
225
|
+
const p1 = params[0] || 0;
|
|
226
|
+
const finalChar = String.fromCharCode(finalByte);
|
|
227
|
+
// Handle intermediate bytes (e.g., space for some sequences)
|
|
228
|
+
if (this.intermediates.length > 0) {
|
|
229
|
+
// CSI ? ... h/l with intermediates, or CSI ... SP q (cursor style), etc.
|
|
230
|
+
// Mostly safe to ignore for screen-scraping purposes.
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (this.decPrivate) {
|
|
234
|
+
return this.executeDecPrivate(finalChar, params);
|
|
235
|
+
}
|
|
236
|
+
switch (finalChar) {
|
|
237
|
+
// ------- Cursor movement -------
|
|
238
|
+
case 'A': // CUU — Cursor Up
|
|
239
|
+
this.screen.setCursor(this.screen.cursorRow - Math.max(p1, 1), this.screen.cursorCol);
|
|
240
|
+
return false;
|
|
241
|
+
case 'B': // CUD — Cursor Down
|
|
242
|
+
this.screen.setCursor(this.screen.cursorRow + Math.max(p1, 1), this.screen.cursorCol);
|
|
243
|
+
return false;
|
|
244
|
+
case 'C': // CUF — Cursor Forward
|
|
245
|
+
this.screen.setCursor(this.screen.cursorRow, this.screen.cursorCol + Math.max(p1, 1));
|
|
246
|
+
return false;
|
|
247
|
+
case 'D': // CUB — Cursor Backward
|
|
248
|
+
this.screen.setCursor(this.screen.cursorRow, this.screen.cursorCol - Math.max(p1, 1));
|
|
249
|
+
return false;
|
|
250
|
+
case 'E': // CNL — Cursor Next Line
|
|
251
|
+
this.screen.setCursor(this.screen.cursorRow + Math.max(p1, 1), 0);
|
|
252
|
+
return false;
|
|
253
|
+
case 'F': // CPL — Cursor Previous Line
|
|
254
|
+
this.screen.setCursor(this.screen.cursorRow - Math.max(p1, 1), 0);
|
|
255
|
+
return false;
|
|
256
|
+
case 'G': // CHA — Cursor Horizontal Absolute
|
|
257
|
+
this.screen.setCursor(this.screen.cursorRow, Math.max(p1, 1) - 1);
|
|
258
|
+
return false;
|
|
259
|
+
case 'H': // CUP — Cursor Position
|
|
260
|
+
case 'f': // HVP — Horizontal and Vertical Position (same as CUP)
|
|
261
|
+
{
|
|
262
|
+
const row = (params[0] || 1) - 1;
|
|
263
|
+
const col = (params[1] || 1) - 1;
|
|
264
|
+
this.screen.setCursor(row, col);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
case 'd': // VPA — Vertical Position Absolute
|
|
268
|
+
this.screen.setCursor(Math.max(p1, 1) - 1, this.screen.cursorCol);
|
|
269
|
+
return false;
|
|
270
|
+
// ------- Erase -------
|
|
271
|
+
case 'J': // ED — Erase in Display
|
|
272
|
+
this.screen.eraseInDisplay(p1);
|
|
273
|
+
return true;
|
|
274
|
+
case 'K': // EL — Erase in Line
|
|
275
|
+
this.screen.eraseInLine(p1);
|
|
276
|
+
return true;
|
|
277
|
+
case 'X': // ECH — Erase Characters
|
|
278
|
+
this.screen.eraseCharacters(Math.max(p1, 1));
|
|
279
|
+
return true;
|
|
280
|
+
// ------- Insert / Delete -------
|
|
281
|
+
case 'L': // IL — Insert Lines
|
|
282
|
+
this.screen.insertLines(Math.max(p1, 1));
|
|
283
|
+
return true;
|
|
284
|
+
case 'M': // DL — Delete Lines
|
|
285
|
+
this.screen.deleteLines(Math.max(p1, 1));
|
|
286
|
+
return true;
|
|
287
|
+
case '@': // ICH — Insert Characters
|
|
288
|
+
this.screen.insertCharacters(Math.max(p1, 1));
|
|
289
|
+
return true;
|
|
290
|
+
case 'P': // DCH — Delete Characters
|
|
291
|
+
this.screen.deleteCharacters(Math.max(p1, 1));
|
|
292
|
+
return true;
|
|
293
|
+
// ------- Scrolling -------
|
|
294
|
+
case 'S': // SU — Scroll Up
|
|
295
|
+
this.screen.scrollUp(Math.max(p1, 1));
|
|
296
|
+
return true;
|
|
297
|
+
case 'T': // SD — Scroll Down
|
|
298
|
+
this.screen.scrollDown(Math.max(p1, 1));
|
|
299
|
+
return true;
|
|
300
|
+
// ------- Attributes -------
|
|
301
|
+
case 'm': // SGR — Select Graphic Rendition
|
|
302
|
+
this.executeSgr(params);
|
|
303
|
+
return false;
|
|
304
|
+
// ------- Scroll region -------
|
|
305
|
+
case 'r': // DECSTBM — Set Top and Bottom Margins
|
|
306
|
+
{
|
|
307
|
+
const top = (params[0] || 1) - 1;
|
|
308
|
+
const bottom = (params[1] || this.screen.rows) - 1;
|
|
309
|
+
this.screen.scrollTop = Math.max(0, Math.min(top, this.screen.rows - 1));
|
|
310
|
+
this.screen.scrollBottom = Math.max(0, Math.min(bottom, this.screen.rows - 1));
|
|
311
|
+
// CUP to home after DECSTBM
|
|
312
|
+
this.screen.setCursor(0, 0);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
// ------- Mode set/reset -------
|
|
316
|
+
case 'h': // SM — Set Mode
|
|
317
|
+
// Standard modes (non-DEC private) — most are irrelevant for screen scraping
|
|
318
|
+
return false;
|
|
319
|
+
case 'l': // RM — Reset Mode
|
|
320
|
+
return false;
|
|
321
|
+
// ------- Device status -------
|
|
322
|
+
case 'n': // DSR — Device Status Report
|
|
323
|
+
// 6 = CPR (Cursor Position Report) — we'd need to send response
|
|
324
|
+
// For now, ignore
|
|
325
|
+
return false;
|
|
326
|
+
case 'c': // DA — Device Attributes
|
|
327
|
+
// Ignore — we'd need to send a response
|
|
328
|
+
return false;
|
|
329
|
+
// ------- Tab -------
|
|
330
|
+
case 'I': // CHT — Cursor Horizontal Tab (forward n tabs)
|
|
331
|
+
{
|
|
332
|
+
const count = Math.max(p1, 1);
|
|
333
|
+
for (let t = 0; t < count; t++)
|
|
334
|
+
this.screen.tabForward();
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
case 'g': // TBC — Tab Clear — ignore
|
|
338
|
+
return false;
|
|
339
|
+
default:
|
|
340
|
+
// Unknown CSI sequence — ignore
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** Handle DEC private mode sequences (CSI ? ... h/l) */
|
|
345
|
+
executeDecPrivate(finalChar, params) {
|
|
346
|
+
const mode = params[0] || 0;
|
|
347
|
+
switch (finalChar) {
|
|
348
|
+
case 'h': // DECSET — Set DEC private mode
|
|
349
|
+
switch (mode) {
|
|
350
|
+
case 1: // DECCKM — Application cursor keys (affects what arrow keys send)
|
|
351
|
+
// Tracked but no screen effect
|
|
352
|
+
return false;
|
|
353
|
+
case 6: // DECOM — Origin mode
|
|
354
|
+
this.screen.originMode = true;
|
|
355
|
+
this.screen.setCursor(0, 0);
|
|
356
|
+
return false;
|
|
357
|
+
case 7: // DECAWM — Auto wrap mode
|
|
358
|
+
this.screen.autoWrap = true;
|
|
359
|
+
return false;
|
|
360
|
+
case 25: // DECTCEM — Show cursor (no visual effect in our buffer)
|
|
361
|
+
return false;
|
|
362
|
+
case 1049: // Save cursor + switch to alternate screen buffer
|
|
363
|
+
this.screen.saveCursor();
|
|
364
|
+
this.screen.eraseInDisplay(2);
|
|
365
|
+
return true;
|
|
366
|
+
case 47:
|
|
367
|
+
case 1047: // Alternate screen buffer
|
|
368
|
+
this.screen.eraseInDisplay(2);
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
case 'l': // DECRST — Reset DEC private mode
|
|
373
|
+
switch (mode) {
|
|
374
|
+
case 1: // DECCKM — Normal cursor keys
|
|
375
|
+
return false;
|
|
376
|
+
case 6: // DECOM — Origin mode off
|
|
377
|
+
this.screen.originMode = false;
|
|
378
|
+
this.screen.setCursor(0, 0);
|
|
379
|
+
return false;
|
|
380
|
+
case 7: // DECAWM — Auto wrap off
|
|
381
|
+
this.screen.autoWrap = false;
|
|
382
|
+
return false;
|
|
383
|
+
case 25: // DECTCEM — Hide cursor
|
|
384
|
+
return false;
|
|
385
|
+
case 1049: // Restore cursor + switch from alternate screen
|
|
386
|
+
this.screen.restoreCursor();
|
|
387
|
+
return true;
|
|
388
|
+
case 47:
|
|
389
|
+
case 1047:
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
default:
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// SGR — Select Graphic Rendition
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
executeSgr(params) {
|
|
401
|
+
// If no params, treat as reset
|
|
402
|
+
if (params.length === 0)
|
|
403
|
+
params = [0];
|
|
404
|
+
const attrs = this.screen.currentAttrs;
|
|
405
|
+
for (let i = 0; i < params.length; i++) {
|
|
406
|
+
const p = params[i];
|
|
407
|
+
switch (p) {
|
|
408
|
+
case SGR.RESET:
|
|
409
|
+
Object.assign(attrs, defaultAttrs());
|
|
410
|
+
break;
|
|
411
|
+
case SGR.BOLD:
|
|
412
|
+
attrs.bold = true;
|
|
413
|
+
break;
|
|
414
|
+
case SGR.DIM:
|
|
415
|
+
attrs.dim = true;
|
|
416
|
+
break;
|
|
417
|
+
case SGR.ITALIC:
|
|
418
|
+
attrs.italic = true;
|
|
419
|
+
break;
|
|
420
|
+
case SGR.UNDERLINE:
|
|
421
|
+
attrs.underline = true;
|
|
422
|
+
break;
|
|
423
|
+
case SGR.BLINK:
|
|
424
|
+
case SGR.RAPID_BLINK:
|
|
425
|
+
attrs.blink = true;
|
|
426
|
+
break;
|
|
427
|
+
case SGR.REVERSE:
|
|
428
|
+
attrs.reverse = true;
|
|
429
|
+
break;
|
|
430
|
+
case SGR.HIDDEN:
|
|
431
|
+
attrs.hidden = true;
|
|
432
|
+
break;
|
|
433
|
+
case SGR.STRIKETHROUGH:
|
|
434
|
+
attrs.strikethrough = true;
|
|
435
|
+
break;
|
|
436
|
+
case SGR.NORMAL_INTENSITY:
|
|
437
|
+
attrs.bold = false;
|
|
438
|
+
attrs.dim = false;
|
|
439
|
+
break;
|
|
440
|
+
case SGR.NO_ITALIC:
|
|
441
|
+
attrs.italic = false;
|
|
442
|
+
break;
|
|
443
|
+
case SGR.NO_UNDERLINE:
|
|
444
|
+
attrs.underline = false;
|
|
445
|
+
break;
|
|
446
|
+
case SGR.NO_BLINK:
|
|
447
|
+
attrs.blink = false;
|
|
448
|
+
break;
|
|
449
|
+
case SGR.NO_REVERSE:
|
|
450
|
+
attrs.reverse = false;
|
|
451
|
+
break;
|
|
452
|
+
case SGR.NO_HIDDEN:
|
|
453
|
+
attrs.hidden = false;
|
|
454
|
+
break;
|
|
455
|
+
case SGR.NO_STRIKETHROUGH:
|
|
456
|
+
attrs.strikethrough = false;
|
|
457
|
+
break;
|
|
458
|
+
default:
|
|
459
|
+
// Foreground colors
|
|
460
|
+
if (p >= 30 && p <= 37) {
|
|
461
|
+
attrs.fg = p - 30;
|
|
462
|
+
}
|
|
463
|
+
else if (p === 39) {
|
|
464
|
+
attrs.fg = 8; // default
|
|
465
|
+
}
|
|
466
|
+
// Background colors
|
|
467
|
+
else if (p >= 40 && p <= 47) {
|
|
468
|
+
attrs.bg = p - 40;
|
|
469
|
+
}
|
|
470
|
+
else if (p === 49) {
|
|
471
|
+
attrs.bg = 8; // default
|
|
472
|
+
}
|
|
473
|
+
// Bright foreground
|
|
474
|
+
else if (p >= 90 && p <= 97) {
|
|
475
|
+
attrs.fg = p - 90; // Map to 0-7 (bright handled by bold in classic VT)
|
|
476
|
+
attrs.bold = true;
|
|
477
|
+
}
|
|
478
|
+
// Bright background
|
|
479
|
+
else if (p >= 100 && p <= 107) {
|
|
480
|
+
attrs.bg = p - 100;
|
|
481
|
+
}
|
|
482
|
+
// 256-color / truecolor: CSI 38;5;n m or CSI 38;2;r;g;b m
|
|
483
|
+
else if (p === 38) {
|
|
484
|
+
if (i + 1 < params.length && params[i + 1] === 5) {
|
|
485
|
+
// 256-color foreground — map to basic 8 if possible
|
|
486
|
+
if (i + 2 < params.length) {
|
|
487
|
+
const color = params[i + 2];
|
|
488
|
+
attrs.fg = color < 8 ? color : 8;
|
|
489
|
+
i += 2;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else if (i + 1 < params.length && params[i + 1] === 2) {
|
|
493
|
+
// Truecolor — skip r,g,b
|
|
494
|
+
i += 4;
|
|
495
|
+
attrs.fg = 8;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else if (p === 48) {
|
|
499
|
+
if (i + 1 < params.length && params[i + 1] === 5) {
|
|
500
|
+
if (i + 2 < params.length) {
|
|
501
|
+
const color = params[i + 2];
|
|
502
|
+
attrs.bg = color < 8 ? color : 8;
|
|
503
|
+
i += 2;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else if (i + 1 < params.length && params[i + 1] === 2) {
|
|
507
|
+
i += 4;
|
|
508
|
+
attrs.bg = 8;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// ---------------------------------------------------------------------------
|
|
516
|
+
// State: OSC
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
handleOsc(byte) {
|
|
519
|
+
// OSC terminated by BEL (0x07) or ST (ESC \)
|
|
520
|
+
if (byte === BEL) {
|
|
521
|
+
// OSC complete — we ignore OSC data (window titles, etc.)
|
|
522
|
+
this.state = 0 /* State.NORMAL */;
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (byte === ESC) {
|
|
526
|
+
// Could be start of ST (ESC \) — next byte should be '\'
|
|
527
|
+
// Simplified: just go to NORMAL; the '\' will be consumed harmlessly
|
|
528
|
+
this.state = 0 /* State.NORMAL */;
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
// Accumulate (ignored but consumed)
|
|
532
|
+
this.oscData += String.fromCharCode(byte);
|
|
533
|
+
}
|
|
534
|
+
}
|