Fix aid keys

This commit is contained in:
2026-08-31 00:11:30 +00:00
parent e18d2f436f
commit 984f2f825c
3 changed files with 167 additions and 5 deletions
@@ -760,11 +760,6 @@ public class GraphicsPlane {
(double) px[offset + i + 1], (double) py[offset + i + 1], (double) px[offset + i + 1], (double) py[offset + i + 1],
boundaryColorArgb, lineType, lineWidth); 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; offset += pLen;
} }
@@ -237,6 +237,86 @@ public class InputProcessor {
return; 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) { if (aidCode == AID_PA1 || aidCode == AID_PA2 || aidCode == AID_PA3) {
// PA keys: send AID + optional PID + cursor address only (no modified data) // PA keys: send AID + optional PID + cursor address only (no modified data)
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -451,4 +451,91 @@ public class InputProcessorTest {
// SBA order at byte 3 // SBA order at byte 3
assertEquals((byte) ORDER_SBA, result[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<byte[]> 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<byte[]> 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
}
} }