This commit is contained in:
@@ -41,7 +41,7 @@ public class DataStreamProcessor {
|
||||
|
||||
// Graphics & Programmed Symbols
|
||||
private final haus.nightmare.lib3270j.graphics.ProgramSymbolManager programSymbolManager = new haus.nightmare.lib3270j.graphics.ProgramSymbolManager();
|
||||
private final haus.nightmare.lib3270j.graphics.GraphicsPlane graphicsPlane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(800, 600);
|
||||
private final haus.nightmare.lib3270j.graphics.GraphicsPlane graphicsPlane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(720, 384);
|
||||
private final haus.nightmare.lib3270j.graphics.GocaDecoder gocaDecoder = new haus.nightmare.lib3270j.graphics.GocaDecoder(graphicsPlane);
|
||||
private final java.io.ByteArrayOutputStream gocaAccumulator = new java.io.ByteArrayOutputStream();
|
||||
private int currentGocaSubtype = 0;
|
||||
@@ -853,6 +853,11 @@ public class DataStreamProcessor {
|
||||
sfSubId, fieldLen, flags, (orderOffset - pos), orderLen, hexDump.toString().trim()));
|
||||
|
||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||
int targetW = screen.getCols() * 9;
|
||||
int targetH = screen.getRows() * 16;
|
||||
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
||||
graphicsPlane.resize(targetW, targetH);
|
||||
}
|
||||
|
||||
if (flags == 0x80) { // SPAN_FIRST
|
||||
gocaAccumulator.reset();
|
||||
|
||||
@@ -293,14 +293,36 @@ public class QueryReplyBuilder {
|
||||
out.write((Yr_3279_2 >> 16) & 0xFF);
|
||||
out.write((Yr_3279_2 >> 8) & 0xFF);
|
||||
out.write(Yr_3279_2 & 0xFF);
|
||||
out.write(SW_3279_2); // AW
|
||||
out.write(SH_3279_2); // AH
|
||||
int charW = getCharWidth();
|
||||
int charH = getCharHeight();
|
||||
out.write(charW); // AW
|
||||
out.write(charH); // AH
|
||||
int buf = maxCols * maxRows;
|
||||
out.write((buf >> 8) & 0xFF); // buffer size high
|
||||
out.write(buf & 0xFF); // buffer size low
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
public int getCharWidth() {
|
||||
return SW_3279_2; // 9
|
||||
}
|
||||
|
||||
public int getCharHeight() {
|
||||
// ARCHITECTURAL NOTE ON 3179G GOCA VERTICAL ALIGNMENT & QUERY REPLIES:
|
||||
// Why hardcoding SH = 12 (0x0C) in Character Sets & Usable Area failed in past iterations:
|
||||
// When SDH/AH is declared as 12 (0x0C) in Query Reply, the mainframe host GDDM engine computes
|
||||
// total presentation space as rows * 12 (e.g. 43 * 12 = 516 units, yMax = 257).
|
||||
// GDDM then places the top menu bar at Row 1 (gy = 187..200).
|
||||
// Meanwhile, the client emulator rendered into a 16-pitch grid (43 * 16 = 688 units, yMax = 343).
|
||||
// On a 688-unit canvas, gy = 200 mapped to Row 9.2 (middle of the screen), leaving a massive void above.
|
||||
// When the user clicked on the visual menu drawn at Row 9, the client emitted gy = 189 with cursor at Row 9,
|
||||
// which GDDM rejected as outside its menu hit box (causing terminal alarm beeps).
|
||||
//
|
||||
// Solution: Declare SDH = 16 (0x10) when Vector Graphics is enabled (3179G standard), ensuring host GDDM
|
||||
// and client GraphicsPlane share the exact same 16-pitch presentation space (720x688, yMax = 343).
|
||||
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
||||
}
|
||||
|
||||
private byte[] buildAlphaPartitions(int maxRows) {
|
||||
int bufSize = screen.getMaxCols() * screen.getMaxRows();
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(4);
|
||||
@@ -312,13 +334,16 @@ public class QueryReplyBuilder {
|
||||
}
|
||||
|
||||
private byte[] buildCharsets() {
|
||||
int charW = getCharWidth();
|
||||
int charH = getCharHeight();
|
||||
|
||||
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
||||
// Programmed Symbols mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
|
||||
out.write(0xa2); // flags: GE (0x80), PS/Loadable Charsets (0x20), CGCSGID present (0x02)
|
||||
out.write(0x00); // more flags
|
||||
out.write(SW_3279_2); // SDW (9)
|
||||
out.write(SH_3279_2); // SDH (12)
|
||||
out.write(charW); // SDW (9)
|
||||
out.write(charH); // SDH (16 for 3179G, 12 for 3279-2)
|
||||
out.write(0x0a); // Load PS format types supported: Format 1 and Format 3
|
||||
out.write(0x00); // Load PS device type (high)
|
||||
out.write(0x00); // Load PS device type (low)
|
||||
@@ -343,8 +368,8 @@ public class QueryReplyBuilder {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(23);
|
||||
out.write(0x82); // flags: GE, CGCSGID present
|
||||
out.write(0x00); // more flags
|
||||
out.write(SW_3279_2); // SDW - default char width (9)
|
||||
out.write(SH_3279_2); // SDH - default char height (12)
|
||||
out.write(charW); // SDW - default char width (9)
|
||||
out.write(charH); // SDH - default char height (16 for 3179G, 12 for 3279-2)
|
||||
out.write(0x00); // LoadPS format (0x00)
|
||||
out.write(0x00);
|
||||
out.write(0x00);
|
||||
|
||||
@@ -6,9 +6,14 @@ package haus.nightmare.lib3270j.graphics;
|
||||
*/
|
||||
public class GraphicInputBuilder {
|
||||
|
||||
// 56-byte template mask from IBM Host On-Demand (HODInput.java)
|
||||
// 56-byte template mask from IBM Host On-Demand (HODInput.java).
|
||||
// Note on Structured Field Length (bytes 0-1):
|
||||
// IBM HOD sets bytes 0-1 to 0x00 0x34 (52 decimal). In 3270 GOCA architecture,
|
||||
// the Data Unit Object Control payload is 52 bytes. Overwriting this with 0x00 0x38 (56)
|
||||
// causes the host's 3270 inbound structured field decoder to misalign the trailing AID byte
|
||||
// (0x7D) and cursor address by 4 bytes, causing host GDDM to reject the click with an alarm beep.
|
||||
private static final byte[] MASK = new byte[] {
|
||||
0x00, 0x38, 0x0F, 0x0F, 0x00, (byte) 0xC0, 0x00, 0x40,
|
||||
0x00, 0x34, 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,
|
||||
@@ -60,9 +65,8 @@ public class GraphicInputBuilder {
|
||||
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);
|
||||
// Bytes 0-1: Structured Field Length (0x0034 = 52 decimal per IBM HODInput.java / GOCA architecture).
|
||||
// Preserved from MASK; do not overwrite with MASK.length (56).
|
||||
|
||||
// Bytes 16-19: Device correlation class descriptor (0x23, 0x00, 0x23, 0x00)
|
||||
// Note: Preserved from MASK per IBM Host On-Demand (HODInput.java); do not overwrite with row/col.
|
||||
|
||||
@@ -36,14 +36,14 @@ public class GraphicsPlane {
|
||||
{-1, -1, -1, -1, -1, -1, -1, -1} // 16: Solid
|
||||
};
|
||||
|
||||
private int canvasWidth = 800;
|
||||
private int canvasHeight = 600;
|
||||
// 3179G Presentation Space metrics: 9x16 cell pitch (720x384 for Model 2, 720x688 for Model 4)
|
||||
private int screenCols = 80;
|
||||
private int screenRows = 24;
|
||||
private int canvasWidth = 720;
|
||||
private int canvasHeight = 384;
|
||||
private int[] rgbBuffer;
|
||||
private boolean hasContent = false;
|
||||
private long updateCount = 0;
|
||||
|
||||
private int screenCols = 80;
|
||||
private int screenRows = 24;
|
||||
private ProgramSymbolManager programSymbolManager;
|
||||
|
||||
public void setProgramSymbolManager(ProgramSymbolManager psm) {
|
||||
@@ -595,20 +595,19 @@ public class GraphicsPlane {
|
||||
int fill = (fillColorArgb != 0) ? fillColorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
int bg = bgColorArgb;
|
||||
|
||||
// Note / Logic for Future Iterations:
|
||||
// In IBM 3179G / GDDM architecture, solid black fills (fillColor = GOCA_COLORS[8] / 0xFF000000,
|
||||
// pattern = PT_SOLID / 16) are the standard mechanism used by host applications to ERASE
|
||||
// dropdown menus, dialog boxes, and dynamic regions from the screen.
|
||||
//
|
||||
// A prior failed attempt added an 'isTransparentBlack' guard:
|
||||
// boolean isTransparentBlack = (fill == GOCA_COLORS[8] || (fill & 0x00FFFFFF) == 0) && (bgMix != MIX_OVER);
|
||||
// if (... && !isTransparentBlack)
|
||||
// This caused GDDM's menu erasure rectangles (which use fillColor = Black and bgMix = 5) to be
|
||||
// silently skipped and discarded. As a result, closed dropdown menus were never erased, causing
|
||||
// multiple menus (FILE, DRAW, TRANSFORM) to linger and stack on top of each other permanently.
|
||||
//
|
||||
// Solid black fills (0xFF000000) must always be rasterized to overwrite and erase previously drawn pixels.
|
||||
if (pattern != GocaConstants.PT_EMPTY && (pattern != 0 || !drawBoundary)) {
|
||||
// ARCHITECTURAL NOTE ON GOCA BACKGROUND MIX & BLACK AREA FILLING:
|
||||
// In GOCA (GA23-0059 / SC31-6805), Color 0 / 8 is the default background/neutral color (Black).
|
||||
// Background Mix (GSBMX / bgMix):
|
||||
// - bgMix == 0 or 2 (BMX_DEFAULT / BMX_LEAVE): Leave destination unchanged (Transparent).
|
||||
// Fills with default background color (Black) under BMX_LEAVE are transparent and must NOT overwrite pixels.
|
||||
// (e.g. ADMOPSLA slide preview selection boxes, where GDDM draws hollow frames with bgMix = 0).
|
||||
// - bgMix == 5 or 1 (BMX_OVER / OVERPAINT): Overwrite background pixels with background color (Opaque).
|
||||
// Fills with Black under BMX_OVER are explicit erasure rectangles used to erase closed menus and dialogs
|
||||
// (e.g. ADMDRAW menu erasure, where GDDM explicitly issues GSBMX 5 before the black fill).
|
||||
boolean isTransparentBlack = ((fill & 0x00FFFFFF) == 0) &&
|
||||
(bgMix == GocaConstants.MIX_DEFAULT || bgMix == GocaConstants.MIX_LEAVE || bgMix == 0);
|
||||
|
||||
if (!isTransparentBlack && pattern != GocaConstants.PT_EMPTY && (pattern != 0 || !drawBoundary)) {
|
||||
// Find polygon vertical bounds across all points
|
||||
int minY = py[0];
|
||||
int maxY = py[0];
|
||||
|
||||
@@ -244,7 +244,43 @@ public class InputProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter, PF keys, PA keys: send AID + optional PID + cursor address + modified field data
|
||||
if (aidCode == AID_SELECT) {
|
||||
// 3270 Selector Pen / Light Pen Immediate Selection (AID 0x7E):
|
||||
// Per IBM 3270 Data Stream Architecture (GA23-0059) and IBM Host On-Demand (DS3270.sendAid lines 727-1019):
|
||||
// The inbound data stream consists of:
|
||||
// 1. AID byte (0x7E)
|
||||
// 2. Cursor address (2 bytes)
|
||||
// 3. For each field with MDT=1:
|
||||
// - SBA order (0x11)
|
||||
// - Designator character address (faAddr + 1)
|
||||
// CRITICAL: NO FIELD CHARACTER DATA IS TRANSMITTED FOR AID_SELECT!
|
||||
// Sending character data in an AID_SELECT stream violates 3270 protocol and causes the host to reject the selection.
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(AID_SELECT);
|
||||
|
||||
byte[] caddr = encodeAddress(screen.getCursorAddress(), screen.getRows(), screen.getCols());
|
||||
out.write(caddr[0] & 0xFF);
|
||||
out.write(caddr[1] & 0xFF);
|
||||
|
||||
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 designatorAddr = (i + 1) % size;
|
||||
out.write(ORDER_SBA);
|
||||
byte[] addr = encodeAddress(designatorAddr, screen.getRows(), screen.getCols());
|
||||
out.write(addr[0] & 0xFF);
|
||||
out.write(addr[1] & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendAidResponse(out.toByteArray());
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter, PF keys: send AID + optional PID + cursor address + modified field data
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
out.write(aidCode);
|
||||
|
||||
Reference in New Issue
Block a user