Skip to content

Add mapping for PF keys on 3278-2 with APL keyboard - #4

Open
hanshuebner wants to merge 21 commits into
lowobservable:masterfrom
hanshuebner:master
Open

hanshuebner wants to merge 21 commits into
lowobservable:masterfrom
hanshuebner:master

Conversation

@hanshuebner

Copy link
Copy Markdown

No description provided.

hanshuebner and others added 12 commits April 8, 2026 11:46
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSL sockets buffer decrypted data internally, but select() only monitors
the underlying OS file descriptor. When a TLS record spans multiple recv()
calls, the remaining data sits in SSL's internal buffer invisible to
select(), causing long delays until the next network event.

Check SSLSocket.pending() before falling back to select() so buffered
SSL data is processed immediately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ä/ö/ü/Ä/Ö/Ü mappings to CHAR_MAP using the GE/APL character
plane (0x50-0x54 lowercase, 0x70-0x74 uppercase).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creates /dev/ttyCoax symlink based on USB serial number.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --keyboard-language option (us/de) for 3278 keyboards. The DE
variant remaps the number row shifted keys, umlauts on ä/ö/ü keys,
ß, § and other German-specific key positions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Display encryption status and hostname on the terminal status line
during TN3270 sessions. Add --no-hostname-status option to suppress
the hostname display. Pad OEC indicator to full status line width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --log-level command line option (debug/info/warning/error) to
control logging verbosity. Add debug log messages for alarm queuing
and delivery to help diagnose buzzer issues on physical terminals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hanshuebner and others added 9 commits April 11, 2026 13:19
A full-screen EAB write delivered in a single EAB_WRITE_ALTERNATE command
(~3832 interleaved bytes) exceeds what some CUT terminals can buffer. The
IBM 3192 silently truncates at its buffer limit, and the IRMA coax adapter
rejects the frame with a protocol error (no TT/AR response). Both result
in a partially-rendered screen and, because the non-display attribute byte
for the password field sits near the end of the write, a password field
that echoes. Real IBM 3278-2 terminals happen to accept the full write,
which is why the issue was not seen earlier.

Make prepare_jumbo_write split at 1024 bytes by default for all paths.
The 3299 multiplexer and legacy Arduino-Mega firmware paths already used
this limit successfully. COAX_JUMBO=ignore still disables splitting for
anyone who needs the old behavior.

Also stop silently leaving address_counter stale on a failed write in
BufferedDisplay._write_range - null it out so the next flush forces a
fresh LOAD_ADDRESS_COUNTER. The regen/EAB buffer and dirty set are
already preserved because _commit() does not run on exception, so the
next flush retries the range.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The IRMA emulator writes something directly to the left of column 17,
which collided with the security indicator. Shift it right by 3 columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pySerial waits for ever on a port opened without a timeout. An interface that
stops answering therefore leaves the controller in a read that never returns:
no keystrokes, no polling, nothing in the log, and a service manager sees a
process that is still running. The interface library raises InterfaceTimeout on
a read that comes back empty, and with a timeout set that is what happens --
the run ends and a fresh one takes its place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
A USB serial device ends a transfer that fills its last packet with a zero
length one, and the read that follows the port becoming readable returns
nothing. pySerial takes that for a disconnected device and raises, so the run
ends over an event that means only "no bytes this time" -- which is what the
terminal coming back online does to a session: the controller dies and the
screen it had just drawn goes with it.

The read is retried until the port's own timeout, so a port that has really
gone quiet still ends the run through the timeout that says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
pySerial gathers bytes for a read in a buffer of its own and raises if the
port then reports data and hands over none -- and that buffer goes with the
exception. Retrying the read therefore resumes past the bytes it dropped, and
the message stream is read out of step from there: responses come back with
lengths that do not match and frames that are a byte long.

Reading through the port's descriptor keeps every byte that arrives, ends the
read at the port's timeout, and leaves the interface library to report the
timeout as its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
Reading around pySerial cost more than it bought. Swallowing its "port
reported data and gave none" left the bytes it had already gathered behind, so
the message stream ran out of step and responses came back with lengths that
did not match; reading the descriptor instead met the port's non-blocking
mode and ended the run on EAGAIN. Neither appeared while the port was simply
given a timeout: that ends a run cleanly on a port that has gone quiet, which
is what the timeout is for, and what starts the next one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
A USB serial device ends a transfer that fills its last packet with a zero
length one, and the read that follows the port becoming readable returns
nothing: about one attach in eight ended there, with pySerial calling it a
disconnected device and the session going with it.

Only the single byte read is retried, which is where this happens and where
pySerial has gathered nothing it could drop. A larger read keeps its own
behaviour, so the message stream cannot resume past bytes lost with an
exception, and a port that stays quiet still ends the read at the timeout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
A USB serial device ends a transfer that fills its last packet with a zero
length one, so a port that has just reported data hands over none: about one
attach in eight ended there, with pySerial calling it a disconnected device
and taking the session with it. Retrying pySerial's read only helped where it
had gathered nothing to drop -- asked for more than a byte it drops what it
has, and the message stream resumes past the hole and reads lengths that do
not match.

Reading the descriptor keeps what arrives. Nothing to hand over yet, as an
empty read or as EAGAIN on the non-blocking descriptor pySerial opens, waits
for more until the port's timeout, and a port that stays quiet ends the read
there, which the interface library reports as its own timeout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
Handing back an empty read is read as the end of the stream: the SLIP decoder
flushes what it is holding and gives it up as a message, so an answer still
arriving comes out truncated and one that has not started comes out empty.
Both reach the interface library as a response it cannot make sense of --
"Invalid response message" and "Response message length mismatch" -- rather
than as the wait that they are.

A read that gathered nothing by the timeout now says so, and the bytes it did
gather are handed over as before for the decoder to carry on with.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018WKhPX5USQQuBdi6jM5nc7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant