ibm5250 0.1.0.dev9__tar.gz → 0.1.0.dev10__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.dev9 → ibm5250-0.1.0.dev10}/PKG-INFO +1 -1
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/pyproject.toml +1 -1
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/connection.py +36 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/datastream.py +11 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/terminal.py +36 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/uv.lock +1 -1
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/.github/copilot-instructions.md +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/.github/workflows/README.md +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/.github/workflows/publish.yml +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/.gitignore +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/LICENSE +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/README.md +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/scripts/compute_version.py +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/__init__.py +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/constants.py +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/data-stream-logic.md +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/exceptions.py +0 -0
- {ibm5250-0.1.0.dev9 → ibm5250-0.1.0.dev10}/src/ibm5250/screen.py +0 -0
|
@@ -64,6 +64,12 @@ _TN5250E_OP_CANCEL_INVITE = 0x0A
|
|
|
64
64
|
# Byte 9 operation code for the Save Screen response record (RFC 2877 §4.3).
|
|
65
65
|
_TN5250E_OP_SAVE_SCREEN_RESPONSE = 0x04
|
|
66
66
|
|
|
67
|
+
# Byte 9 operation codes for immediate-read replies (RFC 2877 §4.3). The host
|
|
68
|
+
# polls the workstation with ESC + Read Immediate (0x72) / Read Screen Immediate
|
|
69
|
+
# (0x62); the workstation answers with these operation codes.
|
|
70
|
+
_TN5250E_OP_READ_IMMEDIATE = 0x06
|
|
71
|
+
_TN5250E_OP_READ_SCREEN = 0x08
|
|
72
|
+
|
|
67
73
|
_MAX_NEG_PASSES = 40 # Maximum Telnet option exchange iterations during negotiation
|
|
68
74
|
|
|
69
75
|
|
|
@@ -358,6 +364,36 @@ class TN5250Connection:
|
|
|
358
364
|
)
|
|
359
365
|
self._send_frame(frame, description="Sending Save Screen response")
|
|
360
366
|
|
|
367
|
+
def send_read_immediate_response(
|
|
368
|
+
self, cursor_pos: int, field_data: bytes, cols: int = 80
|
|
369
|
+
) -> None:
|
|
370
|
+
"""Answer a host Read Immediate (0x72) poll with the current input.
|
|
371
|
+
|
|
372
|
+
The reply carries operation code 0x06, a cursor/AID variable header
|
|
373
|
+
(AID 0x00 — no key was pressed), then the concatenated input-field data.
|
|
374
|
+
"""
|
|
375
|
+
if not self._tn5250e_active:
|
|
376
|
+
return
|
|
377
|
+
cursor_row, cursor_col = encode_addr(cursor_pos, cols)
|
|
378
|
+
var_hdr = bytes([cursor_row, cursor_col, 0x00]) + field_data
|
|
379
|
+
frame = _build_tn5250e_frame(
|
|
380
|
+
req_resp=0x00, opcode=_TN5250E_OP_READ_IMMEDIATE, var_hdr=var_hdr
|
|
381
|
+
)
|
|
382
|
+
self._send_frame(frame, description="Sending Read Immediate response")
|
|
383
|
+
|
|
384
|
+
def send_read_screen_response(self, screen_data: bytes) -> None:
|
|
385
|
+
"""Answer a host Read Screen Immediate (0x62) poll with the buffer.
|
|
386
|
+
|
|
387
|
+
The reply carries operation code 0x08 and the raw presentation-space
|
|
388
|
+
character buffer as its payload (no cursor/AID header).
|
|
389
|
+
"""
|
|
390
|
+
if not self._tn5250e_active:
|
|
391
|
+
return
|
|
392
|
+
frame = _build_tn5250e_frame(
|
|
393
|
+
req_resp=0x00, opcode=_TN5250E_OP_READ_SCREEN, var_hdr=screen_data
|
|
394
|
+
)
|
|
395
|
+
self._send_frame(frame, description="Sending Read Screen response")
|
|
396
|
+
|
|
361
397
|
def recv_record(self) -> bytes:
|
|
362
398
|
"""Block until a complete record arrives; return the raw 5250 payload."""
|
|
363
399
|
while True:
|
|
@@ -239,6 +239,17 @@ class DataStreamParser:
|
|
|
239
239
|
data=restore_data,
|
|
240
240
|
)
|
|
241
241
|
return
|
|
242
|
+
elif sub_command in (
|
|
243
|
+
Command.READ_INPUT_FIELDS,
|
|
244
|
+
Command.READ_MDT_FIELDS,
|
|
245
|
+
Command.READ_IMMEDIATE,
|
|
246
|
+
Command.READ_SCREEN_IMMEDIATE,
|
|
247
|
+
):
|
|
248
|
+
# ESC + Read command (e.g. 04 72): the host polls the
|
|
249
|
+
# workstation for input; no data stream follows.
|
|
250
|
+
byte_offset += 1
|
|
251
|
+
yield DSEvent(type=DSEventType.COMMAND, command=sub_command)
|
|
252
|
+
return
|
|
242
253
|
else:
|
|
243
254
|
if sub_command == Command.WRITE_TO_DISPLAY:
|
|
244
255
|
byte_offset += 1 # consume the 0x11 WTD byte
|
|
@@ -586,6 +586,16 @@ class Terminal:
|
|
|
586
586
|
):
|
|
587
587
|
if self._restore_screen(event.data):
|
|
588
588
|
restore_index = i
|
|
589
|
+
elif (
|
|
590
|
+
event.type == DSEventType.COMMAND
|
|
591
|
+
and event.command == Command.READ_IMMEDIATE
|
|
592
|
+
):
|
|
593
|
+
self._respond_to_read_immediate()
|
|
594
|
+
elif (
|
|
595
|
+
event.type == DSEventType.COMMAND
|
|
596
|
+
and event.command == Command.READ_SCREEN_IMMEDIATE
|
|
597
|
+
):
|
|
598
|
+
self._respond_to_read_screen()
|
|
589
599
|
elif event.type == DSEventType.RESIZE_SCREEN:
|
|
590
600
|
self._resize(event.rows, event.cols)
|
|
591
601
|
# Only apply events after a RESTORE — pre-RESTORE events targeted
|
|
@@ -611,6 +621,32 @@ class Terminal:
|
|
|
611
621
|
self._parser = DataStreamParser(rows, cols)
|
|
612
622
|
log.debug("Parser resized to %d rows × %d cols", rows, cols)
|
|
613
623
|
|
|
624
|
+
def _respond_to_read_immediate(self) -> None:
|
|
625
|
+
"""Answer a host Read Immediate (0x72) with the current input fields.
|
|
626
|
+
|
|
627
|
+
Immediate reads solicit the device's input without an AID key, so the
|
|
628
|
+
reply carries AID 0x00 and each input field's data concatenated in
|
|
629
|
+
screen order (no SBA), matching a real 5250 workstation.
|
|
630
|
+
"""
|
|
631
|
+
cols = self._screen.cols
|
|
632
|
+
field_data = bytearray()
|
|
633
|
+
for input_field in self._screen.input_fields:
|
|
634
|
+
row_remaining = cols - (input_field.data_pos % cols)
|
|
635
|
+
length = min(input_field.length, row_remaining)
|
|
636
|
+
field_data += bytes(
|
|
637
|
+
self._screen._chars[
|
|
638
|
+
input_field.data_pos : input_field.data_pos + length
|
|
639
|
+
]
|
|
640
|
+
)
|
|
641
|
+
self._conn.send_read_immediate_response(
|
|
642
|
+
self._screen.cursor_pos, bytes(field_data), cols=cols
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
def _respond_to_read_screen(self) -> None:
|
|
646
|
+
"""Answer a host Read Screen Immediate (0x62) with the presentation space."""
|
|
647
|
+
size = self._screen.rows * self._screen.cols
|
|
648
|
+
self._conn.send_read_screen_response(bytes(self._screen._chars[:size]))
|
|
649
|
+
|
|
614
650
|
def _save_screen(self) -> None:
|
|
615
651
|
"""Acknowledge the host's Save Screen with a serialized presentation space.
|
|
616
652
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|