Admdraw appears
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
# Fixed Bugs
|
||||
- Complete failure when using `cp term conmode 3270` under VM:
|
||||
Fixed in InputProcessor by sending raw line-mode EBCDIC character data in SSCP-LU mode instead of 3270 AID headers and 1920-byte buffer dumps, and correctly processing subsequent 3270 stream transitions upon CONMODE 3270.
|
||||
|
||||
- Unable to start a 2nd transfer after first completed ("A transfer is already in progress"):
|
||||
Fixed in FTDft by signaling completion upon handling `TR_CLOSE_REQ` / host completion messages, and adding state reset in FileTransfer.
|
||||
|
||||
- IND$FILE CMS and TSO:
|
||||
Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode.
|
||||
|
||||
- Local keyboard input and cursor updates not rendering in terminal:
|
||||
Fixed in ScreenBuffer, InputProcessor, and TerminalPanel by ensuring display snapshot and cursor address are updated synchronously on user input operations (typing, backspace, delete, cursor movement, erase) so the presentation layer immediately renders user keystrokes.
|
||||
|
||||
+1
-4
@@ -118,10 +118,7 @@ public class TestRunner {
|
||||
public static void main(String[] args) {
|
||||
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
|
||||
.selectors(
|
||||
selectPackage("haus.nightmare.lib3270j.graphics"),
|
||||
selectPackage("haus.nightmare.lib3270j.datastream"),
|
||||
selectPackage("haus.nightmare.lib3270j.screen"),
|
||||
selectPackage("haus.nightmare.lib3270j.protocol")
|
||||
selectPackage("haus.nightmare.lib3270j")
|
||||
)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user