+ } else if (lCell.isRGB()
+ && (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
+ && (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+ // Both colors changed, attributes the same
+ sb.append(colorRGB(lCell.getForeColorRGB(),
+ lCell.getBackColorRGB()));
+
+ if (debugToStderr) {
+ System.err.printf("1 Change only fore/back colors (RGB)\n");
+ }
+ } else if ((lCell.getForeColor() != lastAttr.getForeColor())
+ && (lCell.getBackColor() != lastAttr.getBackColor())
+ && (!lCell.isRGB())
+ && (lCell.isBold() != lastAttr.isBold())
+ && (lCell.isReverse() != lastAttr.isReverse())
+ && (lCell.isUnderline() != lastAttr.isUnderline())
+ && (lCell.isBlink() != lastAttr.isBlink())
+ ) {
+ // Everything is different
+ sb.append(color(lCell.getForeColor(),
+ lCell.getBackColor(),
+ lCell.isBold(), lCell.isReverse(),
+ lCell.isBlink(),
+ lCell.isUnderline()));
+
+ if (debugToStderr) {
+ System.err.printf("2 Set all attributes\n");
+ }
+ } else if ((lCell.getForeColor() != lastAttr.getForeColor())
+ && (lCell.getBackColor() == lastAttr.getBackColor())
+ && (!lCell.isRGB())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+
+ // Attributes same, foreColor different
+ sb.append(color(lCell.isBold(),
+ lCell.getForeColor(), true));
+
+ if (debugToStderr) {
+ System.err.printf("3 Change foreColor\n");
+ }
+ } else if (lCell.isRGB()
+ && (lCell.getForeColorRGB() != lastAttr.getForeColorRGB())
+ && (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
+ && (lCell.getForeColorRGB() >= 0)
+ && (lCell.getBackColorRGB() >= 0)
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+ // Attributes same, foreColor different
+ sb.append(colorRGB(lCell.getForeColorRGB(), true));
+
+ if (debugToStderr) {
+ System.err.printf("3 Change foreColor (RGB)\n");
+ }
+ } else if ((lCell.getForeColor() == lastAttr.getForeColor())
+ && (lCell.getBackColor() != lastAttr.getBackColor())
+ && (!lCell.isRGB())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+ // Attributes same, backColor different
+ sb.append(color(lCell.isBold(),
+ lCell.getBackColor(), false));
+
+ if (debugToStderr) {
+ System.err.printf("4 Change backColor\n");
+ }
+ } else if (lCell.isRGB()
+ && (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
+ && (lCell.getBackColorRGB() != lastAttr.getBackColorRGB())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+ // Attributes same, foreColor different
+ sb.append(colorRGB(lCell.getBackColorRGB(), false));
+
+ if (debugToStderr) {
+ System.err.printf("4 Change backColor (RGB)\n");
+ }
+ } else if ((lCell.getForeColor() == lastAttr.getForeColor())
+ && (lCell.getBackColor() == lastAttr.getBackColor())
+ && (lCell.getForeColorRGB() == lastAttr.getForeColorRGB())
+ && (lCell.getBackColorRGB() == lastAttr.getBackColorRGB())
+ && (lCell.isBold() == lastAttr.isBold())
+ && (lCell.isReverse() == lastAttr.isReverse())
+ && (lCell.isUnderline() == lastAttr.isUnderline())
+ && (lCell.isBlink() == lastAttr.isBlink())
+ ) {
+
+ // All attributes the same, just print the char
+ // NOP
+
+ if (debugToStderr) {
+ System.err.printf("5 Only emit character\n");
+ }
+ } else {
+ // Just reset everything again
+ if (!lCell.isRGB()) {
+ sb.append(color(lCell.getForeColor(),
+ lCell.getBackColor(),
+ lCell.isBold(),
+ lCell.isReverse(),
+ lCell.isBlink(),
+ lCell.isUnderline()));
+
+ if (debugToStderr) {
+ System.err.printf("6 Change all attributes\n");
+ }
+ } else {
+ sb.append(colorRGB(lCell.getForeColorRGB(),
+ lCell.getBackColorRGB(),
+ lCell.isBold(),
+ lCell.isReverse(),
+ lCell.isBlink(),
+ lCell.isUnderline()));
+ if (debugToStderr) {
+ System.err.printf("6 Change all attributes (RGB)\n");
+ }
+ }
+
+ }
+ // Emit the character
+ if (wideCharImages
+ // Don't emit the right-half of full-width chars.
+ || (!wideCharImages
+ && (lCell.getWidth() != Cell.Width.RIGHT))
+ ) {
+ sb.append(Character.toChars(lCell.getChar()));
+ }
+
+ // Save the last rendered cell
+ lastX = x;
+ lastAttr.setTo(lCell);
+
+ // Physical is always updated
+ physical[x][y].setTo(lCell);
+
+ } // if (!lCell.equals(pCell) || (reallyCleared == true))
+
+ } // for (int x = 0; x < width; x++)
+ }
+
+ /**
+ * Render the screen to a string that can be emitted to something that
+ * knows how to process ECMA-48/ANSI X3.64 escape sequences.
+ *
+ * @param sb StringBuilder to write escape sequences to
+ * @return escape sequences string that provides the updates to the
+ * physical screen
+ */
+ private String flushString(final StringBuilder sb) {
+ CellAttributes attr = null;
+
+ if (reallyCleared) {
+ attr = new CellAttributes();
+ sb.append(clearAll());
+ }
+
+ /*
+ * For images support, draw all of the image output first, and then
+ * draw everything else afterwards. This works OK, but performance
+ * is still a drag on larger pictures.
+ */
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ // If physical had non-image data that is now image data, the
+ // entire row must be redrawn.
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+ if (lCell.isImage() && !pCell.isImage()) {
+ unsetImageRow(y);
+ break;
+ }
+ }
+ }
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ Cell lCell = logical[x][y];
+ Cell pCell = physical[x][y];
+
+ if (!lCell.isImage()
+ || (!wideCharImages
+ && (lCell.getWidth() != Cell.Width.SINGLE))
+ ) {
+ continue;
+ }
+
+ int left = x;
+ int right = x;
+ while ((right < width)
+ && (logical[right][y].isImage())
+ && (!logical[right][y].equals(physical[right][y])
+ || reallyCleared)
+ ) {
+ right++;
+ }
+ ArrayList<Cell> cellsToDraw = new ArrayList<Cell>();
+ for (int i = 0; i < (right - x); i++) {
+ assert (logical[x + i][y].isImage());
+ cellsToDraw.add(logical[x + i][y]);
+
+ // Physical is always updated.
+ physical[x + i][y].setTo(lCell);
+ }
+ if (cellsToDraw.size() > 0) {
+ if (iterm2Images) {
+ sb.append(toIterm2Image(x, y, cellsToDraw));
+ } else if (jexerImages) {
+ sb.append(toJexerImage(x, y, cellsToDraw));
+ } else {
+ sb.append(toSixel(x, y, cellsToDraw));
+ }
+ }
+
+ x = right;
+ }
+ }
+
+ // Draw the text part now.
+ for (int y = 0; y < height; y++) {
+ flushLine(y, sb, attr);
+ }
+
+ reallyCleared = false;
+
+ String result = sb.toString();
+ if (debugToStderr) {
+ System.err.printf("flushString(): %s\n", result);
+ }
+ return result;
+ }
+
+ /**
+ * Reset keyboard/mouse input parser.
+ */
+ private void resetParser() {
+ state = ParseState.GROUND;
+ params = new ArrayList<String>();
+ params.clear();
+ params.add("");
+ decPrivateModeFlag = false;
+ }
+
+ /**
+ * Produce a control character or one of the special ones (ENTER, TAB,
+ * etc.).
+ *
+ * @param ch Unicode code point
+ * @param alt if true, set alt on the TKeypress
+ * @return one TKeypress event, either a control character (e.g. isKey ==
+ * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
+ * fnKey == ESC)
+ */
+ private TKeypressEvent controlChar(final char ch, final boolean alt) {
+ // System.err.printf("controlChar: %02x\n", ch);
+
+ switch (ch) {
+ case 0x0D:
+ // Carriage return --> ENTER
+ return new TKeypressEvent(kbEnter, alt, false, false);
+ case 0x0A:
+ // Linefeed --> ENTER
+ return new TKeypressEvent(kbEnter, alt, false, false);
+ case 0x1B:
+ // ESC
+ return new TKeypressEvent(kbEsc, alt, false, false);
+ case '\t':
+ // TAB
+ return new TKeypressEvent(kbTab, alt, false, false);
+ default:
+ // Make all other control characters come back as the alphabetic
+ // character with the ctrl field set. So SOH would be 'A' +
+ // ctrl.
+ return new TKeypressEvent(false, 0, (char)(ch + 0x40),
+ alt, true, false);
+ }
+ }
+
+ /**
+ * Produce special key from CSI Pn ; Pm ; ... ~
+ *
+ * @return one KEYPRESS event representing a special key
+ */
+ private TInputEvent csiFnKey() {
+ int key = 0;
+ if (params.size() > 0) {
+ key = Integer.parseInt(params.get(0));
+ }
+ boolean alt = false;
+ boolean ctrl = false;
+ boolean shift = false;
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+
+ switch (key) {
+ case 1:
+ return new TKeypressEvent(kbHome, alt, ctrl, shift);
+ case 2:
+ return new TKeypressEvent(kbIns, alt, ctrl, shift);
+ case 3:
+ return new TKeypressEvent(kbDel, alt, ctrl, shift);
+ case 4:
+ return new TKeypressEvent(kbEnd, alt, ctrl, shift);
+ case 5:
+ return new TKeypressEvent(kbPgUp, alt, ctrl, shift);
+ case 6:
+ return new TKeypressEvent(kbPgDn, alt, ctrl, shift);
+ case 15:
+ return new TKeypressEvent(kbF5, alt, ctrl, shift);
+ case 17:
+ return new TKeypressEvent(kbF6, alt, ctrl, shift);
+ case 18:
+ return new TKeypressEvent(kbF7, alt, ctrl, shift);
+ case 19:
+ return new TKeypressEvent(kbF8, alt, ctrl, shift);
+ case 20:
+ return new TKeypressEvent(kbF9, alt, ctrl, shift);
+ case 21:
+ return new TKeypressEvent(kbF10, alt, ctrl, shift);
+ case 23:
+ return new TKeypressEvent(kbF11, alt, ctrl, shift);
+ case 24:
+ return new TKeypressEvent(kbF12, alt, ctrl, shift);
+ default:
+ // Unknown
+ return null;
+ }
+ }
+
+ /**
+ * Produce mouse events based on "Any event tracking" and UTF-8
+ * coordinates. See
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+ *
+ * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
+ */
+ private TInputEvent parseMouse() {
+ int buttons = params.get(0).charAt(0) - 32;
+ int x = params.get(0).charAt(1) - 32 - 1;
+ int y = params.get(0).charAt(2) - 32 - 1;
+
+ // Clamp X and Y to the physical screen coordinates.
+ if (x >= windowResize.getWidth()) {
+ x = windowResize.getWidth() - 1;
+ }
+ if (y >= windowResize.getHeight()) {
+ y = windowResize.getHeight() - 1;
+ }
+
+ TMouseEvent.Type eventType = TMouseEvent.Type.MOUSE_DOWN;
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ boolean eventMouseWheelUp = false;
+ boolean eventMouseWheelDown = false;
+
+ // System.err.printf("buttons: %04x\r\n", buttons);
+
+ switch (buttons) {
+ case 0:
+ eventMouse1 = true;
+ mouse1 = true;
+ break;
+ case 1:
+ eventMouse2 = true;
+ mouse2 = true;
+ break;
+ case 2:
+ eventMouse3 = true;
+ mouse3 = true;
+ break;
+ case 3:
+ // Release or Move
+ if (!mouse1 && !mouse2 && !mouse3) {
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ } else {
+ eventType = TMouseEvent.Type.MOUSE_UP;
+ }
+ if (mouse1) {
+ mouse1 = false;
+ eventMouse1 = true;
+ }
+ if (mouse2) {
+ mouse2 = false;
+ eventMouse2 = true;
+ }
+ if (mouse3) {
+ mouse3 = false;
+ eventMouse3 = true;
+ }
+ break;
+
+ case 32:
+ // Dragging with mouse1 down
+ eventMouse1 = true;
+ mouse1 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 33:
+ // Dragging with mouse2 down
+ eventMouse2 = true;
+ mouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 34:
+ // Dragging with mouse3 down
+ eventMouse3 = true;
+ mouse3 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 96:
+ // Dragging with mouse2 down after wheelUp
+ eventMouse2 = true;
+ mouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 97:
+ // Dragging with mouse2 down after wheelDown
+ eventMouse2 = true;
+ mouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 64:
+ eventMouseWheelUp = true;
+ break;
+
+ case 65:
+ eventMouseWheelDown = true;
+ break;
+
+ default:
+ // Unknown, just make it motion
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+ }
+ return new TMouseEvent(eventType, x, y, x, y,
+ eventMouse1, eventMouse2, eventMouse3,
+ eventMouseWheelUp, eventMouseWheelDown);
+ }
+
+ /**
+ * Produce mouse events based on "Any event tracking" and SGR
+ * coordinates. See
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
+ *
+ * @param release if true, this was a release ('m')
+ * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
+ */
+ private TInputEvent parseMouseSGR(final boolean release) {
+ // SGR extended coordinates - mode 1006
+ if (params.size() < 3) {
+ // Invalid position, bail out.
+ return null;
+ }
+ int buttons = Integer.parseInt(params.get(0));
+ int x = Integer.parseInt(params.get(1)) - 1;
+ int y = Integer.parseInt(params.get(2)) - 1;
+
+ // Clamp X and Y to the physical screen coordinates.
+ if (x >= windowResize.getWidth()) {
+ x = windowResize.getWidth() - 1;
+ }
+ if (y >= windowResize.getHeight()) {
+ y = windowResize.getHeight() - 1;
+ }
+
+ TMouseEvent.Type eventType = TMouseEvent.Type.MOUSE_DOWN;
+ boolean eventMouse1 = false;
+ boolean eventMouse2 = false;
+ boolean eventMouse3 = false;
+ boolean eventMouseWheelUp = false;
+ boolean eventMouseWheelDown = false;
+
+ if (release) {
+ eventType = TMouseEvent.Type.MOUSE_UP;
+ }
+
+ switch (buttons) {
+ case 0:
+ eventMouse1 = true;
+ break;
+ case 1:
+ eventMouse2 = true;
+ break;
+ case 2:
+ eventMouse3 = true;
+ break;
+ case 35:
+ // Motion only, no buttons down
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 32:
+ // Dragging with mouse1 down
+ eventMouse1 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 33:
+ // Dragging with mouse2 down
+ eventMouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 34:
+ // Dragging with mouse3 down
+ eventMouse3 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 96:
+ // Dragging with mouse2 down after wheelUp
+ eventMouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 97:
+ // Dragging with mouse2 down after wheelDown
+ eventMouse2 = true;
+ eventType = TMouseEvent.Type.MOUSE_MOTION;
+ break;
+
+ case 64:
+ eventMouseWheelUp = true;
+ break;
+
+ case 65:
+ eventMouseWheelDown = true;
+ break;
+
+ default:
+ // Unknown, bail out
+ return null;
+ }
+ return new TMouseEvent(eventType, x, y, x, y,
+ eventMouse1, eventMouse2, eventMouse3,
+ eventMouseWheelUp, eventMouseWheelDown);
+ }
+
+ /**
+ * Return any events in the IO queue due to timeout.
+ *
+ * @param queue list to append new events to
+ */
+ private void getIdleEvents(final List<TInputEvent> queue) {
+ long nowTime = System.currentTimeMillis();
+
+ // Check for new window size
+ long windowSizeDelay = nowTime - windowSizeTime;
+ if (windowSizeDelay > 1000) {
+ int oldTextWidth = getTextWidth();
+ int oldTextHeight = getTextHeight();
+
+ sessionInfo.queryWindowSize();
+ int newWidth = sessionInfo.getWindowWidth();
+ int newHeight = sessionInfo.getWindowHeight();
+
+ if ((newWidth != windowResize.getWidth())
+ || (newHeight != windowResize.getHeight())
+ ) {
+
+ // Request xterm report window dimensions in pixels again.
+ // Between now and then, ensure that the reported text cell
+ // size is the same by setting widthPixels and heightPixels
+ // to match the new dimensions.
+ widthPixels = oldTextWidth * newWidth;
+ heightPixels = oldTextHeight * newHeight;
+
+ if (debugToStderr) {
+ System.err.println("Screen size changed, old size " +
+ windowResize);
+ System.err.println(" new size " +
+ newWidth + " x " + newHeight);
+ System.err.println(" old pixels " +
+ oldTextWidth + " x " + oldTextHeight);
+ System.err.println(" new pixels " +
+ getTextWidth() + " x " + getTextHeight());
+ }
+
+ this.output.printf("%s", xtermReportPixelDimensions());
+ this.output.flush();
+
+ TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
+ newWidth, newHeight);
+ windowResize = new TResizeEvent(TResizeEvent.Type.SCREEN,
+ newWidth, newHeight);
+ queue.add(event);
+ }
+ windowSizeTime = nowTime;
+ }
+
+ // ESCDELAY type timeout
+ if (state == ParseState.ESCAPE) {
+ long escDelay = nowTime - escapeTime;
+ if (escDelay > 100) {
+ // After 0.1 seconds, assume a true escape character
+ queue.add(controlChar((char)0x1B, false));
+ resetParser();
+ }
+ }
+ }
+
+ /**
+ * Returns true if the CSI parameter for a keyboard command means that
+ * shift was down.
+ */
+ private boolean csiIsShift(final String x) {
+ if ((x.equals("2"))
+ || (x.equals("4"))
+ || (x.equals("6"))
+ || (x.equals("8"))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the CSI parameter for a keyboard command means that
+ * alt was down.
+ */
+ private boolean csiIsAlt(final String x) {
+ if ((x.equals("3"))
+ || (x.equals("4"))
+ || (x.equals("7"))
+ || (x.equals("8"))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the CSI parameter for a keyboard command means that
+ * ctrl was down.
+ */
+ private boolean csiIsCtrl(final String x) {
+ if ((x.equals("5"))
+ || (x.equals("6"))
+ || (x.equals("7"))
+ || (x.equals("8"))
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parses the next character of input to see if an InputEvent is
+ * fully here.
+ *
+ * @param events list to append new events to
+ * @param ch Unicode code point
+ */
+ private void processChar(final List<TInputEvent> events, final char ch) {
+
+ // ESCDELAY type timeout
+ long nowTime = System.currentTimeMillis();
+ if (state == ParseState.ESCAPE) {
+ long escDelay = nowTime - escapeTime;
+ if (escDelay > 250) {
+ // After 0.25 seconds, assume a true escape character
+ events.add(controlChar((char)0x1B, false));
+ resetParser();
+ }
+ }
+
+ // TKeypress fields
+ boolean ctrl = false;
+ boolean alt = false;
+ boolean shift = false;
+
+ // System.err.printf("state: %s ch %c\r\n", state, ch);
+
+ switch (state) {
+ case GROUND:
+
+ if (ch == 0x1B) {
+ state = ParseState.ESCAPE;
+ escapeTime = nowTime;
+ return;
+ }
+
+ if (ch <= 0x1F) {
+ // Control character
+ events.add(controlChar(ch, false));
+ resetParser();
+ return;
+ }
+
+ if (ch >= 0x20) {
+ // Normal character
+ events.add(new TKeypressEvent(false, 0, ch,
+ false, false, false));
+ resetParser();
+ return;
+ }
+
+ break;
+
+ case ESCAPE:
+ if (ch <= 0x1F) {
+ // ALT-Control character
+ events.add(controlChar(ch, true));
+ resetParser();
+ return;
+ }
+
+ if (ch == 'O') {
+ // This will be one of the function keys
+ state = ParseState.ESCAPE_INTERMEDIATE;
+ return;
+ }
+
+ // '[' goes to CSI_ENTRY
+ if (ch == '[') {
+ state = ParseState.CSI_ENTRY;
+ return;
+ }
+
+ // Everything else is assumed to be Alt-keystroke
+ if ((ch >= 'A') && (ch <= 'Z')) {
+ shift = true;
+ }
+ alt = true;
+ events.add(new TKeypressEvent(false, 0, ch, alt, ctrl, shift));
+ resetParser();
+ return;
+
+ case ESCAPE_INTERMEDIATE:
+ if ((ch >= 'P') && (ch <= 'S')) {
+ // Function key
+ switch (ch) {
+ case 'P':
+ events.add(new TKeypressEvent(kbF1));
+ break;
+ case 'Q':
+ events.add(new TKeypressEvent(kbF2));
+ break;
+ case 'R':
+ events.add(new TKeypressEvent(kbF3));
+ break;
+ case 'S':
+ events.add(new TKeypressEvent(kbF4));
+ break;
+ default:
+ break;
+ }
+ resetParser();
+ return;
+ }
+
+ // Unknown keystroke, ignore
+ resetParser();
+ return;
+
+ case CSI_ENTRY:
+ // Numbers - parameter values
+ if ((ch >= '0') && (ch <= '9')) {
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
+ state = ParseState.CSI_PARAM;
+ return;
+ }
+ // Parameter separator
+ if (ch == ';') {
+ params.add("");
+ return;
+ }
+
+ if ((ch >= 0x30) && (ch <= 0x7E)) {
+ switch (ch) {
+ case 'A':
+ // Up
+ events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'B':
+ // Down
+ events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'C':
+ // Right
+ events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'D':
+ // Left
+ events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'H':
+ // Home
+ events.add(new TKeypressEvent(kbHome));
+ resetParser();
+ return;
+ case 'F':
+ // End
+ events.add(new TKeypressEvent(kbEnd));
+ resetParser();
+ return;
+ case 'Z':
+ // CBT - Cursor backward X tab stops (default 1)
+ events.add(new TKeypressEvent(kbBackTab));
+ resetParser();
+ return;
+ case 'M':
+ // Mouse position
+ state = ParseState.MOUSE;
+ return;
+ case '<':
+ // Mouse position, SGR (1006) coordinates
+ state = ParseState.MOUSE_SGR;
+ return;
+ case '?':
+ // DEC private mode flag
+ decPrivateModeFlag = true;
+ return;
+ default:
+ break;
+ }
+ }
+
+ // Unknown keystroke, ignore
+ resetParser();
+ return;
+
+ case MOUSE_SGR:
+ // Numbers - parameter values
+ if ((ch >= '0') && (ch <= '9')) {
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
+ return;
+ }
+ // Parameter separator
+ if (ch == ';') {
+ params.add("");
+ return;
+ }
+
+ switch (ch) {
+ case 'M':
+ // Generate a mouse press event
+ TInputEvent event = parseMouseSGR(false);
+ if (event != null) {
+ events.add(event);
+ }
+ resetParser();
+ return;
+ case 'm':
+ // Generate a mouse release event
+ event = parseMouseSGR(true);
+ if (event != null) {
+ events.add(event);
+ }
+ resetParser();
+ return;
+ default:
+ break;
+ }
+
+ // Unknown keystroke, ignore
+ resetParser();
+ return;
+
+ case CSI_PARAM:
+ // Numbers - parameter values
+ if ((ch >= '0') && (ch <= '9')) {
+ params.set(params.size() - 1,
+ params.get(params.size() - 1) + ch);
+ state = ParseState.CSI_PARAM;
+ return;
+ }
+ // Parameter separator
+ if (ch == ';') {
+ params.add("");
+ return;
+ }
+
+ if (ch == '~') {
+ events.add(csiFnKey());
+ resetParser();
+ return;
+ }
+
+ if ((ch >= 0x30) && (ch <= 0x7E)) {
+ switch (ch) {
+ case 'A':
+ // Up
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbUp, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'B':
+ // Down
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbDown, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'C':
+ // Right
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbRight, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'D':
+ // Left
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbLeft, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'H':
+ // Home
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbHome, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'F':
+ // End
+ if (params.size() > 1) {
+ shift = csiIsShift(params.get(1));
+ alt = csiIsAlt(params.get(1));
+ ctrl = csiIsCtrl(params.get(1));
+ }
+ events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
+ resetParser();
+ return;
+ case 'c':
+ // Device Attributes
+ if (decPrivateModeFlag == false) {
+ break;
+ }
+ for (String x: params) {
+ if (x.equals("4")) {
+ // Terminal reports sixel support
+ if (debugToStderr) {
+ System.err.println("Device Attributes: sixel");
+ }
+ }
+ if (x.equals("444")) {
+ // Terminal reports Jexer images support
+ if (debugToStderr) {
+ System.err.println("Device Attributes: Jexer images");
+ }
+ jexerImages = true;
+ }
+ }
+ return;
+ case 't':
+ // windowOps
+ if ((params.size() > 2) && (params.get(0).equals("4"))) {
+ if (debugToStderr) {
+ System.err.printf("windowOp pixels: " +
+ "height %s width %s\n",
+ params.get(1), params.get(2));
+ }
+ try {
+ widthPixels = Integer.parseInt(params.get(2));
+ heightPixels = Integer.parseInt(params.get(1));
+ } catch (NumberFormatException e) {
+ if (debugToStderr) {
+ e.printStackTrace();
+ }
+ }
+ if (widthPixels <= 0) {
+ widthPixels = 640;
+ }
+ if (heightPixels <= 0) {
+ heightPixels = 400;
+ }
+ }
+ if ((params.size() > 2) && (params.get(0).equals("6"))) {
+ if (debugToStderr) {
+ System.err.printf("windowOp text cell pixels: " +
+ "height %s width %s\n",
+ params.get(1), params.get(2));
+ }
+ try {
+ widthPixels = width * Integer.parseInt(params.get(2));
+ heightPixels = height * Integer.parseInt(params.get(1));
+ } catch (NumberFormatException e) {
+ if (debugToStderr) {
+ e.printStackTrace();
+ }
+ }
+ if (widthPixels <= 0) {
+ widthPixels = 640;
+ }
+ if (heightPixels <= 0) {
+ heightPixels = 400;
+ }
+ }
+ resetParser();
+ return;
+ default:
+ break;
+ }
+ }
+
+ // Unknown keystroke, ignore
+ resetParser();
+ return;
+
+ case MOUSE:
+ params.set(0, params.get(params.size() - 1) + ch);
+ if (params.get(0).length() == 3) {
+ // We have enough to generate a mouse event
+ events.add(parseMouse());
+ resetParser();
+ }
+ return;