green-screen-proxy 1.1.1 → 1.2.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/README.md +93 -8
- package/dist/cli.js +0 -6
- package/dist/cli.js.map +1 -1
- package/dist/controller.d.ts +3 -0
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +14 -5
- package/dist/controller.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -16
- package/dist/index.js.map +1 -1
- package/dist/protocols/tn3270-handler.d.ts +2 -1
- package/dist/protocols/tn3270-handler.d.ts.map +1 -1
- package/dist/protocols/tn3270-handler.js +10 -2
- package/dist/protocols/tn3270-handler.js.map +1 -1
- package/dist/protocols/tn5250-handler.d.ts +3 -2
- package/dist/protocols/tn5250-handler.d.ts.map +1 -1
- package/dist/protocols/tn5250-handler.js +85 -26
- package/dist/protocols/tn5250-handler.js.map +1 -1
- package/dist/protocols/types.d.ts +30 -2
- package/dist/protocols/types.d.ts.map +1 -1
- package/dist/protocols/types.js +32 -0
- package/dist/protocols/types.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +261 -30
- package/dist/routes.js.map +1 -1
- package/dist/server.js +1 -5
- package/dist/server.js.map +1 -1
- package/dist/session-store.d.ts +68 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +40 -0
- package/dist/session-store.js.map +1 -0
- package/dist/session.d.ts +32 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +105 -9
- package/dist/session.js.map +1 -1
- package/dist/standalone.d.ts +3 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +6 -0
- package/dist/standalone.js.map +1 -0
- package/dist/tn3270/connection.d.ts +1 -1
- package/dist/tn3270/connection.d.ts.map +1 -1
- package/dist/tn3270/connection.js +2 -2
- package/dist/tn3270/connection.js.map +1 -1
- package/dist/tn3270/screen.d.ts +1 -0
- package/dist/tn3270/screen.d.ts.map +1 -1
- package/dist/tn3270/screen.js +1 -0
- package/dist/tn3270/screen.js.map +1 -1
- package/dist/tn5250/connection.d.ts +6 -1
- package/dist/tn5250/connection.d.ts.map +1 -1
- package/dist/tn5250/connection.js +27 -3
- package/dist/tn5250/connection.js.map +1 -1
- package/dist/tn5250/constants.d.ts +39 -3
- package/dist/tn5250/constants.d.ts.map +1 -1
- package/dist/tn5250/constants.js +51 -3
- package/dist/tn5250/constants.js.map +1 -1
- package/dist/tn5250/ebcdic-jp-builtin.d.ts +45 -0
- package/dist/tn5250/ebcdic-jp-builtin.d.ts.map +1 -0
- package/dist/tn5250/ebcdic-jp-builtin.js +124 -0
- package/dist/tn5250/ebcdic-jp-builtin.js.map +1 -0
- package/dist/tn5250/ebcdic-jp.d.ts +61 -0
- package/dist/tn5250/ebcdic-jp.d.ts.map +1 -0
- package/dist/tn5250/ebcdic-jp.js +188 -0
- package/dist/tn5250/ebcdic-jp.js.map +1 -0
- package/dist/tn5250/ebcdic.d.ts +13 -4
- package/dist/tn5250/ebcdic.d.ts.map +1 -1
- package/dist/tn5250/ebcdic.js +30 -8
- package/dist/tn5250/ebcdic.js.map +1 -1
- package/dist/tn5250/encoder.d.ts +41 -9
- package/dist/tn5250/encoder.d.ts.map +1 -1
- package/dist/tn5250/encoder.js +228 -41
- package/dist/tn5250/encoder.js.map +1 -1
- package/dist/tn5250/parser.d.ts +14 -0
- package/dist/tn5250/parser.d.ts.map +1 -1
- package/dist/tn5250/parser.js +428 -53
- package/dist/tn5250/parser.js.map +1 -1
- package/dist/tn5250/screen.d.ts +144 -24
- package/dist/tn5250/screen.d.ts.map +1 -1
- package/dist/tn5250/screen.js +245 -13
- package/dist/tn5250/screen.js.map +1 -1
- package/dist/ui/assets/index-B51sr7HL.js +56 -0
- package/dist/ui/assets/index-B9wpEWAh.css +1 -0
- package/dist/ui/assets/index-BrUnECmE.css +1 -0
- package/dist/ui/assets/index-CDBbEXbH.js +56 -0
- package/dist/ui/index.html +16 -0
- package/dist/websocket.d.ts +3 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +90 -1
- package/dist/websocket.js.map +1 -1
- package/dist/worker/index.js +5573 -0
- package/package.json +1 -1
package/dist/tn5250/parser.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CMD, ORDER, OPCODE, ATTR, WDSF_TYPE, WDSF_CLASS } from './constants.js';
|
|
2
2
|
import { ebcdicToChar, ebcdicSymbolChar } from './ebcdic.js';
|
|
3
|
+
import { SI, SO, decodeDbcsPair } from './ebcdic-jp.js';
|
|
3
4
|
/**
|
|
4
5
|
* Parses 5250 data stream records and updates the screen buffer.
|
|
5
6
|
*/
|
|
@@ -103,8 +104,10 @@ export class TN5250Parser {
|
|
|
103
104
|
}
|
|
104
105
|
/** Try to parse data that doesn't have a proper GDS header */
|
|
105
106
|
tryParseRawData(record) {
|
|
106
|
-
// Some servers send command data without the full GDS wrapper
|
|
107
|
-
|
|
107
|
+
// Some servers send command data without the full GDS wrapper.
|
|
108
|
+
// Use parseCommandsFromOffset to skip 0x04 escape markers and find
|
|
109
|
+
// the first known command byte — handles non-GDS hosts.
|
|
110
|
+
return this.parseCommandsFromOffset(record, 0);
|
|
108
111
|
}
|
|
109
112
|
/**
|
|
110
113
|
* Handle records with non-standard framing (e.g. opcode 0x04 from
|
|
@@ -138,12 +141,42 @@ export class TN5250Parser {
|
|
|
138
141
|
const cmd = data[pos];
|
|
139
142
|
switch (cmd) {
|
|
140
143
|
case CMD.CLEAR_UNIT:
|
|
144
|
+
// Per lib5250 display.c:1889-1907: resize to 24x80, clear content,
|
|
145
|
+
// lock keyboard, clear pending insert cursor, clear message lines.
|
|
146
|
+
this.screen.resize(24, 80);
|
|
147
|
+
this.screen.keyboardLocked = true;
|
|
148
|
+
this.screen.insertMode = false;
|
|
149
|
+
this.screen.savedCursorBeforeError = null;
|
|
150
|
+
this.screen.windowList = [];
|
|
151
|
+
this.screen.selectionFields = [];
|
|
152
|
+
this.screen.scrollbarList = [];
|
|
153
|
+
this.screen.savedMsgLine = null;
|
|
154
|
+
this.screen.savedMsgLineRow = -1;
|
|
155
|
+
this.screen.headerData = [];
|
|
156
|
+
this.winRowOff = 0;
|
|
157
|
+
this.winColOff = 0;
|
|
158
|
+
pos++;
|
|
159
|
+
modified = true;
|
|
160
|
+
break;
|
|
141
161
|
case CMD.CLEAR_UNIT_ALT:
|
|
142
|
-
|
|
162
|
+
// Per lib5250 display.c:1919-1937: resize to 27x132 and reset state.
|
|
163
|
+
// A flag byte follows (0x00 = alt screen, others reserved).
|
|
164
|
+
this.screen.resize(27, 132);
|
|
165
|
+
this.screen.keyboardLocked = true;
|
|
166
|
+
this.screen.insertMode = false;
|
|
167
|
+
this.screen.savedCursorBeforeError = null;
|
|
143
168
|
this.screen.windowList = [];
|
|
169
|
+
this.screen.selectionFields = [];
|
|
170
|
+
this.screen.scrollbarList = [];
|
|
171
|
+
this.screen.savedMsgLine = null;
|
|
172
|
+
this.screen.savedMsgLineRow = -1;
|
|
173
|
+
this.screen.headerData = [];
|
|
144
174
|
this.winRowOff = 0;
|
|
145
175
|
this.winColOff = 0;
|
|
146
176
|
pos++;
|
|
177
|
+
// Skip the trailing flag byte (per 5250 spec)
|
|
178
|
+
if (pos < data.length)
|
|
179
|
+
pos++;
|
|
147
180
|
modified = true;
|
|
148
181
|
break;
|
|
149
182
|
case CMD.CLEAR_FORMAT_TABLE:
|
|
@@ -229,8 +262,12 @@ export class TN5250Parser {
|
|
|
229
262
|
}
|
|
230
263
|
// Save cursor so Reset can restore it after unlocking
|
|
231
264
|
this.screen.savedCursorBeforeError = { row: this.screen.cursorRow, col: this.screen.cursorCol };
|
|
232
|
-
//
|
|
233
|
-
|
|
265
|
+
// Save the current contents of the message-line row so Reset can
|
|
266
|
+
// restore it (per lib5250 display.c:2489-2502).
|
|
267
|
+
this.screen.saveMsgLine();
|
|
268
|
+
// Message line row is configurable via SOH header byte 3, or the
|
|
269
|
+
// last row by default (per lib5250 dbuffer.c:934-944).
|
|
270
|
+
const msgRow = this.screen.msgLineRow();
|
|
234
271
|
let msgCol = 0;
|
|
235
272
|
let endY = this.screen.cursorRow;
|
|
236
273
|
let endX = this.screen.cursorCol;
|
|
@@ -250,7 +287,7 @@ export class TN5250Parser {
|
|
|
250
287
|
}
|
|
251
288
|
// Printable EBCDIC character → write to message line
|
|
252
289
|
if (c >= 0x40 || c === 0x00) {
|
|
253
|
-
const ch = ebcdicToChar(c);
|
|
290
|
+
const ch = ebcdicToChar(c, this.screen.codePage);
|
|
254
291
|
const addr = this.screen.offset(msgRow, msgCol);
|
|
255
292
|
if (addr < this.screen.size) {
|
|
256
293
|
this.screen.setCharAt(addr, ch);
|
|
@@ -284,6 +321,32 @@ export class TN5250Parser {
|
|
|
284
321
|
}
|
|
285
322
|
break;
|
|
286
323
|
}
|
|
324
|
+
case CMD.READ_INPUT_FIELDS:
|
|
325
|
+
case CMD.READ_MDT_FIELDS:
|
|
326
|
+
case CMD.READ_MDT_FIELDS_ALT:
|
|
327
|
+
case CMD.READ_IMMEDIATE:
|
|
328
|
+
case CMD.READ_IMMEDIATE_ALT: {
|
|
329
|
+
// Per lib5250 session.c:2328-2354 (tn5250_session_read_cmd):
|
|
330
|
+
// Reads 2 CC bytes, handles CC1/CC2, unlocks the keyboard (if
|
|
331
|
+
// normally locked), and sets read_opcode. The CC bytes have the
|
|
332
|
+
// same semantics as WTD CC1/CC2.
|
|
333
|
+
const readOp = cmd;
|
|
334
|
+
pos++;
|
|
335
|
+
if (pos + 1 < data.length) {
|
|
336
|
+
const cc1 = data[pos++];
|
|
337
|
+
const cc2 = data[pos++];
|
|
338
|
+
// Lock keyboard if CC1 non-zero (same as WTD behavior)
|
|
339
|
+
if ((cc1 & 0xE0) !== 0x00) {
|
|
340
|
+
this.screen.keyboardLocked = true;
|
|
341
|
+
}
|
|
342
|
+
this.handleCC2(cc2);
|
|
343
|
+
}
|
|
344
|
+
// Unlock keyboard on Read command — host is requesting input
|
|
345
|
+
this.screen.keyboardLocked = false;
|
|
346
|
+
this.screen.readOpcode = readOp;
|
|
347
|
+
modified = true;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
287
350
|
case CMD.ROLL: {
|
|
288
351
|
// ROLL: 3 bytes — direction, topRow(1-based), bottomRow(1-based)
|
|
289
352
|
// Per lib5250 session.c:1463-1487
|
|
@@ -328,6 +391,49 @@ export class TN5250Parser {
|
|
|
328
391
|
let afterSBA = false; // Track if we just processed an SBA order (field attrs follow SBA)
|
|
329
392
|
let pendingICRow = -1; // IC stores pending cursor position, applied after WTD
|
|
330
393
|
let pendingICCol = -1;
|
|
394
|
+
// Pending extended attributes set by WEA orders; applied to subsequent
|
|
395
|
+
// cells until modified/reset. Per 5250 Functions Reference the ECB
|
|
396
|
+
// values persist across characters until explicitly changed.
|
|
397
|
+
let extColor = 0;
|
|
398
|
+
let extHighlight = 0;
|
|
399
|
+
let extCharSet = 0;
|
|
400
|
+
// DBCS shift state: once SO (0x0E) is seen, read bytes in pairs until
|
|
401
|
+
// SI (0x0F). Per 5250 Functions Reference and IBM i Japanese support.
|
|
402
|
+
let dbcsMode = false;
|
|
403
|
+
let dbcsPending = -1; // first byte of a pair awaiting its companion
|
|
404
|
+
// Helper: build an ExtAttr snapshot, or null if nothing is set.
|
|
405
|
+
const snapExt = () => (extColor || extHighlight || extCharSet)
|
|
406
|
+
? { color: extColor, highlight: extHighlight, charSet: extCharSet }
|
|
407
|
+
: null;
|
|
408
|
+
// Helper: write a single rendered character at currentAddr, applying
|
|
409
|
+
// current attributes and resetting the DBCS continuation flag for SBCS.
|
|
410
|
+
const writeChar = (ch) => {
|
|
411
|
+
if (currentAddr < this.screen.size) {
|
|
412
|
+
this.screen.setCharAt(currentAddr, ch);
|
|
413
|
+
this.screen.setAttrAt(currentAddr, currentAttr);
|
|
414
|
+
this.screen.setExtAttrAt(currentAddr, snapExt());
|
|
415
|
+
this.screen.dbcsCont[currentAddr] = false;
|
|
416
|
+
}
|
|
417
|
+
currentAddr++;
|
|
418
|
+
};
|
|
419
|
+
// Helper: write a DBCS character — glyph in cell N, empty continuation
|
|
420
|
+
// in cell N+1, both flagged as DBCS.
|
|
421
|
+
const writeDbcs = (ch) => {
|
|
422
|
+
if (currentAddr < this.screen.size) {
|
|
423
|
+
this.screen.setCharAt(currentAddr, ch);
|
|
424
|
+
this.screen.setAttrAt(currentAddr, currentAttr);
|
|
425
|
+
this.screen.setExtAttrAt(currentAddr, snapExt());
|
|
426
|
+
this.screen.dbcsCont[currentAddr] = false;
|
|
427
|
+
}
|
|
428
|
+
currentAddr++;
|
|
429
|
+
if (currentAddr < this.screen.size) {
|
|
430
|
+
this.screen.setCharAt(currentAddr, '');
|
|
431
|
+
this.screen.setAttrAt(currentAddr, currentAttr);
|
|
432
|
+
this.screen.setExtAttrAt(currentAddr, snapExt());
|
|
433
|
+
this.screen.dbcsCont[currentAddr] = true;
|
|
434
|
+
}
|
|
435
|
+
currentAddr++;
|
|
436
|
+
};
|
|
331
437
|
while (pos < data.length) {
|
|
332
438
|
const byte = data[pos];
|
|
333
439
|
// Within a WTD, all bytes are orders or EBCDIC data.
|
|
@@ -377,7 +483,10 @@ export class TN5250Parser {
|
|
|
377
483
|
break;
|
|
378
484
|
}
|
|
379
485
|
case ORDER.RA: {
|
|
380
|
-
// Repeat to Address: 3 bytes (row, col, char) — 1-based address
|
|
486
|
+
// Repeat to Address: 3 bytes (row, col, char) — 1-based address.
|
|
487
|
+
// Per lib5250 session.c:2161-2207: fill from current position up to
|
|
488
|
+
// AND INCLUDING the target position (the loop writes the target cell
|
|
489
|
+
// then breaks).
|
|
381
490
|
if (pos + 3 >= data.length)
|
|
382
491
|
return data.length;
|
|
383
492
|
pos++;
|
|
@@ -385,22 +494,21 @@ export class TN5250Parser {
|
|
|
385
494
|
const raCol = data[pos++] - 1 + this.winColOff;
|
|
386
495
|
const charByte = data[pos++];
|
|
387
496
|
const targetAddr = this.screen.offset(raRow, raCol);
|
|
388
|
-
const ch = ebcdicToChar(charByte);
|
|
389
|
-
// lib5250 uses addch which wraps; we fill up to target (inclusive of current pos)
|
|
497
|
+
const ch = ebcdicToChar(charByte, this.screen.codePage);
|
|
390
498
|
if (targetAddr >= currentAddr) {
|
|
391
|
-
while (currentAddr
|
|
499
|
+
while (currentAddr <= targetAddr && currentAddr < this.screen.size) {
|
|
392
500
|
this.screen.setCharAt(currentAddr, ch);
|
|
393
501
|
currentAddr++;
|
|
394
502
|
}
|
|
395
503
|
}
|
|
396
504
|
else {
|
|
397
|
-
// Wrap-around: fill to end of screen, then from start to target
|
|
505
|
+
// Wrap-around: fill to end of screen, then from start to target (inclusive)
|
|
398
506
|
while (currentAddr < this.screen.size) {
|
|
399
507
|
this.screen.setCharAt(currentAddr, ch);
|
|
400
508
|
currentAddr++;
|
|
401
509
|
}
|
|
402
510
|
currentAddr = 0;
|
|
403
|
-
while (currentAddr
|
|
511
|
+
while (currentAddr <= targetAddr && currentAddr < this.screen.size) {
|
|
404
512
|
this.screen.setCharAt(currentAddr, ch);
|
|
405
513
|
currentAddr++;
|
|
406
514
|
}
|
|
@@ -408,34 +516,53 @@ export class TN5250Parser {
|
|
|
408
516
|
break;
|
|
409
517
|
}
|
|
410
518
|
case ORDER.EA: {
|
|
411
|
-
// Erase to Address:
|
|
412
|
-
// Per
|
|
413
|
-
//
|
|
414
|
-
|
|
519
|
+
// Erase to Address: row, col, length, then (length-1) attribute type
|
|
520
|
+
// bytes. Per lib5250 session.c:2088-2148 — length is in [2,5]; the
|
|
521
|
+
// attribute list selects which kinds of attributes to erase (0xFF =
|
|
522
|
+
// all). Region is inclusive of the target address.
|
|
523
|
+
if (pos + 3 >= data.length)
|
|
415
524
|
return data.length;
|
|
416
525
|
pos++;
|
|
417
526
|
const eaRow = data[pos++] - 1 + this.winRowOff;
|
|
418
527
|
const eaCol = data[pos++] - 1 + this.winColOff;
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
528
|
+
const eaLength = data[pos++];
|
|
529
|
+
// Consume (length-1) attribute bytes
|
|
530
|
+
const attrCount = Math.max(0, eaLength - 1);
|
|
531
|
+
let eraseAll = false;
|
|
532
|
+
for (let i = 0; i < attrCount && pos < data.length; i++) {
|
|
533
|
+
if (data[pos] === 0xFF)
|
|
534
|
+
eraseAll = true;
|
|
535
|
+
pos++;
|
|
425
536
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
537
|
+
// lib5250 only erases characters when attribute 0xFF is present
|
|
538
|
+
// (we don't track extended attributes separately). Default on any
|
|
539
|
+
// attribute list so the region is cleared — safe for typical hosts.
|
|
540
|
+
if (eraseAll || attrCount === 0) {
|
|
541
|
+
const eaTarget = this.screen.offset(eaRow, eaCol);
|
|
542
|
+
if (eaTarget >= currentAddr) {
|
|
543
|
+
while (currentAddr <= eaTarget && currentAddr < this.screen.size) {
|
|
544
|
+
this.screen.setCharAt(currentAddr, ' ');
|
|
545
|
+
currentAddr++;
|
|
546
|
+
}
|
|
431
547
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
this.screen.
|
|
435
|
-
|
|
548
|
+
else {
|
|
549
|
+
// Wrap-around
|
|
550
|
+
while (currentAddr < this.screen.size) {
|
|
551
|
+
this.screen.setCharAt(currentAddr, ' ');
|
|
552
|
+
currentAddr++;
|
|
553
|
+
}
|
|
554
|
+
currentAddr = 0;
|
|
555
|
+
while (currentAddr <= eaTarget && currentAddr < this.screen.size) {
|
|
556
|
+
this.screen.setCharAt(currentAddr, ' ');
|
|
557
|
+
currentAddr++;
|
|
558
|
+
}
|
|
436
559
|
}
|
|
560
|
+
// Per lib5250: EA sets the current display address to target+1
|
|
561
|
+
// (wrapping if at screen boundary).
|
|
562
|
+
currentAddr = eaTarget + 1;
|
|
563
|
+
if (currentAddr >= this.screen.size)
|
|
564
|
+
currentAddr = 0;
|
|
437
565
|
}
|
|
438
|
-
currentAddr = eaTarget;
|
|
439
566
|
break;
|
|
440
567
|
}
|
|
441
568
|
case ORDER.SOH: {
|
|
@@ -452,7 +579,11 @@ export class TN5250Parser {
|
|
|
452
579
|
pendingICRow = -1;
|
|
453
580
|
pendingICCol = -1;
|
|
454
581
|
const hdrLen = data[pos++];
|
|
455
|
-
|
|
582
|
+
// Per lib5250 dbuffer.c:167-178: copy header bytes for later use
|
|
583
|
+
// (byte 3 carries the error message line row).
|
|
584
|
+
const safeLen = Math.max(0, Math.min(hdrLen, data.length - pos));
|
|
585
|
+
this.screen.headerData = Array.from(data.subarray(pos, pos + safeLen));
|
|
586
|
+
pos += safeLen;
|
|
456
587
|
break;
|
|
457
588
|
}
|
|
458
589
|
case ORDER.TD: {
|
|
@@ -464,19 +595,42 @@ export class TN5250Parser {
|
|
|
464
595
|
const tdLen = (data[pos] << 8) | data[pos + 1];
|
|
465
596
|
pos += 2;
|
|
466
597
|
for (let i = 0; i < tdLen && pos < data.length; i++) {
|
|
467
|
-
this.screen.setCharAt(currentAddr++, ebcdicToChar(data[pos++]));
|
|
598
|
+
this.screen.setCharAt(currentAddr++, ebcdicToChar(data[pos++], this.screen.codePage));
|
|
468
599
|
}
|
|
469
600
|
break;
|
|
470
601
|
}
|
|
471
602
|
case ORDER.WEA: {
|
|
472
|
-
// Write Extended Attribute: 2 bytes (attr type + value)
|
|
603
|
+
// Write Extended Attribute: 2 bytes (attr type + value).
|
|
604
|
+
// Per 5250 Functions Reference:
|
|
605
|
+
// type 0x01 — extended color
|
|
606
|
+
// type 0x02 — extended highlighting (underscore/blink/reverse/col-sep)
|
|
607
|
+
// type 0x03 — character set (CGCS id)
|
|
608
|
+
// type 0x04 — transparency / field outlining
|
|
609
|
+
// type 0xFF with value 0xFF — reset all extended attributes
|
|
610
|
+
// Extended attributes persist for subsequent characters until changed.
|
|
473
611
|
if (pos + 2 >= data.length)
|
|
474
612
|
return data.length;
|
|
475
613
|
pos++;
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
614
|
+
const extType = data[pos++];
|
|
615
|
+
const extValue = data[pos++];
|
|
616
|
+
switch (extType) {
|
|
617
|
+
case 0x01:
|
|
618
|
+
extColor = extValue;
|
|
619
|
+
break;
|
|
620
|
+
case 0x02:
|
|
621
|
+
extHighlight = extValue;
|
|
622
|
+
break;
|
|
623
|
+
case 0x03:
|
|
624
|
+
extCharSet = extValue;
|
|
625
|
+
break;
|
|
626
|
+
case 0xFF:
|
|
627
|
+
if (extValue === 0xFF) {
|
|
628
|
+
extColor = 0;
|
|
629
|
+
extHighlight = 0;
|
|
630
|
+
extCharSet = 0;
|
|
631
|
+
}
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
480
634
|
// Preserve afterSBA — WEA can appear between SBA and a field attribute
|
|
481
635
|
continue;
|
|
482
636
|
}
|
|
@@ -524,15 +678,43 @@ export class TN5250Parser {
|
|
|
524
678
|
case WDSF_TYPE.DEFINE_SELECTION_FIELD:
|
|
525
679
|
this.parseDefineSelectionField(data, pos, wdsfEnd);
|
|
526
680
|
break;
|
|
527
|
-
case WDSF_TYPE.
|
|
528
|
-
|
|
681
|
+
case WDSF_TYPE.DEFINE_SCROLL_BAR:
|
|
682
|
+
this.parseDefineScrollbar(data, pos, wdsfEnd);
|
|
683
|
+
break;
|
|
684
|
+
case WDSF_TYPE.REM_GUI_WINDOW: {
|
|
685
|
+
// Per lib5250 session.c:3465-3505: find the window containing
|
|
686
|
+
// the cursor (hit-test) and remove it, not the last-created.
|
|
687
|
+
// Note: flagbyte1, flagbyte2, reserved are read but unused.
|
|
688
|
+
const cy = this.screen.cursorRow;
|
|
689
|
+
const cx = this.screen.cursorCol;
|
|
690
|
+
const idx = this.screen.windowList.findIndex(w => cy >= w.row && cy <= w.row + w.height &&
|
|
691
|
+
cx >= w.col && cx <= w.col + w.width);
|
|
692
|
+
if (idx >= 0) {
|
|
693
|
+
this.screen.windowList.splice(idx, 1);
|
|
694
|
+
}
|
|
695
|
+
else if (this.screen.windowList.length > 0) {
|
|
696
|
+
// Fallback: remove the top (last) window
|
|
529
697
|
this.screen.windowList.pop();
|
|
530
698
|
}
|
|
531
699
|
this.winRowOff = 0;
|
|
532
700
|
this.winColOff = 0;
|
|
533
701
|
break;
|
|
702
|
+
}
|
|
703
|
+
case WDSF_TYPE.REM_GUI_SEL_FIELD:
|
|
704
|
+
// Per lib5250 session.c:3095-3116: destroys all menubars /
|
|
705
|
+
// selection fields.
|
|
706
|
+
this.screen.selectionFields = [];
|
|
707
|
+
break;
|
|
708
|
+
case WDSF_TYPE.REM_GUI_SCROLL_BAR:
|
|
709
|
+
// Per lib5250 stream handling: clear all scrollbars.
|
|
710
|
+
this.screen.scrollbarList = [];
|
|
711
|
+
break;
|
|
534
712
|
case WDSF_TYPE.REM_ALL_GUI_CONSTRUCTS:
|
|
713
|
+
// Per lib5250 session.c:3518-3562: destroy all windows,
|
|
714
|
+
// scrollbars and selection fields.
|
|
535
715
|
this.screen.windowList = [];
|
|
716
|
+
this.screen.selectionFields = [];
|
|
717
|
+
this.screen.scrollbarList = [];
|
|
536
718
|
this.winRowOff = 0;
|
|
537
719
|
this.winColOff = 0;
|
|
538
720
|
break;
|
|
@@ -557,14 +739,42 @@ export class TN5250Parser {
|
|
|
557
739
|
pos = this.parseFieldAttribute(data, pos, currentAddr, currentAttr);
|
|
558
740
|
currentAddr++; // attribute byte occupies one screen position
|
|
559
741
|
}
|
|
560
|
-
else {
|
|
561
|
-
//
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
742
|
+
else if (byte === SO && !dbcsMode) {
|
|
743
|
+
// Shift-Out: enter DBCS Kanji mode (per IBM i Japanese DBCS/SBCS
|
|
744
|
+
// mixed code pages 930/939). SO itself occupies a screen cell as
|
|
745
|
+
// a space to preserve column alignment (matches IBM PCOMM).
|
|
746
|
+
dbcsMode = true;
|
|
747
|
+
dbcsPending = -1;
|
|
748
|
+
writeChar(' ');
|
|
749
|
+
pos++;
|
|
750
|
+
}
|
|
751
|
+
else if (byte === SI && dbcsMode) {
|
|
752
|
+
// Shift-In: exit DBCS mode, return to SBCS. Any orphan pending
|
|
753
|
+
// byte is discarded (malformed stream). SI also occupies a cell.
|
|
754
|
+
dbcsMode = false;
|
|
755
|
+
dbcsPending = -1;
|
|
756
|
+
writeChar(' ');
|
|
757
|
+
pos++;
|
|
758
|
+
}
|
|
759
|
+
else if (dbcsMode) {
|
|
760
|
+
// Collect DBCS byte pairs. Each pair renders as one full-width
|
|
761
|
+
// glyph occupying two screen cells.
|
|
762
|
+
if (dbcsPending < 0) {
|
|
763
|
+
dbcsPending = byte;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
const glyph = decodeDbcsPair(dbcsPending, byte);
|
|
767
|
+
writeDbcs(glyph);
|
|
768
|
+
dbcsPending = -1;
|
|
566
769
|
}
|
|
567
|
-
|
|
770
|
+
pos++;
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
// Regular EBCDIC character data in the active single-byte code page.
|
|
774
|
+
const ch = useSymbolCharSet
|
|
775
|
+
? ebcdicSymbolChar(byte)
|
|
776
|
+
: ebcdicToChar(byte, this.screen.codePage);
|
|
777
|
+
writeChar(ch);
|
|
568
778
|
pos++;
|
|
569
779
|
}
|
|
570
780
|
break;
|
|
@@ -632,6 +842,30 @@ export class TN5250Parser {
|
|
|
632
842
|
let curByte = data[pos++];
|
|
633
843
|
let ffw1 = 0, ffw2 = 0, fcw1 = 0, fcw2 = 0;
|
|
634
844
|
let inputField = false;
|
|
845
|
+
// Continuation / wordwrap flags collected from FCW pairs
|
|
846
|
+
// (per lib5250 session.c:1617-1641)
|
|
847
|
+
let continuous = false;
|
|
848
|
+
let contFirst = false;
|
|
849
|
+
let contMiddle = false;
|
|
850
|
+
let contLast = false;
|
|
851
|
+
let wordwrap = false;
|
|
852
|
+
// All other FCW metadata (per lib5250 session.c:1577-1661)
|
|
853
|
+
let resequence = 0;
|
|
854
|
+
let progressionId = 0;
|
|
855
|
+
let highlightEntryAttr = 0;
|
|
856
|
+
let transparency = 0;
|
|
857
|
+
let pointerAid = 0;
|
|
858
|
+
let forwardEdge = false;
|
|
859
|
+
let selfCheckMod10 = false;
|
|
860
|
+
let selfCheckMod11 = false;
|
|
861
|
+
let ideographicOnly = false;
|
|
862
|
+
let ideographicData = false;
|
|
863
|
+
let ideographicEither = false;
|
|
864
|
+
let ideographicOpen = false;
|
|
865
|
+
let magstripe = false;
|
|
866
|
+
let lightpen = false;
|
|
867
|
+
let magandlight = false;
|
|
868
|
+
let lightandattn = false;
|
|
635
869
|
if ((curByte & 0xE0) !== 0x20) {
|
|
636
870
|
// Input field: curByte is FFW1
|
|
637
871
|
inputField = true;
|
|
@@ -646,6 +880,63 @@ export class TN5250Parser {
|
|
|
646
880
|
while ((curByte & 0xE0) !== 0x20 && pos < data.length) {
|
|
647
881
|
fcw1 = curByte;
|
|
648
882
|
fcw2 = data[pos++];
|
|
883
|
+
const fcw = (fcw1 << 8) | fcw2;
|
|
884
|
+
// Resequence / cursor progression (per session.c:1577-1579, 1643-1645)
|
|
885
|
+
if (fcw1 === 0x80)
|
|
886
|
+
resequence = fcw2;
|
|
887
|
+
else if (fcw1 === 0x88)
|
|
888
|
+
progressionId = fcw2;
|
|
889
|
+
else if (fcw1 === 0x89)
|
|
890
|
+
highlightEntryAttr = fcw2;
|
|
891
|
+
else if (fcw1 === 0x84)
|
|
892
|
+
transparency = fcw2;
|
|
893
|
+
else if (fcw1 === 0x8A)
|
|
894
|
+
pointerAid = fcw2;
|
|
895
|
+
// Peripheral input (per session.c:1581-1595)
|
|
896
|
+
else if (fcw === 0x8101)
|
|
897
|
+
magstripe = true;
|
|
898
|
+
else if (fcw === 0x8102)
|
|
899
|
+
lightpen = true;
|
|
900
|
+
else if (fcw === 0x8103)
|
|
901
|
+
magandlight = true;
|
|
902
|
+
else if (fcw === 0x8106)
|
|
903
|
+
lightandattn = true;
|
|
904
|
+
// Ideographic / DBCS (per session.c:1597-1611)
|
|
905
|
+
else if (fcw === 0x8200)
|
|
906
|
+
ideographicOnly = true;
|
|
907
|
+
else if (fcw === 0x8220)
|
|
908
|
+
ideographicData = true;
|
|
909
|
+
else if (fcw === 0x8240)
|
|
910
|
+
ideographicEither = true;
|
|
911
|
+
else if (fcw === 0x8280 || fcw === 0x82C0)
|
|
912
|
+
ideographicOpen = true;
|
|
913
|
+
// Forward-edge trigger (per session.c:1617-1619)
|
|
914
|
+
else if (fcw === 0x8501)
|
|
915
|
+
forwardEdge = true;
|
|
916
|
+
// Continuation flags (per session.c:1621-1641)
|
|
917
|
+
else if (fcw === 0x8601) {
|
|
918
|
+
continuous = true;
|
|
919
|
+
contFirst = true;
|
|
920
|
+
}
|
|
921
|
+
else if (fcw === 0x8603) {
|
|
922
|
+
continuous = true;
|
|
923
|
+
contMiddle = true;
|
|
924
|
+
}
|
|
925
|
+
else if (fcw === 0x8602) {
|
|
926
|
+
continuous = true;
|
|
927
|
+
contLast = true;
|
|
928
|
+
}
|
|
929
|
+
else if (fcw === 0x8680) {
|
|
930
|
+
wordwrap = true;
|
|
931
|
+
}
|
|
932
|
+
// Self-check (per session.c:1655-1661)
|
|
933
|
+
else if (fcw === 0xB140)
|
|
934
|
+
selfCheckMod11 = true;
|
|
935
|
+
else if (fcw === 0xB1A0)
|
|
936
|
+
selfCheckMod10 = true;
|
|
937
|
+
// Per C: if this is the "last" with wordwrap flag, drop wordwrap.
|
|
938
|
+
if (fcw === 0x8602 && wordwrap)
|
|
939
|
+
wordwrap = false;
|
|
649
940
|
if (pos >= data.length)
|
|
650
941
|
return pos;
|
|
651
942
|
curByte = data[pos++];
|
|
@@ -674,6 +965,27 @@ export class TN5250Parser {
|
|
|
674
965
|
attribute: fieldDisplayAttr,
|
|
675
966
|
rawAttrByte,
|
|
676
967
|
modified: false,
|
|
968
|
+
continuous: continuous || undefined,
|
|
969
|
+
continuedFirst: contFirst || undefined,
|
|
970
|
+
continuedMiddle: contMiddle || undefined,
|
|
971
|
+
continuedLast: contLast || undefined,
|
|
972
|
+
wordwrap: wordwrap || undefined,
|
|
973
|
+
resequence: resequence || undefined,
|
|
974
|
+
progressionId: progressionId || undefined,
|
|
975
|
+
highlightEntryAttr: highlightEntryAttr || undefined,
|
|
976
|
+
transparency: transparency || undefined,
|
|
977
|
+
pointerAid: pointerAid || undefined,
|
|
978
|
+
forwardEdge: forwardEdge || undefined,
|
|
979
|
+
selfCheckMod10: selfCheckMod10 || undefined,
|
|
980
|
+
selfCheckMod11: selfCheckMod11 || undefined,
|
|
981
|
+
ideographicOnly: ideographicOnly || undefined,
|
|
982
|
+
ideographicData: ideographicData || undefined,
|
|
983
|
+
ideographicEither: ideographicEither || undefined,
|
|
984
|
+
ideographicOpen: ideographicOpen || undefined,
|
|
985
|
+
magstripe: magstripe || undefined,
|
|
986
|
+
lightpen: lightpen || undefined,
|
|
987
|
+
magandlight: magandlight || undefined,
|
|
988
|
+
lightandattn: lightandattn || undefined,
|
|
677
989
|
};
|
|
678
990
|
this.clearStaleFieldsOnce();
|
|
679
991
|
this.screen.fields.push(field);
|
|
@@ -757,7 +1069,14 @@ export class TN5250Parser {
|
|
|
757
1069
|
}
|
|
758
1070
|
const winRow = this.screen.cursorRow;
|
|
759
1071
|
const winCol = this.screen.cursorCol;
|
|
760
|
-
this.screen.windowList.push({
|
|
1072
|
+
this.screen.windowList.push({
|
|
1073
|
+
row: winRow,
|
|
1074
|
+
col: winCol,
|
|
1075
|
+
height: depth,
|
|
1076
|
+
width: width,
|
|
1077
|
+
title: titleText || undefined,
|
|
1078
|
+
footer: footerText || undefined,
|
|
1079
|
+
});
|
|
761
1080
|
// Render border with custom characters from host
|
|
762
1081
|
this.screen.renderWindowBorderCustom(winRow, winCol, depth, width, borderChars, titleText, footerText);
|
|
763
1082
|
// Erase content area inside the border
|
|
@@ -845,6 +1164,56 @@ export class TN5250Parser {
|
|
|
845
1164
|
choices,
|
|
846
1165
|
});
|
|
847
1166
|
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Parse DEFINE_SCROLL_BAR structured field data.
|
|
1169
|
+
* Per lib5250 session.c:3362-3451.
|
|
1170
|
+
*
|
|
1171
|
+
* Payload format (after WDSF class/type):
|
|
1172
|
+
* [flagbyte1] 0x80 = horizontal, else vertical
|
|
1173
|
+
* [reserved]
|
|
1174
|
+
* [totalrowscols1..4] BCD digits (1000s, 100s, 10s, 1s)
|
|
1175
|
+
* [sliderpos1..4] BCD digits (1000s, 100s, 10s, 1s)
|
|
1176
|
+
* [size]
|
|
1177
|
+
*
|
|
1178
|
+
* Position is the current cursor (1-based per lib5250 convention).
|
|
1179
|
+
*/
|
|
1180
|
+
parseDefineScrollbar(data, pos, end) {
|
|
1181
|
+
if (pos + 11 > end)
|
|
1182
|
+
return;
|
|
1183
|
+
const flagbyte1 = data[pos++];
|
|
1184
|
+
pos++; // reserved
|
|
1185
|
+
const totalrowscols = 1000 * data[pos++] +
|
|
1186
|
+
100 * data[pos++] +
|
|
1187
|
+
10 * data[pos++] +
|
|
1188
|
+
data[pos++];
|
|
1189
|
+
const sliderpos = 1000 * data[pos++] +
|
|
1190
|
+
100 * data[pos++] +
|
|
1191
|
+
10 * data[pos++] +
|
|
1192
|
+
data[pos++];
|
|
1193
|
+
const size = data[pos++];
|
|
1194
|
+
const row = this.screen.cursorRow + 1;
|
|
1195
|
+
const col = this.screen.cursorCol + 1;
|
|
1196
|
+
const direction = (flagbyte1 & 0x80) !== 0 ? 1 : 0;
|
|
1197
|
+
// Hit-test: if an existing scrollbar is at the same position, update it
|
|
1198
|
+
// rather than creating a duplicate (per session.c:3385-3391).
|
|
1199
|
+
const existing = this.screen.scrollbarList.find(s => s.row === row && s.col === col);
|
|
1200
|
+
if (existing) {
|
|
1201
|
+
existing.direction = direction;
|
|
1202
|
+
existing.rowscols = totalrowscols;
|
|
1203
|
+
existing.sliderpos = sliderpos;
|
|
1204
|
+
existing.size = size;
|
|
1205
|
+
}
|
|
1206
|
+
else {
|
|
1207
|
+
this.screen.scrollbarList.push({
|
|
1208
|
+
row,
|
|
1209
|
+
col,
|
|
1210
|
+
direction,
|
|
1211
|
+
rowscols: totalrowscols,
|
|
1212
|
+
sliderpos,
|
|
1213
|
+
size,
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
848
1217
|
/**
|
|
849
1218
|
* Roll (scroll) screen buffer rows within [top, bot] by `lines` rows.
|
|
850
1219
|
* Negative = scroll up, positive = scroll down.
|
|
@@ -852,6 +1221,8 @@ export class TN5250Parser {
|
|
|
852
1221
|
*/
|
|
853
1222
|
rollBuffer(top, bot, lines) {
|
|
854
1223
|
const cols = this.screen.cols;
|
|
1224
|
+
const buf = this.screen.buffer;
|
|
1225
|
+
const attr = this.screen.attrBuffer;
|
|
855
1226
|
if (lines < 0) {
|
|
856
1227
|
// Scroll up: move rows upward
|
|
857
1228
|
for (let r = top; r <= bot; r++) {
|
|
@@ -859,7 +1230,8 @@ export class TN5250Parser {
|
|
|
859
1230
|
const dstOff = (r + lines) * cols;
|
|
860
1231
|
const srcOff = r * cols;
|
|
861
1232
|
for (let c = 0; c < cols; c++) {
|
|
862
|
-
|
|
1233
|
+
buf[dstOff + c] = buf[srcOff + c];
|
|
1234
|
+
attr[dstOff + c] = attr[srcOff + c];
|
|
863
1235
|
}
|
|
864
1236
|
}
|
|
865
1237
|
}
|
|
@@ -868,7 +1240,8 @@ export class TN5250Parser {
|
|
|
868
1240
|
if (r >= top) {
|
|
869
1241
|
const off = r * cols;
|
|
870
1242
|
for (let c = 0; c < cols; c++) {
|
|
871
|
-
|
|
1243
|
+
buf[off + c] = ' ';
|
|
1244
|
+
attr[off + c] = ATTR.NORMAL;
|
|
872
1245
|
}
|
|
873
1246
|
}
|
|
874
1247
|
}
|
|
@@ -880,7 +1253,8 @@ export class TN5250Parser {
|
|
|
880
1253
|
const dstOff = (r + lines) * cols;
|
|
881
1254
|
const srcOff = r * cols;
|
|
882
1255
|
for (let c = 0; c < cols; c++) {
|
|
883
|
-
|
|
1256
|
+
buf[dstOff + c] = buf[srcOff + c];
|
|
1257
|
+
attr[dstOff + c] = attr[srcOff + c];
|
|
884
1258
|
}
|
|
885
1259
|
}
|
|
886
1260
|
}
|
|
@@ -889,7 +1263,8 @@ export class TN5250Parser {
|
|
|
889
1263
|
if (r <= bot) {
|
|
890
1264
|
const off = r * cols;
|
|
891
1265
|
for (let c = 0; c < cols; c++) {
|
|
892
|
-
|
|
1266
|
+
buf[off + c] = ' ';
|
|
1267
|
+
attr[off + c] = ATTR.NORMAL;
|
|
893
1268
|
}
|
|
894
1269
|
}
|
|
895
1270
|
}
|