Admdraw appears

This commit is contained in:
2026-08-25 22:50:12 +00:00
parent 3526e682a1
commit 2584f4289f
7 changed files with 103 additions and 51 deletions
@@ -251,26 +251,26 @@ public class GocaDecoder {
*/
private int getOrderLength(byte[] data, int idx, int end) {
int order = data[idx] & 0xFF;
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 ||
order == GocaConstants.G_GEAR || order == GocaConstants.G_ENDSEGM ||
order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_GEIMG ||
order == GocaConstants.G_GPOP || order == GocaConstants.G_GERASE) {
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 || order == GocaConstants.G_COMT) {
return 1;
}
// Orders with optional 0x00 trailing length/qualifier byte (e.g. 3E 00, 71 00, 60 00, 7E 00, 3F 00, 91 00)
if (order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_ENDSEGM ||
order == GocaConstants.G_GEAR || order == GocaConstants.G_GERASE ||
order == GocaConstants.G_GPOP || order == GocaConstants.G_GEIMG) {
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
}
if (idx + 1 >= end) {
return -1;
}
// Fixed 2-byte orders: 1-byte opcode + 1-byte operand
if (order == 0x04 || order == GocaConstants.G_GSMC || order == GocaConstants.G_GSPS ||
order == GocaConstants.G_GSCOL || order == GocaConstants.G_GSMX ||
order == GocaConstants.G_GSBMX || order == GocaConstants.G_GSLT ||
order == GocaConstants.G_GSLW || order == GocaConstants.G_GSMS) {
// All 1-byte operand short orders in 0x02..0x1F range (GSCOL, GSLT, GSLW, GSMS, GSMC, GSPS, GSMX, GSBMX, etc.)
if (order < 0x20) {
return 2;
}
// Flexible 1-byte attribute orders (support both short 2-byte or long 3-byte if len byte == 1)
if (order == GocaConstants.G_GSPT || order == GocaConstants.G_GSMT ||
order == GocaConstants.G_GSCS || order == GocaConstants.G_GSCD ||
order == GocaConstants.G_GSCC || order == GocaConstants.G_GSMP ||
order == GocaConstants.G_GSCC ||
order == GocaConstants.G_GSMS_SET || order == GocaConstants.G_GBAR) {
return (data[idx + 1] == 0x01 && idx + 2 < end) ? 3 : 2;
}
@@ -558,11 +558,16 @@ public class GocaDecoder {
idx += orderLen;
break;
}
case 0x06:
case 0x11:
case GocaConstants.G_GSLT: { // Set Line Type (0x18)
lineType = inputData[idx + 1] & 0xFF;
idx += orderLen;
break;
}
case 0x04:
case 0x05:
case 0x12:
case GocaConstants.G_GSLW: { // Set Line Width (0x19)
lineWidth = inputData[idx + 1] & 0xFF;
idx += orderLen;
@@ -612,9 +617,7 @@ public class GocaDecoder {
idx += orderLen;
break;
}
case 0x04:
case GocaConstants.G_GSMX:
case GocaConstants.G_GSFLW:
case GocaConstants.G_GSMS_SET:
case GocaConstants.G_GPOP: {
idx += orderLen;
@@ -804,6 +807,31 @@ public class GocaDecoder {
break;
}
case GocaConstants.P_SCUDEF: { // 0x21: Drawing Process Control / Segment Execute
if (idx + 5 < end) {
int pLen = data[idx + 1] & 0xFF;
if (pLen >= 6) {
int flags0 = data[idx + 2] & 0xFF;
int flags1 = data[idx + 3] & 0xFF;
int startSeg = ((data[idx + 4] & 0xFF) << 8) | (data[idx + 5] & 0xFF);
int endSeg = (idx + 7 < end) ? (((data[idx + 6] & 0xFF) << 8) | (data[idx + 7] & 0xFF)) : startSeg;
logger.info(String.format("GOCA P_SCUDEF: flags0=0x%02x flags1=0x%02x startSeg=%d endSeg=%d",
flags0, flags1, startSeg, endSeg));
for (int s = startSeg; s <= endSeg; s++) {
byte[] segBytes = segmentStore.get(s);
if (segBytes != null) {
int savedSegId = currentSegId;
currentSegId = s;
SegmentBounds sb = segmentBoundsMap.computeIfAbsent(s, SegmentBounds::new);
activeSegmentsInOrder.remove(sb);
activeSegmentsInOrder.add(sb);
callDepth++;
decodeStreamDirect(segBytes, 0, segBytes.length);
callDepth--;
currentSegId = savedSegId;
}
}
}
}
if (idx + 1 < end) {
int len = (data[idx + 1] & 0xFF) + 2;
idx += len;
@@ -1164,6 +1192,7 @@ public class GocaDecoder {
List<Double> ptsY = new ArrayList<>();
if (fromCurPos) {
trackPoint(curX, curY);
ptsX.add(plane.mapXDouble(curX));
ptsY.add(plane.mapYDouble(curY));
if (inArea) {
@@ -1178,6 +1207,7 @@ public class GocaDecoder {
while (pos + 4 <= end) {
int x = readCoord(data, pos);
int y = readCoord(data, pos + 2);
trackPoint(x, y);
ptsX.add(plane.mapXDouble(x));
ptsY.add(plane.mapYDouble(y));
if (inArea) {
@@ -8,11 +8,11 @@ public class GraphicInputBuilder {
// 56-byte template mask from IBM Host On-Demand (HODInput.java)
private static final byte[] MASK = new byte[] {
0x00, 0x34, 0x0F, 0x0F, 0x00, (byte) 0xC0, 0x00, 0x40,
0x00, 0x38, 0x0F, 0x0F, 0x00, (byte) 0xC0, 0x00, 0x40,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x23, 0x00, 0x23, 0x00, 0x00, 0x00, 0x1F, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x80, 0x00
};
@@ -22,15 +22,15 @@ public class GraphicInputBuilder {
*
* @param gocaX GOCA signed X coordinate (-xMax..+xMax)
* @param gocaY GOCA signed Y coordinate (-yMax..+yMax)
* @param aidCode The 3270 AID code (e.g. 0x7D for ENTER, 0xF3 for PF3)
* @param buttonOrAidCode The mouse button number (1=Pick, 2=Action) or 3270 AID code for keyboard
* @param isMouseAction true if triggered directly by mouse button press, false for keyboard AID
* @param isShift true if shift key was down
* @param isCtrl true if ctrl key was down
* @return 56-byte payload
*/
public static byte[] buildGraphicInput(int gocaX, int gocaY, int aidCode,
public static byte[] buildGraphicInput(int gocaX, int gocaY, int buttonOrAidCode,
boolean isMouseAction, boolean isShift, boolean isCtrl) {
return buildGraphicInput(gocaX, gocaY, aidCode, isMouseAction, isShift, isCtrl, 0, 0);
return buildGraphicInput(gocaX, gocaY, buttonOrAidCode, isMouseAction, isShift, isCtrl, 0, 0);
}
/**
@@ -38,16 +38,18 @@ public class GraphicInputBuilder {
*
* IMPORTANT ARCHITECTURE NOTE:
* Per IBM GA23-0059 / GDDM specifications:
* - Bytes 0-1: Total structured field length (0x0038 = 56 bytes).
* - Bytes 24-27: (gocaX, gocaY) cursor coordinates.
* - Bytes 28-31: Picked Segment Identifier (32-bit big-endian). When clicking on a menu item or
* interactive element (e.g. DRAW button = Segment 2, EXIT button = Segment 5), GDDM requires
* the exact segment ID in bytes 28-31. If hardcoded or mismatched, GDDM rejects the click
* with a WCC 0xF7 alarm beep.
* the exact segment ID in bytes 28-31. If mismatched, GDDM rejects the click with a WCC 0xF7 alarm beep.
* - Bytes 32-33: Pick Correlation Tag (16-bit big-endian) set by G_GSETAG (0x39).
* - Byte 34: Modifier flags (Shift = 0x80, Ctrl = 0x40, 0xFF for keyboard AID).
* - Byte 35: Button ID (0x01 = Button 1 / Pick, 0x02 = Button 2 / Action) or 3270 AID code.
*
* @param gocaX GOCA signed X coordinate (-xMax..+xMax)
* @param gocaY GOCA signed Y coordinate (-yMax..+yMax)
* @param aidCode The 3270 AID code (e.g. 0x7D for ENTER, 0xF3 for PF3)
* @param buttonOrAidCode The mouse button number (1=Pick, 2=Action) or 3270 AID code for keyboard
* @param isMouseAction true if triggered directly by mouse button press, false for keyboard AID
* @param isShift true if shift key was down
* @param isCtrl true if ctrl key was down
@@ -55,12 +57,16 @@ public class GraphicInputBuilder {
* @param pickTag Pick correlation tag
* @return 56-byte payload
*/
public static byte[] buildGraphicInput(int gocaX, int gocaY, int aidCode,
public static byte[] buildGraphicInput(int gocaX, int gocaY, int buttonOrAidCode,
boolean isMouseAction, boolean isShift, boolean isCtrl,
int pickedSegId, int pickTag) {
byte[] sf = new byte[MASK.length];
System.arraycopy(MASK, 0, sf, 0, MASK.length);
// Byte 0-1: Structured Field Length (56 bytes)
sf[0] = (byte) ((MASK.length >> 8) & 0xFF);
sf[1] = (byte) (MASK.length & 0xFF);
// Byte 24-25: GOCA X coordinate (signed 16-bit big-endian)
sf[24] = (byte) ((gocaX >> 8) & 0xFF);
sf[25] = (byte) (gocaX & 0xFF);
@@ -69,28 +75,29 @@ public class GraphicInputBuilder {
sf[26] = (byte) ((gocaY >> 8) & 0xFF);
sf[27] = (byte) (gocaY & 0xFF);
if (pickedSegId != 0) {
if (isMouseAction) {
// Byte 28-31: Picked Segment ID (4 bytes big-endian)
sf[28] = (byte) ((pickedSegId >> 24) & 0xFF);
sf[29] = (byte) ((pickedSegId >> 16) & 0xFF);
sf[30] = (byte) ((pickedSegId >> 8) & 0xFF);
sf[31] = (byte) (pickedSegId & 0xFF);
}
if (isMouseAction) {
if (pickTag != 0) {
// Byte 32-33: Pick Tag / Correlation (2 bytes big-endian)
sf[32] = (byte) ((pickTag >> 8) & 0xFF);
sf[33] = (byte) (pickTag & 0xFF);
}
// Byte 32-33: Pick Tag / Correlation (2 bytes big-endian)
sf[32] = (byte) ((pickTag >> 8) & 0xFF);
sf[33] = (byte) (pickTag & 0xFF);
sf[34] = isShift ? (byte) 0x80 : (isCtrl ? (byte) 0x40 : 0x00);
sf[35] = (byte) (aidCode == 2 ? 0x02 : 0x01); // Button 1 = Pick, Button 2 = Action
sf[35] = (byte) (buttonOrAidCode == 2 ? 0x02 : 0x01); // Button 1 = Pick, Button 2 = Action
} else {
// Keyboard AID (Enter, PF keys)
sf[28] = 0x00;
sf[29] = 0x00;
sf[30] = 0x00;
sf[31] = 0x07;
sf[32] = 0x00;
sf[33] = 0x07;
sf[34] = (byte) 0xFF;
sf[35] = (byte) (aidCode & 0xFF);
sf[35] = (byte) (buttonOrAidCode & 0xFF);
}
return sf;
@@ -325,7 +325,7 @@ public class InputProcessor {
int pickedSeg = gocaDecoder.findPickedSegment(gx, gy);
int pickTag = gocaDecoder.getSegmentTag(pickedSeg);
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
gx, gy, aidCode, true, isShift, isCtrl, pickedSeg, pickTag
gx, gy, button, true, isShift, isCtrl, pickedSeg, pickTag
);
System.err.println(String.format(
"sendGraphicMouseAid: goca=(%d, %d) pickedSeg=%d pickTag=%d btn=%d shift=%b ctrl=%b",
@@ -138,7 +138,7 @@ public class InputProcessorTest {
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(100, -50, AID_ENTER, false, false, false);
assertEquals(56, sf.length);
assertEquals(0x00, sf[0]);
assertEquals(0x34, sf[1]); // Length = 52
assertEquals(0x38, sf[1]); // Length = 56 bytes (0x0038)
assertEquals(0x0F, sf[2]); // SF ID = 0x0F
assertEquals(0x0F, sf[3]); // SF ID = 0x0F
@@ -153,6 +153,37 @@ public class InputProcessorTest {
assertEquals(0x07, sf[33]);
assertEquals((byte) 0xFF, sf[34]);
assertEquals((byte) AID_ENTER, sf[35]);
// Mouse Button 1 with picked segment 2 and tag 5
byte[] sfMouse = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
294, 192, 1, true, false, false, 2, 5
);
assertEquals(56, sfMouse.length);
assertEquals(0x00, sfMouse[0]);
assertEquals(0x38, sfMouse[1]);
int mx = (sfMouse[24] << 8) | (sfMouse[25] & 0xFF);
int my = (sfMouse[26] << 8) | (sfMouse[27] & 0xFF);
assertEquals(294, (short) mx);
assertEquals(192, (short) my);
int segId = ((sfMouse[28] & 0xFF) << 24) | ((sfMouse[29] & 0xFF) << 16) |
((sfMouse[30] & 0xFF) << 8) | (sfMouse[31] & 0xFF);
assertEquals(2, segId);
int tag = ((sfMouse[32] & 0xFF) << 8) | (sfMouse[33] & 0xFF);
assertEquals(5, tag);
assertEquals(0x00, sfMouse[34]);
assertEquals(0x01, sfMouse[35]); // Button 1
// Mouse Button 2 (Action) with no segment (0)
byte[] sfMouse2 = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
0, 0, 2, true, true, false, 0, 0
);
int segId2 = ((sfMouse2[28] & 0xFF) << 24) | ((sfMouse2[29] & 0xFF) << 16) |
((sfMouse2[30] & 0xFF) << 8) | (sfMouse2[31] & 0xFF);
assertEquals(0, segId2);
int tag2 = ((sfMouse2[32] & 0xFF) << 8) | (sfMouse2[33] & 0xFF);
assertEquals(0, tag2);
assertEquals((byte) 0x80, sfMouse2[34]); // Shift modifier
assertEquals(0x02, sfMouse2[35]); // Button 2
}
@Test