+ events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
+ resetParser();
+ 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;
+
+ default:
+ break;
+ }
+
+ // This "should" be impossible to reach
+ return;
+ }
+
+ /**
+ * Request (u)xterm to report the current window size dimensions.
+ *
+ * @return the string to emit to xterm
+ */
+ private String xtermReportWindowPixelDimensions() {
+ // We will ask for both window and text cell dimensions, and
+ // hopefully one of them will work.
+ return "\033[14t\033[16t";
+ }
+
+ /**
+ * Tell (u)xterm that we want alt- keystrokes to send escape + character
+ * rather than set the 8th bit. Anyone who wants UTF8 should want this
+ * enabled.
+ *
+ * @param on if true, enable metaSendsEscape
+ * @return the string to emit to xterm
+ */
+ private String xtermMetaSendsEscape(final boolean on) {
+ if (on) {
+ return "\033[?1036h\033[?1034l";
+ }
+ return "\033[?1036l";
+ }
+
+ /**
+ * Create an xterm OSC sequence to change the window title.
+ *
+ * @param title the new title
+ * @return the string to emit to xterm
+ */
+ private String getSetTitleString(final String title) {
+ return "\033]2;" + title + "\007";
+ }
+
+ // ------------------------------------------------------------------------
+ // Sixel output support ---------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the number of colors in the sixel palette.
+ *
+ * @return the palette size
+ */
+ public int getSixelPaletteSize() {
+ return sixelPaletteSize;
+ }
+
+ /**
+ * Set the number of colors in the sixel palette.
+ *
+ * @param paletteSize the new palette size
+ */
+ public void setSixelPaletteSize(final int paletteSize) {
+ if (paletteSize == sixelPaletteSize) {
+ return;
+ }
+
+ switch (paletteSize) {
+ case 2:
+ case 256:
+ case 512:
+ case 1024:
+ case 2048:
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported sixel palette " +
+ " size: " + paletteSize);
+ }
+
+ // Don't step on the screen refresh thread.
+ synchronized (this) {
+ sixelPaletteSize = paletteSize;
+ palette = null;
+ sixelCache = null;
+ clearPhysical();
+ }
+ }
+
+ /**
+ * Start a sixel string for display one row's worth of bitmap data.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String startSixel(final int x, final int y) {
+ StringBuilder sb = new StringBuilder();
+
+ assert (sixel == true);
+
+ // Place the cursor
+ sb.append(gotoXY(x, y));
+
+ // DCS
+ sb.append("\033Pq");
+
+ if (palette == null) {
+ palette = new SixelPalette();
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * End a sixel string for display one row's worth of bitmap data.
+ *
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String endSixel() {
+ assert (sixel == true);
+
+ // ST
+ return ("\033\\");
+ }
+
+ /**
+ * Create a sixel string representing a row of several cells containing
+ * bitmap data.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param cells the cells containing the bitmap data
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String toSixel(final int x, final int y,
+ final ArrayList<Cell> cells) {
+
+ StringBuilder sb = new StringBuilder();
+
+ assert (cells != null);
+ assert (cells.size() > 0);
+ assert (cells.get(0).getImage() != null);
+
+ if (sixel == false) {
+ sb.append(normal());
+ sb.append(gotoXY(x, y));
+ for (int i = 0; i < cells.size(); i++) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ if (sixelCache == null) {
+ sixelCache = new SixelCache(height * 10);
+ }
+
+ // Save and get rows to/from the cache that do NOT have inverted
+ // cells.
+ boolean saveInCache = true;
+ for (Cell cell: cells) {
+ if (cell.isInvertedImage()) {
+ saveInCache = false;
+ }
+ }
+ if (saveInCache) {
+ String cachedResult = sixelCache.get(cells);
+ if (cachedResult != null) {
+ // System.err.println("CACHE HIT");
+ sb.append(startSixel(x, y));
+ sb.append(cachedResult);
+ sb.append(endSixel());
+ return sb.toString();
+ }
+ // System.err.println("CACHE MISS");
+ }
+
+ int imageWidth = cells.get(0).getImage().getWidth();
+ int imageHeight = cells.get(0).getImage().getHeight();
+
+ // cells.get(x).getImage() has a dithered bitmap containing indexes
+ // into the color palette. Piece these together into one larger
+ // image for final rendering.
+ int totalWidth = 0;
+ int fullWidth = cells.size() * getTextWidth();
+ int fullHeight = getTextHeight();
+ for (int i = 0; i < cells.size(); i++) {
+ totalWidth += cells.get(i).getImage().getWidth();
+ }
+
+ BufferedImage image = new BufferedImage(fullWidth,
+ fullHeight, BufferedImage.TYPE_INT_ARGB);
+
+ int [] rgbArray;
+ for (int i = 0; i < cells.size() - 1; i++) {
+ int tileWidth = Math.min(cells.get(i).getImage().getWidth(),
+ imageWidth);
+ int tileHeight = Math.min(cells.get(i).getImage().getHeight(),
+ imageHeight);
+ if (false && cells.get(i).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do
+ // that anymore.
+ rgbArray = new int[imageWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ try {
+ rgbArray = cells.get(i).getImage().getRGB(0, 0,
+ tileWidth, tileHeight, null, 0, tileWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight +
+ "tile " + tileWidth + "x" +
+ tileHeight +
+ " cells.get(i).getImage() " +
+ cells.get(i).getImage() +
+ " i " + i +
+ " fullWidth " + fullWidth +
+ " fullHeight " + fullHeight, e);
+ }
+ }
+
+ /*
+ System.err.printf("calling image.setRGB(): %d %d %d %d %d\n",
+ i * imageWidth, 0, imageWidth, imageHeight,
+ 0, imageWidth);
+ System.err.printf(" fullWidth %d fullHeight %d cells.size() %d textWidth %d\n",
+ fullWidth, fullHeight, cells.size(), getTextWidth());
+ */
+
+ image.setRGB(i * imageWidth, 0, tileWidth, tileHeight,
+ rgbArray, 0, tileWidth);
+ if (tileHeight < fullHeight) {
+ int backgroundColor = cells.get(i).getBackground().getRGB();
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int imageY = imageHeight; imageY < fullHeight;
+ imageY++) {
+
+ image.setRGB(imageX, imageY, backgroundColor);
+ }