cleaning and adding updates
This commit is contained in:
@@ -51,6 +51,7 @@ public class Telnet3270Client {
|
||||
|
||||
// Wire up the output sender: DSProcessor -> FSM -> TelnetConnection
|
||||
dsProcessor.setOutputSender(fsm::send3270Data);
|
||||
dsProcessor.setInputProcessor(inputProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,9 @@ public class DataStreamProcessor {
|
||||
// File Transfer DFT handler
|
||||
private org.lib3270j.ft.FTDft ftDft;
|
||||
|
||||
// Input processor reference to manage keyboard locking state
|
||||
private org.lib3270j.input.InputProcessor inputProcessor;
|
||||
|
||||
/** Functional interface for sending output back through the telnet stack. */
|
||||
@FunctionalInterface
|
||||
public interface OutputSender {
|
||||
@@ -56,6 +59,10 @@ public class DataStreamProcessor {
|
||||
this.ftDft = ftDft;
|
||||
}
|
||||
|
||||
public void setInputProcessor(org.lib3270j.input.InputProcessor inputProcessor) {
|
||||
this.inputProcessor = inputProcessor;
|
||||
}
|
||||
|
||||
public void addScreenUpdateListener(ScreenUpdateListener l) {
|
||||
screenListeners.add(l);
|
||||
}
|
||||
@@ -231,6 +238,12 @@ 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 (resetMdt) {
|
||||
resetAllMDT();
|
||||
}
|
||||
@@ -471,6 +484,10 @@ public class DataStreamProcessor {
|
||||
if (!faIsProtected(faVal & 0xFF)) {
|
||||
ea.ec = 0;
|
||||
ea.ucs4 = 0;
|
||||
ea.fg = 0;
|
||||
ea.bg = 0;
|
||||
ea.gr = 0;
|
||||
ea.cs = 0;
|
||||
}
|
||||
}
|
||||
baddr = (baddr + 1) % size;
|
||||
@@ -614,6 +631,10 @@ public class DataStreamProcessor {
|
||||
// ========== Read Modified ==========
|
||||
|
||||
private void processReadModified(boolean all) {
|
||||
if (ftDft != null) {
|
||||
ftDft.readModified();
|
||||
}
|
||||
|
||||
outputPos = 0;
|
||||
|
||||
int aid = AID_NO; // Last AID
|
||||
|
||||
@@ -183,6 +183,12 @@ public final class FTConstants {
|
||||
* Decode a CUT-mode base-64 encoded integer from EBCDIC.
|
||||
* Converts a table6-encoded EBCDIC character to its 6-bit value.
|
||||
*/
|
||||
public static int from6(int ebcdicByte, org.lib3270j.charset.EbcdicTranslator translator) {
|
||||
char ascii = translator.ebcdicToUnicode(ebcdicByte & 0xFF);
|
||||
int idx = TABLE6.indexOf(ascii);
|
||||
return idx >= 0 ? idx : 0;
|
||||
}
|
||||
|
||||
public static int from6(int ebcdicByte) {
|
||||
// First convert EBCDIC to ASCII via IND$FILE's table
|
||||
int ascii = FT2ASC[ebcdicByte & 0xFF];
|
||||
@@ -193,6 +199,12 @@ public final class FTConstants {
|
||||
/**
|
||||
* Encode a 6-bit value into a table6-encoded EBCDIC character.
|
||||
*/
|
||||
public static int to6(int value, org.lib3270j.charset.EbcdicTranslator translator) {
|
||||
char ascii = TABLE6.charAt(value & 0x3F);
|
||||
int ebc = translator.unicodeToEbcdic(ascii);
|
||||
return ebc >= 0 ? ebc : ASC2FT[ascii & 0xFF];
|
||||
}
|
||||
|
||||
public static int to6(int value) {
|
||||
char ascii = TABLE6.charAt(value & 0x3F);
|
||||
return ASC2FT[ascii & 0xFF];
|
||||
|
||||
@@ -7,6 +7,7 @@ import static org.lib3270j.ft.FTConstants.*;
|
||||
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@@ -49,10 +50,10 @@ public class FTCut {
|
||||
|
||||
private static final String ALPHAS = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%&_()<+,-./:>?";
|
||||
|
||||
private static final int SELECTOR_0 = 0x5E; // ';'
|
||||
private static final int SELECTOR_1 = 0x7E; // '='
|
||||
private static final int SELECTOR_2 = 0x5C; // '*'
|
||||
private static final int SELECTOR_3 = 0x7D; // '\''
|
||||
private static final int SELECTOR_0 = 0x5E; // ';' (EBCDIC)
|
||||
private static final int SELECTOR_1 = 0x7E; // '=' (EBCDIC)
|
||||
private static final int SELECTOR_2 = 0x5C; // '*' (EBCDIC)
|
||||
private static final int SELECTOR_3 = 0x7D; // '\'' (EBCDIC)
|
||||
|
||||
private static final int[] XLATE_0 = {
|
||||
0x40,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7, 0xc8,0xc9,0xd1,0xd2,
|
||||
@@ -110,6 +111,55 @@ public class FTCut {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CUT mode with active streams.
|
||||
*/
|
||||
public void initTransfer(InputStream in, OutputStream out) {
|
||||
this.inputStream = in;
|
||||
this.outputStream = out;
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CUT mode for a new transfer.
|
||||
*/
|
||||
public void initTransfer(File localFile) throws IOException {
|
||||
FTConfig config = listener.getConfig();
|
||||
resetState();
|
||||
|
||||
if (config.isReceive()) {
|
||||
boolean append = config.isAppend();
|
||||
outputStream = new FileOutputStream(localFile, append);
|
||||
inputStream = null;
|
||||
} else {
|
||||
inputStream = new FileInputStream(localFile);
|
||||
outputStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetState() {
|
||||
xferInProgress = false;
|
||||
expandedLength = 0;
|
||||
quadrant = -1;
|
||||
xlateBuffered = 0;
|
||||
xlateBufIx = 0;
|
||||
cutEof = false;
|
||||
lastCr = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up CUT mode resources.
|
||||
*/
|
||||
public void cleanup() {
|
||||
xferInProgress = false;
|
||||
try {
|
||||
if (inputStream != null) { inputStream.close(); inputStream = null; }
|
||||
if (outputStream != null) { outputStream.close(); outputStream = null; }
|
||||
} catch (IOException e) {
|
||||
log.warning("Error closing file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a CUT-mode screen update.
|
||||
* Called by the transfer coordinator when the screen changes during a transfer.
|
||||
@@ -120,7 +170,12 @@ public class FTCut {
|
||||
// CUT frames MUST have a skip field attribute at O_SF (1919)
|
||||
byte sfAttr = screen.getCellFAByte(O_SF);
|
||||
if (sfAttr == 0 || !isSkip(sfAttr)) {
|
||||
// Not a CUT frame (likely local echo or intermediate screen), ignore
|
||||
// Not a CUT frame (likely local echo, menu return, or intermediate screen)
|
||||
if (xferInProgress) {
|
||||
log.warning("CUT: Received non-CUT frame while transfer in progress. Host aborted.");
|
||||
xferInProgress = false;
|
||||
listener.onTransferAborted("Host aborted transfer (returned to menu)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,42 +204,6 @@ public class FTCut {
|
||||
return (attr & FA_PROTECT) != 0 && (attr & FA_NUMERIC) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CUT mode for a new transfer.
|
||||
*/
|
||||
public void initTransfer(File localFile) throws IOException {
|
||||
FTConfig config = listener.getConfig();
|
||||
xferInProgress = false;
|
||||
expandedLength = 0;
|
||||
quadrant = -1;
|
||||
xlateBuffered = 0;
|
||||
xlateBufIx = 0;
|
||||
cutEof = false;
|
||||
lastCr = false;
|
||||
|
||||
if (config.isReceive()) {
|
||||
boolean append = config.isAppend();
|
||||
outputStream = new FileOutputStream(localFile, append);
|
||||
inputStream = null;
|
||||
} else {
|
||||
inputStream = new FileInputStream(localFile);
|
||||
outputStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up CUT mode resources.
|
||||
*/
|
||||
public void cleanup() {
|
||||
xferInProgress = false;
|
||||
try {
|
||||
if (inputStream != null) { inputStream.close(); inputStream = null; }
|
||||
if (outputStream != null) { outputStream.close(); outputStream = null; }
|
||||
} catch (IOException e) {
|
||||
log.warning("Error closing file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Control Code Processing ==========
|
||||
|
||||
private void cutControlCode() {
|
||||
@@ -218,7 +237,6 @@ public class FTCut {
|
||||
xferInProgress = false;
|
||||
cutAck();
|
||||
|
||||
// Extract error message from the host (positions 4-83)
|
||||
String msg = extractHostMessage();
|
||||
listener.onTransferAborted(msg);
|
||||
break;
|
||||
@@ -242,7 +260,6 @@ public class FTCut {
|
||||
char ch = translator.ebcdicToUnicode(ebc);
|
||||
sb.append(ch);
|
||||
}
|
||||
// Trim trailing spaces and '$'
|
||||
String msg = sb.toString().stripTrailing();
|
||||
if (msg.endsWith("$")) {
|
||||
msg = msg.substring(0, msg.length() - 1).stripTrailing();
|
||||
@@ -252,12 +269,10 @@ public class FTCut {
|
||||
|
||||
// ========== Data Request (Upload) ==========
|
||||
|
||||
/**
|
||||
* Process a data request from the host (upload: send data to host).
|
||||
*/
|
||||
private void cutDataRequest() {
|
||||
int seq = screen.getCellEC(O_DR_FRAME_SEQ);
|
||||
log.fine("CUT: DATA_REQUEST seq=" + from6(seq));
|
||||
int seqEbc = screen.getCellEC(O_DR_FRAME_SEQ);
|
||||
int seq = FTConstants.from6(seqEbc, translator);
|
||||
log.fine("CUT: DATA_REQUEST seq=" + seq);
|
||||
|
||||
if (listener.getCurrentState() == FTState.ABORT_WAIT) {
|
||||
cutAbort("Transfer cancelled by user", SC_ABORT_FILE);
|
||||
@@ -266,7 +281,6 @@ public class FTCut {
|
||||
|
||||
FTConfig config = listener.getConfig();
|
||||
|
||||
// Read data from local file into screen buffer
|
||||
int count = 0;
|
||||
try {
|
||||
while (count < O_UP_MAX && !cutEof) {
|
||||
@@ -284,32 +298,28 @@ public class FTCut {
|
||||
return;
|
||||
}
|
||||
|
||||
// EOF with no data → send EOF marker
|
||||
if (count == 0 && cutEof) {
|
||||
screen.setCell(O_UP_DATA, EOF_DATA1);
|
||||
screen.setCell(O_UP_DATA + 1, EOF_DATA2);
|
||||
count = 2;
|
||||
}
|
||||
|
||||
// Compute frame fields
|
||||
screen.setCell(O_UP_FRAME_SEQ, seq);
|
||||
screen.setCell(O_UP_FRAME_SEQ, seqEbc);
|
||||
|
||||
// Checksum
|
||||
int cs = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
cs ^= screen.getCellEC(O_UP_DATA + i);
|
||||
}
|
||||
screen.setCell(O_UP_CSUM, to6(cs));
|
||||
screen.setCell(O_UP_LEN, to6((count >> 6) & 0x3F));
|
||||
screen.setCell(O_UP_LEN + 1, to6(count & 0x3F));
|
||||
screen.setCell(O_UP_CSUM, FTConstants.to6(cs & 0x3F, translator));
|
||||
screen.setCell(O_UP_LEN, FTConstants.to6((count >> 6) & 0x3F, translator));
|
||||
screen.setCell(O_UP_LEN + 1, FTConstants.to6(count & 0x3F, translator));
|
||||
|
||||
// Hide the data field (change SF attribute to zero intensity)
|
||||
// Hide data field by setting zero intensity on field attribute
|
||||
byte attr = screen.getCellFAByte(O_DR_SF);
|
||||
attr = (byte) ((attr & ~FA_INTENSITY) | FA_INT_ZERO_NSEL);
|
||||
attr = (byte) ((attr & ~FA_INTENSITY) | FA_INT_ZERO_NSEL | FA_MODIFY);
|
||||
screen.setCellFA(O_DR_SF, attr);
|
||||
|
||||
// Send it
|
||||
log.fine("CUT: > DATA seq=" + from6(seq) + " len=" + count);
|
||||
log.fine("CUT: > DATA seq=" + seq + " len=" + count);
|
||||
expandedLength += count;
|
||||
listener.onBytesTransferred(expandedLength);
|
||||
input.sendAidForFT(AID_ENTER);
|
||||
@@ -317,9 +327,6 @@ public class FTCut {
|
||||
|
||||
// ========== Data (Download) ==========
|
||||
|
||||
/**
|
||||
* Process data from the host (download: receive data from host).
|
||||
*/
|
||||
private void cutData() {
|
||||
log.fine("CUT: DATA");
|
||||
|
||||
@@ -330,9 +337,8 @@ public class FTCut {
|
||||
|
||||
FTConfig config = listener.getConfig();
|
||||
|
||||
// Extract raw data
|
||||
int rawLength = (from6(screen.getCellEC(O_DT_LEN)) << 6) |
|
||||
from6(screen.getCellEC(O_DT_LEN + 1));
|
||||
int rawLength = (FTConstants.from6(screen.getCellEC(O_DT_LEN), translator) << 6) |
|
||||
FTConstants.from6(screen.getCellEC(O_DT_LEN + 1), translator);
|
||||
|
||||
if (rawLength > O_RESPONSE - O_DT_DATA) {
|
||||
cutAbort("Oversized CUT data frame", SC_ABORT_XMIT);
|
||||
@@ -344,7 +350,6 @@ public class FTCut {
|
||||
rawData[i] = (byte) screen.getCellEC(O_DT_DATA + i);
|
||||
}
|
||||
|
||||
// Check for EOF marker
|
||||
if (rawLength == 2 && (rawData[0] & 0xFF) == EOF_DATA1 &&
|
||||
(rawData[1] & 0xFF) == EOF_DATA2) {
|
||||
log.fine("CUT: EOF marker received");
|
||||
@@ -352,7 +357,6 @@ public class FTCut {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert and write to local file
|
||||
try {
|
||||
byte[] converted = convertDownload(rawData, rawLength, config);
|
||||
if (outputStream != null) {
|
||||
@@ -386,31 +390,20 @@ public class FTCut {
|
||||
private void cutAbort(String message, int reason) {
|
||||
log.warning("CUT: ABORT — " + message);
|
||||
|
||||
// Write abort frame to the response area
|
||||
screen.setCell(RO_FRAME_TYPE, RFT_CONTROL_CODE);
|
||||
screen.setCell(RO_FRAME_SEQ, screen.getCellEC(O_DT_FRAME_SEQ));
|
||||
screen.setCell(RO_REASON_CODE, (reason >> 8) & 0xFF);
|
||||
screen.setCell(RO_REASON_CODE + 1, reason & 0xFF);
|
||||
|
||||
// Send PF2 to signal abort
|
||||
input.sendAidForFT(AID_PF2);
|
||||
|
||||
listener.onTransferAborted(message);
|
||||
}
|
||||
|
||||
// ========== Character Translation ==========
|
||||
|
||||
/**
|
||||
* Convert received EBCDIC data to local format.
|
||||
* Handles ASCII mode (with optional CR processing and remapping)
|
||||
* and binary mode (raw passthrough).
|
||||
*
|
||||
* The quadrant tables decode wire bytes into IND$FILE's "pseudo-ASCII"
|
||||
* representation. For the remap path, control codes (< 0x20 and
|
||||
* 0x80-0x9E) are treated as direct Unicode codepoints, while printable
|
||||
* characters are re-mapped through ASC2FT (inverting IND$FILE's built-in
|
||||
* EBCDIC→ASCII table) back to real EBCDIC, then to Unicode.
|
||||
* This matches x3270's upload_convert() logic.
|
||||
* Convert received CUT EBCDIC data to local format.
|
||||
* Matching x3270 upload_convert logic.
|
||||
*/
|
||||
private byte[] convertDownload(byte[] rawData, int length, FTConfig config)
|
||||
throws IOException {
|
||||
@@ -421,81 +414,59 @@ public class FTCut {
|
||||
|
||||
while (true) {
|
||||
if (quadrant < 0) {
|
||||
// Find the quadrant
|
||||
for (quadrant = 0; quadrant < 4; quadrant++) {
|
||||
if (c == SELECTORS[quadrant]) break;
|
||||
}
|
||||
if (quadrant >= 4) {
|
||||
throw new IOException("CUT conversion error (quadrant selector not found)");
|
||||
}
|
||||
break; // continue outer loop
|
||||
break;
|
||||
}
|
||||
|
||||
if (c < 0x40 || c > 0xF9) {
|
||||
throw new IOException("CUT conversion error (data out of bounds)");
|
||||
}
|
||||
|
||||
char asciiChar = translator.ebcdicToUnicode(c);
|
||||
char asciiChar = (char) FT2ASC[c & 0xFF];
|
||||
int ix = ALPHAS.indexOf(asciiChar);
|
||||
if (ix < 0) {
|
||||
// Try a different quadrant
|
||||
quadrant = -1;
|
||||
continue; // retry loop
|
||||
continue;
|
||||
}
|
||||
|
||||
if (quadrant != 2 && c != 0xC1 && QUADS[quadrant][ix] == 0) {
|
||||
// Try a different quadrant
|
||||
if (!(quadrant == 2 && c == 0xC1) && QUADS[quadrant][ix] == 0) {
|
||||
quadrant = -1;
|
||||
continue; // retry
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map the character — this produces IND$FILE's pseudo-ASCII byte
|
||||
int decoded = QUADS[quadrant][ix];
|
||||
|
||||
if (config.isAscii() && config.isCrFlag() && (decoded == 0x0D || decoded == 0x1A)) {
|
||||
break; // Ignore bare CR and EOF markers in ASCII stream
|
||||
break;
|
||||
}
|
||||
|
||||
if (!config.isAscii() || !config.isRemapFlag()) {
|
||||
// Binary or ASCII-no-remap: emit the decoded byte as-is
|
||||
out.write(decoded);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* ASCII with remap: invert IND$FILE's EBCDIC→ASCII translation.
|
||||
*
|
||||
* Control codes (< 0x20) and high control range (0x80-0x9E,
|
||||
* except 0x9F) are treated as direct Unicode codepoints.
|
||||
* IND$FILE maps EBCDIC 0xE1 to pseudo-ASCII 0x9F, so
|
||||
* 0xFF is the special marker that maps back to U+009F.
|
||||
* Everything else is a printable character that needs
|
||||
* the ASC2FT→ebcdicToUnicode remap chain.
|
||||
*/
|
||||
if (decoded < 0x20 || (decoded >= 0x80 && decoded < 0xA0 && decoded != 0x9F)) {
|
||||
// Control code — emit as its Unicode codepoint directly
|
||||
out.write(String.valueOf((char) decoded).getBytes(
|
||||
java.nio.charset.StandardCharsets.UTF_8));
|
||||
out.write(String.valueOf((char) decoded).getBytes(StandardCharsets.UTF_8));
|
||||
} else if (decoded == 0xFF) {
|
||||
// Special case: 0xFF → U+009F
|
||||
out.write(String.valueOf((char) 0x9F).getBytes(
|
||||
java.nio.charset.StandardCharsets.UTF_8));
|
||||
out.write(String.valueOf((char) 0x9F).getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
// Displayable character: invert IND$FILE's table
|
||||
int ebc = FTConstants.ASC2FT[decoded & 0xFF];
|
||||
int ebc = ASC2FT[decoded & 0xFF];
|
||||
char unicodeChar = translator.ebcdicToUnicode(ebc);
|
||||
out.write(String.valueOf(unicodeChar).getBytes(
|
||||
java.nio.charset.StandardCharsets.UTF_8));
|
||||
out.write(String.valueOf(unicodeChar).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
break;
|
||||
} // end while(true) chunk
|
||||
}
|
||||
}
|
||||
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private int xlateGetc(FTConfig config) throws IOException {
|
||||
// Return buffered data first
|
||||
if (xlateBuffered > 0) {
|
||||
int r = xlateBuf[xlateBufIx++];
|
||||
xlateBuffered--;
|
||||
@@ -509,7 +480,7 @@ public class FTCut {
|
||||
|
||||
int localByte = c & 0xFF;
|
||||
int nc = 0;
|
||||
int[] cbuf = new int[4]; // max 4 bytes (2 for \r + 2 for \n if quadrant encoded)
|
||||
int[] cbuf = new int[4];
|
||||
|
||||
if (config.isAscii()) {
|
||||
if (config.isCrFlag() && !lastCr && localByte == '\n') {
|
||||
@@ -532,7 +503,6 @@ public class FTCut {
|
||||
private int uploadConvert(int localByte, int[] cbuf, int offset, FTConfig config) {
|
||||
int ebc;
|
||||
if (localByte == 0) {
|
||||
// Nulls are special in the 'OTHER_2' quadrant
|
||||
if (quadrant != 2) {
|
||||
quadrant = 2; // OTHER_2
|
||||
cbuf[offset] = SELECTORS[quadrant];
|
||||
@@ -545,25 +515,25 @@ public class FTCut {
|
||||
}
|
||||
|
||||
if (!config.isAscii() || !config.isRemapFlag()) {
|
||||
// Binary or Ascii without remap: treat byte directly
|
||||
ebc = localByte & 0xFF;
|
||||
} else {
|
||||
// Ascii with remap: locally read ASCII translates to actual EBCDIC, then maps to IND$FILE's pseudo-ASCII
|
||||
int standardEbc = translator.unicodeToEbcdic((char) localByte);
|
||||
if (standardEbc < 0) {
|
||||
standardEbc = 0x40; // Space as fallback
|
||||
standardEbc = 0x40;
|
||||
}
|
||||
ebc = org.lib3270j.ft.FTConstants.FT2ASC[standardEbc & 0xFF];
|
||||
ebc = FTConstants.FT2ASC[standardEbc & 0xFF];
|
||||
}
|
||||
|
||||
return storeUpload(ebc, cbuf, offset);
|
||||
}
|
||||
|
||||
private int storeUpload(int ebc, int[] obBuf, int offset) {
|
||||
private int storeUpload(int pseudoAsciiByte, int[] obBuf, int offset) {
|
||||
if (quadrant >= 0) {
|
||||
for (int i = 0; i < 77; i++) {
|
||||
if (QUADS[quadrant][i] == ebc) {
|
||||
obBuf[offset] = translator.unicodeToEbcdic(ALPHAS.charAt(i));
|
||||
if (QUADS[quadrant][i] == pseudoAsciiByte) {
|
||||
char ch = ALPHAS.charAt(i);
|
||||
int ebc = translator.unicodeToEbcdic(ch);
|
||||
obBuf[offset] = ebc >= 0 ? ebc : 0x40;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -572,16 +542,18 @@ public class FTCut {
|
||||
for (quadrant = 0; quadrant < 4; quadrant++) {
|
||||
if (quadrant == oq) continue;
|
||||
for (int i = 0; i < 77; i++) {
|
||||
if (QUADS[quadrant][i] == ebc) {
|
||||
if (QUADS[quadrant][i] == pseudoAsciiByte) {
|
||||
char ch = ALPHAS.charAt(i);
|
||||
int ebc = translator.unicodeToEbcdic(ch);
|
||||
obBuf[offset] = SELECTORS[quadrant];
|
||||
obBuf[offset + 1] = translator.unicodeToEbcdic(ALPHAS.charAt(i));
|
||||
obBuf[offset + 1] = ebc >= 0 ? ebc : 0x40;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
quadrant = -1;
|
||||
// Fallback safety measure
|
||||
obBuf[offset] = translator.unicodeToEbcdic('?');
|
||||
int questionEbc = translator.unicodeToEbcdic('?');
|
||||
obBuf[offset] = questionEbc >= 0 ? questionEbc : 0x6F;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.lib3270j.charset.EbcdicTranslator;
|
||||
import static org.lib3270j.ft.FTConstants.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,9 @@ public class FTDft {
|
||||
|
||||
private static final Logger log = Logger.getLogger(FTDft.class.getName());
|
||||
|
||||
private static final String OPEN_MSG = "FT:MSG";
|
||||
private static final String END_TRANSFER = "TRANS03";
|
||||
|
||||
/** Callback for transfer events (shared interface with CUT) */
|
||||
public interface FTDftListener {
|
||||
void onTransferRunning();
|
||||
@@ -34,7 +38,7 @@ public class FTDft {
|
||||
private final FTDftListener listener;
|
||||
|
||||
// DFT state
|
||||
private long recnum = 0;
|
||||
private long recnum = 1;
|
||||
private boolean dftEof = false;
|
||||
private boolean messageFlag = false;
|
||||
private long bytesTransferred = 0;
|
||||
@@ -56,17 +60,20 @@ public class FTDft {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize DFT mode for a new transfer.
|
||||
* Initialize DFT mode with active streams.
|
||||
*/
|
||||
public void initTransfer(InputStream in, OutputStream out) {
|
||||
this.inputStream = in;
|
||||
this.outputStream = out;
|
||||
resetState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize DFT mode for a new transfer from file.
|
||||
*/
|
||||
public void initTransfer(File localFile) throws IOException {
|
||||
FTConfig config = listener.getConfig();
|
||||
recnum = 0;
|
||||
dftEof = false;
|
||||
messageFlag = false;
|
||||
bytesTransferred = 0;
|
||||
dftSaveBuf = null;
|
||||
dftSaveBufLen = 0;
|
||||
lastCr = false;
|
||||
resetState();
|
||||
|
||||
if (config.isReceive()) {
|
||||
outputStream = new FileOutputStream(localFile, config.isAppend());
|
||||
@@ -77,6 +84,16 @@ public class FTDft {
|
||||
}
|
||||
}
|
||||
|
||||
private void resetState() {
|
||||
recnum = 1;
|
||||
dftEof = false;
|
||||
messageFlag = false;
|
||||
bytesTransferred = 0;
|
||||
dftSaveBuf = null;
|
||||
dftSaveBufLen = 0;
|
||||
lastCr = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up DFT mode resources.
|
||||
*/
|
||||
@@ -115,7 +132,7 @@ public class FTDft {
|
||||
|
||||
switch (requestCode) {
|
||||
case TR_OPEN_REQ:
|
||||
dftOpenRequest();
|
||||
dftOpenRequest(data, offset, length);
|
||||
break;
|
||||
case TR_INSERT_REQ:
|
||||
dftInsertRequest(data, payloadStart, offset + length - payloadStart);
|
||||
@@ -142,11 +159,53 @@ public class FTDft {
|
||||
|
||||
// ========== Open Request ==========
|
||||
|
||||
private void dftOpenRequest() {
|
||||
log.fine("DFT: Open");
|
||||
listener.onTransferRunning();
|
||||
// Send acknowledgement
|
||||
dftDataAck();
|
||||
private void dftOpenRequest(byte[] data, int sfOffset, int sfLength) {
|
||||
log.fine("DFT: Open request");
|
||||
|
||||
// Parse open request payload matching x3270
|
||||
// sfLength is the 2-byte length value at sfOffset
|
||||
int sfLenVal = ((data[sfOffset] & 0xFF) << 8) | (data[sfOffset + 1] & 0xFF);
|
||||
String nameBuf = "";
|
||||
|
||||
if (sfLenVal == 0x23 && sfOffset + 3 + 25 <= data.length) {
|
||||
nameBuf = extractName(data, sfOffset + 3 + 25, 7);
|
||||
} else if (sfLenVal == 0x29 && sfOffset + 3 + 31 <= data.length) {
|
||||
nameBuf = extractName(data, sfOffset + 3 + 31, 7);
|
||||
}
|
||||
|
||||
if (OPEN_MSG.equalsIgnoreCase(nameBuf)) {
|
||||
messageFlag = true;
|
||||
log.info("DFT: Open request for message stream");
|
||||
} else {
|
||||
messageFlag = false;
|
||||
listener.onTransferRunning();
|
||||
}
|
||||
|
||||
dftEof = false;
|
||||
recnum = 1;
|
||||
|
||||
// Acknowledge Open matching x3270 (SF_TRANSFER_DATA + 0x0009)
|
||||
dftOpenAck();
|
||||
}
|
||||
|
||||
private String extractName(byte[] data, int start, int maxLen) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < maxLen && (start + i) < data.length; i++) {
|
||||
int b = data[start + i] & 0xFF;
|
||||
if (b == 0) break;
|
||||
char ch = translator.ebcdicToUnicode(b);
|
||||
sb.append(ch);
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
private void dftOpenAck() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(6);
|
||||
out.write(AID_SF);
|
||||
out.write(0); out.write(5); // SF length
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
out.write(0); out.write(9); // OpenAck response code 0x0009
|
||||
input.sendStructuredFieldData(out.toByteArray());
|
||||
}
|
||||
|
||||
// ========== Insert Request (host sending data for download) ==========
|
||||
@@ -159,12 +218,11 @@ public class FTDft {
|
||||
private void dftDataInsert(byte[] data, int offset, int length) {
|
||||
FTConfig config = listener.getConfig();
|
||||
|
||||
if (listener.getCurrentState() == FTState.ABORT_WAIT) {
|
||||
if (!messageFlag && listener.getCurrentState() == FTState.ABORT_WAIT) {
|
||||
dftAbort("Transfer cancelled by user", TR_DATA_INSERT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the SF payload to find data
|
||||
// Skip the 2-byte request code
|
||||
int pos = offset + 2;
|
||||
int end = offset + length;
|
||||
@@ -174,13 +232,19 @@ public class FTDft {
|
||||
int headerCode = data[pos] & 0xFF;
|
||||
|
||||
if (headerCode == TR_BEGIN_DATA) {
|
||||
// Next 2 bytes are data length (including the 3-byte header)
|
||||
if (pos + 3 > end) break;
|
||||
int dataLen = ((data[pos + 1] & 0xFF) << 8) | (data[pos + 2] & 0xFF);
|
||||
int actualDataLen = dataLen - 3; // subtract header
|
||||
int actualDataLen = dataLen - 3;
|
||||
pos += 3;
|
||||
|
||||
if (actualDataLen > 0 && pos + actualDataLen <= end) {
|
||||
if (messageFlag) {
|
||||
// Handle message payload from host
|
||||
dftDataAck();
|
||||
handleHostMessage(data, pos, actualDataLen);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
writeDownloadData(data, pos, actualDataLen, config);
|
||||
bytesTransferred += actualDataLen;
|
||||
@@ -192,74 +256,84 @@ public class FTDft {
|
||||
}
|
||||
pos += Math.max(0, actualDataLen);
|
||||
} else if (pos + 1 < end) {
|
||||
// Skip other headers (2-byte code + length-based skip)
|
||||
// Most headers are 4-6 bytes, but we just skip based on known patterns
|
||||
int hdrCode16 = ((data[pos] & 0xFF) << 8) | (data[pos + 1] & 0xFF);
|
||||
if (hdrCode16 == TR_RECNUM_HDR) {
|
||||
pos += 6; // 2-byte code + 4-byte record number
|
||||
pos += 6;
|
||||
} else if (hdrCode16 == TR_NOT_COMPRESSED) {
|
||||
pos += 2;
|
||||
} else {
|
||||
pos += 2; // skip unknown 2-byte header
|
||||
pos += 2;
|
||||
}
|
||||
} else {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
// Send acknowledgement
|
||||
// Send acknowledgement for file data
|
||||
dftDataAck();
|
||||
}
|
||||
|
||||
private void handleHostMessage(byte[] data, int offset, int length) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int b = data[offset + i] & 0xFF;
|
||||
if (b == 0 || b == '$') break;
|
||||
char ch = translator.ebcdicToUnicode(b);
|
||||
sb.append(ch);
|
||||
}
|
||||
String msg = sb.toString().trim();
|
||||
log.info("DFT message: " + msg);
|
||||
|
||||
if (msg.startsWith(END_TRANSFER)) {
|
||||
listener.onTransferComplete(null);
|
||||
} else if (listener.getCurrentState() == FTState.ABORT_SENT) {
|
||||
listener.onTransferAborted(msg.isEmpty() ? "Transfer aborted" : msg);
|
||||
} else {
|
||||
listener.onTransferComplete(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write download data to the local file, handling ASCII conversion.
|
||||
* Matching x3270 upload_convert logic.
|
||||
*/
|
||||
private void writeDownloadData(byte[] data, int offset, int length,
|
||||
FTConfig config) throws IOException {
|
||||
if (outputStream == null) return;
|
||||
|
||||
if (!config.isAscii()) {
|
||||
// Binary: write raw
|
||||
outputStream.write(data, offset, length);
|
||||
return;
|
||||
}
|
||||
|
||||
// ASCII mode with optional CR stripping and remapping
|
||||
for (int i = 0; i < length; i++) {
|
||||
int b = data[offset + i] & 0xFF;
|
||||
|
||||
if (config.isRemapFlag()) {
|
||||
// Use IND$FILE's EBCDIC→ASCII table
|
||||
int ascii = FT2ASC[b];
|
||||
if (config.isCrFlag() && (b == '\r' || b == 0x1A)) {
|
||||
continue; // Strip CR and EOF ^Z
|
||||
}
|
||||
|
||||
if (config.isCrFlag()) {
|
||||
if (ascii == '\r') {
|
||||
lastCr = true;
|
||||
continue;
|
||||
}
|
||||
if (lastCr) {
|
||||
lastCr = false;
|
||||
if (ascii == '\n') {
|
||||
outputStream.write('\n');
|
||||
continue;
|
||||
}
|
||||
outputStream.write('\r');
|
||||
}
|
||||
}
|
||||
outputStream.write(ascii);
|
||||
if (!config.isRemapFlag()) {
|
||||
outputStream.write(b);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* ASCII mode with remap:
|
||||
* Host IND$FILE sends pseudo-ASCII byte b.
|
||||
* Map pseudo-ASCII b to EBCDIC byte via ASC2FT[b],
|
||||
* then convert EBCDIC byte to Unicode UTF-8 character.
|
||||
*/
|
||||
if (b < 0x20 || (b >= 0x80 && b < 0xA0 && b != 0x9F)) {
|
||||
// Control code — write as Unicode directly
|
||||
outputStream.write(String.valueOf((char) b).getBytes(StandardCharsets.UTF_8));
|
||||
} else if (b == 0xFF) {
|
||||
// Special 0xFF -> U+009F
|
||||
outputStream.write(String.valueOf((char) 0x9F).getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
// Standard EBCDIC→Unicode
|
||||
char ch = translator.ebcdicToUnicode(b);
|
||||
if (config.isCrFlag()) {
|
||||
if (ch == '\r') { lastCr = true; continue; }
|
||||
if (lastCr) {
|
||||
lastCr = false;
|
||||
if (ch == '\n') { outputStream.write('\n'); continue; }
|
||||
outputStream.write('\r');
|
||||
}
|
||||
}
|
||||
outputStream.write(String.valueOf(ch).getBytes(
|
||||
java.nio.charset.StandardCharsets.UTF_8));
|
||||
int ebc = ASC2FT[b & 0xFF];
|
||||
char ch = translator.ebcdicToUnicode(ebc);
|
||||
outputStream.write(String.valueOf(ch).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +350,7 @@ public class FTDft {
|
||||
}
|
||||
|
||||
int bufferSize = config.getDftBufferSize();
|
||||
int numbytes = bufferSize - 27; // reserve space for headers
|
||||
int numbytes = bufferSize - 27;
|
||||
byte[] readBuf = new byte[numbytes];
|
||||
int totalRead = 0;
|
||||
|
||||
@@ -290,7 +364,6 @@ public class FTDft {
|
||||
}
|
||||
readBuf[totalRead++] = (byte) b;
|
||||
} else {
|
||||
// Binary read
|
||||
if (inputStream == null) { dftEof = true; break; }
|
||||
int n = inputStream.read(readBuf, totalRead, numbytes - totalRead);
|
||||
if (n <= 0) {
|
||||
@@ -305,25 +378,20 @@ public class FTDft {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build SF response
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(bufferSize);
|
||||
out.write(AID_SF);
|
||||
|
||||
out.write(AID_SF); // AID byte
|
||||
|
||||
// Placeholder for SF length (2 bytes, filled in later)
|
||||
int sfLenPos = out.size();
|
||||
out.write(0); out.write(0);
|
||||
|
||||
out.write(SF_TRANSFER_DATA); // SF type
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
|
||||
if (totalRead > 0) {
|
||||
log.fine("DFT: > GetReply rec=" + recnum + " " + totalRead + " bytes");
|
||||
|
||||
// TR_GET_REPLY
|
||||
out.write((TR_GET_REPLY >> 8) & 0xFF);
|
||||
out.write(TR_GET_REPLY & 0xFF);
|
||||
|
||||
// Record number header
|
||||
out.write((TR_RECNUM_HDR >> 8) & 0xFF);
|
||||
out.write(TR_RECNUM_HDR & 0xFF);
|
||||
out.write((int) ((recnum >> 24) & 0xFF));
|
||||
@@ -332,56 +400,47 @@ public class FTDft {
|
||||
out.write((int) (recnum & 0xFF));
|
||||
recnum++;
|
||||
|
||||
// Not compressed
|
||||
out.write((TR_NOT_COMPRESSED >> 8) & 0xFF);
|
||||
out.write(TR_NOT_COMPRESSED & 0xFF);
|
||||
|
||||
// Begin data
|
||||
out.write(TR_BEGIN_DATA);
|
||||
int dataFieldLen = totalRead + 5;
|
||||
out.write((dataFieldLen >> 8) & 0xFF);
|
||||
out.write(dataFieldLen & 0xFF);
|
||||
|
||||
// The actual data
|
||||
out.write(readBuf, 0, totalRead);
|
||||
|
||||
bytesTransferred += totalRead;
|
||||
} else {
|
||||
log.fine("DFT: > GetReply EOF");
|
||||
|
||||
// EOF reply
|
||||
out.write((TR_GET_REQ >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_REPLY & 0xFF);
|
||||
|
||||
// Error header
|
||||
out.write((TR_ERROR_HDR >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_HDR & 0xFF);
|
||||
|
||||
// EOF error code
|
||||
out.write((TR_ERR_EOF >> 8) & 0xFF);
|
||||
out.write(TR_ERR_EOF & 0xFF);
|
||||
|
||||
dftEof = true;
|
||||
}
|
||||
|
||||
// Set the SF length
|
||||
byte[] result = out.toByteArray();
|
||||
int sfLen = result.length - 1; // exclude AID byte
|
||||
int sfLen = result.length - 1;
|
||||
result[sfLenPos] = (byte) ((sfLen >> 8) & 0xFF);
|
||||
result[sfLenPos + 1] = (byte) (sfLen & 0xFF);
|
||||
|
||||
// Save for potential Read Modified retransmit
|
||||
dftSaveBuf = result.clone();
|
||||
dftSaveBufLen = result.length;
|
||||
|
||||
// Send it
|
||||
input.sendStructuredFieldData(result);
|
||||
listener.onBytesTransferred(bytesTransferred);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a byte from the local file in ASCII mode with CR expansion and remapping.
|
||||
* Returns -1 for EOF.
|
||||
* Read a byte from local file for upload, handling ASCII conversion and remapping.
|
||||
* Matching x3270 dft_ascii_read logic.
|
||||
*/
|
||||
private int dftAsciiRead(FTConfig config) throws IOException {
|
||||
if (inputStream == null) return -1;
|
||||
@@ -389,30 +448,35 @@ public class FTDft {
|
||||
int c = inputStream.read();
|
||||
if (c == -1) return -1;
|
||||
|
||||
if (config.isRemapFlag()) {
|
||||
// CR expansion: insert \r before \n
|
||||
if (config.isCrFlag() && !lastCr && c == '\n') {
|
||||
// We need to return \r now and buffer \n
|
||||
lastCr = false;
|
||||
// Can't easily buffer here, so return \r and push \n back
|
||||
// Actually, let's handle this inline
|
||||
// Return the CR equivalent, then handle NL next call
|
||||
return ASC2FT['\r' & 0xFF];
|
||||
// Note: the \n will be read on next call naturally
|
||||
}
|
||||
lastCr = (c == '\r');
|
||||
return ASC2FT[c & 0xFF];
|
||||
} else {
|
||||
// No remap — convert to standard EBCDIC
|
||||
if (config.isCrFlag() && !lastCr && c == '\n') {
|
||||
lastCr = false;
|
||||
int ebc = translator.unicodeToEbcdic('\r');
|
||||
return ebc >= 0 ? ebc : 0x0D;
|
||||
}
|
||||
lastCr = (c == '\r');
|
||||
if (config.isCrFlag() && !lastCr && c == '\n') {
|
||||
lastCr = false;
|
||||
// Expand \n to \r\n: return \r byte now
|
||||
int rEbc = translator.unicodeToEbcdic('\r');
|
||||
if (rEbc < 0) rEbc = 0x0D;
|
||||
return config.isRemapFlag() ? FT2ASC[rEbc & 0xFF] : rEbc;
|
||||
}
|
||||
lastCr = (c == '\r');
|
||||
|
||||
if (!config.isRemapFlag()) {
|
||||
int ebc = translator.unicodeToEbcdic((char) c);
|
||||
return ebc >= 0 ? ebc : 0x40;
|
||||
}
|
||||
|
||||
/*
|
||||
* ASCII mode with remap:
|
||||
* Translate Unicode char c -> EBCDIC -> host pseudo-ASCII FT2ASC[ebc].
|
||||
*/
|
||||
int ebc;
|
||||
if (c < 0x20 || (c >= 0x80 && c < 0x9F)) {
|
||||
ebc = ASC2FT[c & 0xFF];
|
||||
} else if (c == 0x9F) {
|
||||
ebc = 0xFF;
|
||||
} else {
|
||||
ebc = translator.unicodeToEbcdic((char) c);
|
||||
}
|
||||
if (ebc < 0) ebc = 0x40;
|
||||
|
||||
return FT2ASC[ebc & 0xFF];
|
||||
}
|
||||
|
||||
// ========== Close Request ==========
|
||||
@@ -420,16 +484,10 @@ public class FTDft {
|
||||
private void dftCloseRequest() {
|
||||
log.fine("DFT: Close");
|
||||
|
||||
// Send close acknowledgement
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(6);
|
||||
out.write(AID_SF);
|
||||
|
||||
// SF length
|
||||
out.write(0); out.write(5);
|
||||
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
|
||||
// TR_CLOSE_REPLY
|
||||
out.write((TR_CLOSE_REPLY >> 8) & 0xFF);
|
||||
out.write(TR_CLOSE_REPLY & 0xFF);
|
||||
|
||||
@@ -441,13 +499,8 @@ public class FTDft {
|
||||
private void dftDataAck() {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(6);
|
||||
out.write(AID_SF);
|
||||
|
||||
// SF length
|
||||
out.write(0); out.write(5);
|
||||
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
|
||||
// TR_NORMAL_REPLY
|
||||
out.write((TR_NORMAL_REPLY >> 8) & 0xFF);
|
||||
out.write(TR_NORMAL_REPLY & 0xFF);
|
||||
|
||||
@@ -461,21 +514,12 @@ public class FTDft {
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(10);
|
||||
out.write(AID_SF);
|
||||
|
||||
// SF length
|
||||
out.write(0); out.write(9);
|
||||
|
||||
out.write(SF_TRANSFER_DATA);
|
||||
|
||||
// Error reply code
|
||||
out.write((code >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_REPLY & 0xFF);
|
||||
|
||||
// Error header
|
||||
out.write((TR_ERROR_HDR >> 8) & 0xFF);
|
||||
out.write(TR_ERROR_HDR & 0xFF);
|
||||
|
||||
// Command failed
|
||||
out.write((TR_ERR_CMDFAIL >> 8) & 0xFF);
|
||||
out.write(TR_ERR_CMDFAIL & 0xFF);
|
||||
|
||||
|
||||
@@ -30,8 +30,46 @@ public class InputProcessor {
|
||||
this.fsm = fsm;
|
||||
}
|
||||
|
||||
public enum OiaStatus {
|
||||
NOT_CONNECTED("OFFLINE"),
|
||||
X_SYSTEM("X SYSTEM"),
|
||||
X_PROT("X PROT"),
|
||||
READY("READY");
|
||||
|
||||
private final String label;
|
||||
OiaStatus(String label) { this.label = label; }
|
||||
public String getLabel() { return label; }
|
||||
}
|
||||
|
||||
public OiaStatus getOiaStatus() {
|
||||
if (fsm == null || fsm.getConnectionState() == null || !fsm.getConnectionState().isConnected()) {
|
||||
return OiaStatus.NOT_CONNECTED;
|
||||
}
|
||||
if (keyboardLocked) {
|
||||
return OiaStatus.X_SYSTEM;
|
||||
}
|
||||
return OiaStatus.READY;
|
||||
}
|
||||
|
||||
public interface LockStateListener {
|
||||
void onLockStateChanged(boolean locked);
|
||||
}
|
||||
|
||||
private LockStateListener lockStateListener;
|
||||
|
||||
public void setLockStateListener(LockStateListener listener) {
|
||||
this.lockStateListener = listener;
|
||||
}
|
||||
|
||||
public boolean isKeyboardLocked() { return keyboardLocked; }
|
||||
public void setKeyboardLocked(boolean locked) { this.keyboardLocked = locked; }
|
||||
public void setKeyboardLocked(boolean locked) {
|
||||
if (this.keyboardLocked != locked) {
|
||||
this.keyboardLocked = locked;
|
||||
if (lockStateListener != null) {
|
||||
lockStateListener.onLockStateChanged(locked);
|
||||
}
|
||||
}
|
||||
}
|
||||
public boolean isInsertMode() { return insertMode; }
|
||||
public void setInsertMode(boolean insert) { this.insertMode = insert; }
|
||||
|
||||
@@ -115,7 +153,7 @@ public class InputProcessor {
|
||||
if (keyboardLocked && aidCode != AID_CLEAR) return;
|
||||
|
||||
lastAid = aidCode;
|
||||
keyboardLocked = true;
|
||||
setKeyboardLocked(true);
|
||||
|
||||
if (aidCode == AID_CLEAR) {
|
||||
screen.clear();
|
||||
@@ -149,8 +187,6 @@ public class InputProcessor {
|
||||
for (int i = 0; i < size; i++) {
|
||||
ExtendedAttribute ea = screen.getCell(i);
|
||||
if (ea.isFieldAttribute() && faIsModified(ea.fa & 0xFF)) {
|
||||
if (faIsProtected(ea.fa & 0xFF)) continue;
|
||||
|
||||
int fieldStart = (i + 1) % size;
|
||||
|
||||
// First, collect field data and find last non-null byte
|
||||
@@ -169,14 +205,14 @@ public class InputProcessor {
|
||||
if (pos == fieldStart) break;
|
||||
}
|
||||
|
||||
// Only send if there's actual data (strip trailing nulls)
|
||||
if (lastNonNull >= 0) {
|
||||
out.write(ORDER_SBA);
|
||||
byte[] addr = encodeAddress(fieldStart, screen.getRows(), screen.getCols());
|
||||
out.write(addr[0] & 0xFF);
|
||||
out.write(addr[1] & 0xFF);
|
||||
// Always send SBA and address
|
||||
out.write(ORDER_SBA);
|
||||
byte[] addr = encodeAddress(fieldStart, screen.getRows(), screen.getCols());
|
||||
out.write(addr[0] & 0xFF);
|
||||
out.write(addr[1] & 0xFF);
|
||||
|
||||
// Write only up to the last non-null byte
|
||||
// Only send data if there is any (strip trailing nulls)
|
||||
if (lastNonNull >= 0) {
|
||||
byte[] allData = fieldData.toByteArray();
|
||||
out.write(allData, 0, lastNonNull + 1);
|
||||
}
|
||||
@@ -260,6 +296,17 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
public void eraseEof() {
|
||||
if (!screen.isFormatted()) {
|
||||
int addr = screen.getCursorAddress();
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
for (int i = addr; i < size; i++) {
|
||||
ExtendedAttribute ea = screen.getCell(i);
|
||||
ea.ec = 0;
|
||||
ea.ucs4 = 0;
|
||||
}
|
||||
screen.markAllChanged();
|
||||
return;
|
||||
}
|
||||
int addr = screen.getCursorAddress();
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
byte faVal = screen.getFieldAttributeAt(addr);
|
||||
@@ -282,6 +329,7 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
public void deleteChar() {
|
||||
if (!screen.isFormatted()) return;
|
||||
int addr = screen.getCursorAddress();
|
||||
byte faVal = screen.getFieldAttributeAt(addr);
|
||||
if (faIsProtected(faVal & 0xFF)) return;
|
||||
@@ -316,7 +364,7 @@ public class InputProcessor {
|
||||
|
||||
/** Reset (unlock keyboard, cancel insert mode). */
|
||||
public void reset() {
|
||||
keyboardLocked = false;
|
||||
setKeyboardLocked(false);
|
||||
insertMode = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user