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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ibm5250
3
- Version: 0.1.0.dev9
3
+ Version: 0.1.0.dev10
4
4
  Summary: IBM 5250 terminal automation library
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ibm5250"
3
- version = "0.1.0.dev9"
3
+ version = "0.1.0.dev10"
4
4
  description = "IBM 5250 terminal automation library"
5
5
  requires-python = ">=3.11"
6
6
 
@@ -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
 
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "ibm5250"
16
- version = "0.1.0.dev9"
16
+ version = "0.1.0.dev10"
17
17
  source = { editable = "." }
18
18
 
19
19
  [package.dev-dependencies]
File without changes
File without changes
File without changes