import java.util.List;
import jexer.TKeypress;
-import jexer.event.TMouseEvent;
+import jexer.backend.GlyphMaker;
import jexer.bits.Color;
import jexer.bits.Cell;
import jexer.bits.CellAttributes;
+import jexer.bits.StringUtils;
+import jexer.event.TInputEvent;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
import jexer.io.ReadTimeoutException;
import jexer.io.TimeoutInputStream;
import static jexer.TKeypress.*;
/**
* XTERM mouse reporting protocols.
*/
- private enum MouseProtocol {
+ public enum MouseProtocol {
OFF,
X10,
NORMAL,
*/
private int textHeight = 20;
+ /**
+ * The last used height of a character cell in pixels, only used for
+ * full-width chars.
+ */
+ private int lastTextHeight = -1;
+
+ /**
+ * The glyph drawer for full-width chars.
+ */
+ private GlyphMaker glyphMaker = null;
+
+ /**
+ * Input queue for keystrokes and mouse events to send to the remote
+ * side.
+ */
+ private ArrayList<TInputEvent> userQueue = new ArrayList<TInputEvent>();
+
/**
* DECSC/DECRC save/restore a subset of the total state. This class
* encapsulates those specific flags/modes.
}
while (!done && !stopReaderThread) {
+ synchronized (userQueue) {
+ while (userQueue.size() > 0) {
+ handleUserEvent(userQueue.remove(0));
+ }
+ }
+
try {
int n = inputStream.available();
// ECMA48 -----------------------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Process keyboard and mouse events from the user.
+ *
+ * @param event the input event to consume
+ */
+ private void handleUserEvent(final TInputEvent event) {
+ if (event instanceof TKeypressEvent) {
+ keypress(((TKeypressEvent) event).getKey());
+ }
+ if (event instanceof TMouseEvent) {
+ mouse((TMouseEvent) event);
+ }
+ }
+
+ /**
+ * Add a keyboard and mouse event from the user to the queue.
+ *
+ * @param event the input event to consume
+ */
+ public void addUserEvent(final TInputEvent event) {
+ synchronized (userQueue) {
+ userQueue.add(event);
+ }
+ }
+
/**
* Return the proper primary Device Attributes string.
*
private void printCharacter(final char ch) {
int rightMargin = this.rightMargin;
+ if (StringUtils.width(ch) == 2) {
+ // This is a full-width character. Save two spaces, and then
+ // draw the character as two image halves.
+ int x0 = currentState.cursorX;
+ int y0 = currentState.cursorY;
+ printCharacter(' ');
+ printCharacter(' ');
+ if ((currentState.cursorX == x0 + 2)
+ && (currentState.cursorY == y0)
+ ) {
+ // We can draw both halves of the character.
+ drawHalves(x0, y0, x0 + 1, y0, ch);
+ } else if ((currentState.cursorX == x0 + 1)
+ && (currentState.cursorY == y0)
+ ) {
+ // VT100 line wrap behavior: we should be at the right
+ // margin. We can draw both halves of the character.
+ drawHalves(x0, y0, x0 + 1, y0, ch);
+ } else {
+ // The character splits across the line. Draw the entire
+ // character on the new line, giving one more space for it.
+ x0 = currentState.cursorX - 1;
+ y0 = currentState.cursorY;
+ printCharacter(' ');
+ drawHalves(x0, y0, x0 + 1, y0, ch);
+ }
+ return;
+ }
+
// Check if we have double-width, and if so chop at 40/66 instead of
// 80/132
if (display.get(currentState.cursorY).isDoubleWidth()) {
CellAttributes newCellAttributes = (CellAttributes) newCell;
newCellAttributes.setTo(currentState.attr);
DisplayLine line = display.get(currentState.cursorY);
- // Insert mode special case
- if (insertMode == true) {
- line.insert(currentState.cursorX, newCell);
- } else {
- // Replace an existing character
- line.replace(currentState.cursorX, newCell);
- }
- // Increment horizontal
- if (wrapLineFlag == false) {
- currentState.cursorX++;
- if (currentState.cursorX > rightMargin) {
- currentState.cursorX--;
+ if (StringUtils.width(ch) == 1) {
+ // Insert mode special case
+ if (insertMode == true) {
+ line.insert(currentState.cursorX, newCell);
+ } else {
+ // Replace an existing character
+ line.replace(currentState.cursorX, newCell);
+ }
+
+ // Increment horizontal
+ if (wrapLineFlag == false) {
+ currentState.cursorX++;
+ if (currentState.cursorX > rightMargin) {
+ currentState.cursorX--;
+ }
}
}
}
*
* @param mouse mouse event received from the local user
*/
- public void mouse(final TMouseEvent mouse) {
+ private void mouse(final TMouseEvent mouse) {
/*
System.err.printf("mouse(): protocol %s encoding %s mouse %s\n",
*
* @param keypress keypress received from the local user
*/
- public void keypress(final TKeypress keypress) {
+ private void keypress(final TKeypress keypress) {
writeRemote(keypressToString(keypress));
}
}
}
+ /**
+ * Perform xterm window operations.
+ */
+ private void xtermWindowOps() {
+ boolean xtermPrivateModeFlag = false;
+
+ for (int i = 0; i < collectBuffer.length(); i++) {
+ if (collectBuffer.charAt(i) == '?') {
+ xtermPrivateModeFlag = true;
+ break;
+ }
+ }
+
+ int i = getCsiParam(0, 0);
+
+ if (!xtermPrivateModeFlag) {
+ if (i == 14) {
+ // Report xterm window in pixels as CSI 4 ; height ; width t
+ writeRemote(String.format("\033[4;%d;%dt", textHeight * height,
+ textWidth * width));
+ }
+ }
+ }
+
/**
* Run this input character through the ECMA48 state machine.
*
}
break;
case 't':
+ if (type == DeviceType.XTERM) {
+ // Window operations
+ xtermWindowOps();
+ }
break;
case 'u':
// Restore cursor (ANSI.SYS)
decstbm();
break;
case 's':
+ break;
case 't':
+ if (type == DeviceType.XTERM) {
+ // Window operations
+ xtermWindowOps();
+ }
+ break;
case 'u':
case 'v':
case 'w':
return hideMousePointer;
}
+ /**
+ * Get the mouse protocol.
+ *
+ * @return MouseProtocol.OFF, MouseProtocol.X10, etc.
+ */
+ public MouseProtocol getMouseProtocol() {
+ return mouseProtocol;
+ }
+
// ------------------------------------------------------------------------
// Sixel support ----------------------------------------------------------
// ------------------------------------------------------------------------
* the text cells.
*/
private void parseSixel() {
- System.err.println("parseSixel(): '" + sixelParseBuffer.toString() +
- "'");
+
+ /*
+ System.err.println("parseSixel(): '" + sixelParseBuffer.toString()
+ + "'");
+ */
Sixel sixel = new Sixel(sixelParseBuffer.toString());
BufferedImage image = sixel.getImage();
- System.err.println("parseSixel(): image " + image);
+ // System.err.println("parseSixel(): image " + image);
if (image == null) {
// Sixel data was malformed in some way, bail out.
}
+ /**
+ * Draw the left and right cells of a two-cell-wide (full-width) glyph.
+ *
+ * @param leftX the x position to draw the left half to
+ * @param leftY the y position to draw the left half to
+ * @param rightX the x position to draw the right half to
+ * @param rightY the y position to draw the right half to
+ * @param ch the character to draw
+ */
+ private void drawHalves(final int leftX, final int leftY,
+ final int rightX, final int rightY, final char ch) {
+
+ // System.err.println("drawHalves(): " + Integer.toHexString(ch));
+
+ if (lastTextHeight != textHeight) {
+ glyphMaker = GlyphMaker.getInstance(textHeight);
+ lastTextHeight = textHeight;
+ }
+
+ Cell cell = new Cell(ch);
+ cell.setAttr(currentState.attr);
+ BufferedImage image = glyphMaker.getImage(cell, textWidth * 2,
+ textHeight);
+ BufferedImage leftImage = image.getSubimage(0, 0, textWidth,
+ textHeight);
+ BufferedImage rightImage = image.getSubimage(textWidth, 0, textWidth,
+ textHeight);
+
+ Cell left = new Cell();
+ left.setTo(cell);
+ left.setImage(leftImage);
+ left.setWidth(Cell.Width.LEFT);
+ display.get(leftY).replace(leftX, left);
+
+ Cell right = new Cell();
+ right.setTo(cell);
+ right.setImage(rightImage);
+ right.setWidth(Cell.Width.RIGHT);
+ display.get(rightY).replace(rightX, right);
+ }
+
}