diff --git a/lib3270j/src/main/java/haus/nightmare/lib3270j/datastream/DataStreamProcessor.java b/lib3270j/src/main/java/haus/nightmare/lib3270j/datastream/DataStreamProcessor.java index b1fc3e3..19f0208 100644 --- a/lib3270j/src/main/java/haus/nightmare/lib3270j/datastream/DataStreamProcessor.java +++ b/lib3270j/src/main/java/haus/nightmare/lib3270j/datastream/DataStreamProcessor.java @@ -257,6 +257,9 @@ public class DataStreamProcessor { case SNA_CMD_EAU: qreplyPending = false; programSymbolManager.commitStagedSymbols(); + if (inputProcessor != null) { + inputProcessor.setLastAid(AID_NO); + } log.info(">>> EAU: erasing all unprotected fields"); screen.eraseAllUnprotected(); currentFg = 0; @@ -290,6 +293,7 @@ public class DataStreamProcessor { cmd == CMD_EWA || cmd == SNA_CMD_EWA); if (!isWriteCmd && keyboardRestore && inputProcessor != null && !contentionResolution) { inputProcessor.setKeyboardLocked(false); + inputProcessor.setLastAid(AID_NO); } // Debug: dump non-empty screen lines @@ -389,10 +393,14 @@ public class DataStreamProcessor { if (kbdRestore) { unlockPending = true; unlockSysPending = true; + if (inputProcessor != null) { + inputProcessor.setLastAid(AID_NO); + } } if (!contentionResolution && kbdRestore && inputProcessor != null) { inputProcessor.setKeyboardLocked(false); + inputProcessor.setLastAid(AID_NO); } if (resetMdt) { @@ -1053,7 +1061,7 @@ public class DataStreamProcessor { // ========== Read Modified ========== public void processReadModified(boolean all) { - int aid = (inputProcessor != null && inputProcessor.getLastAid() != 0) + int aid = (inputProcessor != null && inputProcessor.isKeyboardLocked() && inputProcessor.getLastAid() != 0 && inputProcessor.getLastAid() != AID_NO) ? inputProcessor.getLastAid() : AID_NO; processReadModified(aid, all); diff --git a/lib3270j/src/main/java/haus/nightmare/lib3270j/input/InputProcessor.java b/lib3270j/src/main/java/haus/nightmare/lib3270j/input/InputProcessor.java index d78e885..6bd6964 100644 --- a/lib3270j/src/main/java/haus/nightmare/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/haus/nightmare/lib3270j/input/InputProcessor.java @@ -129,6 +129,9 @@ public class InputProcessor { lockStateListener.onLockStateChanged(locked); } } + if (!locked) { + this.lastAid = AID_NO; + } } public boolean isInsertMode() { return insertMode; } public void setInsertMode(boolean insert) { @@ -756,7 +759,7 @@ public class InputProcessor { * Build inbound 3270 Read Modified data stream (AID + Cursor + SBA + Modified fields). */ public byte[] buildReadModifiedInboundData() { - return buildReadModifiedInboundData(lastAid != 0 ? lastAid : AID_ENTER, false); + return buildReadModifiedInboundData(lastAid != 0 ? lastAid : AID_NO, false); } /** @@ -1659,6 +1662,7 @@ public class InputProcessor { /** Reset (unlock keyboard, cancel insert mode, clear OIA error states). */ public void reset() { setKeyboardLocked(false); + lastAid = AID_NO; insertMode = false; if (oia != null) { oia.setInputInhibited(ECLConstants.INHIBIT_NOT_INHIBITED); diff --git a/lib3270j/src/test/java/haus/nightmare/lib3270j/datastream/DataStreamProcessorTest.java b/lib3270j/src/test/java/haus/nightmare/lib3270j/datastream/DataStreamProcessorTest.java index 947712d..7c1aaff 100644 --- a/lib3270j/src/test/java/haus/nightmare/lib3270j/datastream/DataStreamProcessorTest.java +++ b/lib3270j/src/test/java/haus/nightmare/lib3270j/datastream/DataStreamProcessorTest.java @@ -358,4 +358,69 @@ public class DataStreamProcessorTest { assertEquals((byte) AID_SF, sent[0], "Query reply header AID must be AID_SF (0x88)"); assertFalse(processor.isQreplyPending(), "qreplyPending should be cleared after reply is sent"); } + + @Test + public void testStandaloneReadModifiedReturnsAidNo() { + haus.nightmare.lib3270j.input.InputProcessor input = new haus.nightmare.lib3270j.input.InputProcessor(screen, translator, null); + processor.setInputProcessor(input); + + // Simulate operator previously pressing Enter + input.setLastAid(AID_ENTER); + input.setKeyboardLocked(true); + + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + processor.setOutputSender(out::writeBytes); + + // Host responds with Erase/Write Alternate (0x7E) with WCC 0xC3 (kbdRestore=true) + byte[] ewa = new byte[] { (byte) SNA_CMD_EWA, (byte) 0xC3 }; + processor.processRecord(ewa, 0, ewa.length, true); + + assertFalse(input.isKeyboardLocked(), "Keyboard should be restored by WCC"); + assertEquals(AID_NO, input.getLastAid(), "lastAid should be reset to AID_NO on keyboard restore"); + + // Host subsequently polls terminal with standalone Read Modified (0xF6) + byte[] rm = new byte[] { (byte) SNA_CMD_RM }; + out.reset(); + processor.processRecord(rm, 0, rm.length, true); + + byte[] sent = out.toByteArray(); + assertTrue(sent.length >= 3, "Read Modified response must contain at least AID and 2-byte cursor address"); + assertEquals((byte) AID_NO, sent[0], "Standalone Read Modified from host must respond with AID_NO (0x60)"); + assertFalse(input.isKeyboardLocked(), "Keyboard must remain unlocked after host Read Modified poll"); + } + + @Test + public void testStandaloneReadModifiedWithModifiedFieldSendsDataAndAidNo() { + haus.nightmare.lib3270j.input.InputProcessor input = new haus.nightmare.lib3270j.input.InputProcessor(screen, translator, null); + processor.setInputProcessor(input); + + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + processor.setOutputSender(out::writeBytes); + + // Write a screen with an unprotected field at pos 10, WCC kbdRestore + byte[] initStream = new byte[] { + (byte) CMD_EW, (byte) 0xC3, + (byte) ORDER_SBA, 0x00, 0x0A, // SBA 10 + (byte) ORDER_SF, (byte) FA_PRINTABLE, // unprotected + (byte) 0xC1, (byte) 0xC2 // EBCDIC 'A', 'B' + }; + processor.processRecord(initStream, 0, initStream.length, true); + + // Modify the field by setting MDT (e.g. user typed into it) + screen.getCell(10).fa = (byte) (screen.getCell(10).fa | FA_MODIFY); + + // Host polls with 0xF6 (SNA_CMD_RM) + out.reset(); + byte[] rm = new byte[] { (byte) SNA_CMD_RM }; + processor.processRecord(rm, 0, rm.length, true); + + byte[] sent = out.toByteArray(); + assertTrue(sent.length > 3, "Should include SBA and field contents for modified field"); + assertEquals((byte) AID_NO, sent[0], "AID must be AID_NO (0x60)"); + assertEquals((byte) ORDER_SBA, sent[3], "Order must be SBA"); + assertEquals((byte) 0xC1, sent[6], "First char should be 'A'"); + assertEquals((byte) 0xC2, sent[7], "Second char should be 'B'"); + assertTrue((screen.getCell(10).fa & FA_MODIFY) != 0, "MDT must not be cleared by Read Modified command"); + } } +