* Physical display width. We start at 80x24, but the user can resize us
* bigger/smaller.
*/
- private int width;
+ private int width = 80;
/**
* Physical display height. We start at 80x24, but the user can resize
* us bigger/smaller.
*/
- private int height;
+ private int height = 24;
/**
* Top margin of the scrolling region.
*/
- private int scrollRegionTop;
+ private int scrollRegionTop = 0;
/**
* Bottom margin of the scrolling region.
*/
- private int scrollRegionBottom;
+ private int scrollRegionBottom = height - 1;
/**
* Right margin column number. This can be selected by the remote side
* to be 80/132 (rightMargin values 79/131), or it can be (width - 1).
*/
- private int rightMargin;
+ private int rightMargin = 79;
/**
* Last character printed.
* 132), but the line does NOT wrap until another character is written to
* column 1 of the next line, after which the cursor moves to column 2.
*/
- private boolean wrapLineFlag;
+ private boolean wrapLineFlag = false;
/**
* VT220 single shift flag.
*/
private ArrayList<TInputEvent> userQueue = new ArrayList<TInputEvent>();
+ /**
+ * Number of bytes/characters passed to consume().
+ */
+ private long readCount = 0;
+
/**
* DECSC/DECRC save/restore a subset of the total state. This class
* encapsulates those specific flags/modes.
for (int i = 0; i < height; i++) {
display.add(new DisplayLine(currentState.attr));
}
+ assert (currentState.cursorY < height);
+ assert (currentState.cursorX < width);
// Spin up the input reader
readerThread = new Thread(this);
// ECMA48 -----------------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Wait for a period of time to get output from the launched process.
+ *
+ * @param millis millis to wait for, or 0 to wait forever
+ * @return true if the launched process has emitted something
+ */
+ public boolean waitForOutput(final int millis) {
+ if (millis < 0) {
+ throw new IllegalArgumentException("timeout must be >= 0");
+ }
+ int waitedMillis = millis;
+ final int pollTimeout = 5;
+ while (true) {
+ if (readCount != 0) {
+ return true;
+ }
+ if ((millis > 0) && (waitedMillis < 0)){
+ return false;
+ }
+ try {
+ Thread.sleep(pollTimeout);
+ } catch (InterruptedException e) {
+ // SQUASH
+ }
+ waitedMillis -= pollTimeout;
+ }
+ }
+
/**
* Process keyboard and mouse events from the user.
*
// the input streams.
if (stopReaderThread == false) {
stopReaderThread = true;
- try {
- readerThread.join(1000);
- } catch (InterruptedException e) {
- // SQUASH
- }
}
// Now close the output stream.
int delta = height - this.height;
this.height = height;
scrollRegionBottom += delta;
- if (scrollRegionBottom < 0) {
- scrollRegionBottom = height;
+ if ((scrollRegionBottom < 0) || (scrollRegionTop > height - 1)) {
+ scrollRegionBottom = height - 1;
}
if (scrollRegionTop >= scrollRegionBottom) {
scrollRegionTop = 0;
currentState = new SaveableState();
savedState = new SaveableState();
scanState = ScanState.GROUND;
- width = 80;
- height = 24;
+ if (displayListener != null) {
+ width = displayListener.getDisplayWidth();
+ height = displayListener.getDisplayHeight();
+ } else {
+ width = 80;
+ height = 24;
+ }
scrollRegionTop = 0;
scrollRegionBottom = height - 1;
rightMargin = width - 1;
arrowKeyMode = ArrowKeyMode.ANSI;
keypadMode = KeypadMode.Numeric;
wrapLineFlag = false;
- if (displayListener != null) {
- width = displayListener.getDisplayWidth();
- height = displayListener.getDisplayHeight();
- rightMargin = width - 1;
- }
// Flags
shiftOut = false;
* Handle a linefeed.
*/
private void linefeed() {
-
if (currentState.cursorY < scrollRegionBottom) {
// Increment screen y
currentState.cursorY++;
if (mouseEncoding == MouseEncoding.SGR) {
sb.append((char) 0x1B);
sb.append("[<");
+ int buttons = 0;
if (mouse.isMouse1()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append("32;");
+ buttons = 32;
} else {
- sb.append("0;");
+ buttons = 0;
}
} else if (mouse.isMouse2()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append("33;");
+ buttons = 33;
} else {
- sb.append("1;");
+ buttons = 1;
}
} else if (mouse.isMouse3()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append("34;");
+ buttons = 34;
} else {
- sb.append("2;");
+ buttons = 2;
}
} else if (mouse.isMouseWheelUp()) {
- sb.append("64;");
+ buttons = 64;
} else if (mouse.isMouseWheelDown()) {
- sb.append("65;");
+ buttons = 65;
} else {
// This is motion with no buttons down.
- sb.append("35;");
+ buttons = 35;
+ }
+ if (mouse.isAlt()) {
+ buttons |= 0x08;
+ }
+ if (mouse.isCtrl()) {
+ buttons |= 0x10;
+ }
+ if (mouse.isShift()) {
+ buttons |= 0x04;
}
- sb.append(String.format("%d;%d", mouse.getX() + 1,
+ sb.append(String.format("%d;%d;%d", buttons, mouse.getX() + 1,
mouse.getY() + 1));
if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
sb.append((char) 0x1B);
sb.append('[');
sb.append('M');
+ int buttons = 0;
if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
- sb.append((char) (0x03 + 32));
+ buttons = 0x03 + 32;
} else if (mouse.isMouse1()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append((char) (0x00 + 32 + 32));
+ buttons = 0x00 + 32 + 32;
} else {
- sb.append((char) (0x00 + 32));
+ buttons = 0x00 + 32;
}
} else if (mouse.isMouse2()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append((char) (0x01 + 32 + 32));
+ buttons = 0x01 + 32 + 32;
} else {
- sb.append((char) (0x01 + 32));
+ buttons = 0x01 + 32;
}
} else if (mouse.isMouse3()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append((char) (0x02 + 32 + 32));
+ buttons = 0x02 + 32 + 32;
} else {
- sb.append((char) (0x02 + 32));
+ buttons = 0x02 + 32;
}
} else if (mouse.isMouseWheelUp()) {
- sb.append((char) (0x04 + 64));
+ buttons = 0x04 + 64;
} else if (mouse.isMouseWheelDown()) {
- sb.append((char) (0x05 + 64));
+ buttons = 0x05 + 64;
} else {
// This is motion with no buttons down.
- sb.append((char) (0x03 + 32));
+ buttons = 0x03 + 32;
+ }
+ if (mouse.isAlt()) {
+ buttons |= 0x08;
+ }
+ if (mouse.isCtrl()) {
+ buttons |= 0x10;
+ }
+ if (mouse.isShift()) {
+ buttons |= 0x04;
}
+ sb.append((char) (buttons & 0xFF));
sb.append((char) (mouse.getX() + 33));
sb.append((char) (mouse.getY() + 33));
}
if (decPrivateModeFlag == true) {
if (value == true) {
// Enable sixel scrolling (default).
- // TODO
+ // Not supported
} else {
// Disable sixel scrolling.
- // TODO
+ // Not supported
}
}
}
case 8:
// Invisible
- // TODO
+ // Not supported
break;
case 90:
// DECSTBM
int top = getCsiParam(0, 1, 1, height) - 1;
int bottom = getCsiParam(1, height, 1, height) - 1;
+ if (bottom > height - 1) {
+ bottom = height - 1;
+ }
if (top > bottom) {
top = bottom;
private void oscPut(final char xtermChar) {
// System.err.println("oscPut: " + xtermChar);
+ boolean oscEnd = false;
+
+ if (xtermChar == 0x07) {
+ oscEnd = true;
+ }
+ if ((xtermChar == '\\')
+ && (collectBuffer.charAt(collectBuffer.length() - 1) == '\033')
+ ) {
+ oscEnd = true;
+ }
+
// Collect first
collectBuffer.append(xtermChar);
// Xterm cases...
- if ((xtermChar == 0x07)
- || (collectBuffer.toString().endsWith("\033\\"))
- ) {
+ if (oscEnd) {
String args = null;
if (xtermChar == 0x07) {
args = collectBuffer.substring(0, collectBuffer.length() - 1);
private void pmPut(final char pmChar) {
// System.err.println("pmPut: " + pmChar);
+ boolean pmEnd = false;
+
+ if ((pmChar == '\\')
+ && (collectBuffer.charAt(collectBuffer.length() - 1) == '\033')
+ ) {
+ pmEnd = true;
+ }
+
// Collect first
collectBuffer.append(pmChar);
// Xterm cases...
- if (collectBuffer.toString().endsWith("\033\\")) {
+ if (pmEnd) {
String arg = null;
arg = collectBuffer.substring(0, collectBuffer.length() - 2);
* @param ch character from the remote side
*/
private void consume(final int ch) {
+ readCount++;
// DEBUG
// System.err.printf("%c STATE = %s\n", ch, scanState);
return;
}
- java.util.Base64.Decoder base64 = java.util.Base64.getDecoder();
- byte [] bytes = base64.decode(data);
+ byte [] bytes = StringUtils.fromBase64(data.getBytes());
if (bytes.length != (imageWidth * imageHeight * 3)) {
return;
}
boolean scroll = false;
BufferedImage image = null;
try {
- java.util.Base64.Decoder base64 = java.util.Base64.getDecoder();
- byte [] bytes = base64.decode(data);
+ byte [] bytes = StringUtils.fromBase64(data.getBytes());
switch (type) {
case 1:
}
Cell cell = new Cell();
- cell.setImage(image.getSubimage(x * textWidth,
- y * textHeight, width, height));
+ if ((width != textWidth) || (height != textHeight)) {
+ BufferedImage newImage;
+ newImage = new BufferedImage(textWidth, textHeight,
+ BufferedImage.TYPE_INT_ARGB);
+
+ java.awt.Graphics gr = newImage.getGraphics();
+ gr.drawImage(image.getSubimage(x * textWidth,
+ y * textHeight, width, height),
+ 0, 0, null, null);
+ gr.dispose();
+ cell.setImage(newImage);
+ } else {
+ cell.setImage(image.getSubimage(x * textWidth,
+ y * textHeight, width, height));
+ }
cells[x][y] = cell;
}
for (int x = 0; x < cellColumns; x++) {
assert (currentState.cursorX <= rightMargin);
- // TODO: Render text of current cell first, then image over
- // it (accounting for blank pixels). For now, just copy the
- // cell.
+ // A real sixel terminal would render the text of the current
+ // cell first, then image over it (accounting for blank
+ // pixels). We do not support that. A cell is either text,
+ // or image, but not a mix of image-over-text.
DisplayLine line = display.get(currentState.cursorY);
line.replace(currentState.cursorX, cells[x][y]);
// At the bottom, no more scrolling, done.
break;
}
+
cursorPosition(currentState.cursorY, x0);
}