From a43969da087723e1d90eca96f42fded6ca07862e Mon Sep 17 00:00:00 2001 From: Rudi Date: Mon, 31 Aug 2026 00:11:30 +0000 Subject: [PATCH] Fix aid keys --- .../lib3270j/graphics/GraphicsPlane.java | 5 -- .../lib3270j/input/InputProcessor.java | 80 +++++++++++++++++ .../lib3270j/input/InputProcessorTest.java | 87 +++++++++++++++++++ 3 files changed, 167 insertions(+), 5 deletions(-) diff --git a/lib3270j/src/main/java/haus/nightmare/lib3270j/graphics/GraphicsPlane.java b/lib3270j/src/main/java/haus/nightmare/lib3270j/graphics/GraphicsPlane.java index dcee093..501cd3f 100644 --- a/lib3270j/src/main/java/haus/nightmare/lib3270j/graphics/GraphicsPlane.java +++ b/lib3270j/src/main/java/haus/nightmare/lib3270j/graphics/GraphicsPlane.java @@ -760,11 +760,6 @@ public class GraphicsPlane { (double) px[offset + i + 1], (double) py[offset + i + 1], boundaryColorArgb, lineType, lineWidth); } - if (pLen >= 3 && (px[offset] != px[offset + pLen - 1] || py[offset] != py[offset + pLen - 1])) { - drawLine((double) px[offset + pLen - 1], (double) py[offset + pLen - 1], - (double) px[offset], (double) py[offset], - boundaryColorArgb, lineType, lineWidth); - } } offset += pLen; } 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 2ca9f4f..4c82018 100644 --- a/lib3270j/src/main/java/haus/nightmare/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/haus/nightmare/lib3270j/input/InputProcessor.java @@ -237,6 +237,86 @@ public class InputProcessor { return; } + if (gocaDecoder != null && gocaDecoder.isGraphicsCursorActive()) { + int gx = gocaDecoder.getGraphicCursorX(); + int gy = gocaDecoder.getGraphicCursorY(); + int cols = (screen != null && screen.getCols() > 0) ? screen.getCols() : 80; + int numRows = (screen != null && screen.getRows() > 0) ? screen.getRows() : 24; + int cursorAddr = screen != null ? screen.getCursorAddress() : 0; + int row = cursorAddr / cols; + int col = cursorAddr % cols; + + byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput( + gx, gy, row, col, aidCode, false, false, false + ); + + StringBuilder sfHex = new StringBuilder(); + for (byte b : sf) { + sfHex.append(String.format("%02X ", b & 0xFF)); + } + log.info(String.format( + "sendAid (graphic): goca=(%d, %d) row=%d col=%d cursorAddr=%d aid=0x%02X SF_HEX=[%s]", + gx, gy, row, col, cursorAddr, aidCode, sfHex.toString().trim() + )); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + // Structured Field AID (0x88) + 56-byte Graphic Input SF + out.write(AID_SF); + try { + out.write(sf); + } catch (java.io.IOException ignored) {} + + // Trailing AID + cursor address + out.write(aidCode); + byte[] caddr = encodeAddress(cursorAddr, numRows, cols); + out.write(caddr[0] & 0xFF); + out.write(caddr[1] & 0xFF); + + if (aidCode == AID_PA1 || aidCode == AID_PA2 || aidCode == AID_PA3) { + sendAidResponse(out.toByteArray()); + return; + } + + if (screen.isFormatted()) { + int size = screen.getRows() * screen.getCols(); + for (int i = 0; i < size; i++) { + ExtendedAttribute ea = screen.getCell(i); + if (ea.isFieldAttribute() && faIsModified(ea.fa & 0xFF)) { + int fieldStart = (i + 1) % size; + + // Always send SBA and address of first character in field + out.write(ORDER_SBA); + byte[] addr = encodeAddress(fieldStart, screen.getRows(), screen.getCols()); + out.write(addr[0] & 0xFF); + out.write(addr[1] & 0xFF); + + // Send all non-null characters in field (suppressing 0x00) + int pos = fieldStart; + while (!screen.getCell(pos).isFieldAttribute()) { + int b = screen.getCell(pos).ec & 0xFF; + if (b != 0x00) { + out.write(b); + } + pos = (pos + 1) % size; + if (pos == fieldStart) break; + } + } + } + } else { + int size = screen.getRows() * screen.getCols(); + for (int i = 0; i < size; i++) { + int b = screen.getCell(i).ec & 0xFF; + if (b != 0x00) { + out.write(b); + } + } + } + + sendAidResponse(out.toByteArray()); + return; + } + if (aidCode == AID_PA1 || aidCode == AID_PA2 || aidCode == AID_PA3) { // PA keys: send AID + optional PID + cursor address only (no modified data) ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/lib3270j/src/test/java/haus/nightmare/lib3270j/input/InputProcessorTest.java b/lib3270j/src/test/java/haus/nightmare/lib3270j/input/InputProcessorTest.java index 6320d06..ab9b850 100644 --- a/lib3270j/src/test/java/haus/nightmare/lib3270j/input/InputProcessorTest.java +++ b/lib3270j/src/test/java/haus/nightmare/lib3270j/input/InputProcessorTest.java @@ -451,4 +451,91 @@ public class InputProcessorTest { // SBA order at byte 3 assertEquals((byte) ORDER_SBA, result[3]); } + + @Test + public void testSendAidWhenGraphicsCursorActiveFraming() { + screen.erase(false); + screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY)); + screen.getCell(1).ec = (byte) 0xC1; // 'A' + screen.setCellFA(5, (byte) (FA_PRINTABLE | FA_PROTECT)); + screen.setCursorAddress(2); + + haus.nightmare.lib3270j.graphics.GraphicsPlane plane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(800, 600); + haus.nightmare.lib3270j.graphics.GocaDecoder goca = new haus.nightmare.lib3270j.graphics.GocaDecoder(plane); + goca.setGraphicsCursorActive(true); + goca.setGraphicCursorPosition(150, -80); + + java.util.concurrent.atomic.AtomicReference sent = new java.util.concurrent.atomic.AtomicReference<>(); + InputProcessor input = new InputProcessor(screen, translator, null) { + @Override + protected void sendAidResponse(byte[] data) { + sent.set(data); + } + }; + input.setGocaDecoder(goca); + + input.sendAid(AID_ENTER); + + byte[] result = sent.get(); + assertNotNull(result); + // Total expected length: + // 1 (AID_SF 0x88) + 56 (SF) + 1 (AID_ENTER 0x7D) + 2 (Cursor Addr) + 1 (SBA) + 2 (Field Addr) + 1 (Data 'A') = 64 bytes + assertEquals(64, result.length); + assertEquals((byte) AID_SF, result[0]); + // SF length = 52 (0x00 0x34) per IBM HOD / GOCA specification + assertEquals(0x00, result[1]); + assertEquals(0x34, result[2]); + // SF ID = 0x0F0F + assertEquals(0x0F, result[3]); + assertEquals(0x0F, result[4]); + // Coordinates in SF at index 1 + 24 = 25 + int gx = (result[25] << 8) | (result[26] & 0xFF); + int gy = (result[27] << 8) | (result[28] & 0xFF); + assertEquals(150, (short) gx); + assertEquals(-80, (short) gy); + // Keyboard constants at index 1 + 31 = 32 and 1 + 33 = 34 + assertEquals(0x07, result[32]); + assertEquals(0x07, result[34]); + assertEquals((byte) 0xFF, result[35]); + assertEquals((byte) AID_ENTER, result[36]); + + // Trailing AID at index 57 + assertEquals((byte) AID_ENTER, result[57]); + // Trailing SBA at 60 + assertEquals((byte) ORDER_SBA, result[60]); + // Trailing field content 'A' at 63 + assertEquals((byte) 0xC1, result[63]); + } + + @Test + public void testSendAidPAWhenGraphicsCursorActiveFraming() { + screen.erase(false); + screen.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY)); + screen.getCell(1).ec = (byte) 0xC1; + screen.setCursorAddress(2); + + haus.nightmare.lib3270j.graphics.GraphicsPlane plane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(800, 600); + haus.nightmare.lib3270j.graphics.GocaDecoder goca = new haus.nightmare.lib3270j.graphics.GocaDecoder(plane); + goca.setGraphicsCursorActive(true); + goca.setGraphicCursorPosition(100, 200); + + java.util.concurrent.atomic.AtomicReference sent = new java.util.concurrent.atomic.AtomicReference<>(); + InputProcessor input = new InputProcessor(screen, translator, null) { + @Override + protected void sendAidResponse(byte[] data) { + sent.set(data); + } + }; + input.setGocaDecoder(goca); + + input.sendAid(AID_PA1); + + byte[] result = sent.get(); + assertNotNull(result); + // 1 (AID_SF 0x88) + 56 (SF) + 1 (AID_PA1 0x6C) + 2 (Cursor Addr) = 60 bytes (no modified field data) + assertEquals(60, result.length); + assertEquals((byte) AID_SF, result[0]); + assertEquals((byte) AID_PA1, result[36]); // Keyboard AID in SF + assertEquals((byte) AID_PA1, result[57]); // Trailing AID + } }