ibm5250 0.1.0.dev7__tar.gz → 0.1.0.dev9__tar.gz
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.
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/PKG-INFO +1 -1
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/pyproject.toml +1 -1
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/__init__.py +8 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/connection.py +39 -31
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/constants.py +79 -44
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/data-stream-logic.md +122 -39
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/datastream.py +380 -213
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/screen.py +104 -37
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/terminal.py +64 -73
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/uv.lock +1 -1
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/.github/copilot-instructions.md +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/.github/workflows/README.md +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/.github/workflows/publish.yml +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/.gitignore +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/LICENSE +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/README.md +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/scripts/compute_version.py +0 -0
- {ibm5250-0.1.0.dev7 → ibm5250-0.1.0.dev9}/src/ibm5250/exceptions.py +0 -0
|
@@ -19,6 +19,7 @@ from .constants import (
|
|
|
19
19
|
FFW,
|
|
20
20
|
SCREEN_COLS,
|
|
21
21
|
SCREEN_ROWS,
|
|
22
|
+
Attr,
|
|
22
23
|
Command,
|
|
23
24
|
Order,
|
|
24
25
|
TelnetCmd,
|
|
@@ -34,6 +35,9 @@ from .datastream import (
|
|
|
34
35
|
DSEventType,
|
|
35
36
|
FieldResponse,
|
|
36
37
|
build_aid_response,
|
|
38
|
+
build_save_screen_response,
|
|
39
|
+
decode_save_screen_fields,
|
|
40
|
+
decode_save_screen_image,
|
|
37
41
|
ebcdic_decode,
|
|
38
42
|
ebcdic_decode_stripped,
|
|
39
43
|
ebcdic_encode,
|
|
@@ -65,6 +69,7 @@ __all__ = [
|
|
|
65
69
|
"TelnetCmd",
|
|
66
70
|
"TelnetOpt",
|
|
67
71
|
"FFW",
|
|
72
|
+
"Attr",
|
|
68
73
|
"SCREEN_ROWS",
|
|
69
74
|
"SCREEN_COLS",
|
|
70
75
|
# Address helpers
|
|
@@ -79,6 +84,9 @@ __all__ = [
|
|
|
79
84
|
"DSEventType",
|
|
80
85
|
"FieldResponse",
|
|
81
86
|
"build_aid_response",
|
|
87
|
+
"build_save_screen_response",
|
|
88
|
+
"decode_save_screen_image",
|
|
89
|
+
"decode_save_screen_fields",
|
|
82
90
|
# EBCDIC helpers
|
|
83
91
|
"ebcdic_encode",
|
|
84
92
|
"ebcdic_decode",
|
|
@@ -26,8 +26,6 @@ from dataclasses import dataclass
|
|
|
26
26
|
|
|
27
27
|
from .constants import (
|
|
28
28
|
AID,
|
|
29
|
-
CC2,
|
|
30
|
-
Command,
|
|
31
29
|
TelnetCmd,
|
|
32
30
|
TelnetOpt,
|
|
33
31
|
TN5250EDataType,
|
|
@@ -49,7 +47,6 @@ _TELNET_OPT_CMD_LEN = 3 # bytes: IAC + command + option (WILL/WONT/DO/DONT)
|
|
|
49
47
|
# SNA / TN5250E wire-format framing constants
|
|
50
48
|
_GDS_RECORD_ID_HI = 0x12 # SNA GDS record-type identifier, high byte
|
|
51
49
|
_GDS_RECORD_ID_LO = 0xA0 # SNA GDS record-type identifier, low byte
|
|
52
|
-
_SAVE_SCREEN_RESP_CC1 = 0x01 # CC1 value in the Save Screen response variable header
|
|
53
50
|
|
|
54
51
|
# TN5250E header byte 7 (request/response flags). Real emulators set bit 0x40
|
|
55
52
|
# here to signal the Attention (Attn) key -- a header-only INPUT record with no
|
|
@@ -64,6 +61,9 @@ _TN5250E_ATTN_FLAG = 0x40
|
|
|
64
61
|
# host will run its Attention program (e.g. the System Request / Assist menu).
|
|
65
62
|
_TN5250E_OP_CANCEL_INVITE = 0x0A
|
|
66
63
|
|
|
64
|
+
# Byte 9 operation code for the Save Screen response record (RFC 2877 §4.3).
|
|
65
|
+
_TN5250E_OP_SAVE_SCREEN_RESPONSE = 0x04
|
|
66
|
+
|
|
67
67
|
_MAX_NEG_PASSES = 40 # Maximum Telnet option exchange iterations during negotiation
|
|
68
68
|
|
|
69
69
|
|
|
@@ -73,6 +73,15 @@ _MAX_NEG_PASSES = 40 # Maximum Telnet option exchange iterations during negotia
|
|
|
73
73
|
|
|
74
74
|
_TN5250E_HEADER_LEN = 10
|
|
75
75
|
|
|
76
|
+
# The 5250 payload starts after the fixed ten-byte TN5250E header. Byte 9 is
|
|
77
|
+
# the per-direction sequence number, not a variable-header length.
|
|
78
|
+
_TN5250E_FIXED_HEADER_LEN = 6
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _payload_offset(frame: bytes) -> int:
|
|
82
|
+
"""Return the offset of the 5250 payload within a TN5250E *frame*."""
|
|
83
|
+
return _TN5250E_HEADER_LEN
|
|
84
|
+
|
|
76
85
|
|
|
77
86
|
@dataclass
|
|
78
87
|
class TN5250EHeader:
|
|
@@ -101,7 +110,7 @@ class TN5250EHeader:
|
|
|
101
110
|
self.data_type, # 6: data type
|
|
102
111
|
0x00, # 7: request/response
|
|
103
112
|
self.err_flag, # 8: error recovery
|
|
104
|
-
var_len, # 9:
|
|
113
|
+
var_len, # 9: operation code (var-header length; 3 = Put/Get)
|
|
105
114
|
]
|
|
106
115
|
)
|
|
107
116
|
+ var_hdr
|
|
@@ -325,24 +334,28 @@ class TN5250Connection:
|
|
|
325
334
|
frame = _build_tn5250e_frame(req_resp=0x00, opcode=_TN5250E_OP_CANCEL_INVITE)
|
|
326
335
|
self._send_frame(frame, description="Sending Cancel-Invite response")
|
|
327
336
|
|
|
328
|
-
def send_save_screen_response(self) -> None:
|
|
337
|
+
def send_save_screen_response(self, screen_data: bytes) -> None:
|
|
329
338
|
"""Send the TN5250E-level Save Screen acknowledgment.
|
|
330
339
|
|
|
331
340
|
When the server issues an ESC+SAVE_SCREEN (0x04 0x02) command the
|
|
332
|
-
terminal
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
341
|
+
terminal replies with a complete, serialized presentation space. The
|
|
342
|
+
host stores this opaque regeneration buffer and returns it in a later
|
|
343
|
+
Restore Screen command.
|
|
344
|
+
|
|
345
|
+
Parameters
|
|
346
|
+
----------
|
|
347
|
+
screen_data:
|
|
348
|
+
A complete length-prefixed Restore Screen response produced by the
|
|
349
|
+
datastream encoder.
|
|
336
350
|
"""
|
|
337
351
|
if not self._tn5250e_active:
|
|
338
352
|
return # save/restore handshake only occurs in TN5250E mode
|
|
339
353
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
354
|
+
frame = _build_tn5250e_frame(
|
|
355
|
+
req_resp=0x00,
|
|
356
|
+
opcode=_TN5250E_OP_SAVE_SCREEN_RESPONSE,
|
|
357
|
+
var_hdr=screen_data,
|
|
344
358
|
)
|
|
345
|
-
frame = _build_tn5250e_frame(req_resp=0x00, opcode=len(var_hdr), var_hdr=var_hdr)
|
|
346
359
|
self._send_frame(frame, description="Sending Save Screen response")
|
|
347
360
|
|
|
348
361
|
def recv_record(self) -> bytes:
|
|
@@ -354,8 +367,8 @@ class TN5250Connection:
|
|
|
354
367
|
log.debug("Short TN5250E frame (%d bytes), skipping", len(frame))
|
|
355
368
|
continue
|
|
356
369
|
hdr = TN5250EHeader.from_bytes(frame)
|
|
357
|
-
self._last_recv_opcode = frame[9] # byte 9: opcode
|
|
358
|
-
payload = bytes(frame[
|
|
370
|
+
self._last_recv_opcode = frame[9] # byte 9: opcode
|
|
371
|
+
payload = bytes(frame[_payload_offset(frame) :])
|
|
359
372
|
log.debug(
|
|
360
373
|
"← TN5250E type=%02X data_type=%02X payload=%d bytes",
|
|
361
374
|
hdr.record_type,
|
|
@@ -365,20 +378,16 @@ class TN5250Connection:
|
|
|
365
378
|
else:
|
|
366
379
|
frame_bytes = bytes(frame)
|
|
367
380
|
# IBM i sends TN5250E-formatted records even when TN5250E was
|
|
368
|
-
# not formally negotiated. The
|
|
369
|
-
#
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
# 5250 payload.
|
|
381
|
+
# not formally negotiated. The header begins with a big-endian
|
|
382
|
+
# 16-bit record length that equals the total frame size, and
|
|
383
|
+
# byte 6 holds the length of the variable header that follows
|
|
384
|
+
# it — the real 5250 payload starts after that.
|
|
373
385
|
if len(frame_bytes) >= _TN5250E_HEADER_LEN and int.from_bytes(
|
|
374
386
|
frame_bytes[:2], "big"
|
|
375
387
|
) == len(frame_bytes):
|
|
376
388
|
hdr = TN5250EHeader.from_bytes(frame_bytes)
|
|
377
389
|
self._last_recv_opcode = frame_bytes[9] # byte 9: opcode
|
|
378
|
-
|
|
379
|
-
9
|
|
380
|
-
] # RFC 2877 variable-header length field
|
|
381
|
-
skip = _TN5250E_HEADER_LEN + var_hdr_len
|
|
390
|
+
skip = _payload_offset(frame_bytes)
|
|
382
391
|
payload = frame_bytes[skip:]
|
|
383
392
|
# Server is speaking TN5250E — our responses must include the header too
|
|
384
393
|
if not self._tn5250e_active:
|
|
@@ -387,9 +396,8 @@ class TN5250Connection:
|
|
|
387
396
|
)
|
|
388
397
|
self._tn5250e_active = True
|
|
389
398
|
log.debug(
|
|
390
|
-
"← implicit TN5250E header stripped (
|
|
391
|
-
|
|
392
|
-
var_hdr_len,
|
|
399
|
+
"← implicit TN5250E header stripped (%d bytes) data_type=%02X payload=%d bytes",
|
|
400
|
+
skip,
|
|
393
401
|
hdr.data_type,
|
|
394
402
|
len(payload),
|
|
395
403
|
)
|
|
@@ -768,7 +776,7 @@ def _iac_escape(data: bytes) -> bytes:
|
|
|
768
776
|
|
|
769
777
|
|
|
770
778
|
def _build_tn5250e_frame(*, req_resp: int, opcode: int, var_hdr: bytes = b"") -> bytes:
|
|
771
|
-
"""Build an IAC-EOR-terminated TN5250E frame with
|
|
779
|
+
"""Build an IAC-EOR-terminated TN5250E frame with an optional 5250 payload.
|
|
772
780
|
|
|
773
781
|
Produces the fixed 10-byte header (RFC 2877 §3.3) followed by an optional
|
|
774
782
|
variable header, IAC-escapes it, and appends the IAC-EOR record terminator.
|
|
@@ -780,7 +788,7 @@ def _build_tn5250e_frame(*, req_resp: int, opcode: int, var_hdr: bytes = b"") ->
|
|
|
780
788
|
req_resp:
|
|
781
789
|
Byte 7 (request/response flags) — e.g. the Attn flag.
|
|
782
790
|
opcode:
|
|
783
|
-
Byte 9
|
|
791
|
+
Byte 9 operation code (RFC 2877 §4.3) — how the host identifies the record.
|
|
784
792
|
var_hdr:
|
|
785
793
|
Optional variable-header bytes appended after the fixed header.
|
|
786
794
|
"""
|
|
@@ -796,7 +804,7 @@ def _build_tn5250e_frame(*, req_resp: int, opcode: int, var_hdr: bytes = b"") ->
|
|
|
796
804
|
TN5250EDataType.INPUT, # 6: data type (workstation → host)
|
|
797
805
|
req_resp, # 7: request/response flags
|
|
798
806
|
0x00, # 8: error recovery
|
|
799
|
-
opcode, # 9:
|
|
807
|
+
opcode, # 9: operation code
|
|
800
808
|
]
|
|
801
809
|
)
|
|
802
810
|
return _iac_escape(header + var_hdr) + bytes(
|
|
@@ -83,11 +83,12 @@ class TN5250ESubNeg:
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
class Command:
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
ESC = 0x04 # precedes every 5250 command in a record
|
|
87
|
+
CLEAR_UNIT = 0x40 # erase the display, reset it to the primary size (24×80)
|
|
88
|
+
CLEAR_UNIT_ALTERNATE = (
|
|
89
|
+
0x20 # erase the display, switch to the alternate size (27×132);
|
|
90
|
+
# followed by a one-byte parameter (0x00 or 0x80)
|
|
88
91
|
)
|
|
89
|
-
ESC = 0x04 # precedes Write Structured Field inline
|
|
90
|
-
CLEAR_UNIT = 0x40 # erase the display and format table
|
|
91
92
|
WRITE_TO_DISPLAY = (
|
|
92
93
|
0x11 # main screen update command; followed by CC1/CC2 and orders
|
|
93
94
|
)
|
|
@@ -105,16 +106,16 @@ class Command:
|
|
|
105
106
|
|
|
106
107
|
# Write to Display control characters (CC1 / CC2)
|
|
107
108
|
class CC1:
|
|
108
|
-
"""Control Character 1 — first byte after WTD command.
|
|
109
|
+
"""Control Character 1 — first byte after the WTD command.
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
CC1 selects which Modified Data Tags and input-field buffers the host wants
|
|
112
|
+
reset before the write is applied. It does **not** erase the screen and it
|
|
113
|
+
does **not** change the screen dimensions — those are driven exclusively by
|
|
114
|
+
the Clear Unit (:data:`Command.CLEAR_UNIT`) and Clear Unit Alternate
|
|
115
|
+
(:data:`Command.CLEAR_UNIT_ALTERNATE`) commands.
|
|
112
116
|
"""
|
|
113
117
|
|
|
114
118
|
RESET = 0x00
|
|
115
|
-
CLEAR_UNIT = 0x20 # Erase entire screen before write
|
|
116
|
-
CLEAR_UNIT_ALT = 0x40 # Erase screen using alternate screen dimensions
|
|
117
|
-
CLEAR_FORMAT_TABLE = 0x60 # Discard field definitions only; no screen erase
|
|
118
119
|
SOUND_ALARM = 0x80 # Audible alarm
|
|
119
120
|
|
|
120
121
|
|
|
@@ -133,22 +134,32 @@ class CC2:
|
|
|
133
134
|
# ---------------------------------------------------------------------------
|
|
134
135
|
|
|
135
136
|
class Order:
|
|
137
|
+
"""5250 orders embedded in a Write to Display data stream.
|
|
138
|
+
|
|
139
|
+
Note these are *5250* order codes -- they differ from the 3270 codes of the
|
|
140
|
+
same name (e.g. 5250 Repeat to Address is 0x02, not 0x3C).
|
|
141
|
+
"""
|
|
142
|
+
|
|
136
143
|
START_OF_HEADER = 0x01 # Start of Header (variable-length; skip past it)
|
|
144
|
+
REPEAT_TO_ADDRESS = 0x02 # Repeat to Address — 2 address bytes + 1 fill char
|
|
145
|
+
ERASE_TO_ADDRESS = (
|
|
146
|
+
0x03 # Erase to Address — 2 address bytes + length + attribute types
|
|
147
|
+
)
|
|
137
148
|
TRANSPARENT_DATA = 0x10 # Transparent Data — 2-byte length + raw EBCDIC
|
|
138
149
|
SET_BUFFER_ADDRESS = 0x11 # Set Buffer Address — 2 address bytes
|
|
139
|
-
|
|
140
|
-
0x12 #
|
|
150
|
+
WRITE_EXTENDED_ATTRIBUTE = (
|
|
151
|
+
0x12 # Write Extended Attribute — 2 bytes; not emulated
|
|
141
152
|
)
|
|
142
153
|
INSERT_CURSOR = 0x13 # Insert Cursor — 2 address bytes; marks cursor pos
|
|
143
154
|
MOVE_CURSOR = 0x14 # Move Cursor — 2 address bytes
|
|
144
|
-
|
|
155
|
+
WRITE_DISPLAY_STRUCTURED_FIELD = (
|
|
156
|
+
0x15 # Write to Display Structured Field — 2-byte length + payload
|
|
157
|
+
)
|
|
145
158
|
START_FIELD = 0x1D # Start Field — 2-byte FFW [+ 2-byte FCW pairs]
|
|
146
|
-
SET_ATTRIBUTE = 0x28 # Set Attribute — 1 attribute type + 1 value byte
|
|
147
|
-
MODIFY_FIELD = 0x2C # Modify Field — like SF but doesn't clear data
|
|
148
|
-
REPEAT_TO_ADDRESS = 0x3C # Repeat to Address — 2 address bytes + 1 fill char
|
|
149
159
|
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
|
|
161
|
+
KNOWN_ORDERS = frozenset(
|
|
162
|
+
v for k, v in vars(Order).items() if isinstance(v, int) and not k.startswith("_")
|
|
152
163
|
)
|
|
153
164
|
|
|
154
165
|
# ---------------------------------------------------------------------------
|
|
@@ -218,36 +229,56 @@ class AID:
|
|
|
218
229
|
|
|
219
230
|
|
|
220
231
|
class FFW:
|
|
221
|
-
"""Field Format Word bits (byte 0 and byte 1 of the 2-byte FFW).
|
|
232
|
+
"""Field Format Word bits (byte 0 and byte 1 of the 2-byte FFW).
|
|
222
233
|
|
|
223
|
-
|
|
234
|
+
Note the FFW carries *behaviour* only. A field's visual appearance comes
|
|
235
|
+
from the separate attribute byte that follows the FFW/FCWs in the SF order
|
|
236
|
+
(see :class:`Attr`).
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
# Byte 0 — bit 0x40 is the "this is an FFW" identifier and is always set.
|
|
240
|
+
IDENTIFIER = 0x40 # Marks the byte as a Field Format Word rather than an attribute
|
|
224
241
|
BYPASS = 0x20 # Protected field — no input allowed (read-only)
|
|
225
242
|
DUP_ENABLE = 0x10 # Dup key enabled — user can duplicate the previous field's value
|
|
226
|
-
MDT =
|
|
227
|
-
|
|
228
|
-
FER = 0x04 # Field Exit Required — user must press Field Exit before leaving
|
|
229
|
-
|
|
230
|
-
# Byte 1 (keyboard shift / display attribute)
|
|
243
|
+
MDT = 0x08 # Modified Data Tag — set when the field has been touched
|
|
244
|
+
# Byte 0, low 3 bits — field type (keyboard shift)
|
|
231
245
|
ALPHA_SHIFT = 0x00 # Alphabetic shift — accepts any character
|
|
232
|
-
ALPHA_ONLY =
|
|
233
|
-
NUM_SHIFT =
|
|
234
|
-
NUM_ONLY =
|
|
235
|
-
KATA_SHIFT =
|
|
236
|
-
DIGITS_ONLY =
|
|
237
|
-
|
|
238
|
-
SIGNED_NUM =
|
|
239
|
-
#
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
ALPHA_ONLY = 0x01 # Alphabetic only — rejects digits and special characters
|
|
247
|
+
NUM_SHIFT = 0x02 # Numeric shift — defaults keyboard to numeric mode
|
|
248
|
+
NUM_ONLY = 0x03 # Numeric only — rejects non-numeric input
|
|
249
|
+
KATA_SHIFT = 0x04 # Katakana shift — Japanese katakana character entry
|
|
250
|
+
DIGITS_ONLY = 0x05 # Digits only — accepts 0–9 with no sign
|
|
251
|
+
MAG_READER = 0x06 # Magnetic stripe reader input only
|
|
252
|
+
SIGNED_NUM = 0x07 # Signed numeric — accepts digits plus a trailing sign character
|
|
253
|
+
FIELD_TYPE_MASK = 0x07 # Bitmask for the field type bits of byte 0
|
|
254
|
+
|
|
255
|
+
# Byte 1
|
|
256
|
+
AUTO_ENTER = 0x80 # Auto Enter — automatically submit when the field is filled
|
|
257
|
+
FER = 0x40 # Field Exit Required — user must press Field Exit before leaving
|
|
258
|
+
MONOCASE = 0x20 # Monocase — lower-case input is folded to upper case
|
|
259
|
+
MANDATORY = 0x08 # Mandatory entry — the field may not be left empty
|
|
260
|
+
RIGHT_ZERO = 0x05 # Right-adjust, zero fill
|
|
261
|
+
RIGHT_BLANK = 0x06 # Right-adjust, blank fill
|
|
262
|
+
MANDATORY_FILL = 0x07 # Mandatory fill — the field must be filled completely
|
|
263
|
+
MANDATORY_FILL_MASK = 0x07 # Bitmask for the adjust/fill bits of byte 1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class Attr:
|
|
267
|
+
"""5250 display attribute byte values (0x20-0x3F).
|
|
268
|
+
|
|
269
|
+
Every attribute occupies one display position and governs the appearance of
|
|
270
|
+
the characters that follow it until the next attribute byte.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
NORMAL = 0x20 # Green
|
|
274
|
+
REVERSE = 0x21 # Green, reverse image
|
|
275
|
+
HIGH_INTENSITY = 0x22 # White
|
|
276
|
+
UNDERLINE = 0x24 # Green, underlined
|
|
277
|
+
NON_DISPLAY = 0x27 # Invisible — used for passwords and command strings
|
|
278
|
+
RED = 0x28 # Red
|
|
279
|
+
BASE = 0x20 # All attribute bytes have this bit pattern in their high bits
|
|
280
|
+
MASK = 0xE0 # ``byte & MASK == BASE`` identifies an attribute byte
|
|
281
|
+
NON_DISPLAY_MASK = 0x07 # ``byte & MASK == 0x07`` means non-display
|
|
251
282
|
|
|
252
283
|
|
|
253
284
|
# ---------------------------------------------------------------------------
|
|
@@ -436,6 +467,10 @@ SCREEN_COLS = 80
|
|
|
436
467
|
SCREEN_ROWS_27 = 27 # 27×132 model
|
|
437
468
|
SCREEN_COLS_132 = 132
|
|
438
469
|
|
|
470
|
+
# Length of the screen identifier the workstation returns in its Save Screen
|
|
471
|
+
# acknowledgment and that the host echoes back in a Restore Screen command.
|
|
472
|
+
SAVE_SCREEN_ID_LEN = 2
|
|
473
|
+
|
|
439
474
|
EBCDIC_SPACE = 0x40
|
|
440
475
|
EBCDIC_NULL = 0x00
|
|
441
476
|
|
|
@@ -19,7 +19,11 @@ The **TN5250Connection** manages the TCP socket to the IBM i (AS/400) host.
|
|
|
19
19
|
1. Reads bytes from the socket into a buffer.
|
|
20
20
|
2. Scans for the IAC-EOR (Interpret As Command — End Of Record) sequence (0xFF 0xEF) which marks the end of a logical record.
|
|
21
21
|
3. Un-escapes any doubled IAC bytes (0xFF 0xFF → 0xFF).
|
|
22
|
-
4. If TN5250E is active, strips the
|
|
22
|
+
4. If TN5250E is active, strips the header → returns the raw 5250 payload. The
|
|
23
|
+
header is six fixed bytes followed by a variable part whose **length is byte 6**
|
|
24
|
+
(normally 4, giving the familiar 10-byte total), so the payload starts at
|
|
25
|
+
`6 + frame[6]`. Byte 9 is the _opcode_, not the header length — confusing the
|
|
26
|
+
two silently truncates the first bytes of the payload.
|
|
23
27
|
5. If the socket has no data within the timeout, raises **RecvTimeout**.
|
|
24
28
|
|
|
25
29
|
### Sending (`send_record`)
|
|
@@ -33,7 +37,14 @@ The **TN5250Connection** manages the TCP socket to the IBM i (AS/400) host.
|
|
|
33
37
|
|
|
34
38
|
### Save Screen Response (`send_save_screen_response`)
|
|
35
39
|
|
|
36
|
-
When the server issues a SAVE_SCREEN command, the terminal must reply with a
|
|
40
|
+
When the server issues a SAVE_SCREEN command, the terminal must reply with a
|
|
41
|
+
TN5250E-framed record carrying `ESC + RESTORE_SCREEN + a two-byte length + the
|
|
42
|
+
current presentation space`. The image is freshly serialized from the current
|
|
43
|
+
screen model: geometry, cursor/home positions, the complete character/attribute
|
|
44
|
+
plane, and the field table. Host-to-terminal WTD, Clear Unit, and Read commands
|
|
45
|
+
are never replayed. The host stores this opaque regeneration buffer and returns
|
|
46
|
+
it in a later Restore Screen command. See
|
|
47
|
+
[Save/Restore Screen Flow](#saverestore-screen-flow).
|
|
37
48
|
|
|
38
49
|
---
|
|
39
50
|
|
|
@@ -43,28 +54,37 @@ The **DataStreamParser** takes a raw 5250 payload (one record from the connectio
|
|
|
43
54
|
|
|
44
55
|
### Record Structure
|
|
45
56
|
|
|
46
|
-
Every
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
57
|
+
Every command in a record is introduced by **ESC (`0x04`)**, and one record may
|
|
58
|
+
carry several commands back to back:
|
|
59
|
+
|
|
60
|
+
| Command | Meaning |
|
|
61
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
62
|
+
| `0x11` (Write to Display) | Main screen update — followed by CC1, CC2, then orders/data |
|
|
63
|
+
| `0x40` (Clear Unit) | Erases the display and field definitions and selects the **primary** size (24×80) |
|
|
64
|
+
| `0x20` (Clear Unit Alternate) | Erases the display and selects the **alternate** size (27×132); takes one parameter byte |
|
|
65
|
+
| `0x02` (Save Screen) | Server wants the client to save the current screen |
|
|
66
|
+
| `0x12` (Restore Screen) | Server wants a previously saved screen restored; followed by its two-byte identifier |
|
|
67
|
+
| `0xF3` (Write Structured Field) | Carries structured data like Query commands |
|
|
68
|
+
| `0x42/0x52/0x72/0x62` | Read commands — server asks client to send back screen/field data |
|
|
69
|
+
| `0x21` (Write Error Code) | Display error on status line |
|
|
70
|
+
| `0x50` (Clear Format Table) | Discard field definitions |
|
|
71
|
+
|
|
72
|
+
### Screen size
|
|
73
|
+
|
|
74
|
+
The display size is **absolute and set only by the clear commands**: Clear Unit
|
|
75
|
+
selects 24×80, Clear Unit Alternate selects 27×132. It is never a toggle, and it
|
|
76
|
+
is never inferred from out-of-range buffer addresses. Getting this wrong is what
|
|
77
|
+
made a screen returning from a 132-column layout keep rendering as 27×132 with
|
|
78
|
+
stale content bleeding through.
|
|
59
79
|
|
|
60
80
|
### Control Characters (CC1 / CC2)
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
Write to Display is followed by two control bytes:
|
|
63
83
|
|
|
64
|
-
- **CC1** —
|
|
65
|
-
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
84
|
+
- **CC1** — Lock/reset flags. Notably CC1 **neither erases the screen nor
|
|
85
|
+
changes its dimensions**; only the clear commands above do that.
|
|
86
|
+
- `0x01`/`0x02`: Reset pending AIDs / unlock after the write
|
|
87
|
+
- `0x04`: Sound the alarm
|
|
68
88
|
|
|
69
89
|
- **CC2** — Keyboard and field flags:
|
|
70
90
|
- `0x08`: Unlock the keyboard (let the user type)
|
|
@@ -75,24 +95,56 @@ After a WTD or Save Screen command, two bytes control screen-level behavior:
|
|
|
75
95
|
|
|
76
96
|
After the command + CC bytes, the rest of the record is a stream of **orders** intermixed with **data bytes**:
|
|
77
97
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
| **
|
|
84
|
-
| **
|
|
85
|
-
| **
|
|
86
|
-
| **
|
|
87
|
-
| **
|
|
88
|
-
| **
|
|
89
|
-
| **
|
|
98
|
+
These are the **5250** order codes; several share a name with a 3270 order but
|
|
99
|
+
use a different byte value (5250 Repeat to Address is `0x02`, not `0x3C`).
|
|
100
|
+
|
|
101
|
+
| Order | What it does |
|
|
102
|
+
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| **SOH** — Start of Header (`0x01`) + length + data | Metadata block, skipped |
|
|
104
|
+
| **RA** — Repeat to Address (`0x02`) + row + col + char | Repeat a character from the current position up to **and including** the target |
|
|
105
|
+
| **EA** — Erase to Address (`0x03`) + row + col + len + types | Erase (fill with nulls) from the current position through the target. `len` counts itself plus the extended-attribute type bytes that follow |
|
|
106
|
+
| **TD** — Transparent Data (`0x10`) + length + data | Write raw bytes (transparent, no order interpretation) |
|
|
107
|
+
| **SBA** — Set Buffer Address (`0x11`) + row + col | Move the write position to a specific screen cell |
|
|
108
|
+
| **WEA** — Write Extended Attribute (`0x12`) + type + value | Extended character buffer attribute; not emulated, operands are consumed |
|
|
109
|
+
| **IC** — Insert Cursor (`0x13`) | Place the cursor at the 2-byte address that follows (falls back to the current position only when no address bytes remain) |
|
|
110
|
+
| **MC** — Move Cursor (`0x14`) + row + col | Move the cursor to a specific position |
|
|
111
|
+
| **WDSF** — Write to Display Structured Field (`0x15`) | GUI constructs (windows, scrollbars); not emulated, the payload is skipped |
|
|
112
|
+
| **SF** — Start Field (`0x1D`) | Define a field at the current position — see below |
|
|
90
113
|
|
|
91
114
|
Anything that isn't a recognized order byte is treated as **EBCDIC (Extended Binary Coded Decimal Interchange Code)** character data and written sequentially into the screen buffer.
|
|
92
115
|
|
|
93
|
-
###
|
|
94
|
-
|
|
95
|
-
|
|
116
|
+
### Attribute bytes
|
|
117
|
+
|
|
118
|
+
Bytes in the range **`0x20`–`0x3F`** are display attributes. They are _data_:
|
|
119
|
+
each one occupies a screen position (rendered as a blank) and governs the
|
|
120
|
+
appearance of everything after it until the next attribute. `0x27` — more
|
|
121
|
+
precisely any attribute whose low three bits are all set — means _non-display_,
|
|
122
|
+
which is how password fields and hidden command strings are rendered blank.
|
|
123
|
+
|
|
124
|
+
Because attributes are the only bytes with that bit pattern, they double as the
|
|
125
|
+
terminator for the variable-length part of an SF order.
|
|
126
|
+
|
|
127
|
+
### Start Field (SF)
|
|
128
|
+
|
|
129
|
+
SF (0x1D) [FFW1 FFW2 [FCW1 FCW2]...] ATTR LEN_HI LEN_LO
|
|
130
|
+
|
|
131
|
+
- An **output-only** field omits the FFW and FCWs entirely: the byte straight
|
|
132
|
+
after SF is then the attribute.
|
|
133
|
+
- The **FFW** (Field Format Word) defines field behaviour — bypass/input,
|
|
134
|
+
keyboard shift, mandatory entry, and so on. Byte 0 always has bit `0x40` set,
|
|
135
|
+
which is how it is told apart from an attribute byte. Byte 1 bit `0x80` is
|
|
136
|
+
**Auto-Enter**: the host expects an Enter AID to be sent automatically the
|
|
137
|
+
moment the field is completely filled (`Field.auto_enter`). On the wire this
|
|
138
|
+
looks like e.g. `1D 40 A0 26 00 01` — FFW `40 A0`, attribute `26`, length `1`
|
|
139
|
+
— which is a plain auto-enter field, _not_ an FCW pair.
|
|
140
|
+
- Zero or more **FCW** (Field Control Word) pairs may follow; the chain ends at
|
|
141
|
+
the first attribute byte, _not_ at a continuation bit.
|
|
142
|
+
- The attribute byte is written at the field's start position, and the field's
|
|
143
|
+
length is the **two bytes after it** — it is not derived from an FCW and it is
|
|
144
|
+
not inferred from where the next field begins.
|
|
145
|
+
- A **Start of Header (SOH)** begins a new format table. Applying it discards
|
|
146
|
+
existing field definitions without clearing display characters; subsequent
|
|
147
|
+
SF orders rebuild the table for the current write.
|
|
96
148
|
|
|
97
149
|
### Write Structured Field (WSF / Query)
|
|
98
150
|
|
|
@@ -127,6 +179,8 @@ The **Screen** holds the actual state that represents what you'd see on a physic
|
|
|
127
179
|
- **Field list** — all SF/MF-defined fields with their position, length, format word, and modified-data-tag.
|
|
128
180
|
- **Cursor position** — where the blinking cursor sits.
|
|
129
181
|
- **Keyboard locked flag** — when True, the terminal won't send AID keys.
|
|
182
|
+
- **Last AID** — the most recent AID successfully sent to the host; included
|
|
183
|
+
in Save Screen device state.
|
|
130
184
|
|
|
131
185
|
### Applying Events
|
|
132
186
|
|
|
@@ -167,8 +221,9 @@ This is the central method — it:
|
|
|
167
221
|
2. Feeds it through `parser.parse()` to get a list of `DSEvent` objects.
|
|
168
222
|
3. Iterates through events looking for special commands:
|
|
169
223
|
- **QUERY** → sends back a Query Reply with terminal capabilities
|
|
170
|
-
- **SAVE_SCREEN** → deep-copies the current `Screen` onto a stack,
|
|
171
|
-
- **RESTORE_SCREEN** → pops the saved
|
|
224
|
+
- **SAVE_SCREEN** → deep-copies the current `Screen` onto a stack, serializes one complete regeneration buffer from that model, and returns it in the save-screen response
|
|
225
|
+
- **RESTORE_SCREEN** → pops the most recently saved `Screen`; the host-returned regeneration buffer is opaque because the local copy is authoritative, then any remaining events from the same record are applied on top
|
|
226
|
+
- **RESIZE_SCREEN** → re-creates the parser and screen at the new dimensions
|
|
172
227
|
4. If no RESTORE occurred, applies all events to the screen normally.
|
|
173
228
|
5. **Post-RESTORE keyboard drain**: After restoring a screen, the keyboard is locked (because it was locked when saved). The server sends a follow-up record to unlock it. The terminal tries one more `_recv_and_apply()` to consume that unlock record. If nothing comes (RecvTimeout), it moves on.
|
|
174
229
|
|
|
@@ -180,6 +235,16 @@ This is the central method — it:
|
|
|
180
235
|
4. Sets `keyboard_locked = True` (the server will unlock it when it replies).
|
|
181
236
|
5. Calls `_recv_and_apply()` to wait for and process the server's response.
|
|
182
237
|
|
|
238
|
+
### Auto-Enter fields
|
|
239
|
+
|
|
240
|
+
When `type_into()` fills a field whose FFW has the Auto-Enter bit set, it
|
|
241
|
+
submits `AID.ENTER` itself (via the normal `send_key` path) rather than waiting
|
|
242
|
+
for the caller — mirroring a physical 5250. The cursor is placed on the
|
|
243
|
+
auto-enter field first, because `set_field_text` otherwise advances it to the
|
|
244
|
+
next field once the current one is full. Only the modified-data tags are cleared
|
|
245
|
+
locally; the resulting screen (often a Save/Restore popup handshake) is driven
|
|
246
|
+
entirely by the host and handled by `_recv_and_apply()`.
|
|
247
|
+
|
|
183
248
|
### Waiting: `wait_for_text(text)`
|
|
184
249
|
|
|
185
250
|
1. Checks if the text already appears on screen (from events already applied).
|
|
@@ -194,12 +259,30 @@ This is the central method — it:
|
|
|
194
259
|
This is how the server does "popup" screens (like overlaying a detail view on a list):
|
|
195
260
|
|
|
196
261
|
1. Server sends **SAVE_SCREEN** → terminal saves a copy of the current screen
|
|
262
|
+
under a fresh two-byte identifier and returns that identifier in the Save
|
|
263
|
+
Screen response
|
|
197
264
|
2. Server sends a normal WTD with new content → terminal shows the popup
|
|
198
265
|
3. User interacts with the popup (sends keys, gets responses)
|
|
199
|
-
4. Server sends **RESTORE_SCREEN** → terminal swaps back to
|
|
266
|
+
4. Server sends **RESTORE_SCREEN + identifier** → terminal swaps back to _that_
|
|
267
|
+
saved screen and discards any screens saved after it
|
|
200
268
|
5. Server sends a follow-up WTD to unlock the keyboard on the restored screen
|
|
201
269
|
|
|
202
|
-
|
|
270
|
+
Saves nest, so this is **not** a stack the terminal may simply pop: the server
|
|
271
|
+
decides how far to unwind and names the screen it wants. Unwinding two levels
|
|
272
|
+
in one go — restore `04 05`, Clear Unit, restore `03 04` — is normal, and
|
|
273
|
+
popping blindly restores the wrong screen.
|
|
274
|
+
|
|
275
|
+
A restored screen brings its own dimensions with it, so the parser must be
|
|
276
|
+
re-created to match whenever one is restored.
|
|
277
|
+
|
|
278
|
+
The other tricky part: after step 4, the restored screen has
|
|
279
|
+
`keyboard_locked = True` (it was saved right after the client sent an AID key).
|
|
280
|
+
The terminal must consume the unlock record (step 5) before attempting the next
|
|
281
|
+
`send_key`, otherwise the server rejects the input.
|
|
282
|
+
|
|
283
|
+
Screens restored this way are usually followed by _partial_ updates that repaint
|
|
284
|
+
only the parts of the screen that changed, so the restored snapshot has to be
|
|
285
|
+
byte-accurate — any error in it survives into the final display.
|
|
203
286
|
|
|
204
287
|
---
|
|
205
288
|
|