Fix ADMDRAW targetting

This commit is contained in:
2026-08-30 23:33:33 +00:00
parent 3a576b79b4
commit a3c4b95379
15 changed files with 1381 additions and 212 deletions
@@ -81,6 +81,10 @@ public class EbcdicTranslator {
return activeCodePage.isDBCS();
}
public synchronized boolean isDBCS() {
return isDBCSCodePage();
}
/**
* Translate double-byte EBCDIC pair (b1, b2) to Unicode.
*/
@@ -1020,7 +1020,7 @@ public class DataStreamProcessor {
switch (type) {
case SF_RP_QUERY:
log.info("ReadPartition Query — sending all query replies");
log.info("ReadPartition Query — sending base query replies");
graphicsPlane.clear();
gocaDecoder.resetDefaults();
sendAllQueryReplies();
@@ -1029,7 +1029,9 @@ public class DataStreamProcessor {
if (fieldLen >= 6) {
int listType = data[offset + 5] & 0xFF;
log.info("ReadPartition QueryList type=" + String.format("0x%02x", listType));
if (listType == SF_RPQ_ALL || listType == SF_RPQ_EQUIV) {
if (listType == SF_RPQ_ALL) {
sendCompleteQueryReplies();
} else if (listType == SF_RPQ_EQUIV) {
sendAllQueryReplies();
} else if (listType == SF_RPQ_LIST) {
// Send only requested query replies
@@ -1071,7 +1073,22 @@ public class DataStreamProcessor {
if ((i + 1) % 32 == 0)
sb.append("\n ");
}
log.warning(">>> SENDING Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
log.warning(">>> SENDING Base Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
if (outputSender != null) {
outputSender.send3270Data(qr);
}
}
private void sendCompleteQueryReplies() {
byte[] qr = qrBuilder.buildCompleteQueryReplies(screen.getMaxCols(), screen.getMaxRows(),
screen.getMaxCols() * screen.getMaxRows());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < qr.length; i++) {
sb.append(String.format("%02x ", qr[i] & 0xFF));
if ((i + 1) % 32 == 0)
sb.append("\n ");
}
log.warning(">>> SENDING Complete Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
if (outputSender != null) {
outputSender.send3270Data(qr);
}
@@ -15,16 +15,17 @@ public class QueryReplyBuilder {
private static final Logger log = Logger.getLogger(QueryReplyBuilder.class.getName());
// Canned values from 3279-2 (matching sf.c)
private static final int SW_3279_2 = 0x09;
private static final int SH_3279_2 = 0x0c;
private static final int Xr_3279_2 = 0x000a02e5;
private static final int Yr_3279_2 = 0x0002006f;
// Usable Area physical dimensions matching IBM Host On-Demand QR_USEAREA_STRING (Inches)
private static final int Xr_HOD = 0x00020089;
private static final int Yr_HOD = 0x00020085;
private final ScreenBuffer screen;
private GraphicsMode graphicsMode = GraphicsMode.BOTH;
// Base query reply codes (text mode)
// Base query reply codes (text mode, matches HOD DS3270.java queryEquiv)
private static final int[] SUPPORTED_QR_BASE = {
QR_SUMMARY, // 0x80 summary must list itself
QR_USABLE_AREA, // 0x81
@@ -34,10 +35,11 @@ public class QueryReplyBuilder {
QR_HIGHLIGHTING, // 0x87
QR_REPLY_MODES, // 0x88
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
QR_IMP_PART, // 0xa6
QR_AUXDA, // 0x99 - Auxiliary Devices
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
};
// Vector graphics query reply codes matching HOD DS3270.java line 1723
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
private static final int[] SUPPORTED_QR_VECTOR = {
QR_SUMMARY, // 0x80
QR_USABLE_AREA, // 0x81
@@ -46,17 +48,17 @@ public class QueryReplyBuilder {
QR_COLOR, // 0x86
QR_HIGHLIGHTING, // 0x87
QR_REPLY_MODES, // 0x88
QR_SAVE_RESTORE, // 0x8c
QR_OUTLINING, // 0x8c
QR_DDM, // 0x95
QR_TRANSPARENCY, // 0x99
QR_AUXDA, // 0x99
QR_IMP_PART, // 0xa6
QR_RPQ_NAMES, // 0xa8
QR_GRAPHICS, // 0xb0
QR_GIMAGE, // 0xb1
QR_AUX_DEV, // 0xb2
QR_OEM_FMT, // 0xb3
QR_GCOLOR, // 0xb4
QR_GSYMBOLS, // 0xb6
QR_TRANSPARENCY, // 0xa8
QR_SEGMENT, // 0xb0
QR_PROCEDURE, // 0xb1
QR_LINETYPE, // 0xb2
QR_PORT, // 0xb3
QR_GRCOLOR, // 0xb4
QR_GRSYMBOLSET, // 0xb6
};
public QueryReplyBuilder(ScreenBuffer screen) {
@@ -77,7 +79,8 @@ public class QueryReplyBuilder {
}
/**
* Build all query replies as a single AID_SF + structured field response.
* Build base query replies in response to a generic Read Partition Query (0x02).
* Returns base text/presentation summary structured fields (matches HOD DS3270.java line 1723).
*/
public byte[] buildAllQueryReplies(int maxCols, int maxRows, int bufferSize) {
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
@@ -85,62 +88,90 @@ public class QueryReplyBuilder {
// AID byte for structured field
out.write(AID_SF);
// Summary
// Summary (0x80) - lists all supported capabilities
appendQueryReply(out, QR_SUMMARY, buildSummary());
// Usable Area
// Usable Area (0x81)
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
// Alpha Partitions
// Alpha Partitions (0x84)
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
// Character Sets
// Character Sets (0x85)
appendQueryReply(out, QR_CHARSETS, buildCharsets());
// Color
// Color (0x86)
appendQueryReply(out, QR_COLOR, buildColor());
// Highlighting
// Highlighting (0x87)
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
// Reply Modes (0x88)
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
if (graphicsMode.isVectorGraphicsEnabled()) {
// Save/Restore (0x8C)
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
if (isDbcs) {
// Outlining (0x8C)
appendQueryReply(out, QR_OUTLINING, buildOutlining());
// DBCS Asia (0x91)
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
}
// Distributed Data Management (0x95)
appendQueryReply(out, QR_DDM, buildDdm(4096));
if (graphicsMode.isVectorGraphicsEnabled()) {
// Transparency (0x99)
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
}
// Auxiliary Devices (0x99)
appendQueryReply(out, QR_AUXDA, buildAuxDa());
// Implicit Partition (0xA6)
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
// Vector Graphics QRs if enabled
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
return out.toByteArray();
}
/**
* Build complete query replies including vector graphics (when SF_RPQ_ALL 0x80 is requested).
*/
public byte[] buildCompleteQueryReplies(int maxCols, int maxRows, int bufferSize) {
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
out.write(AID_SF);
appendQueryReply(out, QR_SUMMARY, buildSummary());
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
appendQueryReply(out, QR_CHARSETS, buildCharsets());
appendQueryReply(out, QR_COLOR, buildColor());
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
appendQueryReply(out, QR_OUTLINING, buildOutlining());
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
if (isDbcs) {
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
}
appendQueryReply(out, QR_DDM, buildDdm(4096));
appendQueryReply(out, QR_AUXDA, buildAuxDa());
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames()); // 0xA8
appendQueryReply(out, QR_GRAPHICS, buildGraphics(maxCols, maxRows)); // 0xB0
appendQueryReply(out, QR_GIMAGE, buildGImage(maxCols, maxRows)); // 0xB1
appendQueryReply(out, QR_AUX_DEV, buildAuxDev()); // 0xB2
appendOemFmt(out); // 0xB3
appendQueryReply(out, QR_GCOLOR, buildGColor()); // 0xB4
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols()); // 0xB6
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency()); // 0xA8
appendQueryReply(out, QR_SEGMENT, buildSegment(maxCols, maxRows)); // 0xB0
appendQueryReply(out, QR_PROCEDURE, buildProcedure(maxCols, maxRows)); // 0xB1
appendQueryReply(out, QR_LINETYPE, buildLineType()); // 0xB2
appendPort(out); // 0xB3
appendQueryReply(out, QR_GRCOLOR, buildGrColor()); // 0xB4
appendQueryReply(out, QR_GRSYMBOLSET, buildGrSymbolSet()); // 0xB6
}
log.info("Built " + out.size() + " bytes of all query replies (graphicsMode=" + graphicsMode + ")");
log.info("Built " + out.size() + " bytes of complete query replies (graphicsMode=" + graphicsMode + ")");
return out.toByteArray();
}
/**
* Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST).
* For any unsupported requested query code, emits a QR_NULL (0xFF) structured field
* matching x3270 sf.c behavior.
* matching HOD DS3270.java line 1835.
*/
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
if (requestedCodes == null || requestedCodes.length == 0) {
@@ -174,72 +205,70 @@ public class QueryReplyBuilder {
case QR_REPLY_MODES:
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
break;
case QR_SAVE_RESTORE:
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
case QR_OUTLINING: // 0x8C
appendQueryReply(out, QR_OUTLINING, buildOutlining());
break;
case QR_DBCS_ASIA: // 0x91
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_DDM:
case QR_DDM: // 0x95
appendQueryReply(out, QR_DDM, buildDdm(4096));
break;
case QR_TRANSPARENCY:
case QR_AUXDA: // 0x99
appendQueryReply(out, QR_AUXDA, buildAuxDa());
break;
case QR_IMP_PART: // 0xA6
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
break;
case QR_TRANSPARENCY: // 0xA8
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_IMP_PART:
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
break;
case QR_RPQ_NAMES:
case QR_RPQNAMES:
case QR_SEGMENT: // 0xB0
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, code, buildRpqNames());
appendQueryReply(out, QR_SEGMENT, buildSegment(maxCols, maxRows));
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_GRAPHICS:
case QR_PROCEDURE: // 0xB1
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_GRAPHICS, buildGraphics(maxCols, maxRows));
appendQueryReply(out, QR_PROCEDURE, buildProcedure(maxCols, maxRows));
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_GIMAGE:
case QR_LINETYPE: // 0xB2
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_GIMAGE, buildGImage(maxCols, maxRows));
appendQueryReply(out, QR_LINETYPE, buildLineType());
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_AUX_DEV:
case QR_PORT: // 0xB3
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_AUX_DEV, buildAuxDev());
appendPort(out);
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_OEM_FMT:
case QR_GRCOLOR: // 0xB4
if (graphicsMode.isVectorGraphicsEnabled()) {
appendOemFmt(out);
appendQueryReply(out, QR_GRCOLOR, buildGrColor());
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_GCOLOR:
case QR_GRSYMBOLSET: // 0xB6
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_GCOLOR, buildGColor());
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
break;
case QR_GSYMBOLS:
if (graphicsMode.isVectorGraphicsEnabled()) {
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols());
appendQueryReply(out, QR_GRSYMBOLSET, buildGrSymbolSet());
} else {
appendQueryReply(out, QR_NULL, new byte[0]);
}
@@ -268,8 +297,12 @@ public class QueryReplyBuilder {
private byte[] buildSummary() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
for (int code : codes) {
out.write(code);
if (isDbcs && code == QR_OUTLINING) {
out.write(QR_DBCS_ASIA); // 0x91
}
}
return out.toByteArray();
}
@@ -282,17 +315,17 @@ public class QueryReplyBuilder {
out.write(maxCols & 0xFF); // usable width low
out.write((maxRows >> 8) & 0xFF); // usable height high
out.write(maxRows & 0xFF); // usable height low
out.write(0x01); // units (mm)
// Xr (4 bytes) - canned from 3279-2
out.write((Xr_3279_2 >> 24) & 0xFF);
out.write((Xr_3279_2 >> 16) & 0xFF);
out.write((Xr_3279_2 >> 8) & 0xFF);
out.write(Xr_3279_2 & 0xFF);
// Yr (4 bytes) - canned from 3279-2
out.write((Yr_3279_2 >> 24) & 0xFF);
out.write((Yr_3279_2 >> 16) & 0xFF);
out.write((Yr_3279_2 >> 8) & 0xFF);
out.write(Yr_3279_2 & 0xFF);
out.write(0x00); // units (0x00 = inches, matching IBM Host On-Demand QR_USEAREA_STRING)
// Xr (4 bytes) - matching IBM Host On-Demand QR_USEAREA_STRING
out.write((Xr_HOD >> 24) & 0xFF);
out.write((Xr_HOD >> 16) & 0xFF);
out.write((Xr_HOD >> 8) & 0xFF);
out.write(Xr_HOD & 0xFF);
// Yr (4 bytes) - matching IBM Host On-Demand QR_USEAREA_STRING
out.write((Yr_HOD >> 24) & 0xFF);
out.write((Yr_HOD >> 16) & 0xFF);
out.write((Yr_HOD >> 8) & 0xFF);
out.write(Yr_HOD & 0xFF);
int charW = getCharWidth();
int charH = getCharHeight();
out.write(charW); // AW
@@ -304,6 +337,9 @@ public class QueryReplyBuilder {
}
public int getCharWidth() {
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
return 12;
}
return SW_3279_2; // 9
}
@@ -458,7 +494,28 @@ public class QueryReplyBuilder {
return out.toByteArray();
}
private byte[] buildGraphics(int maxCols, int maxRows) {
public byte[] buildOutlining() {
// HOD QueryReply3270Constants.java QR_OUTLINING_STRING ("\u0000\n\u0081\u008c\u0000\u0000\u0000\u0000\u0000\u0000")
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
}
public byte[] buildDbcsAsia() {
// HOD QueryReply3270Constants.java QR_DBCS_ASIA_STRING ("\u0000\u000b\u0081\u0091\u0000\u0003\u0001\u0080\u0003\u0002\u0001")
return new byte[]{ 0x00, 0x03, 0x01, (byte) 0x80, 0x03, 0x02, 0x01 };
}
public byte[] buildAuxDa() {
// HOD QueryReply3270Constants.java QR_AUXDA_STRING ("\u0000\u0006\u0081\u0099\u0000\u0000")
return new byte[]{ 0x00, 0x00 };
}
public byte[] buildTransparency() {
// HOD QueryReply3270Constants.java QR_TRANSPARENCY_STRING ("\u0000\t\u0081\u00a8\u0002\u0000\u00f0\u00ff\u00ff")
return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF };
}
public byte[] buildSegment(int maxCols, int maxRows) {
// HOD QueryReply3270Constants.java QR_SEGMENT_STRING ("\u0000\u000b\u0081\u00b0\u0080\u0002\u0000\u0000\u0000\u00fc\u0000")
return new byte[]{
(byte) 0x80, 0x02,
0x00, 0x00,
@@ -467,7 +524,12 @@ public class QueryReplyBuilder {
};
}
private byte[] buildGImage(int maxCols, int maxRows) {
public byte[] buildGraphics(int maxCols, int maxRows) {
return buildSegment(maxCols, maxRows);
}
public byte[] buildProcedure(int maxCols, int maxRows) {
// HOD QueryReply3270Constants.java QR_PROCEDURE_STRING ("\u0000\u0015\u0081\u00b1\u0000\u0001\u0000\u0000\u0000\u00fc\u0000\u0006@\u0006@\u0006\u0001\u00ff\u00ff\u00ff\u00f0")
return new byte[]{
0x00, 0x01,
0x00, 0x00,
@@ -478,11 +540,12 @@ public class QueryReplyBuilder {
};
}
public byte[] buildAuxDevice() {
return buildAuxDev();
public byte[] buildGImage(int maxCols, int maxRows) {
return buildProcedure(maxCols, maxRows);
}
private byte[] buildAuxDev() {
public byte[] buildLineType() {
// HOD QueryReply3270Constants.java QR_LINETYPE_STRING ("\u0000\u0018\u0081\u00b2\u0000\t\u0000\u0007\u0001\u0001\u0002\u0002\u0003\u0003\u0004\u0004\u0005\u0005\u0006\u0006\u0007\u0007\b\b")
return new byte[]{
0x00, 0x09, 0x00, 0x07,
0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04,
@@ -490,38 +553,36 @@ public class QueryReplyBuilder {
};
}
private byte[] buildSaveRestore() {
// HOD DS3270.java line 1768: 6 bytes payload
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
public byte[] buildAuxDev() {
return buildLineType();
}
private byte[] buildTransparency() {
// HOD DS3270.java line 1782: 2 bytes payload
return new byte[]{ 0x00, 0x00 };
public byte[] buildAuxDevice() {
return buildLineType();
}
private byte[] buildRpqNames() {
// HOD DS3270.java line 1798: 5 bytes payload
return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF };
}
private void appendOemFmt(ByteArrayOutputStream out) {
// HOD DS3270.java line 1814: 4 distinct OEM format sub-fields
appendQueryReply(out, QR_OEM_FMT, new byte[]{
public void appendPort(ByteArrayOutputStream out) {
// HOD QueryReply3270Constants.java QR_PORT_STRING (4 OEM format sub-fields, 64 bytes total)
appendQueryReply(out, QR_PORT, new byte[]{
0x00, 0x03, 0x02, (byte) 0x90, 0x09, 0x01, 0x00, 0x03, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
});
appendQueryReply(out, QR_OEM_FMT, new byte[]{
appendQueryReply(out, QR_PORT, new byte[]{
0x00, 0x04, 0x08, 0x40, 0x07, 0x03, 0x00, 0x03, (byte) 0x80, 0x00, 0x02
});
appendQueryReply(out, QR_OEM_FMT, new byte[]{
appendQueryReply(out, QR_PORT, new byte[]{
0x00, 0x05, 0x02, (byte) 0x80, 0x09, 0x01, 0x00, 0x01, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
});
appendQueryReply(out, QR_OEM_FMT, new byte[]{
appendQueryReply(out, QR_PORT, new byte[]{
0x00, 0x07, 0x08, 0x40, 0x07, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1C
});
}
private byte[] buildGColor() {
public void appendOemFmt(ByteArrayOutputStream out) {
appendPort(out);
}
public byte[] buildGrColor() {
// HOD QueryReply3270Constants.java QR_GRCOLOR_STRING (109 bytes total)
ByteArrayOutputStream out = new ByteArrayOutputStream(110);
out.write(0x00); out.write(0x04); out.write(0x00); out.write(0xFF); out.write(0xFF);
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x10);
@@ -541,11 +602,36 @@ public class QueryReplyBuilder {
return out.toByteArray();
}
private byte[] buildGSymbols() {
public byte[] buildGColor() {
return buildGrColor();
}
public byte[] buildGrSymbolSet() {
int charW = getCharWidth();
int charH = getCharHeight();
int cgcsgid = 0x02B9;
int cpgid = 0x0025;
if (screen != null && screen.getTranslator() != null) {
cgcsgid = screen.getTranslator().getCgcsgid();
cpgid = screen.getTranslator().getCpgid();
}
// HOD QueryReply3270Constants.java QR_GRSYMBOLSET_S_STRING ("\u0000!\u0081\u00b6...")
return new byte[]{
0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00,
0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
0x01, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00,
(byte) charW, (byte) charH, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
}
public byte[] buildGSymbols() {
return buildGrSymbolSet();
}
public byte[] buildSaveRestore() {
return buildOutlining();
}
public byte[] buildRpqNames() {
return buildTransparency();
}
}
@@ -148,6 +148,8 @@ public class GocaDecoder {
return graphicCursorY;
}
public static final int GDDM_CURSOR_OFFSET_Y = 0;
public synchronized void setGraphicCursorPosition(int x, int y) {
this.graphicCursorX = x;
this.graphicCursorY = y;
@@ -925,11 +927,7 @@ public class GocaDecoder {
}
break;
}
case GocaConstants.P_SETCUR: { // 0x31: Set Graphic Cursor Position
if (idx + 5 <= end) {
this.graphicCursorX = readCoord(data, idx + 2);
this.graphicCursorY = readCoord(data, idx + 4);
}
case GocaConstants.P_SETCUR: { // 0x31: Set Graphic Cursor Position (HODGraphicCursorPosition - No-op per HOD architecture)
if (idx + 1 < end) {
int len = data[idx + 1] & 0xFF;
idx += 2 + len;
@@ -1372,24 +1370,41 @@ public class GocaDecoder {
double cw = charWidth > 0 ? ((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 10.0;
double ch = charHeight > 0 ? ((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 16.0)) : 14.0;
int totalW = textLen * (charWidth > 0 ? charWidth : 9);
int totalH = (charHeight > 0 ? charHeight : 14);
trackPoint(startX, startY);
trackPoint(startX + totalW, startY + totalH);
trackPoint(startX + totalW, startY - totalH);
int cellW = (charWidth > 0 ? charWidth : 9);
int cellH = (charHeight > 0 ? charHeight : 16);
switch (charDir) {
case GocaConstants.CD_TB:
trackPoint(startX, startY);
trackPoint(startX + cellW, startY - textLen * cellH);
break;
case GocaConstants.CD_RL:
trackPoint(startX, startY);
trackPoint(startX - textLen * cellW, startY + cellH);
break;
case GocaConstants.CD_BT:
trackPoint(startX, startY);
trackPoint(startX + cellW, startY + textLen * cellH);
break;
case GocaConstants.CD_LR:
case GocaConstants.CD_DEFAULT:
default:
trackPoint(startX, startY);
trackPoint(startX + textLen * cellW, startY + cellH);
break;
}
if (charSet != 0 && programSymbolManager != null) {
for (int i = 0; i < textLen; i++) {
int code = data[pos + i] & 0xFF;
double px = plane.mapXDouble(startX);
double py = plane.mapYDouble(startY) - ch;
double py = plane.mapYDouble(startY);
ProgramSymbolSet.SymbolSlot slot = programSymbolManager.getSymbol(charSet, code);
if (slot != null) {
int[] rgb = slot.getRgbPixels(curColor, 0);
int symW = slot.getWidth();
int symH = slot.getHeight();
int ipx = (int) Math.round(px);
int ipy = (int) Math.round(py);
int ipy = (int) Math.round(py - ch);
int icw = (int) Math.round(cw);
int ich = (int) Math.round(ch);
for (int dy = 0; dy < ich; dy++) {
@@ -1420,15 +1435,33 @@ public class GocaDecoder {
String text = new String(chars);
if (charPrecision == GocaConstants.CP_STROKE) {
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY) - ch, text,
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
curColor, cw, ch, charDir, charAngle);
} else {
plane.drawText(plane.mapXDouble(startX), plane.mapYDouble(startY) - ch, text,
plane.drawText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
curColor, cw, ch, charDir, charAngle);
}
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
curY = startY;
switch (charDir) {
case GocaConstants.CD_TB:
curX = startX;
curY = startY - (textLen * (charHeight > 0 ? charHeight : 16));
break;
case GocaConstants.CD_RL:
curX = startX - (textLen * (charWidth > 0 ? charWidth : 9));
curY = startY;
break;
case GocaConstants.CD_BT:
curX = startX;
curY = startY + (textLen * (charHeight > 0 ? charHeight : 16));
break;
case GocaConstants.CD_LR:
case GocaConstants.CD_DEFAULT:
default:
curX = startX + (textLen * (charWidth > 0 ? charWidth : 9));
curY = startY;
break;
}
}
/**
@@ -885,10 +885,15 @@ public class GraphicsPlane {
if (text == null || text.isEmpty()) return;
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
double curX = x;
double curY = y;
double cw = cellWidth > 0 ? cellWidth : 12.0;
double ch = cellHeight > 0 ? cellHeight : 20.0;
double curX = x;
double curY = y;
if (dir == GocaConstants.CD_TB) {
curY += ch;
} else if (dir == GocaConstants.CD_RL) {
curX -= cw;
}
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
@@ -942,7 +947,7 @@ public class GraphicsPlane {
int vx = ((VectorSymbolData.vss_data[dataPtr + p * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 1] & 0xFF);
int vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
px[p] = x + ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
py[p] = y + ((double) (VectorSymbolData.VSS_HEIGHT - vy) / VectorSymbolData.VSS_HEIGHT) * ch;
py[p] = y - ((double) vy / VectorSymbolData.VSS_HEIGHT) * ch;
ipx[p] = (int) Math.round(px[p]);
ipy[p] = (int) Math.round(py[p]);
}
@@ -177,6 +177,10 @@ public class InputProcessor {
*/
public void sendAid(int aidCode) {
System.err.println("sendAid called: 0x" + Integer.toHexString(aidCode) + " locked=" + keyboardLocked);
if (aidCode == AID_SYSREQ) {
sysReq();
return;
}
if (keyboardLocked && aidCode != AID_CLEAR) {
System.err.println("Keyboard locked, dropping AID");
return;
@@ -358,21 +362,12 @@ public class InputProcessor {
if (gocaDecoder != null && gocaDecoder.isGraphicsCursorActive()) {
int gx = gocaDecoder.getGraphicCursorX();
int gy = gocaDecoder.getGraphicCursorY();
int cursorAddr = screen != null ? screen.getCursorAddress() : 0;
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;
if (gocaDecoder.getGraphicsPlane() != null) {
int px = gocaDecoder.getGraphicsPlane().mapX(gx);
int py = gocaDecoder.getGraphicsPlane().mapY(gy);
int canvasW = gocaDecoder.getGraphicsPlane().getCanvasWidth();
int canvasH = gocaDecoder.getGraphicsPlane().getCanvasHeight();
int numRows = (screen != null && screen.getRows() > 0) ? screen.getRows() : 43;
if (canvasH > 0) row = (py * numRows) / canvasH;
if (canvasW > 0) col = (px * cols) / canvasW;
}
byte[] sf = haus.nightmare.lib3270j.graphics.GraphicInputBuilder.buildGraphicInput(
gx, gy, row, col, button, true, isShift, isCtrl
);
@@ -382,8 +377,8 @@ public class InputProcessor {
sfHex.append(String.format("%02X ", b & 0xFF));
}
log.info(String.format(
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d btn=%d shift=%b ctrl=%b SF_HEX=[%s]",
gx, gy, row, col, button, isShift, isCtrl, sfHex.toString().trim()
"sendGraphicMouseAid: goca=(%d, %d) row=%d col=%d cursorAddr=%d btn=%d shift=%b ctrl=%b SF_HEX=[%s]",
gx, gy, row, col, cursorAddr, button, isShift, isCtrl, sfHex.toString().trim()
));
// Structured Field AID (0x88) + 56-byte Graphic Input SF
@@ -394,7 +389,7 @@ public class InputProcessor {
// Trailing AID + cursor address + modified fields matching HOD DS3270.sendMouseAid
out.write(aidCode);
byte[] caddr = encodeAddress(screen.getCursorAddress(), screen.getRows(), screen.getCols());
byte[] caddr = encodeAddress(cursorAddr, numRows, cols);
out.write(caddr[0] & 0xFF);
out.write(caddr[1] & 0xFF);
@@ -755,7 +750,11 @@ public class InputProcessor {
/** SysReq key. */
public void sysReq() {
reset();
if (fsm != null && fsm.isTn3270eNegotiated()) {
fsm.handleSysReq();
} else {
reset();
}
}
/** Reset (unlock keyboard, cancel insert mode). */
@@ -208,29 +208,40 @@ public final class DS3270Constants {
public static final int SF_TRANSFER_DATA = 0xd0;
// ========== Query Reply codes ==========
public static final int QR_SUMMARY = 0x80;
public static final int QR_USABLE_AREA = 0x81;
public static final int QR_IMAGE = 0x82;
public static final int QR_TEXT_PART = 0x83;
public static final int QR_ALPHA_PART = 0x84;
public static final int QR_CHARSETS = 0x85;
public static final int QR_COLOR = 0x86;
public static final int QR_HIGHLIGHTING = 0x87;
public static final int QR_REPLY_MODES = 0x88;
public static final int QR_SAVE_RESTORE = 0x8c;
public static final int QR_DBCS_ASIA = 0x91;
public static final int QR_DDM = 0x95;
public static final int QR_TRANSPARENCY = 0x99;
public static final int QR_RPQNAMES = 0xa1;
public static final int QR_IMP_PART = 0xa6;
public static final int QR_RPQ_NAMES = 0xa8;
public static final int QR_GRAPHICS = 0xb0; // 3270-PC Vector Graphics
public static final int QR_GIMAGE = 0xb1; // Image / Graphics Planes
public static final int QR_AUX_DEV = 0xb2; // Auxiliary Device
public static final int QR_OEM_FMT = 0xb3; // OEM Format
public static final int QR_GCOLOR = 0xb4; // Graphic Color Table
public static final int QR_GSYMBOLS = 0xb6; // Graphic Symbol Sets
public static final int QR_NULL = 0xff;
public static final int QR_SUMMARY = 0x80; // Summary
public static final int QR_USABLE_AREA = 0x81; // Usable Area
public static final int QR_IMAGE = 0x82; // Image (non-GOCA)
public static final int QR_TEXT_PART = 0x83; // Text Partitions
public static final int QR_ALPHA_PART = 0x84; // Alphanumeric Partitions
public static final int QR_CHARSETS = 0x85; // Character Sets
public static final int QR_COLOR = 0x86; // Color Table / Alphanumeric Color
public static final int QR_HIGHLIGHTING = 0x87; // Extended Highlighting
public static final int QR_REPLY_MODES = 0x88; // Reply Modes
public static final int QR_OUTLINING = 0x8c; // Field Outlining
public static final int QR_SAVE_RESTORE = 0x8c; // Legacy alias for QR_OUTLINING
public static final int QR_DBCS_ASIA = 0x91; // DBCS Asia
public static final int QR_DDM = 0x95; // Distributed Data Management
public static final int QR_AUXDA = 0x99; // Auxiliary Devices
public static final int QR_FILE = 0x9f; // File Transfer
public static final int QR_DEVICECHAR = 0xa0; // Device Characteristics (Printer)
public static final int QR_RPQNAMES = 0xa1; // RPQ Names (legacy)
public static final int QR_IMP_PART = 0xa6; // Implicit Partition Sizes
public static final int QR_IMPLICIT = 0xa6; // Alias for QR_IMP_PART
public static final int QR_TRANSPARENCY = 0xa8; // Background Transparency
public static final int QR_RPQ_NAMES = 0xa8; // Legacy alias pointing to 0xa8
public static final int QR_SEGMENT = 0xb0; // Segment Characteristics (3270-PC Vector Graphics)
public static final int QR_GRAPHICS = 0xb0; // Legacy alias for QR_SEGMENT
public static final int QR_PROCEDURE = 0xb1; // Procedure Characteristics (Image/GOCA)
public static final int QR_GIMAGE = 0xb1; // Legacy alias for QR_PROCEDURE
public static final int QR_LINETYPE = 0xb2; // Line Type Support (Aux Dev)
public static final int QR_AUX_DEV = 0xb2; // Legacy alias for QR_LINETYPE
public static final int QR_PORT = 0xb3; // Port Characteristics (OEM Format)
public static final int QR_OEM_FMT = 0xb3; // Legacy alias for QR_PORT
public static final int QR_GRCOLOR = 0xb4; // Graphic Color Table
public static final int QR_GCOLOR = 0xb4; // Legacy alias for QR_GRCOLOR
public static final int QR_GRSYMBOLSET = 0xb6; // Graphic Symbol Sets
public static final int QR_GSYMBOLS = 0xb6; // Legacy alias for QR_GRSYMBOLSET
public static final int QR_NULL = 0xff; // Query Reply Null (Unsupported)
// ========== Screen model sizes ==========
public static final int MODEL_2_ROWS = 24;
@@ -229,6 +229,11 @@ public class ScreenBuffer {
this.cursorAddress = addr;
this.displayCursorAddress = addr;
}
public synchronized void setCursorPosition(int row, int col) {
int r = Math.max(0, Math.min(row, rows - 1));
int c = Math.max(0, Math.min(col, cols - 1));
setCursorAddress(r * cols + c);
}
public int getCursorRow() { return cursorAddress / cols; }
public int getCursorCol() { return cursorAddress % cols; }
@@ -402,11 +402,7 @@ public class TelnetFSM {
} else if (!myOpts[opt]) {
myOpts[opt] = true;
sendCommand(WILL, opt);
// Start TN3270E sub-negotiation: send device type request
if (!tn3270eDeviceTypeSent) {
sendTN3270EDeviceTypeRequest();
tn3270eDeviceTypeSent = true;
}
tn3270eDeviceTypeSent = false;
}
break;
@@ -863,25 +859,52 @@ public class TelnetFSM {
changeState(ConnectionState.CONNECTED_TN3270E);
tn3270eSubmode = TN3270ESubmode.E_3270;
}
dsProcessor.processRecord(data, EH_SIZE, data.length - EH_SIZE, true);
notifyScreenUpdate();
}
// Send positive response if required
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
try {
dsProcessor.processRecord(data, EH_SIZE, data.length - EH_SIZE, true);
notifyScreenUpdate();
// Send positive response if required
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
}
} catch (Exception e) {
log.log(Level.WARNING, "Error processing 3270 record", e);
if (eFuncs[FUNC_RESPONSES] && (responseFlag == RSF_ALWAYS_RESPONSE || responseFlag == RSF_ERROR_RESPONSE)) {
sendTN3270ENegativeResponse(seqNumber, NEG_OPERATION_CHECK);
}
}
} else {
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
}
}
break;
case DT_SSCP_LU_DATA:
if (connectionState == ConnectionState.CONNECTED_UNBOUND) {
// Clear screen on first SSCP-LU transition to remove stale data
screenBuffer.clear();
if (connectionState != ConnectionState.CONNECTED_SSCP) {
if (connectionState == ConnectionState.CONNECTED_UNBOUND) {
// Clear screen on first SSCP-LU transition to remove stale data
screenBuffer.clear();
}
changeState(ConnectionState.CONNECTED_SSCP);
tn3270eSubmode = TN3270ESubmode.E_SSCP;
}
if (data.length > EH_SIZE) {
dsProcessor.processSscpLuData(data, EH_SIZE, data.length - EH_SIZE);
notifyScreenUpdate();
try {
dsProcessor.processSscpLuData(data, EH_SIZE, data.length - EH_SIZE);
notifyScreenUpdate();
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
}
} catch (Exception e) {
log.log(Level.WARNING, "Error processing SSCP-LU record", e);
if (eFuncs[FUNC_RESPONSES] && (responseFlag == RSF_ALWAYS_RESPONSE || responseFlag == RSF_ERROR_RESPONSE)) {
sendTN3270ENegativeResponse(seqNumber, NEG_OPERATION_CHECK);
}
}
} else {
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
}
}
break;
@@ -899,7 +922,21 @@ public class TelnetFSM {
changeState(ConnectionState.CONNECTED_E_NVT);
tn3270eSubmode = TN3270ESubmode.E_NVT;
if (data.length > EH_SIZE) {
processNVTData(data, EH_SIZE, data.length - EH_SIZE);
try {
processNVTData(data, EH_SIZE, data.length - EH_SIZE);
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
}
} catch (Exception e) {
log.log(Level.WARNING, "Error processing NVT record", e);
if (eFuncs[FUNC_RESPONSES] && (responseFlag == RSF_ALWAYS_RESPONSE || responseFlag == RSF_ERROR_RESPONSE)) {
sendTN3270ENegativeResponse(seqNumber, NEG_OPERATION_CHECK);
}
}
} else {
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
sendTN3270EPositiveResponse(seqNumber);
}
}
break;
@@ -1393,6 +1430,31 @@ public class TelnetFSM {
return tn3270eBound || connectionState == ConnectionState.CONNECTED_3270;
}
public boolean isTn3270eBound() {
return tn3270eBound;
}
public void handleSysReq() {
if (tn3270eNegotiated) {
byte[] ao = new byte[] { (byte) IAC, (byte) AO };
sendBytes(ao);
screenBuffer.clear();
screenBuffer.setCursorAddress(0);
screenBuffer.markAllChanged();
if (dsProcessor != null && dsProcessor.getInputProcessor() != null) {
dsProcessor.getInputProcessor().reset();
}
if (connectionState != ConnectionState.CONNECTED_SSCP) {
changeState(ConnectionState.CONNECTED_SSCP);
tn3270eSubmode = TN3270ESubmode.E_SSCP;
} else if (tn3270eBound) {
changeState(ConnectionState.CONNECTED_TN3270E);
tn3270eSubmode = TN3270ESubmode.E_3270;
}
notifyScreenUpdate();
}
}
private static String tn3270eOpName(int op) {
switch (op) {
case OP_ASSOCIATE: return "ASSOCIATE";