Clear cruft
This commit is contained in:
@@ -58,6 +58,17 @@ public class ConnectionConfig {
|
||||
private java.util.Map<String, String> environmentVariables = new java.util.LinkedHashMap<>();
|
||||
private java.util.Map<String, String> userVariables = new java.util.LinkedHashMap<>();
|
||||
|
||||
// IBM HoD autoSysUnlock parity
|
||||
private boolean autoSysUnlock = true;
|
||||
|
||||
// Phase 11: Enterprise Connection Resilience & Heartbeat
|
||||
private boolean keepAliveEnabled = true;
|
||||
private int keepAliveIntervalSeconds = 120;
|
||||
private String keepAliveType = "NOP";
|
||||
private boolean autoReconnect = false;
|
||||
private int reconnectMaxRetries = 5;
|
||||
private int tcpUserTimeoutMs = 0;
|
||||
|
||||
public ConnectionConfig() {}
|
||||
|
||||
public ConnectionConfig(String host, int port) {
|
||||
@@ -280,6 +291,27 @@ public class ConnectionConfig {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAutoSysUnlock() { return autoSysUnlock; }
|
||||
public void setAutoSysUnlock(boolean autoSysUnlock) { this.autoSysUnlock = autoSysUnlock; }
|
||||
|
||||
public boolean isKeepAliveEnabled() { return keepAliveEnabled; }
|
||||
public void setKeepAliveEnabled(boolean enabled) { this.keepAliveEnabled = enabled; }
|
||||
|
||||
public int getKeepAliveIntervalSeconds() { return keepAliveIntervalSeconds; }
|
||||
public void setKeepAliveIntervalSeconds(int seconds) { this.keepAliveIntervalSeconds = seconds; }
|
||||
|
||||
public String getKeepAliveType() { return keepAliveType; }
|
||||
public void setKeepAliveType(String type) { this.keepAliveType = (type != null) ? type.trim().toUpperCase() : "NOP"; }
|
||||
|
||||
public boolean isAutoReconnect() { return autoReconnect; }
|
||||
public void setAutoReconnect(boolean autoReconnect) { this.autoReconnect = autoReconnect; }
|
||||
|
||||
public int getReconnectMaxRetries() { return reconnectMaxRetries; }
|
||||
public void setReconnectMaxRetries(int retries) { this.reconnectMaxRetries = Math.max(0, retries); }
|
||||
|
||||
public int getTcpUserTimeoutMs() { return tcpUserTimeoutMs; }
|
||||
public void setTcpUserTimeoutMs(int timeoutMs) { this.tcpUserTimeoutMs = Math.max(0, timeoutMs); }
|
||||
|
||||
/**
|
||||
* Parse a host connection string which may include prefixes for TLS (e.g. "L:host:port", "ssl:host:port", "y:host:port"),
|
||||
* plain TN3270 (e.g. "P:host:port", "plain:host:port", "non-e:host:port"), proxy flags (e.g. "--proxy=http://proxy:8080 host:23"),
|
||||
@@ -303,10 +335,21 @@ public class ConnectionConfig {
|
||||
String pUser = null;
|
||||
String pPass = null;
|
||||
|
||||
boolean keepAlive = true;
|
||||
boolean autoReconnect = false;
|
||||
|
||||
String[] tokens = s.split("\\s+");
|
||||
StringBuilder remaining = new StringBuilder();
|
||||
for (String tok : tokens) {
|
||||
if (tok.startsWith("--proxy=") || tok.startsWith("-proxy=")) {
|
||||
if (tok.equalsIgnoreCase("--keepalive") || tok.equalsIgnoreCase("-keepalive")) {
|
||||
keepAlive = true;
|
||||
} else if (tok.equalsIgnoreCase("--no-keepalive") || tok.equalsIgnoreCase("-no-keepalive")) {
|
||||
keepAlive = false;
|
||||
} else if (tok.equalsIgnoreCase("--autoreconnect") || tok.equalsIgnoreCase("-autoreconnect")) {
|
||||
autoReconnect = true;
|
||||
} else if (tok.equalsIgnoreCase("--no-autoreconnect") || tok.equalsIgnoreCase("-no-autoreconnect")) {
|
||||
autoReconnect = false;
|
||||
} else if (tok.startsWith("--proxy=") || tok.startsWith("-proxy=")) {
|
||||
String proxyUrl = tok.substring(tok.indexOf('=') + 1).trim();
|
||||
try {
|
||||
java.net.URI uri = new java.net.URI(proxyUrl);
|
||||
@@ -417,6 +460,8 @@ public class ConnectionConfig {
|
||||
ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4);
|
||||
config.setUseTls(tls);
|
||||
config.setTn3270eEnabled(tn3270e);
|
||||
config.setKeepAliveEnabled(keepAlive);
|
||||
config.setAutoReconnect(autoReconnect);
|
||||
if (dynamic) {
|
||||
config.setDynamicDimensions(dynRows, dynCols);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ public class Telnet3270Client {
|
||||
private final haus.nightmare.lib3270j.ecl.ECLXfer xfer;
|
||||
private TelnetConnection connection;
|
||||
|
||||
private final java.util.concurrent.atomic.AtomicBoolean reconnecting = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||
private volatile Thread reconnectThread;
|
||||
|
||||
public Telnet3270Client(ConnectionConfig config) {
|
||||
this.config = config;
|
||||
this.translator = new EbcdicTranslator(config.getCodePage());
|
||||
@@ -58,6 +61,7 @@ public class Telnet3270Client {
|
||||
this.screenBuffer = new ScreenBuffer(config.getModel(), translator);
|
||||
}
|
||||
this.dsProcessor = new DataStreamProcessor(screenBuffer, translator);
|
||||
this.dsProcessor.setAutoSysUnlock(config.isAutoSysUnlock());
|
||||
this.dsProcessor.getQueryReplyBuilder().setGraphicsMode(config.getGraphicsMode());
|
||||
this.fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
||||
this.inputProcessor = new InputProcessor(screenBuffer, translator, fsm);
|
||||
@@ -87,6 +91,33 @@ public class Telnet3270Client {
|
||||
@Override public void onSoundAlarm() {
|
||||
ps.notifyAlarm();
|
||||
}
|
||||
@Override public void onKeyboardUnlocked() {
|
||||
ps.notifyKeyUnlocked();
|
||||
}
|
||||
});
|
||||
|
||||
// Phase 11: Auto-reconnection and OIA status coordinator
|
||||
fsm.addConnectionListener(new ConnectionListener() {
|
||||
@Override
|
||||
public void onConnectionStateChanged(ConnectionState oldState, ConnectionState newState) {
|
||||
if (newState == ConnectionState.RECONNECTING) {
|
||||
if (oia != null) {
|
||||
oia.writeToOIA("X RECONNECT");
|
||||
}
|
||||
initiateAutoReconnect();
|
||||
} else if (newState == ConnectionState.NOT_CONNECTED) {
|
||||
if (oia != null) {
|
||||
oia.setInputInhibited(haus.nightmare.lib3270j.ecl.ECLOIA.INHIBIT_COMMCHECK);
|
||||
}
|
||||
} else if (newState.isFullSession()) {
|
||||
if (oia != null) {
|
||||
oia.setInputInhibited(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionError(String message) {}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,11 +190,146 @@ public class Telnet3270Client {
|
||||
* Disconnect from the host.
|
||||
*/
|
||||
public void disconnect() {
|
||||
cancelAutoReconnect();
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
connection = null;
|
||||
}
|
||||
fsm.onDisconnect();
|
||||
fsm.onDisconnect(false);
|
||||
}
|
||||
|
||||
public void cancelAutoReconnect() {
|
||||
reconnecting.set(false);
|
||||
if (reconnectThread != null) {
|
||||
reconnectThread.interrupt();
|
||||
reconnectThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void initiateAutoReconnect() {
|
||||
if (config == null || !config.isAutoReconnect()) {
|
||||
return;
|
||||
}
|
||||
if (!reconnecting.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
reconnectThread = new Thread(this::runAutoReconnect, "TN3270-AutoReconnect");
|
||||
reconnectThread.setDaemon(true);
|
||||
reconnectThread.start();
|
||||
}
|
||||
|
||||
private void runAutoReconnect() {
|
||||
int maxRetries = (config != null) ? config.getReconnectMaxRetries() : 5;
|
||||
log.info("Starting automatic reconnection loop (maxRetries=" + maxRetries + ")");
|
||||
try {
|
||||
for (int attempt = 1; attempt <= maxRetries && reconnecting.get(); attempt++) {
|
||||
// Exponential backoff: 1s, 2s, 4s, 8s, 16s, capped at 30s
|
||||
long delaySeconds = Math.min(30, (long) Math.pow(2, attempt - 1));
|
||||
log.info("Auto-reconnect attempt " + attempt + "/" + maxRetries + " scheduled in " + delaySeconds + "s");
|
||||
|
||||
for (int s = 0; s < delaySeconds * 10; s++) {
|
||||
if (!reconnecting.get() || Thread.currentThread().isInterrupted()) {
|
||||
log.info("Auto-reconnect cancelled during backoff delay");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
log.info("Auto-reconnect thread interrupted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reconnecting.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Executing auto-reconnect attempt " + attempt + "/" + maxRetries + " to " + config.getHost() + ":" + config.getPort());
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
connection = null;
|
||||
}
|
||||
fsm.resetSessionState();
|
||||
screenBuffer.erase(false);
|
||||
|
||||
connection = new TelnetConnection(config, fsm);
|
||||
fsm.setConnection(connection);
|
||||
connection.connect();
|
||||
fsm.onConnected();
|
||||
|
||||
log.info("Auto-reconnect successful on attempt " + attempt);
|
||||
reconnecting.set(false);
|
||||
fsm.notifyScreenUpdate();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
log.warning("Auto-reconnect attempt " + attempt + " failed: " + e.getMessage());
|
||||
if (attempt < maxRetries && reconnecting.get()) {
|
||||
fsm.setConnectionState(ConnectionState.RECONNECTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All retries failed
|
||||
log.warning("All " + maxRetries + " automatic reconnection attempts failed");
|
||||
reconnecting.set(false);
|
||||
fsm.onDisconnect(false);
|
||||
fsm.onError("Automatic reconnection failed after " + maxRetries + " attempts");
|
||||
} finally {
|
||||
reconnecting.set(false);
|
||||
reconnectThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAutoReconnect() {
|
||||
return (config != null) && config.isAutoReconnect();
|
||||
}
|
||||
|
||||
public void setAutoReconnect(boolean autoReconnect) {
|
||||
if (config != null) {
|
||||
config.setAutoReconnect(autoReconnect);
|
||||
}
|
||||
}
|
||||
|
||||
public int getReconnectMaxRetries() {
|
||||
return (config != null) ? config.getReconnectMaxRetries() : 5;
|
||||
}
|
||||
|
||||
public void setReconnectMaxRetries(int retries) {
|
||||
if (config != null) {
|
||||
config.setReconnectMaxRetries(retries);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReconnecting() {
|
||||
return reconnecting.get() || fsm.getConnectionState() == ConnectionState.RECONNECTING;
|
||||
}
|
||||
|
||||
public boolean isKeepAliveEnabled() {
|
||||
return (config != null) && config.isKeepAliveEnabled();
|
||||
}
|
||||
|
||||
public void setKeepAliveEnabled(boolean enabled) {
|
||||
if (config != null) {
|
||||
config.setKeepAliveEnabled(enabled);
|
||||
}
|
||||
if (connection != null) {
|
||||
if (enabled) connection.startKeepAlive();
|
||||
else connection.stopKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
public int getKeepAliveIntervalSeconds() {
|
||||
return (config != null) ? config.getKeepAliveIntervalSeconds() : 120;
|
||||
}
|
||||
|
||||
public void setKeepAliveIntervalSeconds(int seconds) {
|
||||
if (config != null) {
|
||||
config.setKeepAliveIntervalSeconds(seconds);
|
||||
}
|
||||
if (connection != null && config != null && config.isKeepAliveEnabled()) {
|
||||
connection.startKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,6 +460,23 @@ public class Telnet3270Client {
|
||||
return fsm;
|
||||
}
|
||||
|
||||
public boolean isAutoSysUnlock() {
|
||||
return (config != null) ? config.isAutoSysUnlock() : true;
|
||||
}
|
||||
|
||||
public void setAutoSysUnlock(boolean autoSysUnlock) {
|
||||
if (config != null) {
|
||||
config.setAutoSysUnlock(autoSysUnlock);
|
||||
}
|
||||
if (dsProcessor != null) {
|
||||
dsProcessor.setAutoSysUnlock(autoSysUnlock);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isContentionResolution() {
|
||||
return fsm != null && fsm.isContentionResolutionNegotiated();
|
||||
}
|
||||
|
||||
/** Send an NVT ASCII character in NVT mode. */
|
||||
public void sendNVTChar(char c) throws IOException {
|
||||
fsm.sendNVTChar(c);
|
||||
|
||||
@@ -47,6 +47,13 @@ public class DataStreamProcessor {
|
||||
private final java.io.ByteArrayOutputStream gocaAccumulator = new java.io.ByteArrayOutputStream();
|
||||
private int currentGocaSubtype = 0;
|
||||
|
||||
// Phase 10: Auto-Unlock & Contention Resolution State
|
||||
private boolean autoSysUnlock = true;
|
||||
private boolean contentionResolution = false;
|
||||
private boolean unlockPending = false;
|
||||
private boolean unlockSysPending = false;
|
||||
private boolean rcvdRead = false;
|
||||
|
||||
/** Functional interface for sending output back through the telnet stack. */
|
||||
@FunctionalInterface
|
||||
public interface OutputSender {
|
||||
@@ -131,6 +138,21 @@ public class DataStreamProcessor {
|
||||
screenListeners.remove(l);
|
||||
}
|
||||
|
||||
public boolean isAutoSysUnlock() { return autoSysUnlock; }
|
||||
public void setAutoSysUnlock(boolean autoSysUnlock) { this.autoSysUnlock = autoSysUnlock; }
|
||||
|
||||
public boolean isContentionResolution() { return contentionResolution; }
|
||||
public void setContentionResolution(boolean cr) { this.contentionResolution = cr; }
|
||||
|
||||
public boolean isUnlockPending() { return unlockPending; }
|
||||
public void setUnlockPending(boolean pending) { this.unlockPending = pending; }
|
||||
|
||||
public boolean isUnlockSysPending() { return unlockSysPending; }
|
||||
public void setUnlockSysPending(boolean pending) { this.unlockSysPending = pending; }
|
||||
|
||||
public boolean isRcvdRead() { return rcvdRead; }
|
||||
public void setRcvdRead(boolean rcvdRead) { this.rcvdRead = rcvdRead; }
|
||||
|
||||
/**
|
||||
* Process a 3270 data stream record.
|
||||
*
|
||||
@@ -197,16 +219,19 @@ public class DataStreamProcessor {
|
||||
break;
|
||||
case CMD_RB:
|
||||
case SNA_CMD_RB:
|
||||
rcvdRead = true;
|
||||
programSymbolManager.commitStagedSymbols();
|
||||
processReadBuffer();
|
||||
break;
|
||||
case CMD_RM:
|
||||
case SNA_CMD_RM:
|
||||
rcvdRead = true;
|
||||
programSymbolManager.commitStagedSymbols();
|
||||
processReadModified(false);
|
||||
break;
|
||||
case CMD_RMA:
|
||||
case SNA_CMD_RMA:
|
||||
rcvdRead = true;
|
||||
programSymbolManager.commitStagedSymbols();
|
||||
processReadModified(true);
|
||||
break;
|
||||
@@ -235,7 +260,10 @@ public class DataStreamProcessor {
|
||||
screen.updateDisplaySnapshot();
|
||||
}
|
||||
|
||||
if (keyboardRestore && inputProcessor != null) {
|
||||
boolean isWriteCmd = (cmd == CMD_W || cmd == SNA_CMD_W ||
|
||||
cmd == CMD_EW || cmd == SNA_CMD_EW ||
|
||||
cmd == CMD_EWA || cmd == SNA_CMD_EWA);
|
||||
if (!isWriteCmd && keyboardRestore && inputProcessor != null && !contentionResolution) {
|
||||
inputProcessor.setKeyboardLocked(false);
|
||||
}
|
||||
|
||||
@@ -333,10 +361,13 @@ public class DataStreamProcessor {
|
||||
log.fine("WCC: " + String.format("0x%02x", wcc) +
|
||||
" reset=" + wccReset(wcc) + " alarm=" + alarm + " kbdRestore=" + kbdRestore + " resetMdt=" + resetMdt);
|
||||
|
||||
if (kbdRestore || inputProcessor != null) {
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.setKeyboardLocked(false);
|
||||
}
|
||||
if (kbdRestore) {
|
||||
unlockPending = true;
|
||||
unlockSysPending = true;
|
||||
}
|
||||
|
||||
if (!contentionResolution && kbdRestore && inputProcessor != null) {
|
||||
inputProcessor.setKeyboardLocked(false);
|
||||
}
|
||||
|
||||
if (resetMdt) {
|
||||
@@ -1101,6 +1132,9 @@ public class DataStreamProcessor {
|
||||
}
|
||||
|
||||
private void notifyScreenUpdated() {
|
||||
if (screen != null) {
|
||||
screen.notifyScreenUpdate();
|
||||
}
|
||||
for (ScreenUpdateListener l : screenListeners) {
|
||||
l.onScreenUpdated();
|
||||
}
|
||||
@@ -1549,7 +1583,11 @@ public class DataStreamProcessor {
|
||||
log.fine("processWCC: " + String.format("0x%02x", wcc) +
|
||||
" reset=" + wccReset(wcc) + " alarm=" + alarm + " kbdRestore=" + kbdRestore + " resetMdt=" + resetMdt);
|
||||
|
||||
if (kbdRestore && inputProcessor != null) {
|
||||
if (kbdRestore) {
|
||||
unlockPending = true;
|
||||
unlockSysPending = true;
|
||||
}
|
||||
if (!contentionResolution && kbdRestore && inputProcessor != null) {
|
||||
inputProcessor.setKeyboardLocked(false);
|
||||
}
|
||||
if (resetMdt) {
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Rectangle;
|
||||
import haus.nightmare.lib3270j.graphics.Rectangle;
|
||||
|
||||
/**
|
||||
* Drop-in IBM Host On-Demand compatible facade for ECLPSGraphicsEvent.
|
||||
@@ -12,11 +11,11 @@ public class ECLPSGraphicsEvent extends haus.nightmare.lib3270j.ecl.ECLPSGraphic
|
||||
super(source, id);
|
||||
}
|
||||
|
||||
public ECLPSGraphicsEvent(haus.nightmare.lib3270j.ecl.ECLPS source, int id, Image image) {
|
||||
public ECLPSGraphicsEvent(haus.nightmare.lib3270j.ecl.ECLPS source, int id, Object image) {
|
||||
super(source, id, image);
|
||||
}
|
||||
|
||||
public ECLPSGraphicsEvent(haus.nightmare.lib3270j.ecl.ECLPS source, int id, Image image, Rectangle rectangle) {
|
||||
public ECLPSGraphicsEvent(haus.nightmare.lib3270j.ecl.ECLPS source, int id, Object image, Rectangle rectangle) {
|
||||
super(source, id, image, rectangle);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package haus.nightmare.lib3270j.eNetwork.ECL.hostgraphics;
|
||||
|
||||
import java.awt.Color;
|
||||
import haus.nightmare.lib3270j.graphics.Color;
|
||||
|
||||
/**
|
||||
* Drop-in IBM Host On-Demand compatible facade for FillArea.
|
||||
|
||||
+1
-3
@@ -1,12 +1,10 @@
|
||||
package haus.nightmare.lib3270j.eNetwork.ECL.hostgraphics;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
* Drop-in IBM Host On-Demand compatible facade for HODBitImage.
|
||||
*/
|
||||
public class HODBitImage extends haus.nightmare.lib3270j.graphics.HODBitImage {
|
||||
public HODBitImage(Component comp, int width, int height, byte[] data, int baseColor, int depth, boolean useGraphicColors) {
|
||||
public HODBitImage(Object comp, int width, int height, byte[] data, int baseColor, int depth, boolean useGraphicColors) {
|
||||
super(comp, width, height, data, baseColor, depth, useGraphicColors);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -1,8 +1,7 @@
|
||||
package haus.nightmare.lib3270j.eNetwork.ECL.hostgraphics;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Rectangle;
|
||||
import haus.nightmare.lib3270j.graphics.Dimension;
|
||||
import haus.nightmare.lib3270j.graphics.Rectangle;
|
||||
|
||||
/**
|
||||
* Drop-in IBM Host On-Demand compatible facade for HODPart.
|
||||
@@ -12,15 +11,15 @@ public class HODPart extends haus.nightmare.lib3270j.graphics.HODPart {
|
||||
super();
|
||||
}
|
||||
|
||||
public HODPart(Component component) {
|
||||
public HODPart(Object component) {
|
||||
super(component);
|
||||
}
|
||||
|
||||
public HODPart(Component component, Dimension dimension) {
|
||||
public HODPart(Object component, Dimension dimension) {
|
||||
super(component, dimension);
|
||||
}
|
||||
|
||||
public HODPart(Component component, Rectangle rectangle) {
|
||||
public HODPart(Object component, Rectangle rectangle) {
|
||||
super(component, rectangle);
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -1,7 +1,5 @@
|
||||
package haus.nightmare.lib3270j.eNetwork.ECL.hostgraphics;
|
||||
|
||||
import java.awt.Image;
|
||||
|
||||
/**
|
||||
* Drop-in IBM Host On-Demand compatible facade for HODWallpaper.
|
||||
*/
|
||||
@@ -14,7 +12,7 @@ public class HODWallpaper extends haus.nightmare.lib3270j.graphics.HODWallpaper
|
||||
super(displayMode);
|
||||
}
|
||||
|
||||
public HODWallpaper(Image image, int displayMode) {
|
||||
public HODWallpaper(Object image, int displayMode) {
|
||||
super(image, displayMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import haus.nightmare.lib3270j.graphics.Color;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Standard implementation of ECLPSGraphicsServices conforming to IBM Host On-Demand ECL.
|
||||
* Completely decoupled from java.awt.
|
||||
*/
|
||||
public class DefaultPSGraphicsServices implements ECLPSGraphicsServices {
|
||||
|
||||
private final ECLPS ps;
|
||||
private Component visualComponent;
|
||||
private Object visualComponent;
|
||||
private Color[] colors;
|
||||
private final List<ECLPSGraphicsListener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
@@ -20,11 +20,11 @@ public class DefaultPSGraphicsServices implements ECLPSGraphicsServices {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisualComponent(Component comp) {
|
||||
public void setVisualComponent(Object comp) {
|
||||
this.visualComponent = comp;
|
||||
}
|
||||
|
||||
public Component getVisualComponent() {
|
||||
public Object getVisualComponent() {
|
||||
return visualComponent;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ public class ECLConnection {
|
||||
private String luName;
|
||||
private String workstationId = "";
|
||||
private boolean ssl = false;
|
||||
private boolean autoSysUnlock = true;
|
||||
private boolean keepAlive = true;
|
||||
private int keepAliveTimeout = 120;
|
||||
private String keepAliveType = "NOP";
|
||||
private boolean autoReconnect = false;
|
||||
private int maxRetry = 5;
|
||||
private boolean contentionResolution = false;
|
||||
private boolean luluSession = false;
|
||||
private boolean isNegCR = false;
|
||||
@@ -71,12 +77,49 @@ public class ECLConnection {
|
||||
if (props != null) {
|
||||
this.properties.putAll(props);
|
||||
convertData(this.properties);
|
||||
String asu = this.properties.getProperty(ECLSession.SESSION_AUTO_SYS_UNLOCK);
|
||||
if (asu != null) {
|
||||
this.autoSysUnlock = "true".equalsIgnoreCase(asu) || "1".equals(asu);
|
||||
}
|
||||
String ka = this.properties.getProperty(ECLSession.SESSION_KEEPALIVE);
|
||||
if (ka != null) {
|
||||
this.keepAlive = "true".equalsIgnoreCase(ka) || "1".equals(ka);
|
||||
}
|
||||
String kat = this.properties.getProperty(ECLSession.KEY_KEEPALIVE_TIMEOUT);
|
||||
if (kat != null) {
|
||||
try { this.keepAliveTimeout = Integer.parseInt(kat.trim()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
String katyp = this.properties.getProperty(ECLSession.KEY_KEEPALIVE_TYPE);
|
||||
if (katyp != null) {
|
||||
this.keepAliveType = katyp;
|
||||
}
|
||||
String ar = this.properties.getProperty(ECLSession.SESSION_AUTORECONNECT);
|
||||
if (ar != null) {
|
||||
this.autoReconnect = "true".equalsIgnoreCase(ar) || "1".equals(ar);
|
||||
}
|
||||
String mr = this.properties.getProperty(ECLSession.SESSION_RECONNECT_RETRIES);
|
||||
if (mr != null) {
|
||||
try { this.maxRetry = Integer.parseInt(mr.trim()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ECLConnection(ECLSession session, Telnet3270Client client) {
|
||||
this.session = session;
|
||||
this.client = client;
|
||||
if (client != null && client.getConfig() != null) {
|
||||
this.autoSysUnlock = client.getConfig().isAutoSysUnlock();
|
||||
this.keepAlive = client.getConfig().isKeepAliveEnabled();
|
||||
this.keepAliveTimeout = client.getConfig().getKeepAliveIntervalSeconds();
|
||||
this.keepAliveType = client.getConfig().getKeepAliveType();
|
||||
this.autoReconnect = client.getConfig().isAutoReconnect();
|
||||
this.maxRetry = client.getConfig().getReconnectMaxRetries();
|
||||
} else if (session != null) {
|
||||
this.autoSysUnlock = session.isAutoSysUnlock();
|
||||
this.keepAlive = session.isKeepAlive();
|
||||
this.keepAliveTimeout = session.getKeepAliveTimeout();
|
||||
this.autoReconnect = session.isAutoReconnect();
|
||||
}
|
||||
|
||||
if (client != null) {
|
||||
client.addConnectionListener(new ConnectionListener() {
|
||||
@@ -114,6 +157,13 @@ public class ECLConnection {
|
||||
state, state, "TN3270E Negotiated", deviceType, deviceName);
|
||||
notifyCommEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTN3270EFunctionsNegotiated(boolean[] functions) {
|
||||
if (functions != null && functions.length > haus.nightmare.lib3270j.protocol.TN3270EConstants.FUNC_CONTENTION_RESOLUTION) {
|
||||
setContentionResolution(functions[haus.nightmare.lib3270j.protocol.TN3270EConstants.FUNC_CONTENTION_RESOLUTION]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -398,17 +448,138 @@ public class ECLConnection {
|
||||
}
|
||||
public void setSSL(boolean ssl) { SetSSL(ssl); }
|
||||
|
||||
public void setContentionResolution(boolean bl) { this.contentionResolution = bl; }
|
||||
public void setContentionResolution(boolean bl) {
|
||||
this.contentionResolution = bl;
|
||||
if (client != null && client.getTelnetFSM() != null) {
|
||||
client.getTelnetFSM().setContentionResolutionNegotiated(bl);
|
||||
}
|
||||
}
|
||||
public void SetContentionResolution(boolean bl) { setContentionResolution(bl); }
|
||||
public boolean getContentionResolution() { return contentionResolution; }
|
||||
public boolean isContentionResolution() { return contentionResolution; }
|
||||
public boolean getContentionResolution() {
|
||||
if (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().isTn3270eNegotiated()) {
|
||||
return client.getTelnetFSM().isContentionResolutionNegotiated();
|
||||
}
|
||||
return contentionResolution;
|
||||
}
|
||||
public boolean isContentionResolution() { return getContentionResolution(); }
|
||||
public boolean GetContentionResolution() { return getContentionResolution(); }
|
||||
public boolean IsContentionResolution() { return getContentionResolution(); }
|
||||
|
||||
public boolean isAutoSysUnlock() {
|
||||
if (client != null && client.getConfig() != null) {
|
||||
return client.getConfig().isAutoSysUnlock();
|
||||
}
|
||||
String s = properties.getProperty(ECLSession.SESSION_AUTO_SYS_UNLOCK);
|
||||
if (s != null) {
|
||||
return "true".equalsIgnoreCase(s) || "1".equals(s);
|
||||
}
|
||||
return autoSysUnlock;
|
||||
}
|
||||
public boolean IsAutoSysUnlock() { return isAutoSysUnlock(); }
|
||||
public boolean getAutoSysUnlock() { return isAutoSysUnlock(); }
|
||||
public boolean GetAutoSysUnlock() { return isAutoSysUnlock(); }
|
||||
|
||||
public void setAutoSysUnlock(boolean unlock) {
|
||||
this.autoSysUnlock = unlock;
|
||||
properties.setProperty(ECLSession.SESSION_AUTO_SYS_UNLOCK, String.valueOf(unlock));
|
||||
if (client != null) {
|
||||
client.setAutoSysUnlock(unlock);
|
||||
}
|
||||
}
|
||||
public void SetAutoSysUnlock(boolean unlock) { setAutoSysUnlock(unlock); }
|
||||
|
||||
public boolean isKeepAlive() {
|
||||
if (client != null && client.getConfig() != null) return client.getConfig().isKeepAliveEnabled();
|
||||
return keepAlive;
|
||||
}
|
||||
public boolean getKeepAlive() { return isKeepAlive(); }
|
||||
public boolean IsKeepAlive() { return isKeepAlive(); }
|
||||
public boolean GetKeepAlive() { return isKeepAlive(); }
|
||||
public void setKeepAlive(boolean ka) {
|
||||
this.keepAlive = ka;
|
||||
this.properties.setProperty(ECLSession.SESSION_KEEPALIVE, String.valueOf(ka));
|
||||
if (client != null) {
|
||||
client.setKeepAliveEnabled(ka);
|
||||
}
|
||||
}
|
||||
public void SetKeepAlive(boolean ka) { setKeepAlive(ka); }
|
||||
|
||||
public int getKeepAliveTimeout() {
|
||||
if (client != null && client.getConfig() != null) return client.getConfig().getKeepAliveIntervalSeconds();
|
||||
return keepAliveTimeout;
|
||||
}
|
||||
public int GetKeepAliveTimeout() { return getKeepAliveTimeout(); }
|
||||
public void setKeepAliveTimeout(int timeout) {
|
||||
this.keepAliveTimeout = timeout;
|
||||
this.properties.setProperty(ECLSession.KEY_KEEPALIVE_TIMEOUT, String.valueOf(timeout));
|
||||
if (client != null) {
|
||||
client.setKeepAliveIntervalSeconds(timeout);
|
||||
}
|
||||
}
|
||||
public void SetKeepAliveTimeout(int timeout) { setKeepAliveTimeout(timeout); }
|
||||
|
||||
public String getKeepAliveType() {
|
||||
if (client != null && client.getConfig() != null) return client.getConfig().getKeepAliveType();
|
||||
return keepAliveType;
|
||||
}
|
||||
public String GetKeepAliveType() { return getKeepAliveType(); }
|
||||
public void setKeepAliveType(String type) {
|
||||
this.keepAliveType = type;
|
||||
this.properties.setProperty(ECLSession.KEY_KEEPALIVE_TYPE, type != null ? type : "");
|
||||
if (client != null && client.getConfig() != null) {
|
||||
client.getConfig().setKeepAliveType(type);
|
||||
}
|
||||
}
|
||||
public void SetKeepAliveType(String type) { setKeepAliveType(type); }
|
||||
|
||||
public boolean isAutoReconnect() {
|
||||
if (client != null && client.getConfig() != null) return client.getConfig().isAutoReconnect();
|
||||
return autoReconnect;
|
||||
}
|
||||
public boolean getAutoReconnect() { return isAutoReconnect(); }
|
||||
public boolean IsAutoReconnect() { return isAutoReconnect(); }
|
||||
public boolean GetAutoReconnect() { return isAutoReconnect(); }
|
||||
public void setAutoReconnect(boolean ar) {
|
||||
this.autoReconnect = ar;
|
||||
this.properties.setProperty(ECLSession.SESSION_AUTORECONNECT, String.valueOf(ar));
|
||||
if (client != null) {
|
||||
client.setAutoReconnect(ar);
|
||||
}
|
||||
}
|
||||
public void SetAutoReconnect(boolean ar) { setAutoReconnect(ar); }
|
||||
|
||||
public int getMaxRetry() {
|
||||
if (client != null && client.getConfig() != null) return client.getConfig().getReconnectMaxRetries();
|
||||
return maxRetry;
|
||||
}
|
||||
public int GetMaxRetry() { return getMaxRetry(); }
|
||||
public void setMaxRetry(int retries) {
|
||||
this.maxRetry = retries;
|
||||
this.properties.setProperty(ECLSession.SESSION_RECONNECT_RETRIES, String.valueOf(retries));
|
||||
if (client != null && client.getConfig() != null) {
|
||||
client.getConfig().setReconnectMaxRetries(retries);
|
||||
}
|
||||
}
|
||||
public void SetMaxRetry(int retries) { setMaxRetry(retries); }
|
||||
public int getReconnectMaxRetries() { return getMaxRetry(); }
|
||||
public void setReconnectMaxRetries(int retries) { setMaxRetry(retries); }
|
||||
|
||||
public void set_LULU_Session(boolean bl) { this.luluSession = bl; }
|
||||
public boolean is_LULU_Session() { return luluSession; }
|
||||
public boolean get_LULU_Session() { return luluSession; }
|
||||
|
||||
public boolean isNegotiateCResolution() { return isNegCR; }
|
||||
public void setNegotiatedCResolution(boolean bl) { this.isNegCR = bl; }
|
||||
public boolean isNegotiateCResolution() {
|
||||
if (client != null && client.getTelnetFSM() != null) {
|
||||
return client.getTelnetFSM().isNegotiateContentionResolution();
|
||||
}
|
||||
return isNegCR;
|
||||
}
|
||||
public void setNegotiatedCResolution(boolean bl) {
|
||||
this.isNegCR = bl;
|
||||
if (client != null && client.getTelnetFSM() != null) {
|
||||
client.getTelnetFSM().setNegotiateContentionResolution(bl);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBIND7FArchitectureViolation() { return isBIND7FArchitectureViolation; }
|
||||
public void setBIND7FArchitectureViolation(boolean bl) { this.isBIND7FArchitectureViolation = bl; }
|
||||
|
||||
@@ -2,6 +2,9 @@ package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
@@ -178,7 +181,20 @@ public class ECLOIA implements ECLConstants {
|
||||
}
|
||||
}
|
||||
|
||||
private final ReentrantLock oiaLock = new ReentrantLock();
|
||||
private final Condition oiaCondition = oiaLock.newCondition();
|
||||
|
||||
public void signalWaiters() {
|
||||
oiaLock.lock();
|
||||
try {
|
||||
oiaCondition.signalAll();
|
||||
} finally {
|
||||
oiaLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyOIAChanged() {
|
||||
signalWaiters();
|
||||
ECLOIAEvent event = new ECLOIAEvent(this, ECLOIAEvent.OIA_UPDATE, getInputInhibited(),
|
||||
getAlphanumericType(), isInsertMode(), getStatusString());
|
||||
for (haus.nightmare.lib3270j.ecl.ECLOIANotify l : listeners) {
|
||||
@@ -413,19 +429,28 @@ public class ECLOIA implements ECLConstants {
|
||||
* @return true if keyboard unlocked, false if timeout occurred.
|
||||
*/
|
||||
public boolean waitForInput(long timeoutMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (getInputInhibited() == INHIBIT_NOT_INHIBITED) {
|
||||
return true;
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
if (getInputInhibited() == INHIBIT_NOT_INHIBITED) {
|
||||
return true;
|
||||
}
|
||||
oiaLock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (getInputInhibited() != INHIBIT_NOT_INHIBITED) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = oiaCondition.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
oiaLock.unlock();
|
||||
}
|
||||
return getInputInhibited() == INHIBIT_NOT_INHIBITED;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -458,20 +483,26 @@ public class ECLOIA implements ECLConstants {
|
||||
* Block until any OIA transition occurs.
|
||||
*/
|
||||
public boolean waitForTransition(long timeoutMs) {
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
int initialInhibit = getInputInhibited();
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (getInputInhibited() != initialInhibit) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
oiaLock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (getInputInhibited() == initialInhibit) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = oiaCondition.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
oiaLock.unlock();
|
||||
}
|
||||
return getInputInhibited() != initialInhibit;
|
||||
}
|
||||
|
||||
public boolean WaitForTransition(long timeoutMs) {
|
||||
|
||||
@@ -2,8 +2,12 @@ package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
/**
|
||||
@@ -37,6 +41,31 @@ public class ECLPS implements ECLConstants {
|
||||
private final java.util.Map<ECLPSListener, ECLScreenDesc> descriptorListeners = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
private final java.util.Map<ECLPSListener, Integer> listenerEventTypes = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
|
||||
// Synchronization primitives for reactive automation waits (Phase 12)
|
||||
private final ReentrantLock fallbackLock = new ReentrantLock();
|
||||
private final Condition fallbackCondition = fallbackLock.newCondition();
|
||||
|
||||
public ReentrantLock getSyncLock() {
|
||||
return (screen != null) ? screen.getSyncLock() : fallbackLock;
|
||||
}
|
||||
|
||||
public Condition getSyncCondition() {
|
||||
return (screen != null) ? screen.getSyncCondition() : fallbackCondition;
|
||||
}
|
||||
|
||||
public void signalWaiters() {
|
||||
if (screen != null) {
|
||||
screen.signalWaiters();
|
||||
} else {
|
||||
fallbackLock.lock();
|
||||
try {
|
||||
fallbackCondition.signalAll();
|
||||
} finally {
|
||||
fallbackLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ECLPS(ScreenBuffer screen, InputProcessor inputProcessor, EbcdicTranslator translator) {
|
||||
this.screen = screen;
|
||||
this.inputProcessor = inputProcessor;
|
||||
@@ -46,6 +75,27 @@ public class ECLPS implements ECLConstants {
|
||||
this.bidiServices = new DefaultPSBIDIServices(this);
|
||||
this.hindiServices = new DefaultPSHindiServices(this);
|
||||
this.thaiServices = new DefaultPSTHAIServices(this);
|
||||
|
||||
if (this.screen != null) {
|
||||
this.screen.addUpdateListener(new ScreenUpdateListener() {
|
||||
@Override
|
||||
public void onScreenUpdated() {
|
||||
signalWaiters();
|
||||
}
|
||||
@Override
|
||||
public void onCursorMoved(int oldAddress, int newAddress) {
|
||||
signalWaiters();
|
||||
}
|
||||
@Override
|
||||
public void onScreenSizeChanged(int rows, int cols) {
|
||||
signalWaiters();
|
||||
}
|
||||
@Override
|
||||
public void onKeyboardUnlocked() {
|
||||
signalWaiters();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ECLPS(ECLSession session) {
|
||||
@@ -494,11 +544,71 @@ public class ECLPS implements ECLConstants {
|
||||
return copyString(sRow, sCol, eRow, eCol);
|
||||
}
|
||||
|
||||
private boolean enablePasteFromExcel = true;
|
||||
private boolean pasteStopAtProtectedLine = false;
|
||||
|
||||
public boolean isEnablePasteFromExcel() {
|
||||
if (session != null && session.getProperties() != null) {
|
||||
String p = session.getProperties().getProperty(ECLSession.ENABLE_PASTE_FROM_EXCEL);
|
||||
if (p != null) return Boolean.parseBoolean(p);
|
||||
}
|
||||
return enablePasteFromExcel;
|
||||
}
|
||||
|
||||
public boolean IsEnablePasteFromExcel() { return isEnablePasteFromExcel(); }
|
||||
|
||||
public void setEnablePasteFromExcel(boolean val) {
|
||||
this.enablePasteFromExcel = val;
|
||||
if (session != null && session.getProperties() != null) {
|
||||
session.getProperties().setProperty(ECLSession.ENABLE_PASTE_FROM_EXCEL, String.valueOf(val));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEnablePasteFromExcel(boolean val) { setEnablePasteFromExcel(val); }
|
||||
|
||||
public boolean isPasteStopAtProtectedLine() {
|
||||
if (session != null && session.getProperties() != null) {
|
||||
String p = session.getProperties().getProperty(ECLSession.PASTE_STOP_AT_PROTECTED_LINE);
|
||||
if (p != null) return Boolean.parseBoolean(p);
|
||||
}
|
||||
return pasteStopAtProtectedLine;
|
||||
}
|
||||
|
||||
public boolean IsPasteStopAtProtectedLine() { return isPasteStopAtProtectedLine(); }
|
||||
|
||||
public void setPasteStopAtProtectedLine(boolean val) {
|
||||
this.pasteStopAtProtectedLine = val;
|
||||
if (session != null && session.getProperties() != null) {
|
||||
session.getProperties().setProperty(ECLSession.PASTE_STOP_AT_PROTECTED_LINE, String.valueOf(val));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPasteStopAtProtectedLine(boolean val) { setPasteStopAtProtectedLine(val); }
|
||||
|
||||
public synchronized int pasteFromExcel(String text, int row, int col) {
|
||||
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||
if (row >= 0 && col >= 0) {
|
||||
setCursorPos(row, col);
|
||||
}
|
||||
if (inputProcessor != null) {
|
||||
return inputProcessor.pasteText(text, true, isPasteStopAtProtectedLine());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int PasteFromExcel(String text, int row, int col) {
|
||||
return pasteFromExcel(text, row, col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a multi-line rectangular block of text starting at (row, col).
|
||||
*/
|
||||
public synchronized int pasteString(String text, int row, int col) {
|
||||
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||
if (inputProcessor != null && (text.contains("\t") || isEnablePasteFromExcel() || isPasteStopAtProtectedLine())) {
|
||||
setCursorPos(row, col);
|
||||
return inputProcessor.pasteText(text, isEnablePasteFromExcel(), isPasteStopAtProtectedLine());
|
||||
}
|
||||
int rows = screen.getRows();
|
||||
int cols = screen.getCols();
|
||||
if (rows <= 0 || cols <= 0) return 0;
|
||||
@@ -532,7 +642,33 @@ public class ECLPS implements ECLConstants {
|
||||
}
|
||||
|
||||
public int pasteRectangular(String text, int row, int col) {
|
||||
return pasteString(text, row, col);
|
||||
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||
int rows = screen.getRows();
|
||||
int cols = screen.getCols();
|
||||
if (rows <= 0 || cols <= 0) return 0;
|
||||
|
||||
String[] lines = text.split("\r?\n");
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int targetRow = (row + i) % rows;
|
||||
String line = lines[i];
|
||||
for (int c = 0; c < line.length() && (col + c) < cols; c++) {
|
||||
int pos = targetRow * cols + (col + c);
|
||||
if (screen.isFormatted()) {
|
||||
byte fa = screen.getFieldAttributeAt(pos);
|
||||
if (faIsProtected(fa & 0xFF) || screen.getCell(pos).isFieldAttribute()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
setCursorPos(pos);
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.typeCharacter(line.charAt(c));
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int PasteRectangular(String text, int row, int col) {
|
||||
@@ -848,6 +984,20 @@ public class ECLPS implements ECLConstants {
|
||||
UnregisterPSEvent(listener);
|
||||
}
|
||||
|
||||
public void dispatchEvent(ECLPSEvent event) {
|
||||
notifyPSEvent(event);
|
||||
}
|
||||
|
||||
public void notifyKeyUnlocked() {
|
||||
int r = (screen != null) ? screen.getRows() : 0;
|
||||
int c = (screen != null) ? screen.getCols() : 0;
|
||||
int cur = (screen != null) ? screen.getCursorAddress() : 0;
|
||||
int ring = ringCounter.incrementAndGet();
|
||||
signalWaiters();
|
||||
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.EVENT_KEY_UNLOCKED, HOST_EVENTS, 0, 0,
|
||||
Math.max(0, r - 1), Math.max(0, c - 1), cur, cur, r, c, false, cursorVisible, ring, false, null));
|
||||
}
|
||||
|
||||
public void notifyPSEvent(ECLPSEvent event) {
|
||||
for (ECLPSListener l : psListeners) {
|
||||
ECLScreenDesc desc = descriptorListeners.get(l);
|
||||
@@ -899,6 +1049,7 @@ public class ECLPS implements ECLConstants {
|
||||
int c = (screen != null) ? screen.getCols() : 0;
|
||||
int cur = (screen != null) ? screen.getCursorAddress() : 0;
|
||||
int ring = ringCounter.incrementAndGet();
|
||||
signalWaiters();
|
||||
ECLPSEvent evt = new ECLPSEvent(this, ECLPSEvent.PS_UPDATE, type, startRow, startCol, endRow, endCol,
|
||||
cur, cur, r, c, full, cursorVisible, ring, startPrinter, null);
|
||||
notifyPSEvent(evt);
|
||||
@@ -910,12 +1061,14 @@ public class ECLPS implements ECLConstants {
|
||||
int row = (c > 0) ? newAddress / c : 0;
|
||||
int col = (c > 0) ? newAddress % c : 0;
|
||||
int ring = ringCounter.incrementAndGet();
|
||||
signalWaiters();
|
||||
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_CURSOR, USER_EVENTS, row, col, row, col,
|
||||
oldAddress, newAddress, r, c, false, cursorVisible, ring, false, null));
|
||||
}
|
||||
|
||||
public void notifyAlarm() {
|
||||
int ring = ringCounter.incrementAndGet();
|
||||
signalWaiters();
|
||||
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_ALARM, HOST_EVENTS, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, false, cursorVisible, ring, false, null));
|
||||
}
|
||||
@@ -923,6 +1076,7 @@ public class ECLPS implements ECLConstants {
|
||||
public void notifyScreenResized(int rows, int cols) {
|
||||
int cur = (screen != null) ? screen.getCursorAddress() : 0;
|
||||
int ring = ringCounter.incrementAndGet();
|
||||
signalWaiters();
|
||||
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_RESIZE, HOST_EVENTS, 0, 0, rows - 1, cols - 1,
|
||||
cur, cur, rows, cols, true, cursorVisible, ring, false, null));
|
||||
}
|
||||
@@ -1067,20 +1221,30 @@ public class ECLPS implements ECLConstants {
|
||||
public boolean waitForScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||
if (desc == null) return true;
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
long start = System.currentTimeMillis();
|
||||
ECLOIA oia = (session != null) ? session.GetOIA() : null;
|
||||
while (System.currentTimeMillis() - start < limit) {
|
||||
if (desc.Matches(this, oia)) {
|
||||
return true;
|
||||
if (desc.Matches(this, oia)) {
|
||||
return true;
|
||||
}
|
||||
ReentrantLock lock = getSyncLock();
|
||||
Condition cond = getSyncCondition();
|
||||
lock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (!desc.Matches(this, oia)) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = cond.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return desc.Matches(this, oia);
|
||||
}
|
||||
|
||||
public boolean WaitForScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||
@@ -1097,20 +1261,30 @@ public class ECLPS implements ECLConstants {
|
||||
public boolean waitWhileScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||
if (desc == null) return true;
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
long start = System.currentTimeMillis();
|
||||
ECLOIA oia = (session != null) ? session.GetOIA() : null;
|
||||
while (System.currentTimeMillis() - start < limit) {
|
||||
if (!desc.Matches(this, oia)) {
|
||||
return true;
|
||||
if (!desc.Matches(this, oia)) {
|
||||
return true;
|
||||
}
|
||||
ReentrantLock lock = getSyncLock();
|
||||
Condition cond = getSyncCondition();
|
||||
lock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (desc.Matches(this, oia)) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = cond.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return !desc.Matches(this, oia);
|
||||
}
|
||||
|
||||
public boolean WaitWhileScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||
@@ -1121,69 +1295,178 @@ public class ECLPS implements ECLConstants {
|
||||
* Block until the specified text appears anywhere on the presentation space.
|
||||
*/
|
||||
public boolean waitForScreen(String text, long timeoutMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (searchString(text) >= 0) {
|
||||
return true;
|
||||
if (text == null || text.isEmpty()) return true;
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
if (searchString(text) >= 0) {
|
||||
return true;
|
||||
}
|
||||
ReentrantLock lock = getSyncLock();
|
||||
Condition cond = getSyncCondition();
|
||||
lock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (searchString(text) < 0) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = cond.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return searchString(text) >= 0;
|
||||
}
|
||||
|
||||
public boolean WaitForScreen(String text, long timeoutMs) {
|
||||
return waitForScreen(text, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitForString(String text) {
|
||||
return waitForScreen(text, -1L);
|
||||
}
|
||||
|
||||
public boolean WaitForString(String text) {
|
||||
return waitForScreen(text, -1L);
|
||||
}
|
||||
|
||||
public boolean waitForString(String text, long timeoutMs) {
|
||||
return waitForScreen(text, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean WaitForString(String text, long timeoutMs) {
|
||||
return waitForScreen(text, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the specified text appears at the given (row, col) coordinate.
|
||||
*/
|
||||
public boolean waitForScreen(String text, int row, int col, long timeoutMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
String onScreen = getString(row, col, text.length());
|
||||
if (text.equals(onScreen)) {
|
||||
return true;
|
||||
if (text == null) return true;
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
if (text.equals(getString(row, col, text.length()))) {
|
||||
return true;
|
||||
}
|
||||
ReentrantLock lock = getSyncLock();
|
||||
Condition cond = getSyncCondition();
|
||||
lock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (!text.equals(getString(row, col, text.length()))) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = cond.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return text.equals(getString(row, col, text.length()));
|
||||
}
|
||||
|
||||
public boolean WaitForScreen(String text, int row, int col, long timeoutMs) {
|
||||
return waitForScreen(text, row, col, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitForString(String text, int row, int col, long timeoutMs) {
|
||||
return waitForScreen(text, row, col, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean WaitForString(String text, int row, int col, long timeoutMs) {
|
||||
return waitForScreen(text, row, col, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the cursor moves to (row, col).
|
||||
*/
|
||||
public boolean waitForCursor(int row, int col, long timeoutMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (getCursorRow() == row && getCursorCol() == col) {
|
||||
return true;
|
||||
long limit = (timeoutMs <= 0) ? 120000L : timeoutMs;
|
||||
if (getCursorRow() == row && getCursorCol() == col) {
|
||||
return true;
|
||||
}
|
||||
ReentrantLock lock = getSyncLock();
|
||||
Condition cond = getSyncCondition();
|
||||
lock.lock();
|
||||
try {
|
||||
long remainingNanos = TimeUnit.MILLISECONDS.toNanos(limit);
|
||||
while (getCursorRow() != row || getCursorCol() != col) {
|
||||
if (remainingNanos <= 0) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
remainingNanos = cond.awaitNanos(remainingNanos);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(25);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return getCursorRow() == row && getCursorCol() == col;
|
||||
}
|
||||
|
||||
public boolean WaitForCursor(int row, int col, long timeoutMs) {
|
||||
return waitForCursor(row, col, timeoutMs);
|
||||
}
|
||||
|
||||
protected boolean locked_SYSLOCK = false;
|
||||
protected boolean locked_TWAIT = false;
|
||||
|
||||
public void lockKeyboard() {
|
||||
lockKeyboard(8);
|
||||
}
|
||||
|
||||
public void lockKeyboard(int reason) {
|
||||
if (reason == 7) locked_TWAIT = true;
|
||||
if (reason == 8) {
|
||||
locked_SYSLOCK = true;
|
||||
if (session != null && session.getOIA() != null) {
|
||||
session.getOIA().setDoNotEnter(8, 0);
|
||||
}
|
||||
}
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.setKeyboardLocked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void unlockKeyboard() {
|
||||
unlockKeyboard(8);
|
||||
}
|
||||
|
||||
public void unlockKeyboard(int reason) {
|
||||
if (reason == 7) locked_TWAIT = false;
|
||||
if (reason == 8) {
|
||||
locked_SYSLOCK = false;
|
||||
if (session != null && session.getOIA() != null) {
|
||||
session.getOIA().clearDoNotEnter();
|
||||
}
|
||||
}
|
||||
if (!locked_TWAIT && !locked_SYSLOCK) {
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.setKeyboardLocked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean islocked_TWAIT() {
|
||||
return locked_TWAIT;
|
||||
}
|
||||
|
||||
public boolean islocked_SYSLOCK() {
|
||||
if (locked_SYSLOCK) return true;
|
||||
if (session != null && session.getOIA() != null && session.getOIA().isXSystem()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public class ECLPSEvent extends EventObject {
|
||||
public static final int EVENT_ALARM = PS_ALARM;
|
||||
public static final int EVENT_RESIZE = PS_RESIZE;
|
||||
public static final int EVENT_CLOSE = PS_CLOSE;
|
||||
public static final int EVENT_KEY_UNLOCKED = PS_UPDATE; // HoD event type 1 for keyboard unlock / update
|
||||
|
||||
private final int eventType;
|
||||
private final int type;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.Rectangle;
|
||||
import haus.nightmare.lib3270j.graphics.PixelBuffer;
|
||||
import haus.nightmare.lib3270j.graphics.Rectangle;
|
||||
|
||||
/**
|
||||
* Conforms to IBM Host On-Demand ECLPSGraphicsEvent.
|
||||
* Decoupled from java.awt.
|
||||
*/
|
||||
public class ECLPSGraphicsEvent {
|
||||
public static final int GRAPHICS_CURSOR_ON = 1;
|
||||
@@ -14,7 +15,7 @@ public class ECLPSGraphicsEvent {
|
||||
public static final int GRAPHICS_UPDATED = 5;
|
||||
|
||||
private int id;
|
||||
private Image image;
|
||||
private Object image;
|
||||
private Rectangle rect;
|
||||
private ECLPS source;
|
||||
|
||||
@@ -23,13 +24,13 @@ public class ECLPSGraphicsEvent {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ECLPSGraphicsEvent(ECLPS source, int id, Image image) {
|
||||
public ECLPSGraphicsEvent(ECLPS source, int id, Object image) {
|
||||
this.source = source;
|
||||
this.id = id;
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public ECLPSGraphicsEvent(ECLPS source, int id, Image image, Rectangle rectangle) {
|
||||
public ECLPSGraphicsEvent(ECLPS source, int id, Object image, Rectangle rectangle) {
|
||||
this.source = source;
|
||||
this.id = id;
|
||||
this.image = image;
|
||||
@@ -46,9 +47,13 @@ public class ECLPSGraphicsEvent {
|
||||
public int getID() { return this.id; }
|
||||
public int GetID() { return this.id; }
|
||||
|
||||
public void setImage(Image image) { this.image = image; }
|
||||
public Image getImage() { return this.image; }
|
||||
public Image GetImage() { return this.image; }
|
||||
public void setImage(Object image) { this.image = image; }
|
||||
public Object getImage() { return this.image; }
|
||||
public Object GetImage() { return this.image; }
|
||||
|
||||
public PixelBuffer getPixelBuffer() {
|
||||
return (this.image instanceof PixelBuffer) ? (PixelBuffer) this.image : null;
|
||||
}
|
||||
|
||||
public void setRectangle(Rectangle rect) { this.rect = rect; }
|
||||
public Rectangle getRectangle() { return this.rect; }
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import haus.nightmare.lib3270j.graphics.Color;
|
||||
|
||||
/**
|
||||
* Presentation Space graphics services interface conforming to IBM Host On-Demand ECL.
|
||||
* Completely decoupled from java.awt.
|
||||
*/
|
||||
public interface ECLPSGraphicsServices {
|
||||
void setVisualComponent(Component comp);
|
||||
void setVisualComponent(Object comp);
|
||||
void setGraphicColor(Color[] colors, boolean b);
|
||||
void mousePressed(int x, int y, int button);
|
||||
void addGraphicsListener(ECLPSGraphicsListener listener);
|
||||
|
||||
@@ -18,7 +18,7 @@ public class ECLSession {
|
||||
|
||||
private static final Logger log = Logger.getLogger(ECLSession.class.getName());
|
||||
|
||||
// Standard IBM HoD Session Property Keys
|
||||
// Standard Session Property Keys
|
||||
public static final String SESSION_HOST = "SESSION_HOST";
|
||||
public static final String SESSION_PORT = "SESSION_PORT";
|
||||
public static final String SESSION_CODE_PAGE = "SESSION_CODE_PAGE";
|
||||
@@ -29,6 +29,24 @@ public class ECLSession {
|
||||
public static final String SESSION_TN3270E = "SESSION_TN3270E";
|
||||
public static final String SESSION_WIN_TITLE = "SESSION_WIN_TITLE";
|
||||
public static final String SESSION_AUTO_CONNECT = "SESSION_AUTO_CONNECT";
|
||||
public static final String SESSION_AUTO_SYS_UNLOCK = "autoSysUnlock";
|
||||
public static final String SESSION_KEEPALIVE = "SESSION_KEEPALIVE";
|
||||
public static final String KEY_KEEPALIVE_TYPE = "keepAliveType";
|
||||
public static final String KEY_KEEPALIVE_TIMEOUT = "keepAliveTimeout";
|
||||
public static final String SESSION_AUTORECONNECT = "SESSION_AUTORECONNECT";
|
||||
public static final String SESSION_RECONNECT_RETRIES = "SESSION_RECONNECT_RETRIES";
|
||||
public static final String ENABLE_PASTE_FROM_EXCEL = "enablePasteFromExcel";
|
||||
public static final String PASTE_TAB_OPTIONS = "pasteTabOptions";
|
||||
public static final String PASTE_STOP_AT_PROTECTED_LINE = "pasteStopAtProtectedLine";
|
||||
public static final String PASTE_FIELD_WRAP = "pasteFieldWrap";
|
||||
public static final String PASTE_LINE_WRAP = "pasteLineWrap";
|
||||
public static final String ENTRYASSIST_DOCMODE = "EntryAssist_DOCmode";
|
||||
public static final String ENTRYASSIST_DOCWORDWRAP = "EntryAssist_DOCwordWrap";
|
||||
public static final String ENTRYASSIST_STARTCOL = "EntryAssist_startCol";
|
||||
public static final String ENTRYASSIST_ENDCOL = "EntryAssist_endCol";
|
||||
public static final String ENTRYASSIST_BELL = "EntryAssist_bell";
|
||||
public static final String ENTRYASSIST_BELLCOL = "EntryAssist_bellCol";
|
||||
public static final String ENTRYASSIST_TABSTOPS = "EntryAssist_tabstops";
|
||||
|
||||
private final Telnet3270Client client;
|
||||
private final ECLConnection connection;
|
||||
@@ -98,6 +116,9 @@ public class ECLSession {
|
||||
String tn3270eStr = getProp(props, SESSION_TN3270E, "tn3270e", "TN3270E", "true");
|
||||
config.setTn3270eEnabled("true".equalsIgnoreCase(tn3270eStr) || "yes".equalsIgnoreCase(tn3270eStr) || "1".equals(tn3270eStr));
|
||||
|
||||
String autoSysStr = getProp(props, SESSION_AUTO_SYS_UNLOCK, "autoSysUnlock", "AutoSysUnlock", "true");
|
||||
config.setAutoSysUnlock("true".equalsIgnoreCase(autoSysStr) || "yes".equalsIgnoreCase(autoSysStr) || "1".equals(autoSysStr));
|
||||
|
||||
String certUrl = getProp(props, "certificateURL", "CERTIFICATE_URL", "certificate_url", null);
|
||||
if (certUrl != null) config.setKeyStorePath(certUrl);
|
||||
String certPwd = getProp(props, "certificatePassword", "CERTIFICATE_PASSWORD", "certificate_password", null);
|
||||
@@ -118,6 +139,25 @@ public class ECLSession {
|
||||
config.setEnabledProtocols(tlsVer);
|
||||
}
|
||||
|
||||
String keepAliveStr = getProp(props, SESSION_KEEPALIVE, "keepAlive", "keepalive", "true");
|
||||
config.setKeepAliveEnabled("true".equalsIgnoreCase(keepAliveStr) || "yes".equalsIgnoreCase(keepAliveStr) || "1".equals(keepAliveStr));
|
||||
|
||||
String kaTimeoutStr = getProp(props, KEY_KEEPALIVE_TIMEOUT, "keepAliveTimeout", "keepalivetimeout", null);
|
||||
if (kaTimeoutStr != null) {
|
||||
try { config.setKeepAliveIntervalSeconds(Integer.parseInt(kaTimeoutStr.trim())); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
String kaTypeStr = getProp(props, KEY_KEEPALIVE_TYPE, "keepAliveType", "keepalivetype", null);
|
||||
if (kaTypeStr != null) config.setKeepAliveType(kaTypeStr);
|
||||
|
||||
String autoReconnectStr = getProp(props, SESSION_AUTORECONNECT, "autoReconnect", "autoreconnect", "false");
|
||||
config.setAutoReconnect("true".equalsIgnoreCase(autoReconnectStr) || "yes".equalsIgnoreCase(autoReconnectStr) || "1".equals(autoReconnectStr));
|
||||
|
||||
String retriesStr = getProp(props, SESSION_RECONNECT_RETRIES, "reconnectMaxRetries", "reconnectRetries", null);
|
||||
if (retriesStr != null) {
|
||||
try { config.setReconnectMaxRetries(Integer.parseInt(retriesStr.trim())); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -161,6 +201,7 @@ public class ECLSession {
|
||||
properties.setProperty(SESSION_SSL, String.valueOf(cfg.isUseTls()));
|
||||
if (cfg.getLuName() != null) properties.setProperty(SESSION_LU_NAME, cfg.getLuName());
|
||||
properties.setProperty(SESSION_TN3270E, String.valueOf(cfg.isTn3270eEnabled()));
|
||||
properties.setProperty(SESSION_AUTO_SYS_UNLOCK, String.valueOf(cfg.isAutoSysUnlock()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,6 +412,42 @@ public class ECLSession {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isKeepAlive() {
|
||||
return (client != null) ? client.isKeepAliveEnabled() : true;
|
||||
}
|
||||
public boolean IsKeepAlive() { return isKeepAlive(); }
|
||||
public void setKeepAlive(boolean keepAlive) {
|
||||
this.properties.setProperty(SESSION_KEEPALIVE, String.valueOf(keepAlive));
|
||||
if (client != null) {
|
||||
client.setKeepAliveEnabled(keepAlive);
|
||||
}
|
||||
}
|
||||
public void SetKeepAlive(boolean keepAlive) { setKeepAlive(keepAlive); }
|
||||
|
||||
public int getKeepAliveTimeout() {
|
||||
return (client != null) ? client.getKeepAliveIntervalSeconds() : 120;
|
||||
}
|
||||
public int GetKeepAliveTimeout() { return getKeepAliveTimeout(); }
|
||||
public void setKeepAliveTimeout(int timeout) {
|
||||
this.properties.setProperty(KEY_KEEPALIVE_TIMEOUT, String.valueOf(timeout));
|
||||
if (client != null) {
|
||||
client.setKeepAliveIntervalSeconds(timeout);
|
||||
}
|
||||
}
|
||||
public void SetKeepAliveTimeout(int timeout) { setKeepAliveTimeout(timeout); }
|
||||
|
||||
public boolean isAutoReconnect() {
|
||||
return (client != null) ? client.isAutoReconnect() : false;
|
||||
}
|
||||
public boolean IsAutoReconnect() { return isAutoReconnect(); }
|
||||
public void setAutoReconnect(boolean autoReconnect) {
|
||||
this.properties.setProperty(SESSION_AUTORECONNECT, String.valueOf(autoReconnect));
|
||||
if (client != null) {
|
||||
client.setAutoReconnect(autoReconnect);
|
||||
}
|
||||
}
|
||||
public void SetAutoReconnect(boolean autoReconnect) { setAutoReconnect(autoReconnect); }
|
||||
|
||||
// ========== Automation Keystrokes & Waits ==========
|
||||
|
||||
/**
|
||||
@@ -455,6 +532,41 @@ public class ECLSession {
|
||||
dispose();
|
||||
}
|
||||
|
||||
public boolean isAutoSysUnlock() {
|
||||
if (client != null && client.getConfig() != null) {
|
||||
return client.getConfig().isAutoSysUnlock();
|
||||
}
|
||||
String s = properties.getProperty(SESSION_AUTO_SYS_UNLOCK);
|
||||
return s != null ? Boolean.parseBoolean(s) : true;
|
||||
}
|
||||
public boolean getAutoSysUnlock() { return isAutoSysUnlock(); }
|
||||
public boolean IsAutoSysUnlock() { return isAutoSysUnlock(); }
|
||||
public boolean GetAutoSysUnlock() { return isAutoSysUnlock(); }
|
||||
|
||||
public void setAutoSysUnlock(boolean unlock) {
|
||||
properties.setProperty(SESSION_AUTO_SYS_UNLOCK, String.valueOf(unlock));
|
||||
if (client != null) {
|
||||
client.setAutoSysUnlock(unlock);
|
||||
}
|
||||
}
|
||||
public void SetAutoSysUnlock(boolean unlock) { setAutoSysUnlock(unlock); }
|
||||
|
||||
public boolean getContentionResolution() {
|
||||
if (connection != null) return connection.getContentionResolution();
|
||||
if (client != null) return client.isContentionResolution();
|
||||
return false;
|
||||
}
|
||||
public boolean isContentionResolution() { return getContentionResolution(); }
|
||||
public boolean GetContentionResolution() { return getContentionResolution(); }
|
||||
public boolean IsContentionResolution() { return getContentionResolution(); }
|
||||
|
||||
public void setContentionResolution(boolean cr) {
|
||||
if (connection != null) {
|
||||
connection.setContentionResolution(cr);
|
||||
}
|
||||
}
|
||||
public void SetContentionResolution(boolean cr) { setContentionResolution(cr); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ECLSession[host=%s, port=%d, connected=%b, state=%s]",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Platform-neutral Color representation encapsulating 32-bit ARGB.
|
||||
* Completely decouples lib3270j from java.awt.Color.
|
||||
*/
|
||||
public class Color implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final Color WHITE = new Color(0xFFFFFFFF);
|
||||
public static final Color LIGHT_GRAY = new Color(0xFFC0C0C0);
|
||||
public static final Color GRAY = new Color(0xFF808080);
|
||||
public static final Color DARK_GRAY = new Color(0xFF404040);
|
||||
public static final Color BLACK = new Color(0xFF000000);
|
||||
public static final Color RED = new Color(0xFFFF0000);
|
||||
public static final Color PINK = new Color(0xFFFFAFAF);
|
||||
public static final Color ORANGE = new Color(0xFFFFC800);
|
||||
public static final Color YELLOW = new Color(0xFFFFFF00);
|
||||
public static final Color GREEN = new Color(0xFF00FF00);
|
||||
public static final Color MAGENTA = new Color(0xFFFF00FF);
|
||||
public static final Color CYAN = new Color(0xFF00FFFF);
|
||||
public static final Color BLUE = new Color(0xFF0000FF);
|
||||
|
||||
private final int value;
|
||||
|
||||
public Color(int rgb) {
|
||||
this.value = 0xFF000000 | rgb;
|
||||
}
|
||||
|
||||
public Color(int rgba, boolean hasAlpha) {
|
||||
if (hasAlpha) {
|
||||
this.value = rgba;
|
||||
} else {
|
||||
this.value = 0xFF000000 | rgba;
|
||||
}
|
||||
}
|
||||
|
||||
public Color(int r, int g, int b) {
|
||||
this(r, g, b, 255);
|
||||
}
|
||||
|
||||
public Color(int r, int g, int b, int a) {
|
||||
this.value = ((a & 0xFF) << 24) |
|
||||
((r & 0xFF) << 16) |
|
||||
((g & 0xFF) << 8) |
|
||||
(b & 0xFF);
|
||||
}
|
||||
|
||||
public Color(float r, float g, float b) {
|
||||
this((int) (r * 255 + 0.5), (int) (g * 255 + 0.5), (int) (b * 255 + 0.5));
|
||||
}
|
||||
|
||||
public Color(float r, float g, float b, float a) {
|
||||
this((int) (r * 255 + 0.5), (int) (g * 255 + 0.5), (int) (b * 255 + 0.5), (int) (a * 255 + 0.5));
|
||||
}
|
||||
|
||||
public int getRGB() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getRed() {
|
||||
return (value >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
public int getGreen() {
|
||||
return (value >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
public int getBlue() {
|
||||
return value & 0xFF;
|
||||
}
|
||||
|
||||
public int getAlpha() {
|
||||
return (value >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Color)) return false;
|
||||
return this.value == ((Color) obj).value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[r=" + getRed() + ",g=" + getGreen() + ",b=" + getBlue() + ",a=" + getAlpha() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Default standalone implementation of PixelBuffer.
|
||||
* Provides pure Java software rasterization into a 32-bit ARGB contiguous integer array.
|
||||
*/
|
||||
public class DefaultPixelBuffer implements PixelBuffer {
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private int[] pixels;
|
||||
|
||||
private boolean hasClip = false;
|
||||
private int clipX;
|
||||
private int clipY;
|
||||
private int clipWidth;
|
||||
private int clipHeight;
|
||||
|
||||
public DefaultPixelBuffer(int width, int height) {
|
||||
this.width = Math.max(1, width);
|
||||
this.height = Math.max(1, height);
|
||||
this.pixels = new int[this.width * this.height];
|
||||
}
|
||||
|
||||
public DefaultPixelBuffer(int width, int height, int[] existingPixels) {
|
||||
this.width = Math.max(1, width);
|
||||
this.height = Math.max(1, height);
|
||||
if (existingPixels != null && existingPixels.length >= this.width * this.height) {
|
||||
this.pixels = existingPixels;
|
||||
} else {
|
||||
this.pixels = new int[this.width * this.height];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getPixels() {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getPixel(int x, int y) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) return 0;
|
||||
return pixels[y * width + x];
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setPixelDirect(int x, int y, int argb) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) return;
|
||||
if (isClipped(x, y)) return;
|
||||
pixels[y * width + x] = argb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setPixel(int x, int y, int argb) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) return;
|
||||
if (isClipped(x, y)) return;
|
||||
|
||||
int srcA = (argb >>> 24) & 0xFF;
|
||||
if (srcA == 0) return;
|
||||
|
||||
int idx = y * width + x;
|
||||
if (srcA == 255) {
|
||||
pixels[idx] = argb;
|
||||
return;
|
||||
}
|
||||
|
||||
int dst = pixels[idx];
|
||||
int dstA = (dst >>> 24) & 0xFF;
|
||||
if (dstA == 0) {
|
||||
pixels[idx] = argb;
|
||||
return;
|
||||
}
|
||||
|
||||
int srcR = (argb >>> 16) & 0xFF;
|
||||
int srcG = (argb >>> 8) & 0xFF;
|
||||
int srcB = argb & 0xFF;
|
||||
|
||||
int dstR = (dst >>> 16) & 0xFF;
|
||||
int dstG = (dst >>> 8) & 0xFF;
|
||||
int dstB = dst & 0xFF;
|
||||
|
||||
int outA = srcA + dstA * (255 - srcA) / 255;
|
||||
if (outA == 0) {
|
||||
pixels[idx] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int outR = (srcR * srcA + dstR * dstA * (255 - srcA) / 255) / outA;
|
||||
int outG = (srcG * srcA + dstG * dstA * (255 - srcA) / 255) / outA;
|
||||
int outB = (srcB * srcA + dstB * dstA * (255 - srcA) / 255) / outA;
|
||||
|
||||
pixels[idx] = (outA << 24) | (outR << 16) | (outG << 8) | outB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear() {
|
||||
clear(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear(int argb) {
|
||||
Arrays.fill(pixels, argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setClip(int x, int y, int width, int height) {
|
||||
this.hasClip = true;
|
||||
this.clipX = x;
|
||||
this.clipY = y;
|
||||
this.clipWidth = Math.max(0, width);
|
||||
this.clipHeight = Math.max(0, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clearClip() {
|
||||
this.hasClip = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClipped(int x, int y) {
|
||||
if (!hasClip) return false;
|
||||
return x < clipX || x >= (clipX + clipWidth) || y < clipY || y >= (clipY + clipHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void fillRect(int x, int y, int w, int h, int argb) {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
int x1 = Math.max(0, x);
|
||||
int y1 = Math.max(0, y);
|
||||
int x2 = Math.min(width, x + w);
|
||||
int y2 = Math.min(height, y + h);
|
||||
|
||||
if (hasClip) {
|
||||
x1 = Math.max(x1, clipX);
|
||||
y1 = Math.max(y1, clipY);
|
||||
x2 = Math.min(x2, clipX + clipWidth);
|
||||
y2 = Math.min(y2, clipY + clipHeight);
|
||||
}
|
||||
|
||||
for (int row = y1; row < y2; row++) {
|
||||
int rowOffset = row * width;
|
||||
Arrays.fill(pixels, rowOffset + x1, rowOffset + x2, argb);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void blit(int[] srcPixels, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY) {
|
||||
if (srcPixels == null || srcW <= 0 || srcH <= 0) return;
|
||||
|
||||
for (int r = 0; r < srcH; r++) {
|
||||
int sy = srcY + r;
|
||||
int dy = dstY + r;
|
||||
if (dy < 0 || dy >= height) continue;
|
||||
|
||||
for (int c = 0; c < srcW; c++) {
|
||||
int sx = srcX + c;
|
||||
int dx = dstX + c;
|
||||
if (dx < 0 || dx >= width) continue;
|
||||
if (isClipped(dx, dy)) continue;
|
||||
|
||||
int sp = srcPixels[sy * srcW + sx];
|
||||
setPixel(dx, dy, sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void drawLine(int x1, int y1, int x2, int y2, int argb) {
|
||||
drawLineBresenham(x1, y1, x2, y2, argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void drawLineBresenham(int x0, int y0, int x1, int y1, int color) {
|
||||
int dx = Math.abs(x1 - x0);
|
||||
int dy = Math.abs(y1 - y0);
|
||||
int sx = (x0 < x1) ? 1 : -1;
|
||||
int sy = (y0 < y1) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
int curX = x0;
|
||||
int curY = y0;
|
||||
|
||||
while (true) {
|
||||
setPixel(curX, curY, color);
|
||||
if (curX == x1 && curY == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
curX += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
curY += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void drawLine(double x0, double y0, double x1, double y1, int colorArgb, int lineType, int lineWidth) {
|
||||
int lw = Math.max(1, lineWidth);
|
||||
int ix0 = (int) Math.round(x0);
|
||||
int iy0 = (int) Math.round(y0);
|
||||
int ix1 = (int) Math.round(x1);
|
||||
int iy1 = (int) Math.round(y1);
|
||||
|
||||
if (lw == 1) {
|
||||
drawLineBresenham(ix0, iy0, ix1, iy1, colorArgb);
|
||||
} else {
|
||||
int half = lw / 2;
|
||||
for (int ox = -half; ox <= half; ox++) {
|
||||
for (int oy = -half; oy <= half; oy++) {
|
||||
drawLineBresenham(ix0 + ox, iy0 + oy, ix1 + ox, iy1 + oy, colorArgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void drawLineAA(double x0, double y0, double x1, double y1, int colorArgb, double strokeWidth) {
|
||||
// Pure Java anti-aliased line rendering
|
||||
double dx = x1 - x0;
|
||||
double dy = y1 - y0;
|
||||
double len = Math.hypot(dx, dy);
|
||||
if (len < 1e-4) {
|
||||
setPixel((int) Math.round(x0), (int) Math.round(y0), colorArgb);
|
||||
return;
|
||||
}
|
||||
|
||||
double radius = Math.max(0.5, strokeWidth * 0.5);
|
||||
int minX = (int) Math.floor(Math.min(x0, x1) - radius - 1);
|
||||
int maxX = (int) Math.ceil(Math.max(x0, x1) + radius + 1);
|
||||
int minY = (int) Math.floor(Math.min(y0, y1) - radius - 1);
|
||||
int maxY = (int) Math.ceil(Math.max(y0, y1) + radius + 1);
|
||||
|
||||
minX = Math.max(0, minX);
|
||||
maxX = Math.min(width - 1, maxX);
|
||||
minY = Math.max(0, minY);
|
||||
maxY = Math.min(height - 1, maxY);
|
||||
|
||||
int baseAlpha = (colorArgb >>> 24) & 0xFF;
|
||||
if (baseAlpha == 0) baseAlpha = 255;
|
||||
int rgbOnly = colorArgb & 0x00FFFFFF;
|
||||
|
||||
double invLenSq = 1.0 / (len * len);
|
||||
|
||||
for (int py = minY; py <= maxY; py++) {
|
||||
for (int px = minX; px <= maxX; px++) {
|
||||
double u = ((px - x0) * dx + (py - y0) * dy) * invLenSq;
|
||||
u = Math.max(0.0, Math.min(1.0, u));
|
||||
double projX = x0 + u * dx;
|
||||
double projY = y0 + u * dy;
|
||||
double dist = Math.hypot(px - projX, py - projY);
|
||||
|
||||
if (dist <= radius) {
|
||||
double coverage = 1.0 - (dist / radius);
|
||||
coverage = Math.sin(coverage * Math.PI * 0.5); // Smooth cosine roll-off
|
||||
int effectiveAlpha = (int) (baseAlpha * coverage);
|
||||
if (effectiveAlpha > 0) {
|
||||
setPixel(px, py, (effectiveAlpha << 24) | rgbOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Lightweight pure-Java POJO dimension for 2D width and height.
|
||||
* Completely decouples lib3270j from java.awt.Dimension.
|
||||
*/
|
||||
public class Dimension implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public int width;
|
||||
public int height;
|
||||
|
||||
public Dimension() {
|
||||
this(0, 0);
|
||||
}
|
||||
|
||||
public Dimension(Dimension d) {
|
||||
this(d != null ? d.width : 0, d != null ? d.height : 0);
|
||||
}
|
||||
|
||||
public Dimension(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setSize(Dimension d) {
|
||||
if (d != null) {
|
||||
this.width = d.width;
|
||||
this.height = d.height;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Dimension)) return false;
|
||||
Dimension d = (Dimension) obj;
|
||||
return (width == d.width) && (height == d.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[width=" + width + ",height=" + height + "]";
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public class FillArea {
|
||||
/**
|
||||
* IBM Host On-Demand multi-polygon constructor.
|
||||
*/
|
||||
public FillArea(int[] px, int[] py, int[] polyCounts, int numPolys, java.awt.Color color) {
|
||||
public FillArea(int[] px, int[] py, int[] polyCounts, int numPolys, Color color) {
|
||||
this();
|
||||
if (color != null) {
|
||||
this.fillColor = color.getRGB();
|
||||
@@ -72,9 +72,28 @@ public class FillArea {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized java.awt.Rectangle getBounds() {
|
||||
public FillArea(int[] px, int[] py, int[] polyCounts, int numPolys, int argb) {
|
||||
this();
|
||||
this.fillColor = argb;
|
||||
if (px != null && py != null && polyCounts != null) {
|
||||
int offset = 0;
|
||||
for (int i = 0; i < numPolys && i < polyCounts.length; i++) {
|
||||
int count = polyCounts[i];
|
||||
if (count >= 2 && offset + count <= px.length && offset + count <= py.length) {
|
||||
int[] sx = new int[count];
|
||||
int[] sy = new int[count];
|
||||
System.arraycopy(px, offset, sx, 0, count);
|
||||
System.arraycopy(py, offset, sy, 0, count);
|
||||
addPolygon(sx, sy, count);
|
||||
}
|
||||
offset += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Rectangle getBounds() {
|
||||
if (edges.isEmpty()) {
|
||||
return new java.awt.Rectangle(0, 0, 0, 0);
|
||||
return new Rectangle(0, 0, 0, 0);
|
||||
}
|
||||
double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE;
|
||||
double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE;
|
||||
@@ -88,7 +107,7 @@ public class FillArea {
|
||||
int y = (int) Math.floor(minY);
|
||||
int w = (int) Math.ceil(maxX) - x + 1;
|
||||
int h = (int) Math.ceil(maxY) - y + 1;
|
||||
return new java.awt.Rectangle(x, y, Math.max(0, w), Math.max(0, h));
|
||||
return new Rectangle(x, y, Math.max(0, w), Math.max(0, h));
|
||||
}
|
||||
|
||||
public synchronized void setFillModeOR() {
|
||||
@@ -123,18 +142,20 @@ public class FillArea {
|
||||
this.pixelPattern = pat;
|
||||
}
|
||||
|
||||
public synchronized java.awt.Image getImage() {
|
||||
java.awt.Rectangle b = getBounds();
|
||||
public synchronized PixelBuffer getPixelBuffer() {
|
||||
Rectangle b = getBounds();
|
||||
if (b.width <= 0 || b.height <= 0) {
|
||||
return new java.awt.image.BufferedImage(1, 1, java.awt.image.BufferedImage.TYPE_INT_ARGB);
|
||||
return new DefaultPixelBuffer(1, 1);
|
||||
}
|
||||
GraphicsPlane tempPlane = new GraphicsPlane(b.x + b.width, b.y + b.height);
|
||||
fill(tempPlane, fillColor, 0, solidFill ? GocaConstants.PT_SOLID : 0, false, 0, 0, 1, 0, 0, null);
|
||||
java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(b.width, b.height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
|
||||
java.awt.Graphics g = img.getGraphics();
|
||||
g.drawImage(tempPlane.getImage(), -b.x, -b.y, null);
|
||||
g.dispose();
|
||||
return img;
|
||||
DefaultPixelBuffer cropped = new DefaultPixelBuffer(b.width, b.height);
|
||||
cropped.blit(tempPlane.getRgbBuffer(), b.x, b.y, b.width, b.height, 0, 0);
|
||||
return cropped;
|
||||
}
|
||||
|
||||
public synchronized Object getImage() {
|
||||
return getPixelBuffer();
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Point;
|
||||
|
||||
/**
|
||||
* Dedicated scaling layer for IBM 3179G / GDDM GOCA graphics presentation space.
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.DirectColorModel;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.SinglePixelPackedSampleModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -23,9 +9,9 @@ import java.util.logging.Logger;
|
||||
/**
|
||||
* Offscreen rendering surface for GOCA vector graphics.
|
||||
* Maintained as an ARGB 32-bit integer pixel buffer that overlays the 3270 character cell matrix.
|
||||
* Pure Java software rasterizer compatible with standard Java SE (Swing) and Android (Bitmap).
|
||||
* Pure Java software rasterizer compatible with standard Java SE, Android, and headless environments.
|
||||
*/
|
||||
public class GraphicsPlane {
|
||||
public class GraphicsPlane implements PixelBuffer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GraphicsPlane.class.getName());
|
||||
|
||||
@@ -111,28 +97,118 @@ public class GraphicsPlane {
|
||||
drawLine((double) x1, (double) y1, (double) x2, (double) y2, currentColorArgb, currentLineType, currentLineWidth);
|
||||
}
|
||||
|
||||
private BufferedImage canvasImage;
|
||||
@Override
|
||||
public synchronized void drawLine(int x1, int y1, int x2, int y2, int argb) {
|
||||
drawLine((double) x1, (double) y1, (double) x2, (double) y2, argb, currentLineType, currentLineWidth);
|
||||
}
|
||||
|
||||
public synchronized BufferedImage toBufferedImage() {
|
||||
if (canvasImage == null && rgbBuffer != null && canvasWidth > 0 && canvasHeight > 0) {
|
||||
DataBufferInt db = new DataBufferInt(rgbBuffer, rgbBuffer.length);
|
||||
DirectColorModel cm = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
|
||||
WritableRaster raster = Raster.createWritableRaster(
|
||||
new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, canvasWidth, canvasHeight,
|
||||
new int[]{0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000}),
|
||||
db, null
|
||||
);
|
||||
canvasImage = new BufferedImage(cm, raster, false, null);
|
||||
@Override
|
||||
public synchronized int getWidth() {
|
||||
return canvasWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getHeight() {
|
||||
return canvasHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int[] getPixels() {
|
||||
return rgbBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void fillRect(int x, int y, int width, int height, int argb) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
int x1 = Math.max(0, x);
|
||||
int y1 = Math.max(0, y);
|
||||
int x2 = Math.min(canvasWidth, x + width);
|
||||
int y2 = Math.min(canvasHeight, y + height);
|
||||
for (int cy = y1; cy < y2; cy++) {
|
||||
for (int cx = x1; cx < x2; cx++) {
|
||||
setPixel(cx, cy, argb);
|
||||
}
|
||||
}
|
||||
return canvasImage;
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
public synchronized Image getImage() {
|
||||
return toBufferedImage();
|
||||
@Override
|
||||
public synchronized void blit(int[] srcPixels, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY) {
|
||||
if (srcPixels == null || srcW <= 0 || srcH <= 0) return;
|
||||
for (int r = 0; r < srcH; r++) {
|
||||
int sy = srcY + r;
|
||||
int dy = dstY + r;
|
||||
if (dy < 0 || dy >= canvasHeight) continue;
|
||||
|
||||
for (int c = 0; c < srcW; c++) {
|
||||
int sx = srcX + c;
|
||||
int dx = dstX + c;
|
||||
if (dx < 0 || dx >= canvasWidth) continue;
|
||||
if (isClipped(dx, dy)) continue;
|
||||
|
||||
int idx = sy * srcW + sx;
|
||||
if (idx < srcPixels.length) {
|
||||
setPixel(dx, dy, srcPixels[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
public synchronized Graphics getGraphics() {
|
||||
return toBufferedImage().getGraphics();
|
||||
public synchronized void copyPixels(int[] srcPixels, int srcOffset, int srcScan, int dstX, int dstY, int width, int height) {
|
||||
if (srcPixels == null || width <= 0 || height <= 0) return;
|
||||
for (int r = 0; r < height; r++) {
|
||||
int cy = dstY + r;
|
||||
if (cy < 0 || cy >= canvasHeight) continue;
|
||||
int srcRowStart = srcOffset + r * srcScan;
|
||||
for (int c = 0; c < width; c++) {
|
||||
int cx = dstX + c;
|
||||
if (cx < 0 || cx >= canvasWidth) continue;
|
||||
int p = srcPixels[srcRowStart + c];
|
||||
setPixel(cx, cy, p);
|
||||
}
|
||||
}
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setClip(int x, int y, int width, int height) {
|
||||
if (width < 0 || height < 0) {
|
||||
clearClip();
|
||||
} else {
|
||||
this.clipPixelXMin = x;
|
||||
this.clipPixelYMin = y;
|
||||
this.clipPixelXMax = x + width;
|
||||
this.clipPixelYMax = y + height;
|
||||
this.viewingWindowActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clearClip() {
|
||||
this.clipPixelXMin = 0;
|
||||
this.clipPixelYMin = 0;
|
||||
this.clipPixelXMax = canvasWidth;
|
||||
this.clipPixelYMax = canvasHeight;
|
||||
this.viewingWindowActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClipped(int x, int y) {
|
||||
if (!viewingWindowActive) {
|
||||
return x < 0 || x >= canvasWidth || y < 0 || y >= canvasHeight;
|
||||
}
|
||||
return x < clipPixelXMin || x > clipPixelXMax || y < clipPixelYMin || y > clipPixelYMax;
|
||||
}
|
||||
|
||||
public synchronized Rectangle getClip() {
|
||||
if (!viewingWindowActive) {
|
||||
return new Rectangle(0, 0, canvasWidth, canvasHeight);
|
||||
}
|
||||
return new Rectangle(clipPixelXMin, clipPixelYMin, clipPixelXMax - clipPixelXMin, clipPixelYMax - clipPixelYMin);
|
||||
}
|
||||
|
||||
public void setProgramSymbolManager(ProgramSymbolManager psm) {
|
||||
@@ -230,16 +306,21 @@ public class GraphicsPlane {
|
||||
this.canvasWidth = w;
|
||||
this.canvasHeight = h;
|
||||
this.rgbBuffer = newBuffer;
|
||||
this.canvasImage = null;
|
||||
updateViewingWindowPixels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear() {
|
||||
clear(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear(int argb) {
|
||||
if (rgbBuffer != null) {
|
||||
Arrays.fill(rgbBuffer, 0);
|
||||
Arrays.fill(rgbBuffer, argb);
|
||||
}
|
||||
this.currentMixMode = 0;
|
||||
hasContent = false;
|
||||
hasContent = (argb != 0);
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
@@ -536,6 +617,7 @@ public class GraphicsPlane {
|
||||
/**
|
||||
* Standard integer Bresenham line algorithm matching IBM 3179G / Host On-Demand 1-pixel rasterization.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void drawLineBresenham(int x0, int y0, int x1, int y1, int color) {
|
||||
int dx = Math.abs(x1 - x0);
|
||||
int dy = Math.abs(y1 - y0);
|
||||
@@ -598,6 +680,7 @@ public class GraphicsPlane {
|
||||
/**
|
||||
* Draws a line matching IBM 3179G / Host On-Demand rasterization.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void drawLine(double x0, double y0, double x1, double y1, int colorArgb, int lineType, int lineWidth) {
|
||||
int color = (colorArgb != 0) ? (colorArgb & 0x00FFFFFF) : (GocaConstants.GOCA_COLORS[0] & 0x00FFFFFF);
|
||||
int ix0 = (int) Math.round(x0);
|
||||
@@ -628,6 +711,111 @@ public class GraphicsPlane {
|
||||
drawLine((double) x1, (double) y1, (double) x2, (double) y2, colorArgb, lineType, lineWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void drawLineAA(double x0, double y0, double x1, double y1, int argb, double strokeWidth) {
|
||||
drawLineAA(x0, y0, x1, y1, argb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an anti-aliased line using Xiaolin Wu's algorithm directly into the pixel buffer.
|
||||
* Pure Java implementation replacing AWT Graphics2D rendering for sub-pixel vector strokes.
|
||||
*/
|
||||
public synchronized void drawLineAA(double x0, double y0, double x1, double y1, int color) {
|
||||
boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
|
||||
if (steep) {
|
||||
double t = x0; x0 = y0; y0 = t;
|
||||
t = x1; x1 = y1; y1 = t;
|
||||
}
|
||||
if (x0 > x1) {
|
||||
double t = x0; x0 = x1; x1 = t;
|
||||
t = y0; y0 = y1; y1 = t;
|
||||
}
|
||||
|
||||
double dx = x1 - x0;
|
||||
double dy = y1 - y0;
|
||||
double gradient = (dx == 0.0) ? 1.0 : dy / dx;
|
||||
|
||||
// Handle first endpoint
|
||||
double xend = Math.round(x0);
|
||||
double yend = y0 + gradient * (xend - x0);
|
||||
double xgap = 1.0 - (x0 + 0.5 - Math.floor(x0 + 0.5));
|
||||
int xpxl1 = (int) xend;
|
||||
int ypxl1 = (int) Math.floor(yend);
|
||||
if (steep) {
|
||||
plotAA(ypxl1, xpxl1, (1.0 - (yend - Math.floor(yend))) * xgap, color);
|
||||
plotAA(ypxl1 + 1, xpxl1, (yend - Math.floor(yend)) * xgap, color);
|
||||
} else {
|
||||
plotAA(xpxl1, ypxl1, (1.0 - (yend - Math.floor(yend))) * xgap, color);
|
||||
plotAA(xpxl1, ypxl1 + 1, (yend - Math.floor(yend)) * xgap, color);
|
||||
}
|
||||
double intery = yend + gradient;
|
||||
|
||||
// Handle second endpoint
|
||||
xend = Math.round(x1);
|
||||
yend = y1 + gradient * (xend - x1);
|
||||
xgap = x1 + 0.5 - Math.floor(x1 + 0.5);
|
||||
int xpxl2 = (int) xend;
|
||||
int ypxl2 = (int) Math.floor(yend);
|
||||
if (steep) {
|
||||
plotAA(ypxl2, xpxl2, (1.0 - (yend - Math.floor(yend))) * xgap, color);
|
||||
plotAA(ypxl2 + 1, xpxl2, (yend - Math.floor(yend)) * xgap, color);
|
||||
} else {
|
||||
plotAA(xpxl2, ypxl2, (1.0 - (yend - Math.floor(yend))) * xgap, color);
|
||||
plotAA(xpxl2, ypxl2 + 1, (yend - Math.floor(yend)) * xgap, color);
|
||||
}
|
||||
|
||||
// Main loop
|
||||
if (steep) {
|
||||
for (int x = xpxl1 + 1; x < xpxl2; x++) {
|
||||
int iy = (int) Math.floor(intery);
|
||||
double fpart = intery - iy;
|
||||
plotAA(iy, x, 1.0 - fpart, color);
|
||||
plotAA(iy + 1, x, fpart, color);
|
||||
intery += gradient;
|
||||
}
|
||||
} else {
|
||||
for (int x = xpxl1 + 1; x < xpxl2; x++) {
|
||||
int iy = (int) Math.floor(intery);
|
||||
double fpart = intery - iy;
|
||||
plotAA(x, iy, 1.0 - fpart, color);
|
||||
plotAA(x, iy + 1, fpart, color);
|
||||
intery += gradient;
|
||||
}
|
||||
}
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
private void plotAA(int x, int y, double brightness, int color) {
|
||||
if (x < 0 || x >= canvasWidth || y < 0 || y >= canvasHeight || brightness <= 0.0) return;
|
||||
if (brightness > 1.0) brightness = 1.0;
|
||||
int sa = (color >>> 24) & 0xFF;
|
||||
if (sa == 0) sa = 0xFF;
|
||||
int alpha = (int) Math.round(sa * brightness);
|
||||
if (alpha <= 0) return;
|
||||
|
||||
int sr = (color >>> 16) & 0xFF;
|
||||
int sg = (color >>> 8) & 0xFF;
|
||||
int sb = color & 0xFF;
|
||||
|
||||
int idx = y * canvasWidth + x;
|
||||
int dst = rgbBuffer[idx];
|
||||
int da = (dst >>> 24) & 0xFF;
|
||||
if (da == 0) {
|
||||
rgbBuffer[idx] = (alpha << 24) | (sr << 16) | (sg << 8) | sb;
|
||||
} else {
|
||||
int dr = (dst >>> 16) & 0xFF;
|
||||
int dg = (dst >>> 8) & 0xFF;
|
||||
int db = dst & 0xFF;
|
||||
int invA = 255 - alpha;
|
||||
int outR = (sr * alpha + dr * invA) / 255;
|
||||
int outG = (sg * alpha + dg * invA) / 255;
|
||||
int outB = (sb * alpha + db * invA) / 255;
|
||||
int outA = Math.min(255, da + alpha);
|
||||
rgbBuffer[idx] = (outA << 24) | (outR << 16) | (outG << 8) | outB;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawStyledLine(int x1, int y1, int x2, int y2, int colorArgb, int lineType, int lineWidth) {
|
||||
int color = (colorArgb != 0) ? colorArgb : GocaConstants.GOCA_COLORS[0];
|
||||
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
||||
@@ -1231,56 +1419,46 @@ public class GraphicsPlane {
|
||||
double tanShear = Math.tan(Math.toRadians(shearAngle));
|
||||
|
||||
if (ch < 6.0) {
|
||||
BufferedImage img = toBufferedImage();
|
||||
if (img != null) {
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
||||
float strokeW = (float) Math.max(0.5, Math.min(0.75, ch / 5.0));
|
||||
g.setStroke(new BasicStroke(strokeW, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
g.setColor(new Color(color, true));
|
||||
int ptr = offset;
|
||||
while (ptr < VectorSymbolData.vss_data.length && VectorSymbolData.vss_data[ptr] != VectorSymbolData.END_DEFAULT) {
|
||||
int order = VectorSymbolData.vss_data[ptr] & 0xFF;
|
||||
if (order == 0xC1) {
|
||||
int byteLen = VectorSymbolData.vss_data[ptr + 1] & 0xFF;
|
||||
int numPoints = byteLen / 4;
|
||||
int dataPtr = ptr + 2;
|
||||
|
||||
int ptr = offset;
|
||||
while (ptr < VectorSymbolData.vss_data.length && VectorSymbolData.vss_data[ptr] != VectorSymbolData.END_DEFAULT) {
|
||||
int order = VectorSymbolData.vss_data[ptr] & 0xFF;
|
||||
if (order == 0xC1) {
|
||||
int byteLen = VectorSymbolData.vss_data[ptr + 1] & 0xFF;
|
||||
int numPoints = byteLen / 4;
|
||||
int dataPtr = ptr + 2;
|
||||
if (numPoints >= 2) {
|
||||
double[] px = new double[numPoints];
|
||||
double[] py = new double[numPoints];
|
||||
for (int p = 0; p < numPoints; p++) {
|
||||
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);
|
||||
|
||||
if (numPoints >= 2) {
|
||||
Path2D.Double path = new Path2D.Double();
|
||||
for (int p = 0; p < numPoints; p++) {
|
||||
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);
|
||||
double nx = ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
|
||||
double ny = -((double) vy / VectorSymbolData.VSS_HEIGHT) * ch;
|
||||
|
||||
double nx = ((double) vx / VectorSymbolData.VSS_WIDTH) * cw;
|
||||
double ny = -((double) vy / VectorSymbolData.VSS_HEIGHT) * ch;
|
||||
double sx = nx - ny * tanShear;
|
||||
double sy = ny;
|
||||
|
||||
double sx = nx - ny * tanShear;
|
||||
double sy = ny;
|
||||
double rx = (angle != 0.0) ? (sx * cosA - sy * sinA) : sx;
|
||||
double ry = (angle != 0.0) ? (sx * sinA + sy * cosA) : sy;
|
||||
|
||||
double rx = (angle != 0.0) ? (sx * cosA - sy * sinA) : sx;
|
||||
double ry = (angle != 0.0) ? (sx * sinA + sy * cosA) : sy;
|
||||
px[p] = x + rx;
|
||||
py[p] = y + ry;
|
||||
}
|
||||
|
||||
double px = x + rx;
|
||||
double py = y + ry;
|
||||
|
||||
if (p == 0) path.moveTo(px, py);
|
||||
else path.lineTo(px, py);
|
||||
}
|
||||
g.draw(path);
|
||||
for (int p = 0; p < numPoints - 1; p++) {
|
||||
drawLineAA(px[p], py[p], px[p + 1], py[p + 1], color);
|
||||
}
|
||||
ptr += 2 + byteLen;
|
||||
} else {
|
||||
ptr++;
|
||||
}
|
||||
ptr += 2 + byteLen;
|
||||
} else {
|
||||
ptr++;
|
||||
}
|
||||
g.dispose();
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
return;
|
||||
}
|
||||
hasContent = true;
|
||||
updateCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
int ptr = offset;
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Image;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.FilteredImageSource;
|
||||
import java.awt.image.MemoryImageSource;
|
||||
|
||||
/**
|
||||
* Bitmap image container matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODBitImage).
|
||||
* Pure Java software implementation completely decoupled from java.awt.
|
||||
*/
|
||||
public class HODBitImage {
|
||||
protected Component vComponent;
|
||||
protected Object vComponent;
|
||||
protected Dimension iSize = new Dimension();
|
||||
protected Dimension iScaledSize = new Dimension();
|
||||
protected int iDepth;
|
||||
protected int iScanLength;
|
||||
protected boolean _iUseGraphicColors;
|
||||
protected byte[] iScaledImageData;
|
||||
protected Image[] hImage;
|
||||
protected Image[] iScaledImage;
|
||||
protected PixelBuffer[] hImage;
|
||||
protected PixelBuffer[] iScaledImage;
|
||||
protected int transparentBG = 0;
|
||||
protected byte[] hImageData;
|
||||
protected int iBaseColor;
|
||||
|
||||
public HODBitImage(Component comp, int width, int height, byte[] data, int baseColor, int depth, boolean useGraphicColors) {
|
||||
public HODBitImage(Object comp, int width, int height, byte[] data, int baseColor, int depth, boolean useGraphicColors) {
|
||||
this.vComponent = comp;
|
||||
this.iSize.width = width;
|
||||
this.iSize.height = height;
|
||||
@@ -32,8 +26,8 @@ public class HODBitImage {
|
||||
this.iBaseColor = baseColor;
|
||||
this.iDepth = depth;
|
||||
this._iUseGraphicColors = useGraphicColors;
|
||||
this.hImage = new Image[depth == 1 ? 17 : 1];
|
||||
this.iScaledImage = new Image[depth == 1 ? 17 : 1];
|
||||
this.hImage = new PixelBuffer[depth == 1 ? 17 : 1];
|
||||
this.iScaledImage = new PixelBuffer[depth == 1 ? 17 : 1];
|
||||
this.buildHODImage();
|
||||
}
|
||||
|
||||
@@ -72,11 +66,19 @@ public class HODBitImage {
|
||||
return nArray;
|
||||
}
|
||||
|
||||
public Image getHODImage(int colorIdx) {
|
||||
return this.getHODImage(this.iSize.width, this.iSize.height, colorIdx);
|
||||
public Object getHODImage(int colorIdx) {
|
||||
return this.getHODPixelBuffer(this.iSize.width, this.iSize.height, colorIdx);
|
||||
}
|
||||
|
||||
public Image getHODImage(int w, int h, int colorIdx) {
|
||||
public Object getHODImage(int w, int h, int colorIdx) {
|
||||
return this.getHODPixelBuffer(w, h, colorIdx);
|
||||
}
|
||||
|
||||
public PixelBuffer getHODPixelBuffer(int colorIdx) {
|
||||
return this.getHODPixelBuffer(this.iSize.width, this.iSize.height, colorIdx);
|
||||
}
|
||||
|
||||
public PixelBuffer getHODPixelBuffer(int w, int h, int colorIdx) {
|
||||
if (w <= 0 || h <= 0) return null;
|
||||
boolean diffColor = this.iBaseColor != colorIdx && this.iDepth == 1;
|
||||
boolean matchesBase = this.iSize.width == w && this.iSize.height == h;
|
||||
@@ -88,13 +90,18 @@ public class HODBitImage {
|
||||
this.iScaledSize.height = h;
|
||||
this.scaleHODImage();
|
||||
}
|
||||
Image image = matchesBase ? this.hImage[colorIdx] : this.iScaledImage[colorIdx];
|
||||
PixelBuffer image = matchesBase ? this.hImage[colorIdx] : this.iScaledImage[colorIdx];
|
||||
if (image == null) {
|
||||
HODColorChangeFilter filter = new HODColorChangeFilter(this.getHODColor(colorIdx));
|
||||
Image base = matchesBase ? this.hImage[this.iBaseColor] : this.iScaledImage[this.iBaseColor];
|
||||
PixelBuffer base = matchesBase ? this.hImage[this.iBaseColor] : this.iScaledImage[this.iBaseColor];
|
||||
if (base != null) {
|
||||
FilteredImageSource source = new FilteredImageSource(base.getSource(), filter);
|
||||
image = Toolkit.getDefaultToolkit().createImage(source);
|
||||
int bw = base.getWidth();
|
||||
int bh = base.getHeight();
|
||||
int[] basePixels = base.getPixels();
|
||||
int[] filtered = new int[bw * bh];
|
||||
System.arraycopy(basePixels, 0, filtered, 0, filtered.length);
|
||||
filter.apply(filtered, 0, filtered.length);
|
||||
image = new DefaultPixelBuffer(bw, bh, filtered);
|
||||
if (matchesBase) {
|
||||
this.hImage[colorIdx] = image;
|
||||
} else {
|
||||
@@ -121,27 +128,47 @@ public class HODBitImage {
|
||||
private void buildHODImage() {
|
||||
if (this.iSize.width <= 0 || this.iSize.height <= 0) return;
|
||||
int[] pixels = getHODImageData(this.iBaseColor);
|
||||
MemoryImageSource mis = new MemoryImageSource(this.iSize.width, this.iSize.height, pixels, 0, this.iSize.width);
|
||||
Image img = Toolkit.getDefaultToolkit().createImage(mis);
|
||||
PixelBuffer buf = new DefaultPixelBuffer(this.iSize.width, this.iSize.height, pixels);
|
||||
if (this.iDepth == 1) {
|
||||
this.hImage[this.iBaseColor] = img;
|
||||
this.hImage[this.iBaseColor] = buf;
|
||||
} else {
|
||||
this.hImage[0] = img;
|
||||
this.hImage[0] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
private void scaleHODImage() {
|
||||
if (this.iScaledSize.width <= 0 || this.iScaledSize.height <= 0) return;
|
||||
Image base = this.hImage[this.iDepth == 1 ? this.iBaseColor : 0];
|
||||
int sw = this.iScaledSize.width;
|
||||
int sh = this.iScaledSize.height;
|
||||
if (sw <= 0 || sh <= 0) return;
|
||||
PixelBuffer base = this.hImage[this.iDepth == 1 ? this.iBaseColor : 0];
|
||||
if (base != null) {
|
||||
this.iScaledImage[this.iDepth == 1 ? this.iBaseColor : 0] =
|
||||
base.getScaledInstance(this.iScaledSize.width, this.iScaledSize.height, Image.SCALE_FAST);
|
||||
int bw = base.getWidth();
|
||||
int bh = base.getHeight();
|
||||
int[] src = base.getPixels();
|
||||
int[] dst = new int[sw * sh];
|
||||
for (int dy = 0; dy < sh; dy++) {
|
||||
int sy = dy * bh / sh;
|
||||
int srcOffset = sy * bw;
|
||||
int dstOffset = dy * sw;
|
||||
for (int dx = 0; dx < sw; dx++) {
|
||||
int sx = dx * bw / sw;
|
||||
dst[dstOffset + dx] = src[srcOffset + sx];
|
||||
}
|
||||
}
|
||||
this.iScaledImage[this.iDepth == 1 ? this.iBaseColor : 0] = new DefaultPixelBuffer(sw, sh, dst);
|
||||
}
|
||||
}
|
||||
|
||||
private int getHODColor(int idx) {
|
||||
if (idx == 0 && this.vComponent != null) {
|
||||
return this.vComponent.getBackground().getRGB();
|
||||
try {
|
||||
java.lang.reflect.Method m = this.vComponent.getClass().getMethod("getBackground");
|
||||
Object bg = m.invoke(this.vComponent);
|
||||
if (bg != null) {
|
||||
java.lang.reflect.Method mRgb = bg.getClass().getMethod("getRGB");
|
||||
return ((Number) mRgb.invoke(bg)).intValue();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
return GocaConstants.getGocaColorArgb(idx);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
/**
|
||||
* Bounding box encapsulation matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODBounds).
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.RGBImageFilter;
|
||||
|
||||
/**
|
||||
* Image filter that replaces occurrences of one color with another color.
|
||||
* Conforms to IBM Host On-Demand HODColorChangeFilter.
|
||||
* Platform-neutral implementation independent of java.awt.
|
||||
*/
|
||||
public class HODColorChangeFilter extends RGBImageFilter {
|
||||
public class HODColorChangeFilter {
|
||||
|
||||
protected boolean canFilterIndexColorModel = true;
|
||||
private int oldRgb;
|
||||
private int newRgb;
|
||||
|
||||
public HODColorChangeFilter(int newRgb) {
|
||||
this.canFilterIndexColorModel = true;
|
||||
this.oldRgb = -1;
|
||||
this.newRgb = newRgb | 0xFF000000;
|
||||
}
|
||||
@@ -23,7 +21,6 @@ public class HODColorChangeFilter extends RGBImageFilter {
|
||||
}
|
||||
|
||||
public HODColorChangeFilter(int oldRgb, int newRgb) {
|
||||
this.canFilterIndexColorModel = true;
|
||||
this.oldRgb = oldRgb & 0x00FFFFFF;
|
||||
this.newRgb = newRgb;
|
||||
}
|
||||
@@ -48,7 +45,6 @@ public class HODColorChangeFilter extends RGBImageFilter {
|
||||
this.newRgb = newRgb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterRGB(int x, int y, int rgb) {
|
||||
if (oldRgb == -1) {
|
||||
if ((rgb & 0xFF000000) != 0) {
|
||||
@@ -61,4 +57,20 @@ public class HODColorChangeFilter extends RGBImageFilter {
|
||||
}
|
||||
return rgb;
|
||||
}
|
||||
|
||||
public void apply(int[] pixels, int offset, int length) {
|
||||
if (pixels == null) return;
|
||||
int end = Math.min(pixels.length, offset + length);
|
||||
for (int i = offset; i < end; i++) {
|
||||
pixels[i] = filterRGB(0, 0, pixels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void apply(PixelBuffer buffer) {
|
||||
if (buffer == null) return;
|
||||
int[] pixels = buffer.getPixels();
|
||||
if (pixels != null) {
|
||||
apply(pixels, 0, pixels.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Headless graphics plane facade matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODGraphicsPlane).
|
||||
* Pure Java software implementation completely decoupled from java.awt.
|
||||
*/
|
||||
public class HODGraphicsPlane {
|
||||
private final GraphicsPlane delegate;
|
||||
@@ -40,6 +32,10 @@ public class HODGraphicsPlane {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public PixelBuffer getPixelBuffer() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public void resize(Dimension dim, boolean keepContent) {
|
||||
if (dim != null) {
|
||||
delegate.setDimensions(dim.width, dim.height);
|
||||
@@ -52,15 +48,19 @@ public class HODGraphicsPlane {
|
||||
this.bounds.set(0, 0, delegate.getCanvasWidth(), delegate.getCanvasHeight());
|
||||
}
|
||||
|
||||
public Graphics getHODGraphics() {
|
||||
return delegate.getGraphics();
|
||||
public Object getHODGraphics() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public Image getHODImage() {
|
||||
return delegate.getImage();
|
||||
public Object getHODImage() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public void setHODTemporaryGraphics(Graphics g) {
|
||||
public PixelBuffer getHODPixelBuffer() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
public void setHODTemporaryGraphics(Object g) {
|
||||
// No-op or temporary override
|
||||
}
|
||||
|
||||
@@ -116,35 +116,32 @@ public class HODGraphicsPlane {
|
||||
}
|
||||
|
||||
public void drawHODArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
|
||||
Graphics g = delegate.getGraphics();
|
||||
if (g != null) {
|
||||
g.setColor(currentColor);
|
||||
g.drawArc(x, y, width, height, startAngle, arcAngle);
|
||||
updateHODBounds(x, y);
|
||||
updateHODBounds(x + width, y + height);
|
||||
}
|
||||
double rx = width / 2.0;
|
||||
double ry = height / 2.0;
|
||||
double cx = x + rx;
|
||||
double cy = y + ry;
|
||||
delegate.drawArc(cx, cy, rx, ry, startAngle, arcAngle, currentColor.getRGB(), currentLineType, currentLineWidth, false);
|
||||
updateHODBounds(x, y);
|
||||
updateHODBounds(x + width, y + height);
|
||||
}
|
||||
|
||||
public void fillHODArc(int x, int y, int width, int height, int startAngle, int arcAngle) {
|
||||
Graphics g = delegate.getGraphics();
|
||||
if (g != null) {
|
||||
g.setColor(currentColor);
|
||||
g.fillArc(x, y, width, height, startAngle, arcAngle);
|
||||
updateHODBounds(x, y);
|
||||
updateHODBounds(x + width, y + height);
|
||||
}
|
||||
double rx = width / 2.0;
|
||||
double ry = height / 2.0;
|
||||
double cx = x + rx;
|
||||
double cy = y + ry;
|
||||
delegate.drawArc(cx, cy, rx, ry, startAngle, arcAngle, currentColor.getRGB(), currentLineType, currentLineWidth, true);
|
||||
updateHODBounds(x, y);
|
||||
updateHODBounds(x + width, y + height);
|
||||
}
|
||||
|
||||
public void drawHODImage(HODBitImage bitImage, int x, int y, int w, int h) {
|
||||
if (bitImage != null) {
|
||||
Image img = bitImage.getHODImage(w, h, currentColorIndex);
|
||||
if (img != null) {
|
||||
Graphics g = delegate.getGraphics();
|
||||
if (g != null) {
|
||||
g.drawImage(img, x, y, null);
|
||||
updateHODBounds(x, y);
|
||||
updateHODBounds(x + w, y + h);
|
||||
}
|
||||
PixelBuffer buf = bitImage.getHODPixelBuffer(w, h, currentColorIndex);
|
||||
if (buf != null) {
|
||||
delegate.blit(buf.getPixels(), 0, 0, buf.getWidth(), buf.getHeight(), x, y);
|
||||
updateHODBounds(x, y);
|
||||
updateHODBounds(x + w, y + h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Visual part container matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODPart).
|
||||
* Completely decoupled from java.awt.
|
||||
*/
|
||||
public class HODPart extends Rectangle implements Serializable {
|
||||
protected Component hodParent;
|
||||
protected Font _hodFont;
|
||||
protected Object hodParent;
|
||||
protected Object _hodFont;
|
||||
protected Color foregroundColor;
|
||||
protected Color backgroundColor;
|
||||
protected Boolean isTransparent;
|
||||
@@ -22,33 +17,36 @@ public class HODPart extends Rectangle implements Serializable {
|
||||
|
||||
protected HODPart() {}
|
||||
|
||||
public HODPart(Component component) {
|
||||
public HODPart(Object component) {
|
||||
this();
|
||||
this.setHODParent(component);
|
||||
}
|
||||
|
||||
public HODPart(Component component, Dimension dimension) {
|
||||
super(dimension);
|
||||
public HODPart(Object component, Dimension dimension) {
|
||||
super(0, 0, dimension != null ? dimension.width : 0, dimension != null ? dimension.height : 0);
|
||||
this.setHODParent(component);
|
||||
}
|
||||
|
||||
public HODPart(Component component, Rectangle rectangle) {
|
||||
super(rectangle);
|
||||
public HODPart(Object component, Rectangle rectangle) {
|
||||
super(rectangle != null ? rectangle.x : 0, rectangle != null ? rectangle.y : 0,
|
||||
rectangle != null ? rectangle.width : 0, rectangle != null ? rectangle.height : 0);
|
||||
this.setHODParent(component);
|
||||
}
|
||||
|
||||
public HODPart(HODPart hODPart) {
|
||||
this(hODPart.getHODParent(), hODPart.getSize());
|
||||
this.setHODBackground(hODPart.getHODBackground());
|
||||
this.setHODForeground(hODPart.getHODForeground());
|
||||
this.setHODFont(hODPart.getHODFont());
|
||||
this(hODPart != null ? hODPart.getHODParent() : null, hODPart != null ? hODPart.getSize() : null);
|
||||
if (hODPart != null) {
|
||||
this.setHODBackground(hODPart.getHODBackground());
|
||||
this.setHODForeground(hODPart.getHODForeground());
|
||||
this.setHODFont(hODPart.getHODFont());
|
||||
}
|
||||
}
|
||||
|
||||
public Component getHODParent() {
|
||||
public Object getHODParent() {
|
||||
return this.hodParent;
|
||||
}
|
||||
|
||||
public void setHODParent(Component component) {
|
||||
public void setHODParent(Object component) {
|
||||
if (component != null && !component.equals(this.hodParent)) {
|
||||
this.hodParent = component;
|
||||
}
|
||||
@@ -56,18 +54,28 @@ public class HODPart extends Rectangle implements Serializable {
|
||||
|
||||
public void repaint() {
|
||||
if (this.hodParent != null) {
|
||||
this.hodParent.repaint(this.x, this.y, this.width, this.height);
|
||||
try {
|
||||
Method m = this.hodParent.getClass().getMethod("repaint", int.class, int.class, int.class, int.class);
|
||||
m.invoke(this.hodParent, this.x, this.y, this.width, this.height);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void paint(Component component, Graphics graphics, int x, int y, int w, int h) {
|
||||
public void paint(Object component, Object graphics, int x, int y, int w, int h) {
|
||||
this.setBounds(x, y, w, h);
|
||||
if (Boolean.TRUE.equals(this._visible)) {
|
||||
this.paintHODView(graphics);
|
||||
}
|
||||
}
|
||||
|
||||
protected void paintHODView(Graphics graphics) {}
|
||||
public void paint(PixelBuffer buffer, int x, int y, int w, int h) {
|
||||
this.setBounds(x, y, w, h);
|
||||
if (Boolean.TRUE.equals(this._visible)) {
|
||||
this.paintHODView(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
protected void paintHODView(Object graphics) {}
|
||||
|
||||
public Color getHODBackground() {
|
||||
return this.backgroundColor;
|
||||
@@ -85,11 +93,11 @@ public class HODPart extends Rectangle implements Serializable {
|
||||
this.foregroundColor = color;
|
||||
}
|
||||
|
||||
public Font getHODFont() {
|
||||
public Object getHODFont() {
|
||||
return this._hodFont;
|
||||
}
|
||||
|
||||
public void setHODFont(Font font) {
|
||||
public void setHODFont(Object font) {
|
||||
this._hodFont = font;
|
||||
}
|
||||
|
||||
|
||||
+50
-9
@@ -1,12 +1,10 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Programmed Symbol Set manager facade matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODProgramSymbolManager).
|
||||
* Completely decoupled from java.awt.
|
||||
*/
|
||||
public class HODProgramSymbolManager {
|
||||
public static final int MAX_HOD_SLOT = 254;
|
||||
@@ -51,14 +49,57 @@ public class HODProgramSymbolManager {
|
||||
delegate.loadProgrammedSymbolSet(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public void drawHODImageCharacter(Graphics g, int lcid, int codepoint, Point pt, int colorIdx, int cellW, int cellH) {
|
||||
if (g == null || pt == null) return;
|
||||
public void drawHODImageCharacter(PixelBuffer pb, int lcid, int codepoint, Point pt, int colorIdx, int cellW, int cellH) {
|
||||
if (pb == null || pt == null) return;
|
||||
ProgramSymbolSet.SymbolSlot slot = delegate.getSymbol(lcid, codepoint);
|
||||
if (slot != null) {
|
||||
int fg = GocaConstants.getGocaColorArgb(colorIdx);
|
||||
BufferedImage img = slot.getScaledImage(cellW, cellH, fg, 0);
|
||||
if (img != null) {
|
||||
g.drawImage(img, pt.x, pt.y, null);
|
||||
PixelBuffer glyph = slot.getScaledPixelBuffer(cellW, cellH, fg, 0);
|
||||
if (glyph != null) {
|
||||
int sw = glyph.getWidth();
|
||||
int sh = glyph.getHeight();
|
||||
int[] srcPx = glyph.getPixels();
|
||||
int[] dstPx = pb.getPixels();
|
||||
int pw = pb.getWidth();
|
||||
int ph = pb.getHeight();
|
||||
for (int r = 0; r < sh; r++) {
|
||||
int dy = pt.y + r;
|
||||
if (dy < 0 || dy >= ph) continue;
|
||||
for (int c = 0; c < sw; c++) {
|
||||
int dx = pt.x + c;
|
||||
if (dx < 0 || dx >= pw) continue;
|
||||
int p = srcPx[r * sw + c];
|
||||
if ((p & 0xFF000000) != 0) {
|
||||
dstPx[dy * pw + dx] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawHODImageCharacter(Object g, int lcid, int codepoint, Point pt, int colorIdx, int cellW, int cellH) {
|
||||
if (g == null || pt == null) return;
|
||||
if (g instanceof PixelBuffer) {
|
||||
drawHODImageCharacter((PixelBuffer) g, lcid, codepoint, pt, colorIdx, cellW, cellH);
|
||||
return;
|
||||
}
|
||||
// Fallback for AWT Graphics if passed reflectively
|
||||
ProgramSymbolSet.SymbolSlot slot = delegate.getSymbol(lcid, codepoint);
|
||||
if (slot != null) {
|
||||
int fg = GocaConstants.getGocaColorArgb(colorIdx);
|
||||
PixelBuffer glyph = slot.getScaledPixelBuffer(cellW, cellH, fg, 0);
|
||||
if (glyph != null) {
|
||||
try {
|
||||
Class<?> biClass = Class.forName("java.awt.image.BufferedImage");
|
||||
Object bi = biClass.getConstructor(int.class, int.class, int.class)
|
||||
.newInstance(glyph.getWidth(), glyph.getHeight(), 2); // TYPE_INT_ARGB
|
||||
Method setRGB = biClass.getMethod("setRGB", int.class, int.class, int.class, int.class, int[].class, int.class, int.class);
|
||||
setRGB.invoke(bi, 0, 0, glyph.getWidth(), glyph.getHeight(), glyph.getPixels(), 0, glyph.getWidth());
|
||||
|
||||
Method drawImg = g.getClass().getMethod("drawImage", Class.forName("java.awt.Image"), int.class, int.class, Class.forName("java.awt.image.ImageObserver"));
|
||||
drawImg.invoke(g, bi, pt.x, pt.y, null);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
/**
|
||||
* Coordinate transform adapter matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODTransform).
|
||||
|
||||
+19
-6
@@ -1,18 +1,16 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.RGBImageFilter;
|
||||
|
||||
/**
|
||||
* Image filter that keys out a specific background color by setting its alpha to 0x00.
|
||||
* Conforms to IBM Host On-Demand HODTransparentColorFilter.
|
||||
* Platform-neutral implementation independent of java.awt.
|
||||
*/
|
||||
public class HODTransparentColorFilter extends RGBImageFilter {
|
||||
public class HODTransparentColorFilter {
|
||||
|
||||
protected boolean canFilterIndexColorModel = true;
|
||||
private int transparentRgb;
|
||||
|
||||
public HODTransparentColorFilter(int rgb) {
|
||||
this.canFilterIndexColorModel = true;
|
||||
this.transparentRgb = rgb & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
@@ -28,11 +26,26 @@ public class HODTransparentColorFilter extends RGBImageFilter {
|
||||
this.transparentRgb = rgb & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterRGB(int x, int y, int rgb) {
|
||||
if ((rgb & 0x00FFFFFF) == transparentRgb) {
|
||||
return 0x00000000;
|
||||
}
|
||||
return rgb;
|
||||
}
|
||||
|
||||
public void apply(int[] pixels, int offset, int length) {
|
||||
if (pixels == null) return;
|
||||
int end = Math.min(pixels.length, offset + length);
|
||||
for (int i = offset; i < end; i++) {
|
||||
pixels[i] = filterRGB(0, 0, pixels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void apply(PixelBuffer buffer) {
|
||||
if (buffer == null) return;
|
||||
int[] pixels = buffer.getPixels();
|
||||
if (pixels != null) {
|
||||
apply(pixels, 0, pixels.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Wallpaper background manager matching IBM Host On-Demand (com.ibm.eNetwork.ECL.hostgraphics.HODWallpaper).
|
||||
* Completely decoupled from java.awt.
|
||||
*/
|
||||
public class HODWallpaper extends HODPart {
|
||||
public static final int HOD_TILE = 0;
|
||||
@@ -17,8 +12,8 @@ public class HODWallpaper extends HODPart {
|
||||
public static final int HOD_STRETCH = 2;
|
||||
|
||||
private int _display = HOD_CENTER;
|
||||
private Image rawImage;
|
||||
private Image backgroundImage;
|
||||
private Object rawImage;
|
||||
private Object backgroundImage;
|
||||
|
||||
public HODWallpaper() {
|
||||
this(HOD_CENTER);
|
||||
@@ -28,7 +23,7 @@ public class HODWallpaper extends HODPart {
|
||||
this.setDisplay(displayMode);
|
||||
}
|
||||
|
||||
public HODWallpaper(Image image, int displayMode) {
|
||||
public HODWallpaper(Object image, int displayMode) {
|
||||
this(displayMode);
|
||||
this.setImage(image);
|
||||
}
|
||||
@@ -46,30 +41,40 @@ public class HODWallpaper extends HODPart {
|
||||
return this._display;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
public void setImage(Object image) {
|
||||
this.rawImage = image;
|
||||
this.backgroundImage = null;
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public Image getHODImage() {
|
||||
public Object getHODImage() {
|
||||
return this.rawImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Component component, Graphics graphics, int x, int y, int w, int h) {
|
||||
public void paint(Object component, Object graphics, int x, int y, int w, int h) {
|
||||
this.setBounds(x, y, w, h);
|
||||
this.setHODParent(component);
|
||||
super.paint(component, graphics, x, y, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintHODView(Graphics graphics) {
|
||||
Image image = this.getHODImage();
|
||||
int display = this.getDisplay();
|
||||
Component component = this.getHODParent();
|
||||
public void paint(PixelBuffer buffer, int x, int y, int w, int h) {
|
||||
this.setBounds(x, y, w, h);
|
||||
super.paint(buffer, x, y, w, h);
|
||||
}
|
||||
|
||||
if (image == null || component == null) {
|
||||
@Override
|
||||
protected void paintHODView(Object graphics) {
|
||||
Object image = this.getHODImage();
|
||||
int display = this.getDisplay();
|
||||
|
||||
if (image == null || graphics == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (graphics instanceof PixelBuffer && image instanceof PixelBuffer) {
|
||||
paintHODViewBuffer((PixelBuffer) graphics, (PixelBuffer) image);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,7 +87,83 @@ public class HODWallpaper extends HODPart {
|
||||
}
|
||||
}
|
||||
|
||||
protected void hodtileImage(Graphics graphics, Image image) {
|
||||
public void paintHODViewBuffer(PixelBuffer dst, PixelBuffer src) {
|
||||
int display = this.getDisplay();
|
||||
Insets insets = this.getInsets();
|
||||
int availX = this.x + insets.left;
|
||||
int availY = this.y + insets.top;
|
||||
int availW = this.width - (insets.left + insets.right);
|
||||
int availH = this.height - (insets.top + insets.bottom);
|
||||
if (availW <= 0 || availH <= 0 || src.getWidth() <= 0 || src.getHeight() <= 0) return;
|
||||
|
||||
if (display == HOD_CENTER) {
|
||||
int cx = availX + (availW - src.getWidth()) / 2;
|
||||
int cy = availY + (availH - src.getHeight()) / 2;
|
||||
blitBuffer(dst, src, cx, cy);
|
||||
} else if (display == HOD_TILE) {
|
||||
int cols = (availW / src.getWidth()) + 1;
|
||||
int rows = (availH / src.getHeight()) + 1;
|
||||
int curX = availX;
|
||||
for (int i = 0; i < cols; i++) {
|
||||
int curY = availY;
|
||||
for (int j = 0; j < rows; j++) {
|
||||
blitBuffer(dst, src, curX, curY);
|
||||
curY += src.getHeight();
|
||||
}
|
||||
curX += src.getWidth();
|
||||
}
|
||||
} else if (display == HOD_STRETCH) {
|
||||
scaleBuffer(dst, src, availX, availY, availW, availH);
|
||||
}
|
||||
}
|
||||
|
||||
private void blitBuffer(PixelBuffer dst, PixelBuffer src, int dstX, int dstY) {
|
||||
int sw = src.getWidth();
|
||||
int sh = src.getHeight();
|
||||
int dw = dst.getWidth();
|
||||
int dh = dst.getHeight();
|
||||
int[] srcPx = src.getPixels();
|
||||
int[] dstPx = dst.getPixels();
|
||||
|
||||
for (int r = 0; r < sh; r++) {
|
||||
int dy = dstY + r;
|
||||
if (dy < 0 || dy >= dh) continue;
|
||||
for (int c = 0; c < sw; c++) {
|
||||
int dx = dstX + c;
|
||||
if (dx < 0 || dx >= dw) continue;
|
||||
int p = srcPx[r * sw + c];
|
||||
if ((p & 0xFF000000) != 0) {
|
||||
dstPx[dy * dw + dx] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scaleBuffer(PixelBuffer dst, PixelBuffer src, int dx, int dy, int dw, int dh) {
|
||||
int sw = src.getWidth();
|
||||
int sh = src.getHeight();
|
||||
int targetW = dst.getWidth();
|
||||
int targetH = dst.getHeight();
|
||||
int[] srcPx = src.getPixels();
|
||||
int[] dstPx = dst.getPixels();
|
||||
|
||||
for (int r = 0; r < dh; r++) {
|
||||
int outY = dy + r;
|
||||
if (outY < 0 || outY >= targetH) continue;
|
||||
int sy = r * sh / dh;
|
||||
for (int c = 0; c < dw; c++) {
|
||||
int outX = dx + c;
|
||||
if (outX < 0 || outX >= targetW) continue;
|
||||
int sx = c * sw / dw;
|
||||
int p = srcPx[sy * sw + sx];
|
||||
if ((p & 0xFF000000) != 0) {
|
||||
dstPx[outY * targetW + outX] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void hodtileImage(Object graphics, Object image) {
|
||||
Dimension imgSize = getImageSize(image);
|
||||
if (imgSize.width <= 0 || imgSize.height <= 0) return;
|
||||
|
||||
@@ -99,40 +180,86 @@ public class HODWallpaper extends HODPart {
|
||||
for (int i = 0; i < cols; i++) {
|
||||
int curY = startY;
|
||||
for (int j = 0; j < rows; j++) {
|
||||
graphics.drawImage(image, curX, curY, this.getHODParent());
|
||||
invokeDrawImage(graphics, image, curX, curY);
|
||||
curY += imgSize.height;
|
||||
}
|
||||
curX += imgSize.width;
|
||||
}
|
||||
}
|
||||
|
||||
protected void centerHODImage(Graphics graphics, Image image) {
|
||||
protected void centerHODImage(Object graphics, Object image) {
|
||||
Dimension imgSize = getImageSize(image);
|
||||
if (imgSize.width <= 0 || imgSize.height <= 0) return;
|
||||
|
||||
Insets insets = this.getInsets();
|
||||
int cx = this.x + insets.left + (this.width - imgSize.width) / 2;
|
||||
int cy = this.y + insets.top + (this.height - imgSize.height) / 2;
|
||||
graphics.drawImage(image, cx, cy, this.getHODParent());
|
||||
invokeDrawImage(graphics, image, cx, cy);
|
||||
}
|
||||
|
||||
protected void stretchHODImage(Graphics graphics, Image image) {
|
||||
protected void stretchHODImage(Object graphics, Object image) {
|
||||
Insets insets = this.getInsets();
|
||||
int sx = this.x + insets.left;
|
||||
int sy = this.y + insets.top;
|
||||
int sw = this.width - (insets.left + insets.right);
|
||||
int sh = this.height - (insets.top + insets.bottom);
|
||||
graphics.drawImage(image, sx, sy, sw, sh, this.getHODParent());
|
||||
invokeDrawImage(graphics, image, sx, sy, sw, sh);
|
||||
}
|
||||
|
||||
private Dimension getImageSize(Image image) {
|
||||
private void invokeDrawImage(Object graphics, Object img, int x, int y) {
|
||||
if (graphics == null || img == null) return;
|
||||
try {
|
||||
for (Method m : graphics.getClass().getMethods()) {
|
||||
if (m.getName().equals("drawImage")) {
|
||||
Class<?>[] pts = m.getParameterTypes();
|
||||
if (pts.length == 4 && pts[1] == int.class && pts[2] == int.class) {
|
||||
m.invoke(graphics, img, x, y, this.getHODParent());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
private void invokeDrawImage(Object graphics, Object img, int x, int y, int w, int h) {
|
||||
if (graphics == null || img == null) return;
|
||||
try {
|
||||
for (Method m : graphics.getClass().getMethods()) {
|
||||
if (m.getName().equals("drawImage")) {
|
||||
Class<?>[] pts = m.getParameterTypes();
|
||||
if (pts.length == 6 && pts[1] == int.class && pts[2] == int.class && pts[3] == int.class && pts[4] == int.class) {
|
||||
m.invoke(graphics, img, x, y, w, h, this.getHODParent());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
private Dimension getImageSize(Object image) {
|
||||
if (image == null) return new Dimension(0, 0);
|
||||
Component c = this.getHODParent();
|
||||
int w = image.getWidth(c);
|
||||
int h = image.getHeight(c);
|
||||
if (w <= 0 && image instanceof BufferedImage) {
|
||||
w = ((BufferedImage) image).getWidth();
|
||||
h = ((BufferedImage) image).getHeight();
|
||||
if (image instanceof PixelBuffer) {
|
||||
PixelBuffer pb = (PixelBuffer) image;
|
||||
return new Dimension(pb.getWidth(), pb.getHeight());
|
||||
}
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
try {
|
||||
Method mw = image.getClass().getMethod("getWidth");
|
||||
w = ((Number) mw.invoke(image)).intValue();
|
||||
Method mh = image.getClass().getMethod("getHeight");
|
||||
h = ((Number) mh.invoke(image)).intValue();
|
||||
} catch (Throwable ignored) {
|
||||
try {
|
||||
for (Method m : image.getClass().getMethods()) {
|
||||
if (m.getName().equals("getWidth") && m.getParameterCount() == 1) {
|
||||
w = ((Number) m.invoke(image, this.getHODParent())).intValue();
|
||||
}
|
||||
if (m.getName().equals("getHeight") && m.getParameterCount() == 1) {
|
||||
h = ((Number) m.invoke(image, this.getHODParent())).intValue();
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored2) {}
|
||||
}
|
||||
return new Dimension(Math.max(0, w), Math.max(0, h));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Lightweight pure-Java POJO representing border insets.
|
||||
* Completely decouples lib3270j from java.awt.Insets.
|
||||
*/
|
||||
public class Insets implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public int top;
|
||||
public int left;
|
||||
public int bottom;
|
||||
public int right;
|
||||
|
||||
public Insets(int top, int left, int bottom, int right) {
|
||||
this.top = top;
|
||||
this.left = left;
|
||||
this.bottom = bottom;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Insets)) return false;
|
||||
Insets i = (Insets) obj;
|
||||
return top == i.top && left == i.left && bottom == i.bottom && right == i.right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(top, left, bottom, right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[top=" + top + ",left=" + left + ",bottom=" + bottom + ",right=" + right + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
/**
|
||||
* Platform-neutral pixel buffer interface for 32-bit ARGB offscreen rasterization.
|
||||
* Compatible with pure Java SE (Swing), Android (Bitmap), and headless environments.
|
||||
*/
|
||||
public interface PixelBuffer {
|
||||
|
||||
/** Returns width of the pixel buffer in pixels. */
|
||||
int getWidth();
|
||||
|
||||
/** Returns height of the pixel buffer in pixels. */
|
||||
int getHeight();
|
||||
|
||||
/** Returns contiguous 32-bit ARGB pixel array. */
|
||||
int[] getPixels();
|
||||
|
||||
/** Returns ARGB pixel at specified coordinate, or 0 if out of bounds. */
|
||||
int getPixel(int x, int y);
|
||||
|
||||
/** Sets ARGB pixel at specified coordinate with alpha blending and mix mode. */
|
||||
void setPixel(int x, int y, int argb);
|
||||
|
||||
/** Sets ARGB pixel directly without blending. */
|
||||
void setPixelDirect(int x, int y, int argb);
|
||||
|
||||
/** Clears buffer to fully transparent (0x00000000). */
|
||||
void clear();
|
||||
|
||||
/** Clears buffer to specified ARGB color. */
|
||||
void clear(int argb);
|
||||
|
||||
/** Draws a 1-pixel line using integer Bresenham algorithm. */
|
||||
void drawLine(int x1, int y1, int x2, int y2, int argb);
|
||||
|
||||
/** Draws a 1-pixel line using Bresenham algorithm. */
|
||||
void drawLineBresenham(int x0, int y0, int x1, int y1, int argb);
|
||||
|
||||
/** Draws a stroked line with line type and line width. */
|
||||
void drawLine(double x0, double y0, double x1, double y1, int argb, int lineType, int lineWidth);
|
||||
|
||||
/** Draws an anti-aliased sub-pixel line segment with stroke width. */
|
||||
void drawLineAA(double x0, double y0, double x1, double y1, int argb, double strokeWidth);
|
||||
|
||||
/** Fills a rectangular region with specified ARGB color. */
|
||||
void fillRect(int x, int y, int width, int height, int argb);
|
||||
|
||||
/** Sets clipping rectangle. */
|
||||
void setClip(int x, int y, int width, int height);
|
||||
|
||||
/** Clears clipping rectangle. */
|
||||
void clearClip();
|
||||
|
||||
/** Checks if coordinate falls outside current clipping bounds. */
|
||||
boolean isClipped(int x, int y);
|
||||
|
||||
/** Copies a rectangular block of pixels from source array into this buffer. */
|
||||
void blit(int[] srcPixels, int srcX, int srcY, int srcW, int srcH, int dstX, int dstY);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Lightweight pure-Java POJO point for 2D presentation coordinates.
|
||||
* Completely decouples lib3270j from java.awt.Point.
|
||||
*/
|
||||
public class Point implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public int x;
|
||||
public int y;
|
||||
|
||||
public Point() {
|
||||
this(0, 0);
|
||||
}
|
||||
|
||||
public Point(Point p) {
|
||||
this(p != null ? p.x : 0, p != null ? p.y : 0);
|
||||
}
|
||||
|
||||
public Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setLocation(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void setLocation(Point p) {
|
||||
if (p != null) {
|
||||
this.x = p.x;
|
||||
this.y = p.y;
|
||||
}
|
||||
}
|
||||
|
||||
public void translate(int dx, int dy) {
|
||||
this.x += dx;
|
||||
this.y += dy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Point)) return false;
|
||||
Point pt = (Point) obj;
|
||||
return (x == pt.x) && (y == pt.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[x=" + x + ",y=" + y + "]";
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package haus.nightmare.lib3270j.graphics;
|
||||
/**
|
||||
* Represents a set of up to 191 custom bitmapped symbols (LCID 0x40 - 0xFE).
|
||||
* Supports both Single-Plane (monochrome) and Triple-Plane (7-color RGB composite).
|
||||
* Decoupled from java.awt using PixelBuffer.
|
||||
*/
|
||||
public class ProgramSymbolSet {
|
||||
|
||||
@@ -64,8 +65,8 @@ public class ProgramSymbolSet {
|
||||
private int[] cachedRgbArray;
|
||||
private int cachedFgRgb = -1;
|
||||
private int cachedBgRgb = -1;
|
||||
private java.awt.image.BufferedImage cachedImage;
|
||||
private java.awt.image.BufferedImage cachedScaledImage;
|
||||
private PixelBuffer cachedPixelBuffer;
|
||||
private PixelBuffer cachedScaledPixelBuffer;
|
||||
private int cachedTargetW = 0;
|
||||
private int cachedTargetH = 0;
|
||||
private int cachedScaledFgRgb = -1;
|
||||
@@ -103,23 +104,21 @@ public class ProgramSymbolSet {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image scaled directly to target dimensions (cellWidth x cellHeight).
|
||||
* Enables unscaled 1:1 hardware blitting in Java2D.
|
||||
* Returns a PixelBuffer scaled directly to target dimensions (cellWidth x cellHeight).
|
||||
*/
|
||||
public synchronized java.awt.image.BufferedImage getScaledImage(int targetW, int targetH, int fgArgb, int bgArgb) {
|
||||
public synchronized PixelBuffer getScaledPixelBuffer(int targetW, int targetH, int fgArgb, int bgArgb) {
|
||||
if (targetW <= 0 || targetH <= 0) {
|
||||
return getImage(fgArgb, bgArgb);
|
||||
return getPixelBuffer(fgArgb, bgArgb);
|
||||
}
|
||||
if (targetW == width && targetH == height) {
|
||||
return getImage(fgArgb, bgArgb);
|
||||
return getPixelBuffer(fgArgb, bgArgb);
|
||||
}
|
||||
if (cachedScaledImage != null && cachedTargetW == targetW && cachedTargetH == targetH
|
||||
if (cachedScaledPixelBuffer != null && cachedTargetW == targetW && cachedTargetH == targetH
|
||||
&& cachedScaledFgRgb == fgArgb && cachedScaledBgRgb == bgArgb) {
|
||||
return cachedScaledImage;
|
||||
return cachedScaledPixelBuffer;
|
||||
}
|
||||
int[] srcRgb = getRgbPixels(fgArgb, bgArgb);
|
||||
java.awt.image.BufferedImage scaled = new java.awt.image.BufferedImage(targetW, targetH, java.awt.image.BufferedImage.TYPE_INT_ARGB);
|
||||
int[] dstRgb = ((java.awt.image.DataBufferInt) scaled.getRaster().getDataBuffer()).getData();
|
||||
int[] dstRgb = new int[targetW * targetH];
|
||||
|
||||
for (int dy = 0; dy < targetH; dy++) {
|
||||
int sy = dy * height / targetH;
|
||||
@@ -131,30 +130,36 @@ public class ProgramSymbolSet {
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedScaledImage = scaled;
|
||||
this.cachedScaledPixelBuffer = new DefaultPixelBuffer(targetW, targetH, dstRgb);
|
||||
this.cachedTargetW = targetW;
|
||||
this.cachedTargetH = targetH;
|
||||
this.cachedScaledFgRgb = fgArgb;
|
||||
this.cachedScaledBgRgb = bgArgb;
|
||||
return scaled;
|
||||
return this.cachedScaledPixelBuffer;
|
||||
}
|
||||
|
||||
public synchronized PixelBuffer getScaledImage(int targetW, int targetH, int fgArgb, int bgArgb) {
|
||||
return getScaledPixelBuffer(targetW, targetH, fgArgb, bgArgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and returns the cached BufferedImage for this symbol glyph.
|
||||
* Eliminates per-cell heap allocations during high frame rate rendering.
|
||||
* Computes and returns the cached PixelBuffer for this symbol glyph.
|
||||
*/
|
||||
public synchronized java.awt.image.BufferedImage getImage(int fgArgb, int bgArgb) {
|
||||
if (cachedImage != null && cachedFgRgb == fgArgb && cachedBgRgb == bgArgb) {
|
||||
return cachedImage;
|
||||
public synchronized PixelBuffer getPixelBuffer(int fgArgb, int bgArgb) {
|
||||
if (cachedPixelBuffer != null && cachedFgRgb == fgArgb && cachedBgRgb == bgArgb) {
|
||||
return cachedPixelBuffer;
|
||||
}
|
||||
int[] rgb = getRgbPixels(fgArgb, bgArgb);
|
||||
java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
|
||||
int[] imgData = ((java.awt.image.DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
||||
int[] imgData = new int[rgb.length];
|
||||
System.arraycopy(rgb, 0, imgData, 0, rgb.length);
|
||||
this.cachedImage = img;
|
||||
this.cachedPixelBuffer = new DefaultPixelBuffer(width, height, imgData);
|
||||
this.cachedFgRgb = fgArgb;
|
||||
this.cachedBgRgb = bgArgb;
|
||||
return img;
|
||||
return this.cachedPixelBuffer;
|
||||
}
|
||||
|
||||
public synchronized PixelBuffer getImage(int fgArgb, int bgArgb) {
|
||||
return getPixelBuffer(fgArgb, bgArgb);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Lightweight pure-Java POJO rectangle for 2D presentation coordinates.
|
||||
* Completely decouples lib3270j from java.awt.Rectangle.
|
||||
*/
|
||||
public class Rectangle implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public int x;
|
||||
public int y;
|
||||
public int width;
|
||||
public int height;
|
||||
|
||||
public Rectangle() {
|
||||
this(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public Rectangle(Rectangle r) {
|
||||
this(r != null ? r.x : 0, r != null ? r.y : 0, r != null ? r.width : 0, r != null ? r.height : 0);
|
||||
}
|
||||
|
||||
public Rectangle(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Rectangle(int width, int height) {
|
||||
this(0, 0, width, height);
|
||||
}
|
||||
|
||||
public Rectangle(Point p, Dimension d) {
|
||||
this(p != null ? p.x : 0, p != null ? p.y : 0, d != null ? d.width : 0, d != null ? d.height : 0);
|
||||
}
|
||||
|
||||
public Rectangle(Point p) {
|
||||
this(p != null ? p.x : 0, p != null ? p.y : 0, 0, 0);
|
||||
}
|
||||
|
||||
public Rectangle(Dimension d) {
|
||||
this(0, 0, d != null ? d.width : 0, d != null ? d.height : 0);
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setBounds(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setBounds(Rectangle r) {
|
||||
if (r != null) {
|
||||
setBounds(r.x, r.y, r.width, r.height);
|
||||
}
|
||||
}
|
||||
|
||||
public Point getLocation() {
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
public void setLocation(Point p) {
|
||||
if (p != null) {
|
||||
this.x = p.x;
|
||||
this.y = p.y;
|
||||
}
|
||||
}
|
||||
|
||||
public void setLocation(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public Dimension getSize() {
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
public void setSize(Dimension d) {
|
||||
if (d != null) {
|
||||
this.width = d.width;
|
||||
this.height = d.height;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public boolean contains(int X, int Y) {
|
||||
int w = this.width;
|
||||
int h = this.height;
|
||||
if ((w | h) < 0) return false;
|
||||
int x = this.x;
|
||||
int y = this.y;
|
||||
if (X < x || Y < y) return false;
|
||||
w += x;
|
||||
h += y;
|
||||
return ((w < x || w > X) && (h < y || h > Y));
|
||||
}
|
||||
|
||||
public boolean contains(Point p) {
|
||||
return p != null && contains(p.x, p.y);
|
||||
}
|
||||
|
||||
public boolean intersects(Rectangle r) {
|
||||
if (r == null) return false;
|
||||
int tw = this.width;
|
||||
int th = this.height;
|
||||
int rw = r.width;
|
||||
int rh = r.height;
|
||||
if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) return false;
|
||||
int tx = this.x;
|
||||
int ty = this.y;
|
||||
int rx = r.x;
|
||||
int ry = r.y;
|
||||
rw += rx;
|
||||
rh += ry;
|
||||
tw += tx;
|
||||
th += ty;
|
||||
return ((rw < rx || rw > tx) &&
|
||||
(rh < ry || rh > ty) &&
|
||||
(tw < tx || tw > rx) &&
|
||||
(th < ty || th > ry));
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return (width <= 0) || (height <= 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Rectangle)) return false;
|
||||
Rectangle r = (Rectangle) obj;
|
||||
return ((x == r.x) &&
|
||||
(y == r.y) &&
|
||||
(width == r.width) &&
|
||||
(height == r.height));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getName() + "[x=" + x + ",y=" + y + ",width=" + width + ",height=" + height + "]";
|
||||
}
|
||||
}
|
||||
@@ -412,6 +412,155 @@ public class InputProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all character locations on the given row are protected.
|
||||
*/
|
||||
public boolean isLineProtected(int row) {
|
||||
if (screen == null || !screen.isFormatted()) return false;
|
||||
int cols = screen.getCols();
|
||||
int rows = screen.getRows();
|
||||
if (row < 0 || row >= rows) return false;
|
||||
int start = row * cols;
|
||||
int end = start + cols;
|
||||
for (int i = start; i < end; i++) {
|
||||
if (screen.getCell(i).isFieldAttribute()) continue;
|
||||
byte fa = screen.getFieldAttributeAt(i);
|
||||
if (!faIsProtected(fa & 0xFF)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste text into the presentation space.
|
||||
* When enablePasteFromExcel is true:
|
||||
* - '\t' triggers a Tab action advancing to the next unprotected input field (SBA).
|
||||
* - '\n' or '\r\n' triggers a NewLine action advancing to the first unprotected field of the next line.
|
||||
* When pasteStopAtProtectedLine is true:
|
||||
* - Halts or truncates paste if cursor reaches a protected line/boundary or if the current field is full.
|
||||
*
|
||||
* @param text text to paste
|
||||
* @param enablePasteFromExcel whether to parse tabs as field advances and newlines as row advances
|
||||
* @param pasteStopAtProtectedLine whether to halt paste when encountering protected boundaries
|
||||
* @return number of characters pasted
|
||||
*/
|
||||
public int pasteText(String text, boolean enablePasteFromExcel, boolean pasteStopAtProtectedLine) {
|
||||
if (text == null || text.isEmpty() || screen == null || keyboardLocked) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isNvtMode()) {
|
||||
try {
|
||||
fsm.sendNVTString(text);
|
||||
return text.length();
|
||||
} catch (IOException e) {
|
||||
log.warning("Failed to send NVT paste: " + e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
int len = text.length();
|
||||
int i = 0;
|
||||
|
||||
while (i < len && !keyboardLocked) {
|
||||
char ch = text.charAt(i);
|
||||
|
||||
// Handle newline sequences: \r\n, \r, or \n
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
if (ch == '\r' && (i + 1) < len && text.charAt(i + 1) == '\n') {
|
||||
i++; // skip \n of \r\n
|
||||
}
|
||||
if (enablePasteFromExcel) {
|
||||
int curRow = screen.getCursorRow();
|
||||
int nextRow = (curRow + 1) % screen.getRows();
|
||||
if (pasteStopAtProtectedLine && isLineProtected(nextRow)) {
|
||||
break; // Stop paste when next line is protected
|
||||
}
|
||||
newline();
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle tab character
|
||||
if (ch == '\t') {
|
||||
if (enablePasteFromExcel) {
|
||||
tab();
|
||||
int newAddr = screen.getCursorAddress();
|
||||
if (pasteStopAtProtectedLine && screen.isFormatted()) {
|
||||
byte fa = screen.getFieldAttributeAt(newAddr);
|
||||
if (faIsProtected(fa & 0xFF)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Regular printable character
|
||||
if (ch >= 0x20 && ch != 0x7F) {
|
||||
if (screen.isFormatted()) {
|
||||
int baddr = screen.getCursorAddress();
|
||||
ExtendedAttribute ea = screen.getCell(baddr);
|
||||
if (ea.isFieldAttribute()) {
|
||||
baddr = (baddr + 1) % (screen.getRows() * screen.getCols());
|
||||
}
|
||||
byte faVal = screen.getFieldAttributeAt(baddr);
|
||||
if (faIsProtected(faVal & 0xFF)) {
|
||||
if (pasteStopAtProtectedLine) {
|
||||
break; // Stop paste immediately at protected boundary
|
||||
} else {
|
||||
tab();
|
||||
baddr = screen.getCursorAddress();
|
||||
if (faIsProtected(screen.getFieldAttributeAt(baddr) & 0xFF)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typeCharacter(ch);
|
||||
count++;
|
||||
|
||||
// If pasteStopAtProtectedLine is enabled, check if the cursor after typing has hit a protected field
|
||||
if (pasteStopAtProtectedLine && screen.isFormatted() && !keyboardLocked) {
|
||||
int curAddr = screen.getCursorAddress();
|
||||
ExtendedAttribute curCell = screen.getCell(curAddr);
|
||||
if (curCell.isFieldAttribute()) {
|
||||
byte nextFa = curCell.fa;
|
||||
if (faIsProtected(nextFa & 0xFF)) {
|
||||
if (i + 1 < len) {
|
||||
char nextCh = text.charAt(i + 1);
|
||||
if (nextCh != '\t' && nextCh != '\r' && nextCh != '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte curFa = screen.getFieldAttributeAt(curAddr);
|
||||
if (faIsProtected(curFa & 0xFF)) {
|
||||
if (i + 1 < len) {
|
||||
char nextCh = text.charAt(i + 1);
|
||||
if (nextCh != '\t' && nextCh != '\r' && nextCh != '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
screen.markAllChanged();
|
||||
screen.updateDisplaySnapshot();
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build inbound 3270 Read Modified data stream (AID + Cursor + SBA + Modified fields).
|
||||
*/
|
||||
|
||||
@@ -14,4 +14,7 @@ public interface ConnectionListener {
|
||||
|
||||
/** Called when TN3270E negotiation completes. */
|
||||
default void onTN3270ENegotiated(String deviceType, String deviceName) {}
|
||||
|
||||
/** Called when TN3270E functions negotiation completes or changes. */
|
||||
default void onTN3270EFunctionsNegotiated(boolean[] functions) {}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,7 @@ public interface ScreenUpdateListener {
|
||||
|
||||
/** Called when the screen size changes (erase/write vs erase/write alternate). */
|
||||
default void onScreenSizeChanged(int rows, int cols) {}
|
||||
|
||||
/** Called when the keyboard is unlocked (e.g. via WCC restore, AUTO_SYS_UNLOCK, or Contention Resolution SDI). */
|
||||
default void onKeyboardUnlocked() {}
|
||||
}
|
||||
|
||||
@@ -1305,6 +1305,9 @@ public class NvtProcessor {
|
||||
}
|
||||
|
||||
private void notifyScreenUpdated() {
|
||||
if (screenBuffer != null) {
|
||||
screenBuffer.notifyScreenUpdate();
|
||||
}
|
||||
for (ScreenUpdateListener l : screenListeners) {
|
||||
l.onScreenUpdated();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@ import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.ecl.ECLField;
|
||||
import haus.nightmare.lib3270j.ecl.ECLFieldList;
|
||||
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
/**
|
||||
@@ -60,6 +65,61 @@ public class ScreenBuffer {
|
||||
private final EbcdicTranslator translator;
|
||||
private final Object renderLock = new Object();
|
||||
|
||||
// Synchronization primitives and screen update listeners (Phase 12)
|
||||
private final ReentrantLock syncLock = new ReentrantLock();
|
||||
private final Condition syncCondition = syncLock.newCondition();
|
||||
private final List<ScreenUpdateListener> updateListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
public ReentrantLock getSyncLock() {
|
||||
return syncLock;
|
||||
}
|
||||
|
||||
public Condition getSyncCondition() {
|
||||
return syncCondition;
|
||||
}
|
||||
|
||||
public void addUpdateListener(ScreenUpdateListener l) {
|
||||
if (l != null && !updateListeners.contains(l)) {
|
||||
updateListeners.add(l);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeUpdateListener(ScreenUpdateListener l) {
|
||||
updateListeners.remove(l);
|
||||
}
|
||||
|
||||
public void signalWaiters() {
|
||||
syncLock.lock();
|
||||
try {
|
||||
syncCondition.signalAll();
|
||||
} finally {
|
||||
syncLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyScreenUpdate() {
|
||||
updateDisplaySnapshot();
|
||||
signalWaiters();
|
||||
for (ScreenUpdateListener l : updateListeners) {
|
||||
try {
|
||||
l.onScreenUpdated();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyCursorMoved(int oldAddress, int newAddress) {
|
||||
signalWaiters();
|
||||
for (ScreenUpdateListener l : updateListeners) {
|
||||
try {
|
||||
l.onCursorMoved(oldAddress, newAddress);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyCursorMoved() {
|
||||
notifyCursorMoved(cursorAddress, cursorAddress);
|
||||
}
|
||||
|
||||
public Object getRenderLock() {
|
||||
return renderLock;
|
||||
}
|
||||
@@ -272,9 +332,16 @@ public class ScreenBuffer {
|
||||
|
||||
// ========== Cursor ==========
|
||||
public int getCursorAddress() { return cursorAddress; }
|
||||
public synchronized void setCursorAddress(int addr) {
|
||||
this.cursorAddress = addr;
|
||||
this.displayCursorAddress = addr;
|
||||
public void setCursorAddress(int addr) {
|
||||
int oldAddr;
|
||||
synchronized (this) {
|
||||
oldAddr = this.cursorAddress;
|
||||
this.cursorAddress = addr;
|
||||
this.displayCursorAddress = addr;
|
||||
}
|
||||
if (oldAddr != addr) {
|
||||
notifyCursorMoved(oldAddr, addr);
|
||||
}
|
||||
}
|
||||
public synchronized void setCursorPosition(int row, int col) {
|
||||
int r = Math.max(0, Math.min(row, rows - 1));
|
||||
@@ -831,19 +898,28 @@ public class ScreenBuffer {
|
||||
}
|
||||
|
||||
public synchronized void setText(String text) {
|
||||
setText(text, 0);
|
||||
}
|
||||
|
||||
public synchronized void setText(String text, int pos) {
|
||||
if (text == null) return;
|
||||
int size = rows * cols;
|
||||
int len = Math.min(text.length(), size);
|
||||
if (pos < 0 || pos >= size) return;
|
||||
int len = Math.min(text.length(), size - pos);
|
||||
for (int i = 0; i < len; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int ebc = translator.unicodeToEbcdic(ch);
|
||||
buffer[i].ec = (byte) (ebc >= 0 ? ebc : 0);
|
||||
buffer[i].ucs4 = ch;
|
||||
buffer[pos + i].ec = (byte) (ebc >= 0 ? ebc : 0);
|
||||
buffer[pos + i].ucs4 = ch;
|
||||
}
|
||||
screenChanged = true;
|
||||
updateDisplaySnapshot();
|
||||
}
|
||||
|
||||
public void setText(String text, int row, int col) {
|
||||
setText(text, row * cols + col);
|
||||
}
|
||||
|
||||
public int searchString(String target) {
|
||||
if (target == null || target.isEmpty()) return -1;
|
||||
String full = getText();
|
||||
@@ -1119,8 +1195,10 @@ public class ScreenBuffer {
|
||||
ExtendedAttribute[] wordCells = new ExtendedAttribute[wordLen];
|
||||
for (int i = 0; i < wordLen; i++) {
|
||||
wordCells[i] = new ExtendedAttribute();
|
||||
wordCells[i].copyFrom(getCell(wordStartAddr + i));
|
||||
getCell(wordStartAddr + i).clear();
|
||||
ExtendedAttribute srcCell = getCell(wordStartAddr + i);
|
||||
wordCells[i].copyFrom(srcCell);
|
||||
srcCell.ec = 0;
|
||||
srcCell.ucs4 = 0;
|
||||
}
|
||||
|
||||
int nextRow = (curRow + 1) % rows;
|
||||
|
||||
@@ -6,6 +6,8 @@ import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -28,6 +30,10 @@ public class TelnetConnection {
|
||||
private final TelnetFSM fsm;
|
||||
private final ConnectionConfig config;
|
||||
|
||||
private final AtomicLong lastActivityTime = new AtomicLong(System.currentTimeMillis());
|
||||
private ScheduledExecutorService keepAliveExecutor;
|
||||
private volatile boolean intentionalDisconnect = false;
|
||||
|
||||
private javax.net.ssl.SSLSession sslSession;
|
||||
|
||||
public TelnetConnection(ConnectionConfig config, TelnetFSM fsm) {
|
||||
@@ -91,6 +97,7 @@ public class TelnetConnection {
|
||||
if (config.getSoTimeoutMs() > 0) {
|
||||
rawSocket.setSoTimeout(config.getSoTimeoutMs());
|
||||
}
|
||||
applyExtendedSocketOptions(rawSocket);
|
||||
rawSocket.connect(new InetSocketAddress(connectHost, connectPort), config.getConnectTimeoutMs());
|
||||
|
||||
// Perform proxy handshake if configured
|
||||
@@ -123,6 +130,7 @@ public class TelnetConnection {
|
||||
if (config.getSoTimeoutMs() > 0) {
|
||||
sslSocket.setSoTimeout(config.getSoTimeoutMs());
|
||||
}
|
||||
applyExtendedSocketOptions(sslSocket);
|
||||
applyTlsSocketSettings(sslSocket);
|
||||
sslSocket.startHandshake();
|
||||
socket = sslSocket;
|
||||
@@ -143,10 +151,13 @@ public class TelnetConnection {
|
||||
|
||||
log.info("Connected to " + socket.getRemoteSocketAddress());
|
||||
|
||||
intentionalDisconnect = false;
|
||||
lastActivityTime.set(System.currentTimeMillis());
|
||||
running = true;
|
||||
readerThread = new Thread(this::readLoop, "TN3270-Reader");
|
||||
readerThread.setDaemon(true);
|
||||
readerThread.start();
|
||||
startKeepAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,6 +178,7 @@ public class TelnetConnection {
|
||||
if (config.getSoTimeoutMs() > 0) {
|
||||
sslSocket.setSoTimeout(config.getSoTimeoutMs());
|
||||
}
|
||||
applyExtendedSocketOptions(sslSocket);
|
||||
applyTlsSocketSettings(sslSocket);
|
||||
sslSocket.startHandshake();
|
||||
this.socket = sslSocket;
|
||||
@@ -200,6 +212,106 @@ public class TelnetConnection {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyExtendedSocketOptions(Socket s) {
|
||||
if (s == null) return;
|
||||
try {
|
||||
Class<?> extClass = Class.forName("jdk.net.ExtendedSocketOptions");
|
||||
// TCP_KEEPIDLE, TCP_KEEPINTERVAL, TCP_KEEPCOUNT
|
||||
if (config != null && config.isSoKeepAlive() && config.getKeepAliveIntervalSeconds() > 0) {
|
||||
try {
|
||||
java.lang.reflect.Field fIdle = extClass.getField("TCP_KEEPIDLE");
|
||||
@SuppressWarnings("unchecked")
|
||||
SocketOption<Integer> optIdle = (SocketOption<Integer>) fIdle.get(null);
|
||||
s.setOption(optIdle, config.getKeepAliveIntervalSeconds());
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
java.lang.reflect.Field fIntv = extClass.getField("TCP_KEEPINTERVAL");
|
||||
@SuppressWarnings("unchecked")
|
||||
SocketOption<Integer> optIntv = (SocketOption<Integer>) fIntv.get(null);
|
||||
s.setOption(optIntv, Math.max(1, Math.min(10, config.getKeepAliveIntervalSeconds())));
|
||||
} catch (Throwable ignored) {}
|
||||
try {
|
||||
java.lang.reflect.Field fCnt = extClass.getField("TCP_KEEPCOUNT");
|
||||
@SuppressWarnings("unchecked")
|
||||
SocketOption<Integer> optCnt = (SocketOption<Integer>) fCnt.get(null);
|
||||
s.setOption(optCnt, 3);
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
// TCP_USER_TIMEOUT
|
||||
if (config != null && config.getTcpUserTimeoutMs() > 0) {
|
||||
try {
|
||||
java.lang.reflect.Field fTimeout = extClass.getField("TCP_USER_TIMEOUT");
|
||||
@SuppressWarnings("unchecked")
|
||||
SocketOption<Integer> optTimeout = (SocketOption<Integer>) fTimeout.get(null);
|
||||
s.setOption(optTimeout, config.getTcpUserTimeoutMs());
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
// Extended socket options not supported on this platform/runtime (e.g. macOS/Android)
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void startKeepAlive() {
|
||||
stopKeepAlive();
|
||||
if (config != null && config.isKeepAliveEnabled() && config.getKeepAliveIntervalSeconds() > 0) {
|
||||
keepAliveExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "TN3270-KeepAlive");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
int interval = config.getKeepAliveIntervalSeconds();
|
||||
long checkPeriod = Math.max(1, Math.min(5, interval));
|
||||
keepAliveExecutor.scheduleWithFixedDelay(this::checkAndSendKeepAlive, checkPeriod, checkPeriod, TimeUnit.SECONDS);
|
||||
log.fine("Keep-Alive heartbeat scheduled every " + interval + "s (check every " + checkPeriod + "s)");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stopKeepAlive() {
|
||||
if (keepAliveExecutor != null) {
|
||||
keepAliveExecutor.shutdownNow();
|
||||
keepAliveExecutor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndSendKeepAlive() {
|
||||
if (!running || !isConnected()) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
long elapsed = now - lastActivityTime.get();
|
||||
long intervalMs = (config != null ? config.getKeepAliveIntervalSeconds() : 120) * 1000L;
|
||||
if (elapsed >= intervalMs) {
|
||||
try {
|
||||
sendKeepAliveHeartbeat();
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Failed to send keep-alive heartbeat", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void sendKeepAliveHeartbeat() throws IOException {
|
||||
if (!isConnected() || outputStream == null) {
|
||||
return;
|
||||
}
|
||||
String type = (config != null) ? config.getKeepAliveType() : "NOP";
|
||||
if ("TIMING-MARK".equalsIgnoreCase(type)) {
|
||||
log.fine("Transmitting Keep-Alive heartbeat: IAC DO TIMING-MARK");
|
||||
byte[] tm = new byte[] { (byte) IAC, (byte) DO, (byte) TELOPT_TM };
|
||||
outputStream.write(tm);
|
||||
outputStream.flush();
|
||||
} else {
|
||||
log.fine("Transmitting Keep-Alive heartbeat: IAC NOP");
|
||||
byte[] nop = new byte[] { (byte) IAC, (byte) NOP };
|
||||
outputStream.write(nop);
|
||||
outputStream.flush();
|
||||
}
|
||||
lastActivityTime.set(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public long getLastActivityTime() {
|
||||
return lastActivityTime.get();
|
||||
}
|
||||
|
||||
private void establishHttpProxy(Socket s, String targetHost, int targetPort, String user, String pass) throws IOException {
|
||||
OutputStream out = s.getOutputStream();
|
||||
InputStream in = s.getInputStream();
|
||||
@@ -411,6 +523,7 @@ public class TelnetConnection {
|
||||
if (outputStream == null) return;
|
||||
outputStream.write(data, offset, length);
|
||||
outputStream.flush();
|
||||
lastActivityTime.set(System.currentTimeMillis());
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.fine("SENT " + length + " bytes: " + formatHex(data, offset, length));
|
||||
}
|
||||
@@ -432,13 +545,16 @@ public class TelnetConnection {
|
||||
byte[] escaped = out.toByteArray();
|
||||
outputStream.write(escaped, 0, escaped.length);
|
||||
outputStream.flush();
|
||||
lastActivityTime.set(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the host.
|
||||
*/
|
||||
public void disconnect() {
|
||||
intentionalDisconnect = true;
|
||||
running = false;
|
||||
stopKeepAlive();
|
||||
try {
|
||||
if (socket != null && !socket.isClosed()) {
|
||||
socket.shutdownInput();
|
||||
@@ -471,10 +587,12 @@ public class TelnetConnection {
|
||||
int n = inputStream.read(buf);
|
||||
if (n < 0) {
|
||||
log.info("Host disconnected (EOF)");
|
||||
fsm.onDisconnect();
|
||||
boolean unexpected = !intentionalDisconnect;
|
||||
fsm.onDisconnect(unexpected);
|
||||
break;
|
||||
}
|
||||
if (n > 0) {
|
||||
lastActivityTime.set(System.currentTimeMillis());
|
||||
if (log.isLoggable(Level.FINE)) {
|
||||
log.fine("RCVD " + n + " bytes: " + formatHex(buf, 0, n));
|
||||
}
|
||||
@@ -486,14 +604,23 @@ public class TelnetConnection {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
if (running && !intentionalDisconnect) {
|
||||
log.warning("Socket read timeout (" + (config != null ? config.getSoTimeoutMs() : 0) + "ms): " + e.getMessage());
|
||||
fsm.onDisconnect(true);
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
if (running) {
|
||||
log.info("Socket closed: " + e.getMessage());
|
||||
fsm.onDisconnect();
|
||||
if (running && !intentionalDisconnect) {
|
||||
log.info("Socket closed unexpectedly: " + e.getMessage());
|
||||
fsm.onDisconnect(true);
|
||||
} else if (running) {
|
||||
fsm.onDisconnect(false);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (running) {
|
||||
log.log(Level.WARNING, "Read error", e);
|
||||
if (running && !intentionalDisconnect) {
|
||||
log.log(Level.WARNING, "Read error: " + e.getMessage());
|
||||
fsm.onDisconnect(true);
|
||||
} else if (running) {
|
||||
fsm.onError("Read error: " + e.getMessage());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
@@ -501,6 +628,8 @@ public class TelnetConnection {
|
||||
log.log(Level.SEVERE, "Unexpected fatal error in readLoop", t);
|
||||
fsm.onError("Network loop error: " + t.getMessage());
|
||||
}
|
||||
} finally {
|
||||
stopKeepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,11 @@ public class TelnetFSM {
|
||||
private String connectedLu;
|
||||
private String connectedType;
|
||||
|
||||
// Phase 10: Contention Resolution & Auto-Unlock State
|
||||
private boolean sdi_flag = false;
|
||||
private boolean kri_flag = false;
|
||||
private boolean negotiateContentionResolution = true;
|
||||
|
||||
public enum TN3270ESubmode { UNBOUND, E_3270, E_NVT, E_SSCP }
|
||||
|
||||
public TelnetFSM(ConnectionConfig config, ScreenBuffer screenBuffer, DataStreamProcessor dsProcessor) {
|
||||
@@ -190,7 +195,7 @@ public class TelnetFSM {
|
||||
eFuncs[FUNC_SYSREQ] = true;
|
||||
eFuncs[FUNC_SNA_SENSE] = true;
|
||||
eFuncs[FUNC_DATA_STREAM_CTL] = true;
|
||||
eFuncs[FUNC_CONTENTION_RESOLUTION] = true;
|
||||
eFuncs[FUNC_CONTENTION_RESOLUTION] = negotiateContentionResolution;
|
||||
|
||||
statusDisplay(STATUS_CONNECTING, "Connecting to host");
|
||||
changeState(ConnectionState.TELNET_PENDING);
|
||||
@@ -951,6 +956,10 @@ public class TelnetFSM {
|
||||
log.info("TN3270E functions negotiated: " + getNegotiatedFunctionNames());
|
||||
log.info("TN3270E negotiation complete");
|
||||
|
||||
if (dsProcessor != null) {
|
||||
dsProcessor.setContentionResolution(isContentionResolutionNegotiated());
|
||||
}
|
||||
|
||||
// RFC 2355: If BIND-IMAGE function is not negotiated, the emulation session is considered
|
||||
// to be bound immediately upon completion of the FUNCTIONS negotiation.
|
||||
if (eFuncs[FUNC_BIND_IMAGE]) {
|
||||
@@ -963,6 +972,7 @@ public class TelnetFSM {
|
||||
// Notify listeners
|
||||
for (ConnectionListener l : connectionListeners) {
|
||||
l.onTN3270ENegotiated(connectedType, connectedLu);
|
||||
l.onTN3270EFunctionsNegotiated(eFuncs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -996,9 +1006,59 @@ public class TelnetFSM {
|
||||
processTN3270ERecord(data);
|
||||
} else {
|
||||
// Plain TN3270 mode: data is raw 3270 data stream
|
||||
dsProcessor.processRecord(data, 0, data.length, true);
|
||||
if (dsProcessor != null) {
|
||||
dsProcessor.processRecord(data, 0, data.length, false);
|
||||
}
|
||||
notifyScreenUpdate();
|
||||
}
|
||||
|
||||
// Phase 10: Contention Resolution & AUTO_SYS_UNLOCK handling on EOR
|
||||
if (dsProcessor != null) {
|
||||
boolean crActive = isContentionResolutionNegotiated();
|
||||
if (crActive) {
|
||||
if (this.sdi_flag && !dsProcessor.isRcvdRead()) {
|
||||
if (dsProcessor.getInputProcessor() != null) {
|
||||
dsProcessor.getInputProcessor().setKeyboardLocked(false);
|
||||
}
|
||||
this.sdi_flag = false;
|
||||
dsProcessor.setUnlockPending(false);
|
||||
if (dsProcessor.isUnlockSysPending() || this.kri_flag) {
|
||||
if (dsProcessor.getInputProcessor() != null && dsProcessor.getInputProcessor().getOIA() != null) {
|
||||
dsProcessor.getInputProcessor().getOIA().setInputInhibited(haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
}
|
||||
}
|
||||
this.kri_flag = false;
|
||||
dsProcessor.setUnlockSysPending(false);
|
||||
notifyKeyboardUnlocked();
|
||||
}
|
||||
this.kri_flag = false;
|
||||
} else {
|
||||
// Contention Resolution is not active
|
||||
boolean autoSysUnlock = (config != null) ? config.isAutoSysUnlock() : true;
|
||||
if (autoSysUnlock && !dsProcessor.isRcvdRead()) {
|
||||
boolean sysLocked = false;
|
||||
if (dsProcessor.getInputProcessor() != null) {
|
||||
if (dsProcessor.getInputProcessor().isKeyboardLocked() ||
|
||||
(dsProcessor.getInputProcessor().getOIA() != null && dsProcessor.getInputProcessor().getOIA().isXSystem())) {
|
||||
sysLocked = true;
|
||||
}
|
||||
}
|
||||
if (sysLocked) {
|
||||
if (dsProcessor.getInputProcessor() != null) {
|
||||
dsProcessor.getInputProcessor().setKeyboardLocked(false);
|
||||
if (dsProcessor.getInputProcessor().getOIA() != null) {
|
||||
dsProcessor.getInputProcessor().getOIA().setInputInhibited(haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
}
|
||||
}
|
||||
dsProcessor.setUnlockSysPending(false);
|
||||
notifyKeyboardUnlocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.sdi_flag = false;
|
||||
this.kri_flag = false;
|
||||
dsProcessor.setRcvdRead(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void processTn3270eHeader(byte[] data) {
|
||||
@@ -1016,6 +1076,12 @@ public class TelnetFSM {
|
||||
int responseFlag = data[2] & 0xFF;
|
||||
int seqNumber = ((data[3] & 0xFF) << 8) | (data[4] & 0xFF);
|
||||
|
||||
this.sdi_flag = (requestFlag & 0x01) != 0;
|
||||
this.kri_flag = (requestFlag & 0x02) != 0;
|
||||
if (dsProcessor != null) {
|
||||
dsProcessor.setContentionResolution(isContentionResolutionNegotiated());
|
||||
}
|
||||
|
||||
log.fine("TN3270E header: type=" + TN3270EConstants.dataTypeName(dataType) +
|
||||
" rqf=" + requestFlag + " rsf=" + responseFlag + " seq=" + seqNumber);
|
||||
|
||||
@@ -1033,7 +1099,7 @@ public class TelnetFSM {
|
||||
tn3270eSubmode = TN3270ESubmode.E_3270;
|
||||
}
|
||||
try {
|
||||
dsProcessor.processRecord(data, EH_SIZE, data.length - EH_SIZE, true);
|
||||
dsProcessor.processRecord(data, EH_SIZE, data.length - EH_SIZE, false);
|
||||
notifyScreenUpdate();
|
||||
// Send positive response if required
|
||||
if (eFuncs[FUNC_RESPONSES] && responseFlag == RSF_ALWAYS_RESPONSE) {
|
||||
@@ -1423,8 +1489,49 @@ public class TelnetFSM {
|
||||
}
|
||||
}
|
||||
|
||||
public void setConnectionState(ConnectionState newState) {
|
||||
changeState(newState);
|
||||
}
|
||||
|
||||
public void onDisconnect() {
|
||||
changeState(ConnectionState.NOT_CONNECTED);
|
||||
onDisconnect(false);
|
||||
}
|
||||
|
||||
public void onDisconnect(boolean unexpected) {
|
||||
if (unexpected && config != null && config.isAutoReconnect()) {
|
||||
log.info("Unexpected connection loss — transitioning to RECONNECTING");
|
||||
changeState(ConnectionState.RECONNECTING);
|
||||
} else {
|
||||
changeState(ConnectionState.NOT_CONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanly reset Telnet and TN3270E session state prior to reconnecting.
|
||||
*/
|
||||
public synchronized void resetSessionState() {
|
||||
state = TNS_DATA;
|
||||
java.util.Arrays.fill(myOpts, false);
|
||||
java.util.Arrays.fill(hisOpts, false);
|
||||
ibuf.reset();
|
||||
sbbuf.reset();
|
||||
tn3270eNegotiated = false;
|
||||
tn3270eSubmode = TN3270ESubmode.UNBOUND;
|
||||
tn3270eBound = false;
|
||||
java.util.Arrays.fill(eFuncs, false);
|
||||
eXmitSeq = 0;
|
||||
lastRcvSeq = 0;
|
||||
lastRespType = 0;
|
||||
lastRespCode = 0;
|
||||
responseRequired = RSF_NO_RESPONSE;
|
||||
deferredWillTtype = false;
|
||||
tn3270eDeviceTypeSent = false;
|
||||
ttypeIndex = 0;
|
||||
luIndex = 0;
|
||||
connectedLu = null;
|
||||
connectedType = null;
|
||||
sdi_flag = false;
|
||||
kri_flag = false;
|
||||
}
|
||||
|
||||
public void onError(String message) {
|
||||
@@ -1434,7 +1541,10 @@ public class TelnetFSM {
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyScreenUpdate() {
|
||||
public void notifyScreenUpdate() {
|
||||
if (screenBuffer != null) {
|
||||
screenBuffer.notifyScreenUpdate();
|
||||
}
|
||||
for (ScreenUpdateListener l : screenListeners) {
|
||||
l.onScreenUpdated();
|
||||
}
|
||||
@@ -1810,4 +1920,41 @@ public class TelnetFSM {
|
||||
public boolean isFunctionNegotiated(int func) {
|
||||
return func >= 0 && func < eFuncs.length && eFuncs[func];
|
||||
}
|
||||
|
||||
public boolean isContentionResolutionNegotiated() {
|
||||
return tn3270eNegotiated && eFuncs[FUNC_CONTENTION_RESOLUTION];
|
||||
}
|
||||
|
||||
public boolean isNegotiateContentionResolution() {
|
||||
return negotiateContentionResolution;
|
||||
}
|
||||
|
||||
public void setNegotiateContentionResolution(boolean neg) {
|
||||
this.negotiateContentionResolution = neg;
|
||||
eFuncs[FUNC_CONTENTION_RESOLUTION] = neg;
|
||||
}
|
||||
|
||||
public void setContentionResolutionNegotiated(boolean cr) {
|
||||
if (cr) {
|
||||
this.tn3270eNegotiated = true;
|
||||
}
|
||||
eFuncs[FUNC_CONTENTION_RESOLUTION] = cr;
|
||||
if (dsProcessor != null) {
|
||||
dsProcessor.setContentionResolution(cr);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSdi_flag() { return sdi_flag; }
|
||||
public void setSdi_flag(boolean sdi) { this.sdi_flag = sdi; }
|
||||
|
||||
public boolean isKri_flag() { return kri_flag; }
|
||||
public void setKri_flag(boolean kri) { this.kri_flag = kri; }
|
||||
|
||||
public void notifyKeyboardUnlocked() {
|
||||
for (ScreenUpdateListener l : screenListeners) {
|
||||
try {
|
||||
l.onKeyboardUnlocked();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +152,14 @@ public class DS3270 {
|
||||
public boolean suppressClearEvent;
|
||||
public boolean wsfvalid = true;
|
||||
|
||||
// Phase 10: State tracking matching HoD DS3270
|
||||
protected boolean autoSysUnlock = true;
|
||||
protected boolean sdi_flag = false;
|
||||
protected boolean kri_flag = false;
|
||||
protected boolean unlock_pending = false;
|
||||
protected boolean unlock_sys_pending = false;
|
||||
protected boolean rcvdRead = false;
|
||||
|
||||
// Underlying lib3270j data stream processor
|
||||
private final DataStreamProcessor delegate;
|
||||
private ECLSession session;
|
||||
@@ -172,9 +180,15 @@ public class DS3270 {
|
||||
public DS3270(ECLSession session, ECLPS ps) {
|
||||
this.session = session;
|
||||
this.ps = ps;
|
||||
if (session != null) {
|
||||
this.autoSysUnlock = session.isAutoSysUnlock();
|
||||
}
|
||||
ScreenBuffer sb = (ps != null) ? ps.getScreenBuffer() : new ScreenBuffer();
|
||||
EbcdicTranslator trans = (ps != null) ? ps.getTranslator() : new EbcdicTranslator();
|
||||
this.delegate = new DataStreamProcessor(sb, trans);
|
||||
if (session != null) {
|
||||
this.delegate.setAutoSysUnlock(session.isAutoSysUnlock());
|
||||
}
|
||||
if (ps != null && ps.getInputProcessor() != null) {
|
||||
this.delegate.setInputProcessor(ps.getInputProcessor());
|
||||
}
|
||||
@@ -214,8 +228,135 @@ public class DS3270 {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isAutoSysUnlock() {
|
||||
return autoSysUnlock;
|
||||
}
|
||||
|
||||
public void setAutoSysUnlock(boolean autoSysUnlock) {
|
||||
this.autoSysUnlock = autoSysUnlock;
|
||||
if (delegate != null) {
|
||||
delegate.setAutoSysUnlock(autoSysUnlock);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSdi_flag() { return sdi_flag; }
|
||||
public void setSdi_flag(boolean sdi) { this.sdi_flag = sdi; }
|
||||
|
||||
public boolean isKri_flag() { return kri_flag; }
|
||||
public void setKri_flag(boolean kri) { this.kri_flag = kri; }
|
||||
|
||||
public boolean isUnlock_pending() { return unlock_pending; }
|
||||
public void setUnlock_pending(boolean pending) { this.unlock_pending = pending; }
|
||||
|
||||
public boolean isUnlock_sys_pending() { return unlock_sys_pending; }
|
||||
public void setUnlock_sys_pending(boolean pending) { this.unlock_sys_pending = pending; }
|
||||
|
||||
public boolean isRcvdRead() {
|
||||
return rcvdRead || (delegate != null && delegate.isRcvdRead());
|
||||
}
|
||||
public void setRcvdRead(boolean rcvd) {
|
||||
this.rcvdRead = rcvd;
|
||||
if (delegate != null) delegate.setRcvdRead(rcvd);
|
||||
}
|
||||
|
||||
public void receiveHeaderData(short s, short s2, short s3, int n) {
|
||||
this.sdi_flag = (s2 & request_bit_SDI) != 0;
|
||||
this.kri_flag = (s2 & request_bit_KRI) != 0;
|
||||
}
|
||||
|
||||
public void receiveHeader(short s, short s2, short s3, int n) {
|
||||
receiveHeaderData(s, s2, s3, n);
|
||||
}
|
||||
|
||||
public void endOfRecord() {
|
||||
log.fine("DS3270 endOfRecord");
|
||||
boolean crActive = (session != null && session.getContentionResolution());
|
||||
if (crActive) {
|
||||
if (this.sdi_flag && !isRcvdRead()) {
|
||||
if (this.ps != null) {
|
||||
this.ps.unlockKeyboard(7);
|
||||
} else if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
delegate.getInputProcessor().setKeyboardLocked(false);
|
||||
}
|
||||
this.sdi_flag = false;
|
||||
this.unlock_pending = false;
|
||||
if (this.unlock_sys_pending || this.kri_flag) {
|
||||
if (this.ps != null) {
|
||||
this.ps.unlockKeyboard(8);
|
||||
} else if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
delegate.getInputProcessor().setKeyboardLocked(false);
|
||||
if (delegate.getInputProcessor().getOIA() != null) {
|
||||
delegate.getInputProcessor().getOIA().setInputInhibited(haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.kri_flag = false;
|
||||
this.unlock_sys_pending = false;
|
||||
}
|
||||
if (this.ps != null) {
|
||||
this.ps.dispatchEvent(new haus.nightmare.lib3270j.ecl.ECLPSEvent(this.ps, haus.nightmare.lib3270j.ecl.ECLPSEvent.EVENT_KEY_UNLOCKED));
|
||||
}
|
||||
} else {
|
||||
// Contention resolution is not active
|
||||
if (this.unlock_pending && !isRcvdRead()) {
|
||||
if (this.ps != null) {
|
||||
this.ps.unlockKeyboard(7);
|
||||
} else if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
delegate.getInputProcessor().setKeyboardLocked(false);
|
||||
}
|
||||
if (this.autoSysUnlock) {
|
||||
if (this.ps != null) {
|
||||
this.ps.unlockKeyboard(8);
|
||||
} else if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
delegate.getInputProcessor().setKeyboardLocked(false);
|
||||
if (delegate.getInputProcessor().getOIA() != null) {
|
||||
delegate.getInputProcessor().getOIA().setInputInhibited(haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
}
|
||||
}
|
||||
this.unlock_sys_pending = false;
|
||||
}
|
||||
this.unlock_pending = false;
|
||||
}
|
||||
if (this.unlock_sys_pending && !isRcvdRead()) {
|
||||
if (this.ps != null) {
|
||||
this.ps.unlockKeyboard(8);
|
||||
} else if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
delegate.getInputProcessor().setKeyboardLocked(false);
|
||||
if (delegate.getInputProcessor().getOIA() != null) {
|
||||
delegate.getInputProcessor().getOIA().setInputInhibited(haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
}
|
||||
}
|
||||
this.unlock_sys_pending = false;
|
||||
}
|
||||
if (this.autoSysUnlock && !isRcvdRead()) {
|
||||
boolean sysLocked = false;
|
||||
if (this.ps != null && this.ps.islocked_SYSLOCK()) {
|
||||
sysLocked = true;
|
||||
} else if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
if (delegate.getInputProcessor().isKeyboardLocked() ||
|
||||
(delegate.getInputProcessor().getOIA() != null && delegate.getInputProcessor().getOIA().isXSystem())) {
|
||||
sysLocked = true;
|
||||
}
|
||||
}
|
||||
if (sysLocked) {
|
||||
if (this.ps != null) {
|
||||
this.ps.unlockKeyboard(8);
|
||||
}
|
||||
if (delegate != null && delegate.getInputProcessor() != null) {
|
||||
delegate.getInputProcessor().setKeyboardLocked(false);
|
||||
if (delegate.getInputProcessor().getOIA() != null) {
|
||||
delegate.getInputProcessor().getOIA().setInputInhibited(haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NOT_INHIBITED);
|
||||
}
|
||||
}
|
||||
this.unlock_sys_pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.rcvdRead = false;
|
||||
if (delegate != null) delegate.setRcvdRead(false);
|
||||
this.unlock_pending = false;
|
||||
this.sdi_flag = false;
|
||||
this.kri_flag = false;
|
||||
}
|
||||
|
||||
public int receiveData(short[] sArray, int off, int len) {
|
||||
@@ -233,6 +374,10 @@ public class DS3270 {
|
||||
|
||||
public void processWCC(short wcc) {
|
||||
delegate.processWCC(wcc);
|
||||
if ((wcc & WCC_RESTORE) > 0) {
|
||||
this.unlock_pending = true;
|
||||
this.unlock_sys_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void processSBA(int baddr) {
|
||||
|
||||
Reference in New Issue
Block a user