TTerminalWindow sixel support wip
[nikiroo-utils.git] / src / jexer / tterminal / ECMA48.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.tterminal;
30
31 import java.awt.Graphics2D;
32 import java.awt.image.BufferedImage;
33 import java.io.BufferedOutputStream;
34 import java.io.CharArrayWriter;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.IOException;
38 import java.io.OutputStream;
39 import java.io.OutputStreamWriter;
40 import java.io.PrintWriter;
41 import java.io.Reader;
42 import java.io.UnsupportedEncodingException;
43 import java.io.Writer;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.List;
48
49 import jexer.TKeypress;
50 import jexer.event.TMouseEvent;
51 import jexer.bits.Color;
52 import jexer.bits.Cell;
53 import jexer.bits.CellAttributes;
54 import jexer.io.ReadTimeoutException;
55 import jexer.io.TimeoutInputStream;
56 import static jexer.TKeypress.*;
57
58 /**
59 * This implements a complex ECMA-48/ISO 6429/ANSI X3.64 type console,
60 * including a scrollback buffer.
61 *
62 * <p>
63 * It currently implements VT100, VT102, VT220, and XTERM with the following
64 * caveats:
65 *
66 * <p>
67 * - The vttest scenario for VT220 8-bit controls (11.1.2.3) reports a
68 * failure with XTERM. This is due to vttest failing to decode the UTF-8
69 * stream.
70 *
71 * <p>
72 * - Smooth scrolling, printing, keyboard locking, keyboard leds, and tests
73 * from VT100 are not supported.
74 *
75 * <p>
76 * - User-defined keys (DECUDK), downloadable fonts (DECDLD), and VT100/ANSI
77 * compatibility mode (DECSCL) from VT220 are not supported. (Also,
78 * because DECSCL is not supported, it will fail the last part of the
79 * vttest "Test of VT52 mode" if DeviceType is set to VT220.)
80 *
81 * <p>
82 * - Numeric/application keys from the number pad are not supported because
83 * they are not exposed from the TKeypress API.
84 *
85 * <p>
86 * - VT52 HOLD SCREEN mode is not supported.
87 *
88 * <p>
89 * - In VT52 graphics mode, the 3/, 5/, and 7/ characters (fraction
90 * numerators) are not rendered correctly.
91 *
92 * <p>
93 * - All data meant for the 'printer' (CSI Pc ? i) is discarded.
94 */
95 public class ECMA48 implements Runnable {
96
97 // ------------------------------------------------------------------------
98 // Constants --------------------------------------------------------------
99 // ------------------------------------------------------------------------
100
101 /**
102 * The emulator can emulate several kinds of terminals.
103 */
104 public enum DeviceType {
105 /**
106 * DEC VT100 but also including the three VT102 functions.
107 */
108 VT100,
109
110 /**
111 * DEC VT102.
112 */
113 VT102,
114
115 /**
116 * DEC VT220.
117 */
118 VT220,
119
120 /**
121 * A subset of xterm.
122 */
123 XTERM
124 }
125
126 /**
127 * Parser character scan states.
128 */
129 private enum ScanState {
130 GROUND,
131 ESCAPE,
132 ESCAPE_INTERMEDIATE,
133 CSI_ENTRY,
134 CSI_PARAM,
135 CSI_INTERMEDIATE,
136 CSI_IGNORE,
137 DCS_ENTRY,
138 DCS_INTERMEDIATE,
139 DCS_PARAM,
140 DCS_PASSTHROUGH,
141 DCS_IGNORE,
142 DCS_SIXEL,
143 SOSPMAPC_STRING,
144 OSC_STRING,
145 VT52_DIRECT_CURSOR_ADDRESS
146 }
147
148 /**
149 * The selected number pad mode (DECKPAM, DECKPNM). We record this, but
150 * can't really use it in keypress() because we do not see number pad
151 * events from TKeypress.
152 */
153 private enum KeypadMode {
154 Application,
155 Numeric
156 }
157
158 /**
159 * Arrow keys can emit three different sequences (DECCKM or VT52
160 * submode).
161 */
162 private enum ArrowKeyMode {
163 VT52,
164 ANSI,
165 VT100
166 }
167
168 /**
169 * Available character sets for GL, GR, G0, G1, G2, G3.
170 */
171 private enum CharacterSet {
172 US,
173 UK,
174 DRAWING,
175 ROM,
176 ROM_SPECIAL,
177 VT52_GRAPHICS,
178 DEC_SUPPLEMENTAL,
179 NRC_DUTCH,
180 NRC_FINNISH,
181 NRC_FRENCH,
182 NRC_FRENCH_CA,
183 NRC_GERMAN,
184 NRC_ITALIAN,
185 NRC_NORWEGIAN,
186 NRC_SPANISH,
187 NRC_SWEDISH,
188 NRC_SWISS
189 }
190
191 /**
192 * Single-shift states used by the C1 control characters SS2 (0x8E) and
193 * SS3 (0x8F).
194 */
195 private enum Singleshift {
196 NONE,
197 SS2,
198 SS3
199 }
200
201 /**
202 * VT220+ lockshift states.
203 */
204 private enum LockshiftMode {
205 NONE,
206 G1_GR,
207 G2_GR,
208 G2_GL,
209 G3_GR,
210 G3_GL
211 }
212
213 /**
214 * XTERM mouse reporting protocols.
215 */
216 private enum MouseProtocol {
217 OFF,
218 X10,
219 NORMAL,
220 BUTTONEVENT,
221 ANYEVENT
222 }
223
224 /**
225 * XTERM mouse reporting encodings.
226 */
227 private enum MouseEncoding {
228 X10,
229 UTF8,
230 SGR
231 }
232
233 // ------------------------------------------------------------------------
234 // Variables --------------------------------------------------------------
235 // ------------------------------------------------------------------------
236
237 /**
238 * The enclosing listening object.
239 */
240 private DisplayListener displayListener;
241
242 /**
243 * When true, the reader thread is expected to exit.
244 */
245 private volatile boolean stopReaderThread = false;
246
247 /**
248 * The reader thread.
249 */
250 private Thread readerThread = null;
251
252 /**
253 * The type of emulator to be.
254 */
255 private DeviceType type = DeviceType.VT102;
256
257 /**
258 * The scrollback buffer characters + attributes.
259 */
260 private volatile ArrayList<DisplayLine> scrollback;
261
262 /**
263 * The raw display buffer characters + attributes.
264 */
265 private volatile ArrayList<DisplayLine> display;
266
267 /**
268 * The maximum number of lines in the scrollback buffer.
269 */
270 private int maxScrollback = 10000;
271
272 /**
273 * The terminal's input. For type == XTERM, this is an InputStreamReader
274 * with UTF-8 encoding.
275 */
276 private Reader input;
277
278 /**
279 * The terminal's raw InputStream. This is used for type != XTERM.
280 */
281 private volatile TimeoutInputStream inputStream;
282
283 /**
284 * The terminal's output. For type == XTERM, this wraps an
285 * OutputStreamWriter with UTF-8 encoding.
286 */
287 private Writer output;
288
289 /**
290 * The terminal's raw OutputStream. This is used for type != XTERM.
291 */
292 private OutputStream outputStream;
293
294 /**
295 * Current scanning state.
296 */
297 private ScanState scanState;
298
299 /**
300 * Which mouse protocol is active.
301 */
302 private MouseProtocol mouseProtocol = MouseProtocol.OFF;
303
304 /**
305 * Which mouse encoding is active.
306 */
307 private MouseEncoding mouseEncoding = MouseEncoding.X10;
308
309 /**
310 * A terminal may request that the mouse pointer be hidden using a
311 * Privacy Message containing either "hideMousePointer" or
312 * "showMousePointer". This is currently only used within Jexer by
313 * TTerminalWindow so that only the bottom-most instance of nested
314 * Jexer's draws the mouse within its application window.
315 */
316 private boolean hideMousePointer = false;
317
318 /**
319 * Physical display width. We start at 80x24, but the user can resize us
320 * bigger/smaller.
321 */
322 private int width;
323
324 /**
325 * Physical display height. We start at 80x24, but the user can resize
326 * us bigger/smaller.
327 */
328 private int height;
329
330 /**
331 * Top margin of the scrolling region.
332 */
333 private int scrollRegionTop;
334
335 /**
336 * Bottom margin of the scrolling region.
337 */
338 private int scrollRegionBottom;
339
340 /**
341 * Right margin column number. This can be selected by the remote side
342 * to be 80/132 (rightMargin values 79/131), or it can be (width - 1).
343 */
344 private int rightMargin;
345
346 /**
347 * Last character printed.
348 */
349 private char repCh;
350
351 /**
352 * VT100-style line wrapping: a character is placed in column 80 (or
353 * 132), but the line does NOT wrap until another character is written to
354 * column 1 of the next line, after which the cursor moves to column 2.
355 */
356 private boolean wrapLineFlag;
357
358 /**
359 * VT220 single shift flag.
360 */
361 private Singleshift singleshift = Singleshift.NONE;
362
363 /**
364 * true = insert characters, false = overwrite.
365 */
366 private boolean insertMode = false;
367
368 /**
369 * VT52 mode as selected by DECANM. True means VT52, false means
370 * ANSI. Default is ANSI.
371 */
372 private boolean vt52Mode = false;
373
374 /**
375 * Visible cursor (DECTCEM).
376 */
377 private boolean cursorVisible = true;
378
379 /**
380 * Screen title as set by the xterm OSC sequence. Lots of applications
381 * send a screenTitle regardless of whether it is an xterm client or not.
382 */
383 private String screenTitle = "";
384
385 /**
386 * Parameter characters being collected.
387 */
388 private List<Integer> csiParams;
389
390 /**
391 * Non-csi collect buffer.
392 */
393 private StringBuilder collectBuffer;
394
395 /**
396 * When true, use the G1 character set.
397 */
398 private boolean shiftOut = false;
399
400 /**
401 * Horizontal tab stop locations.
402 */
403 private List<Integer> tabStops;
404
405 /**
406 * S8C1T. True means 8bit controls, false means 7bit controls.
407 */
408 private boolean s8c1t = false;
409
410 /**
411 * Printer mode. True means send all output to printer, which discards
412 * it.
413 */
414 private boolean printerControllerMode = false;
415
416 /**
417 * LMN line mode. If true, linefeed() puts the cursor on the first
418 * column of the next line. If false, linefeed() puts the cursor one
419 * line down on the current line. The default is false.
420 */
421 private boolean newLineMode = false;
422
423 /**
424 * Whether arrow keys send ANSI, VT100, or VT52 sequences.
425 */
426 private ArrowKeyMode arrowKeyMode;
427
428 /**
429 * Whether number pad keys send VT100 or VT52, application or numeric
430 * sequences.
431 */
432 @SuppressWarnings("unused")
433 private KeypadMode keypadMode;
434
435 /**
436 * When true, the terminal is in 132-column mode (DECCOLM).
437 */
438 private boolean columns132 = false;
439
440 /**
441 * true = reverse video. Set by DECSCNM.
442 */
443 private boolean reverseVideo = false;
444
445 /**
446 * false = echo characters locally.
447 */
448 private boolean fullDuplex = true;
449
450 /**
451 * The current terminal state.
452 */
453 private SaveableState currentState;
454
455 /**
456 * The last saved terminal state.
457 */
458 private SaveableState savedState;
459
460 /**
461 * The 88- or 256-color support RGB colors.
462 */
463 private List<Integer> colors88;
464
465 /**
466 * Sixel collection buffer.
467 */
468 private StringBuilder sixelParseBuffer;
469
470 /**
471 * The width of a character cell in pixels.
472 */
473 private int textWidth = 16;
474
475 /**
476 * The height of a character cell in pixels.
477 */
478 private int textHeight = 20;
479
480 /**
481 * DECSC/DECRC save/restore a subset of the total state. This class
482 * encapsulates those specific flags/modes.
483 */
484 private class SaveableState {
485
486 /**
487 * When true, cursor positions are relative to the scrolling region.
488 */
489 public boolean originMode = false;
490
491 /**
492 * The current editing X position.
493 */
494 public int cursorX = 0;
495
496 /**
497 * The current editing Y position.
498 */
499 public int cursorY = 0;
500
501 /**
502 * Which character set is currently selected in G0.
503 */
504 public CharacterSet g0Charset = CharacterSet.US;
505
506 /**
507 * Which character set is currently selected in G1.
508 */
509 public CharacterSet g1Charset = CharacterSet.DRAWING;
510
511 /**
512 * Which character set is currently selected in G2.
513 */
514 public CharacterSet g2Charset = CharacterSet.US;
515
516 /**
517 * Which character set is currently selected in G3.
518 */
519 public CharacterSet g3Charset = CharacterSet.US;
520
521 /**
522 * Which character set is currently selected in GR.
523 */
524 public CharacterSet grCharset = CharacterSet.DRAWING;
525
526 /**
527 * The current drawing attributes.
528 */
529 public CellAttributes attr;
530
531 /**
532 * GL lockshift mode.
533 */
534 public LockshiftMode glLockshift = LockshiftMode.NONE;
535
536 /**
537 * GR lockshift mode.
538 */
539 public LockshiftMode grLockshift = LockshiftMode.NONE;
540
541 /**
542 * Line wrap.
543 */
544 public boolean lineWrap = true;
545
546 /**
547 * Reset to defaults.
548 */
549 public void reset() {
550 originMode = false;
551 cursorX = 0;
552 cursorY = 0;
553 g0Charset = CharacterSet.US;
554 g1Charset = CharacterSet.DRAWING;
555 g2Charset = CharacterSet.US;
556 g3Charset = CharacterSet.US;
557 grCharset = CharacterSet.DRAWING;
558 attr = new CellAttributes();
559 glLockshift = LockshiftMode.NONE;
560 grLockshift = LockshiftMode.NONE;
561 lineWrap = true;
562 }
563
564 /**
565 * Copy attributes from another instance.
566 *
567 * @param that the other instance to match
568 */
569 public void setTo(final SaveableState that) {
570 this.originMode = that.originMode;
571 this.cursorX = that.cursorX;
572 this.cursorY = that.cursorY;
573 this.g0Charset = that.g0Charset;
574 this.g1Charset = that.g1Charset;
575 this.g2Charset = that.g2Charset;
576 this.g3Charset = that.g3Charset;
577 this.grCharset = that.grCharset;
578 this.attr = new CellAttributes();
579 this.attr.setTo(that.attr);
580 this.glLockshift = that.glLockshift;
581 this.grLockshift = that.grLockshift;
582 this.lineWrap = that.lineWrap;
583 }
584
585 /**
586 * Public constructor.
587 */
588 public SaveableState() {
589 reset();
590 }
591 }
592
593 // ------------------------------------------------------------------------
594 // Constructors -----------------------------------------------------------
595 // ------------------------------------------------------------------------
596
597 /**
598 * Public constructor.
599 *
600 * @param type one of the DeviceType constants to select VT100, VT102,
601 * VT220, or XTERM
602 * @param inputStream an InputStream connected to the remote side. For
603 * type == XTERM, inputStream is converted to a Reader with UTF-8
604 * encoding.
605 * @param outputStream an OutputStream connected to the remote user. For
606 * type == XTERM, outputStream is converted to a Writer with UTF-8
607 * encoding.
608 * @param displayListener a callback to the outer display, or null for
609 * default VT100 behavior
610 * @throws UnsupportedEncodingException if an exception is thrown when
611 * creating the InputStreamReader
612 */
613 public ECMA48(final DeviceType type, final InputStream inputStream,
614 final OutputStream outputStream, final DisplayListener displayListener)
615 throws UnsupportedEncodingException {
616
617 assert (inputStream != null);
618 assert (outputStream != null);
619
620 csiParams = new ArrayList<Integer>();
621 tabStops = new ArrayList<Integer>();
622 scrollback = new ArrayList<DisplayLine>();
623 display = new ArrayList<DisplayLine>();
624
625 this.type = type;
626 if (inputStream instanceof TimeoutInputStream) {
627 this.inputStream = (TimeoutInputStream)inputStream;
628 } else {
629 this.inputStream = new TimeoutInputStream(inputStream, 2000);
630 }
631 if (type == DeviceType.XTERM) {
632 this.input = new InputStreamReader(this.inputStream, "UTF-8");
633 this.output = new OutputStreamWriter(new
634 BufferedOutputStream(outputStream), "UTF-8");
635 this.outputStream = null;
636 } else {
637 this.output = null;
638 this.outputStream = new BufferedOutputStream(outputStream);
639 }
640 this.displayListener = displayListener;
641
642 reset();
643 for (int i = 0; i < height; i++) {
644 display.add(new DisplayLine(currentState.attr));
645 }
646
647 // Spin up the input reader
648 readerThread = new Thread(this);
649 readerThread.start();
650 }
651
652 // ------------------------------------------------------------------------
653 // Runnable ---------------------------------------------------------------
654 // ------------------------------------------------------------------------
655
656 /**
657 * Read function runs on a separate thread.
658 */
659 public final void run() {
660 boolean utf8 = false;
661 boolean done = false;
662
663 if (type == DeviceType.XTERM) {
664 utf8 = true;
665 }
666
667 // available() will often return > 1, so we need to read in chunks to
668 // stay caught up.
669 char [] readBufferUTF8 = null;
670 byte [] readBuffer = null;
671 if (utf8) {
672 readBufferUTF8 = new char[128];
673 } else {
674 readBuffer = new byte[128];
675 }
676
677 while (!done && !stopReaderThread) {
678 try {
679 int n = inputStream.available();
680
681 // System.err.printf("available() %d\n", n); System.err.flush();
682 if (utf8) {
683 if (readBufferUTF8.length < n) {
684 // The buffer wasn't big enough, make it huger
685 int newSizeHalf = Math.max(readBufferUTF8.length,
686 n);
687
688 readBufferUTF8 = new char[newSizeHalf * 2];
689 }
690 } else {
691 if (readBuffer.length < n) {
692 // The buffer wasn't big enough, make it huger
693 int newSizeHalf = Math.max(readBuffer.length, n);
694 readBuffer = new byte[newSizeHalf * 2];
695 }
696 }
697 if (n == 0) {
698 try {
699 Thread.sleep(2);
700 } catch (InterruptedException e) {
701 // SQUASH
702 }
703 continue;
704 }
705
706 int rc = -1;
707 try {
708 if (utf8) {
709 rc = input.read(readBufferUTF8, 0,
710 readBufferUTF8.length);
711 } else {
712 rc = inputStream.read(readBuffer, 0,
713 readBuffer.length);
714 }
715 } catch (ReadTimeoutException e) {
716 rc = 0;
717 }
718
719 // System.err.printf("read() %d\n", rc); System.err.flush();
720 if (rc == -1) {
721 // This is EOF
722 done = true;
723 } else {
724 // Don't step on UI events
725 synchronized (this) {
726 for (int i = 0; i < rc; i++) {
727 int ch = 0;
728 if (utf8) {
729 ch = readBufferUTF8[i];
730 } else {
731 ch = readBuffer[i];
732 }
733
734 consume((char) ch);
735 }
736 }
737 // Permit my enclosing UI to know that I updated.
738 if (displayListener != null) {
739 displayListener.displayChanged();
740 }
741 }
742 // System.err.println("end while loop"); System.err.flush();
743 } catch (IOException e) {
744 done = true;
745
746 // This is an unusual case. We want to see the stack trace,
747 // but it is related to the spawned process rather than the
748 // actual UI. We will generate the stack trace, and consume
749 // it as though it was emitted by the shell.
750 CharArrayWriter writer= new CharArrayWriter();
751 // Send a ST and RIS to clear the emulator state.
752 try {
753 writer.write("\033\\\033c");
754 writer.write("\n-----------------------------------\n");
755 e.printStackTrace(new PrintWriter(writer));
756 writer.write("\n-----------------------------------\n");
757 } catch (IOException e2) {
758 // SQUASH
759 }
760 char [] stackTrace = writer.toCharArray();
761 for (int i = 0; i < stackTrace.length; i++) {
762 if (stackTrace[i] == '\n') {
763 consume('\r');
764 }
765 consume(stackTrace[i]);
766 }
767 }
768
769 } // while ((done == false) && (stopReaderThread == false))
770
771 // Let the rest of the world know that I am done.
772 stopReaderThread = true;
773
774 try {
775 inputStream.cancelRead();
776 inputStream.close();
777 inputStream = null;
778 } catch (IOException e) {
779 // SQUASH
780 }
781 try {
782 input.close();
783 input = null;
784 } catch (IOException e) {
785 // SQUASH
786 }
787
788 // Permit my enclosing UI to know that I updated.
789 if (displayListener != null) {
790 displayListener.displayChanged();
791 }
792
793 // System.err.println("*** run() exiting..."); System.err.flush();
794 }
795
796 // ------------------------------------------------------------------------
797 // ECMA48 -----------------------------------------------------------------
798 // ------------------------------------------------------------------------
799
800 /**
801 * Return the proper primary Device Attributes string.
802 *
803 * @return string to send to remote side that is appropriate for the
804 * this.type
805 */
806 private String deviceTypeResponse() {
807 switch (type) {
808 case VT100:
809 // "I am a VT100 with advanced video option" (often VT102)
810 return "\033[?1;2c";
811
812 case VT102:
813 // "I am a VT102"
814 return "\033[?6c";
815
816 case VT220:
817 case XTERM:
818 // "I am a VT220" - 7 bit version
819 if (!s8c1t) {
820 return "\033[?62;1;6c";
821 }
822 // "I am a VT220" - 8 bit version
823 return "\u009b?62;1;6c";
824 default:
825 throw new IllegalArgumentException("Invalid device type: " + type);
826 }
827 }
828
829 /**
830 * Return the proper TERM environment variable for this device type.
831 *
832 * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
833 * @return "vt100", "xterm", etc.
834 */
835 public static String deviceTypeTerm(final DeviceType deviceType) {
836 switch (deviceType) {
837 case VT100:
838 return "vt100";
839
840 case VT102:
841 return "vt102";
842
843 case VT220:
844 return "vt220";
845
846 case XTERM:
847 return "xterm";
848
849 default:
850 throw new IllegalArgumentException("Invalid device type: "
851 + deviceType);
852 }
853 }
854
855 /**
856 * Return the proper LANG for this device type. Only XTERM devices know
857 * about UTF-8, the others are defined by their standard to be either
858 * 7-bit or 8-bit characters only.
859 *
860 * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
861 * @param baseLang a base language without UTF-8 flag such as "C" or
862 * "en_US"
863 * @return "en_US", "en_US.UTF-8", etc.
864 */
865 public static String deviceTypeLang(final DeviceType deviceType,
866 final String baseLang) {
867
868 switch (deviceType) {
869
870 case VT100:
871 case VT102:
872 case VT220:
873 return baseLang;
874
875 case XTERM:
876 return baseLang + ".UTF-8";
877
878 default:
879 throw new IllegalArgumentException("Invalid device type: "
880 + deviceType);
881 }
882 }
883
884 /**
885 * Write a string directly to the remote side.
886 *
887 * @param str string to send
888 */
889 public void writeRemote(final String str) {
890 if (stopReaderThread) {
891 // Reader hit EOF, bail out now.
892 close();
893 return;
894 }
895
896 // System.err.printf("writeRemote() '%s'\n", str);
897
898 switch (type) {
899 case VT100:
900 case VT102:
901 case VT220:
902 if (outputStream == null) {
903 return;
904 }
905 try {
906 outputStream.flush();
907 for (int i = 0; i < str.length(); i++) {
908 outputStream.write(str.charAt(i));
909 }
910 outputStream.flush();
911 } catch (IOException e) {
912 // Assume EOF
913 close();
914 }
915 break;
916 case XTERM:
917 if (output == null) {
918 return;
919 }
920 try {
921 output.flush();
922 output.write(str);
923 output.flush();
924 } catch (IOException e) {
925 // Assume EOF
926 close();
927 }
928 break;
929 default:
930 throw new IllegalArgumentException("Invalid device type: " + type);
931 }
932 }
933
934 /**
935 * Close the input and output streams and stop the reader thread. Note
936 * that it is safe to call this multiple times.
937 */
938 public final void close() {
939
940 // Tell the reader thread to stop looking at input. It will close
941 // the input streams.
942 if (stopReaderThread == false) {
943 stopReaderThread = true;
944 try {
945 readerThread.join(1000);
946 } catch (InterruptedException e) {
947 // SQUASH
948 }
949 }
950
951 // Now close the output stream.
952 switch (type) {
953 case VT100:
954 case VT102:
955 case VT220:
956 if (outputStream != null) {
957 try {
958 outputStream.close();
959 } catch (IOException e) {
960 // SQUASH
961 }
962 outputStream = null;
963 }
964 break;
965 case XTERM:
966 if (outputStream != null) {
967 try {
968 outputStream.close();
969 } catch (IOException e) {
970 // SQUASH
971 }
972 outputStream = null;
973 }
974 if (output != null) {
975 try {
976 output.close();
977 } catch (IOException e) {
978 // SQUASH
979 }
980 output = null;
981 }
982 break;
983 default:
984 throw new IllegalArgumentException("Invalid device type: " +
985 type);
986 }
987 }
988
989 /**
990 * See if the reader thread is still running.
991 *
992 * @return if true, we are still connected to / reading from the remote
993 * side
994 */
995 public final boolean isReading() {
996 return (!stopReaderThread);
997 }
998
999 /**
1000 * Obtain a new blank display line for an external user
1001 * (e.g. TTerminalWindow).
1002 *
1003 * @return new blank line
1004 */
1005 public final DisplayLine getBlankDisplayLine() {
1006 return new DisplayLine(currentState.attr);
1007 }
1008
1009 /**
1010 * Get the scrollback buffer.
1011 *
1012 * @return the scrollback buffer
1013 */
1014 public final List<DisplayLine> getScrollbackBuffer() {
1015 return scrollback;
1016 }
1017
1018 /**
1019 * Get the display buffer.
1020 *
1021 * @return the display buffer
1022 */
1023 public final List<DisplayLine> getDisplayBuffer() {
1024 return display;
1025 }
1026
1027 /**
1028 * Get the display width.
1029 *
1030 * @return the width (usually 80 or 132)
1031 */
1032 public final int getWidth() {
1033 return width;
1034 }
1035
1036 /**
1037 * Set the display width.
1038 *
1039 * @param width the new width
1040 */
1041 public final void setWidth(final int width) {
1042 this.width = width;
1043 rightMargin = width - 1;
1044 if (currentState.cursorX >= width) {
1045 currentState.cursorX = width - 1;
1046 }
1047 if (savedState.cursorX >= width) {
1048 savedState.cursorX = width - 1;
1049 }
1050 }
1051
1052 /**
1053 * Get the display height.
1054 *
1055 * @return the height (usually 24)
1056 */
1057 public final int getHeight() {
1058 return height;
1059 }
1060
1061 /**
1062 * Set the display height.
1063 *
1064 * @param height the new height
1065 */
1066 public final void setHeight(final int height) {
1067 int delta = height - this.height;
1068 this.height = height;
1069 scrollRegionBottom += delta;
1070 if (scrollRegionBottom < 0) {
1071 scrollRegionBottom = height;
1072 }
1073 if (scrollRegionTop >= scrollRegionBottom) {
1074 scrollRegionTop = 0;
1075 }
1076 if (currentState.cursorY >= height) {
1077 currentState.cursorY = height - 1;
1078 }
1079 if (savedState.cursorY >= height) {
1080 savedState.cursorY = height - 1;
1081 }
1082 while (display.size() < height) {
1083 DisplayLine line = new DisplayLine(currentState.attr);
1084 line.setReverseColor(reverseVideo);
1085 display.add(line);
1086 }
1087 while (display.size() > height) {
1088 scrollback.add(display.remove(0));
1089 }
1090 }
1091
1092 /**
1093 * Get visible cursor flag.
1094 *
1095 * @return if true, the cursor is visible
1096 */
1097 public final boolean isCursorVisible() {
1098 return cursorVisible;
1099 }
1100
1101 /**
1102 * Get the screen title as set by the xterm OSC sequence. Lots of
1103 * applications send a screenTitle regardless of whether it is an xterm
1104 * client or not.
1105 *
1106 * @return screen title
1107 */
1108 public final String getScreenTitle() {
1109 return screenTitle;
1110 }
1111
1112 /**
1113 * Get 132 columns value.
1114 *
1115 * @return if true, the terminal is in 132 column mode
1116 */
1117 public final boolean isColumns132() {
1118 return columns132;
1119 }
1120
1121 /**
1122 * Clear the CSI parameters and flags.
1123 */
1124 private void toGround() {
1125 csiParams.clear();
1126 collectBuffer = new StringBuilder(8);
1127 scanState = ScanState.GROUND;
1128 }
1129
1130 /**
1131 * Reset the tab stops list.
1132 */
1133 private void resetTabStops() {
1134 tabStops.clear();
1135 for (int i = 0; (i * 8) <= rightMargin; i++) {
1136 tabStops.add(Integer.valueOf(i * 8));
1137 }
1138 }
1139
1140 /**
1141 * Reset the 88- or 256-colors.
1142 */
1143 private void resetColors() {
1144 colors88 = new ArrayList<Integer>(256);
1145 for (int i = 0; i < 256; i++) {
1146 colors88.add(0);
1147 }
1148
1149 // Set default system colors.
1150 colors88.set(0, 0x00000000);
1151 colors88.set(1, 0x00a80000);
1152 colors88.set(2, 0x0000a800);
1153 colors88.set(3, 0x00a85400);
1154 colors88.set(4, 0x000000a8);
1155 colors88.set(5, 0x00a800a8);
1156 colors88.set(6, 0x0000a8a8);
1157 colors88.set(7, 0x00a8a8a8);
1158
1159 colors88.set(8, 0x00545454);
1160 colors88.set(9, 0x00fc5454);
1161 colors88.set(10, 0x0054fc54);
1162 colors88.set(11, 0x00fcfc54);
1163 colors88.set(12, 0x005454fc);
1164 colors88.set(13, 0x00fc54fc);
1165 colors88.set(14, 0x0054fcfc);
1166 colors88.set(15, 0x00fcfcfc);
1167 }
1168
1169 /**
1170 * Get the RGB value of one of the indexed colors.
1171 *
1172 * @param index the color index
1173 * @return the RGB value
1174 */
1175 private int get88Color(final int index) {
1176 // System.err.print("get88Color: " + index);
1177 if ((index < 0) || (index > colors88.size())) {
1178 // System.err.println(" -- UNKNOWN");
1179 return 0;
1180 }
1181 // System.err.printf(" %08x\n", colors88.get(index));
1182 return colors88.get(index);
1183 }
1184
1185 /**
1186 * Set one of the indexed colors to a color specification.
1187 *
1188 * @param index the color index
1189 * @param spec the specification, typically something like "rgb:aa/bb/cc"
1190 */
1191 private void set88Color(final int index, final String spec) {
1192 // System.err.println("set88Color: " + index + " '" + spec + "'");
1193
1194 if ((index < 0) || (index > colors88.size())) {
1195 return;
1196 }
1197 if (spec.startsWith("rgb:")) {
1198 String [] rgbTokens = spec.substring(4).split("/");
1199 if (rgbTokens.length == 3) {
1200 try {
1201 int rgb = (Integer.parseInt(rgbTokens[0], 16) << 16);
1202 rgb |= Integer.parseInt(rgbTokens[1], 16) << 8;
1203 rgb |= Integer.parseInt(rgbTokens[2], 16);
1204 // System.err.printf(" set to %08x\n", rgb);
1205 colors88.set(index, rgb);
1206 } catch (NumberFormatException e) {
1207 // SQUASH
1208 }
1209 }
1210 return;
1211 }
1212
1213 if (spec.toLowerCase().equals("black")) {
1214 colors88.set(index, 0x00000000);
1215 } else if (spec.toLowerCase().equals("red")) {
1216 colors88.set(index, 0x00a80000);
1217 } else if (spec.toLowerCase().equals("green")) {
1218 colors88.set(index, 0x0000a800);
1219 } else if (spec.toLowerCase().equals("yellow")) {
1220 colors88.set(index, 0x00a85400);
1221 } else if (spec.toLowerCase().equals("blue")) {
1222 colors88.set(index, 0x000000a8);
1223 } else if (spec.toLowerCase().equals("magenta")) {
1224 colors88.set(index, 0x00a800a8);
1225 } else if (spec.toLowerCase().equals("cyan")) {
1226 colors88.set(index, 0x0000a8a8);
1227 } else if (spec.toLowerCase().equals("white")) {
1228 colors88.set(index, 0x00a8a8a8);
1229 }
1230
1231 }
1232
1233 /**
1234 * Reset the emulation state.
1235 */
1236 private void reset() {
1237
1238 currentState = new SaveableState();
1239 savedState = new SaveableState();
1240 scanState = ScanState.GROUND;
1241 width = 80;
1242 height = 24;
1243 scrollRegionTop = 0;
1244 scrollRegionBottom = height - 1;
1245 rightMargin = width - 1;
1246 newLineMode = false;
1247 arrowKeyMode = ArrowKeyMode.ANSI;
1248 keypadMode = KeypadMode.Numeric;
1249 wrapLineFlag = false;
1250 if (displayListener != null) {
1251 width = displayListener.getDisplayWidth();
1252 height = displayListener.getDisplayHeight();
1253 rightMargin = width - 1;
1254 }
1255
1256 // Flags
1257 shiftOut = false;
1258 vt52Mode = false;
1259 insertMode = false;
1260 columns132 = false;
1261 newLineMode = false;
1262 reverseVideo = false;
1263 fullDuplex = true;
1264 cursorVisible = true;
1265
1266 // VT220
1267 singleshift = Singleshift.NONE;
1268 s8c1t = false;
1269 printerControllerMode = false;
1270
1271 // XTERM
1272 mouseProtocol = MouseProtocol.OFF;
1273 mouseEncoding = MouseEncoding.X10;
1274
1275 // Tab stops
1276 resetTabStops();
1277
1278 // Reset extra colors
1279 resetColors();
1280
1281 // Clear CSI stuff
1282 toGround();
1283 }
1284
1285 /**
1286 * Append a new line to the bottom of the display, adding lines off the
1287 * top to the scrollback buffer.
1288 */
1289 private void newDisplayLine() {
1290 // Scroll the top line off into the scrollback buffer
1291 scrollback.add(display.get(0));
1292 if (scrollback.size() > maxScrollback) {
1293 scrollback.remove(0);
1294 scrollback.trimToSize();
1295 }
1296 display.remove(0);
1297 display.trimToSize();
1298 DisplayLine line = new DisplayLine(currentState.attr);
1299 line.setReverseColor(reverseVideo);
1300 display.add(line);
1301 }
1302
1303 /**
1304 * Wraps the current line.
1305 */
1306 private void wrapCurrentLine() {
1307 if (currentState.cursorY == height - 1) {
1308 newDisplayLine();
1309 }
1310 if (currentState.cursorY < height - 1) {
1311 currentState.cursorY++;
1312 }
1313 currentState.cursorX = 0;
1314 }
1315
1316 /**
1317 * Handle a carriage return.
1318 */
1319 private void carriageReturn() {
1320 currentState.cursorX = 0;
1321 wrapLineFlag = false;
1322 }
1323
1324 /**
1325 * Reverse the color of the visible display.
1326 */
1327 private void invertDisplayColors() {
1328 for (DisplayLine line: display) {
1329 line.setReverseColor(!line.isReverseColor());
1330 }
1331 }
1332
1333 /**
1334 * Handle a linefeed.
1335 */
1336 private void linefeed() {
1337
1338 if (currentState.cursorY < scrollRegionBottom) {
1339 // Increment screen y
1340 currentState.cursorY++;
1341 } else {
1342
1343 // Screen y does not increment
1344
1345 /*
1346 * Two cases: either we're inside a scrolling region or not. If
1347 * the scrolling region bottom is the bottom of the screen, then
1348 * push the top line into the buffer. Else scroll the scrolling
1349 * region up.
1350 */
1351 if ((scrollRegionBottom == height - 1) && (scrollRegionTop == 0)) {
1352
1353 // We're at the bottom of the scroll region, AND the scroll
1354 // region is the entire screen.
1355
1356 // New line
1357 newDisplayLine();
1358
1359 } else {
1360 // We're at the bottom of the scroll region, AND the scroll
1361 // region is NOT the entire screen.
1362 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
1363 }
1364 }
1365
1366 if (newLineMode) {
1367 currentState.cursorX = 0;
1368 }
1369 wrapLineFlag = false;
1370 }
1371
1372 /**
1373 * Prints one character to the display buffer.
1374 *
1375 * @param ch character to display
1376 */
1377 private void printCharacter(final char ch) {
1378 int rightMargin = this.rightMargin;
1379
1380 // Check if we have double-width, and if so chop at 40/66 instead of
1381 // 80/132
1382 if (display.get(currentState.cursorY).isDoubleWidth()) {
1383 rightMargin = ((rightMargin + 1) / 2) - 1;
1384 }
1385
1386 // Check the unusually-complicated line wrapping conditions...
1387 if (currentState.cursorX == rightMargin) {
1388
1389 if (currentState.lineWrap == true) {
1390 /*
1391 * This case happens when: the cursor was already on the
1392 * right margin (either through printing or by an explicit
1393 * placement command), and a character was printed.
1394 *
1395 * The line wraps only when a new character arrives AND the
1396 * cursor is already on the right margin AND has placed a
1397 * character in its cell. Easier to see than to explain.
1398 */
1399 if (wrapLineFlag == false) {
1400 /*
1401 * This block marks the case that we are in the margin
1402 * and the first character has been received and printed.
1403 */
1404 wrapLineFlag = true;
1405 } else {
1406 /*
1407 * This block marks the case that we are in the margin
1408 * and the second character has been received and
1409 * printed.
1410 */
1411 wrapLineFlag = false;
1412 wrapCurrentLine();
1413 }
1414 }
1415 } else if (currentState.cursorX <= rightMargin) {
1416 /*
1417 * This is the normal case: a character came in and was printed
1418 * to the left of the right margin column.
1419 */
1420
1421 // Turn off VT100 special-case flag
1422 wrapLineFlag = false;
1423 }
1424
1425 // "Print" the character
1426 Cell newCell = new Cell(ch);
1427 CellAttributes newCellAttributes = (CellAttributes) newCell;
1428 newCellAttributes.setTo(currentState.attr);
1429 DisplayLine line = display.get(currentState.cursorY);
1430 // Insert mode special case
1431 if (insertMode == true) {
1432 line.insert(currentState.cursorX, newCell);
1433 } else {
1434 // Replace an existing character
1435 line.replace(currentState.cursorX, newCell);
1436 }
1437
1438 // Increment horizontal
1439 if (wrapLineFlag == false) {
1440 currentState.cursorX++;
1441 if (currentState.cursorX > rightMargin) {
1442 currentState.cursorX--;
1443 }
1444 }
1445 }
1446
1447 /**
1448 * Translate the mouse event to a VT100, VT220, or XTERM sequence and
1449 * send to the remote side.
1450 *
1451 * @param mouse mouse event received from the local user
1452 */
1453 public void mouse(final TMouseEvent mouse) {
1454
1455 /*
1456 System.err.printf("mouse(): protocol %s encoding %s mouse %s\n",
1457 mouseProtocol, mouseEncoding, mouse);
1458 */
1459
1460 if (mouseEncoding == MouseEncoding.X10) {
1461 // We will support X10 but only for (160,94) and smaller.
1462 if ((mouse.getX() >= 160) || (mouse.getY() >= 94)) {
1463 return;
1464 }
1465 }
1466
1467 switch (mouseProtocol) {
1468
1469 case OFF:
1470 // Do nothing
1471 return;
1472
1473 case X10:
1474 // Only report button presses
1475 if (mouse.getType() != TMouseEvent.Type.MOUSE_DOWN) {
1476 return;
1477 }
1478 break;
1479
1480 case NORMAL:
1481 // Only report button presses and releases
1482 if ((mouse.getType() != TMouseEvent.Type.MOUSE_DOWN)
1483 && (mouse.getType() != TMouseEvent.Type.MOUSE_UP)
1484 ) {
1485 return;
1486 }
1487 break;
1488
1489 case BUTTONEVENT:
1490 /*
1491 * Only report button presses, button releases, and motions that
1492 * have a button down (i.e. drag-and-drop).
1493 */
1494 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1495 if (!mouse.isMouse1()
1496 && !mouse.isMouse2()
1497 && !mouse.isMouse3()
1498 && !mouse.isMouseWheelUp()
1499 && !mouse.isMouseWheelDown()
1500 ) {
1501 return;
1502 }
1503 }
1504 break;
1505
1506 case ANYEVENT:
1507 // Report everything
1508 break;
1509 }
1510
1511 // Now encode the event
1512 StringBuilder sb = new StringBuilder(6);
1513 if (mouseEncoding == MouseEncoding.SGR) {
1514 sb.append((char) 0x1B);
1515 sb.append("[<");
1516
1517 if (mouse.isMouse1()) {
1518 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1519 sb.append("32;");
1520 } else {
1521 sb.append("0;");
1522 }
1523 } else if (mouse.isMouse2()) {
1524 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1525 sb.append("33;");
1526 } else {
1527 sb.append("1;");
1528 }
1529 } else if (mouse.isMouse3()) {
1530 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1531 sb.append("34;");
1532 } else {
1533 sb.append("2;");
1534 }
1535 } else if (mouse.isMouseWheelUp()) {
1536 sb.append("64;");
1537 } else if (mouse.isMouseWheelDown()) {
1538 sb.append("65;");
1539 } else {
1540 // This is motion with no buttons down.
1541 sb.append("35;");
1542 }
1543
1544 sb.append(String.format("%d;%d", mouse.getX() + 1,
1545 mouse.getY() + 1));
1546
1547 if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
1548 sb.append("m");
1549 } else {
1550 sb.append("M");
1551 }
1552
1553 } else {
1554 // X10 and UTF8 encodings
1555 sb.append((char) 0x1B);
1556 sb.append('[');
1557 sb.append('M');
1558 if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
1559 sb.append((char) (0x03 + 32));
1560 } else if (mouse.isMouse1()) {
1561 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1562 sb.append((char) (0x00 + 32 + 32));
1563 } else {
1564 sb.append((char) (0x00 + 32));
1565 }
1566 } else if (mouse.isMouse2()) {
1567 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1568 sb.append((char) (0x01 + 32 + 32));
1569 } else {
1570 sb.append((char) (0x01 + 32));
1571 }
1572 } else if (mouse.isMouse3()) {
1573 if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
1574 sb.append((char) (0x02 + 32 + 32));
1575 } else {
1576 sb.append((char) (0x02 + 32));
1577 }
1578 } else if (mouse.isMouseWheelUp()) {
1579 sb.append((char) (0x04 + 64));
1580 } else if (mouse.isMouseWheelDown()) {
1581 sb.append((char) (0x05 + 64));
1582 } else {
1583 // This is motion with no buttons down.
1584 sb.append((char) (0x03 + 32));
1585 }
1586
1587 sb.append((char) (mouse.getX() + 33));
1588 sb.append((char) (mouse.getY() + 33));
1589 }
1590
1591 // System.err.printf("Would write: \'%s\'\n", sb.toString());
1592 writeRemote(sb.toString());
1593 }
1594
1595 /**
1596 * Translate the keyboard press to a VT100, VT220, or XTERM sequence and
1597 * send to the remote side.
1598 *
1599 * @param keypress keypress received from the local user
1600 */
1601 public void keypress(final TKeypress keypress) {
1602 writeRemote(keypressToString(keypress));
1603 }
1604
1605 /**
1606 * Build one of the complex xterm keystroke sequences, storing the result in
1607 * xterm_keystroke_buffer.
1608 *
1609 * @param ss3 the prefix to use based on VT100 state.
1610 * @param first the first character, usually a number.
1611 * @param first the last character, one of the following: ~ A B C D F H
1612 * @param ctrl whether or not ctrl is down
1613 * @param alt whether or not alt is down
1614 * @param shift whether or not shift is down
1615 * @return the buffer with the full key sequence
1616 */
1617 private String xtermBuildKeySequence(final String ss3, final char first,
1618 final char last, boolean ctrl, boolean alt, boolean shift) {
1619
1620 StringBuilder sb = new StringBuilder(ss3);
1621 if ((last == '~') || (ctrl == true) || (alt == true)
1622 || (shift == true)
1623 ) {
1624 sb.append(first);
1625 if ( (ctrl == false) && (alt == false) && (shift == true)) {
1626 sb.append(";2");
1627 } else if ((ctrl == false) && (alt == true) && (shift == false)) {
1628 sb.append(";3");
1629 } else if ((ctrl == false) && (alt == true) && (shift == true)) {
1630 sb.append(";4");
1631 } else if ((ctrl == true) && (alt == false) && (shift == false)) {
1632 sb.append(";5");
1633 } else if ((ctrl == true) && (alt == false) && (shift == true)) {
1634 sb.append(";6");
1635 } else if ((ctrl == true) && (alt == true) && (shift == false)) {
1636 sb.append(";7");
1637 } else if ((ctrl == true) && (alt == true) && (shift == true)) {
1638 sb.append(";8");
1639 }
1640 }
1641 sb.append(last);
1642 return sb.toString();
1643 }
1644
1645 /**
1646 * Translate the keyboard press to a VT100, VT220, or XTERM sequence.
1647 *
1648 * @param keypress keypress received from the local user
1649 * @return string to transmit to the remote side
1650 */
1651 @SuppressWarnings("fallthrough")
1652 private String keypressToString(final TKeypress keypress) {
1653
1654 if ((fullDuplex == false) && (!keypress.isFnKey())) {
1655 /*
1656 * If this is a control character, process it like it came from
1657 * the remote side.
1658 */
1659 if (keypress.getChar() < 0x20) {
1660 handleControlChar(keypress.getChar());
1661 } else {
1662 // Local echo for everything else
1663 printCharacter(keypress.getChar());
1664 }
1665 }
1666
1667 if ((newLineMode == true) && (keypress.equals(kbEnter))) {
1668 // NLM: send CRLF
1669 return "\015\012";
1670 }
1671
1672 // Handle control characters
1673 if ((keypress.isCtrl()) && (!keypress.isFnKey())) {
1674 StringBuilder sb = new StringBuilder();
1675 char ch = keypress.getChar();
1676 ch -= 0x40;
1677 sb.append(ch);
1678 return sb.toString();
1679 }
1680
1681 // Handle alt characters
1682 if ((keypress.isAlt()) && (!keypress.isFnKey())) {
1683 StringBuilder sb = new StringBuilder("\033");
1684 char ch = keypress.getChar();
1685 sb.append(ch);
1686 return sb.toString();
1687 }
1688
1689 if (keypress.equals(kbBackspaceDel)) {
1690 switch (type) {
1691 case VT100:
1692 return "\010";
1693 case VT102:
1694 return "\010";
1695 case VT220:
1696 return "\177";
1697 case XTERM:
1698 return "\177";
1699 }
1700 }
1701
1702 if (keypress.equalsWithoutModifiers(kbLeft)) {
1703 switch (type) {
1704 case XTERM:
1705 switch (arrowKeyMode) {
1706 case ANSI:
1707 return xtermBuildKeySequence("\033[", '1', 'D',
1708 keypress.isCtrl(), keypress.isAlt(),
1709 keypress.isShift());
1710 case VT52:
1711 return xtermBuildKeySequence("\033", '1', 'D',
1712 keypress.isCtrl(), keypress.isAlt(),
1713 keypress.isShift());
1714 case VT100:
1715 return xtermBuildKeySequence("\033O", '1', 'D',
1716 keypress.isCtrl(), keypress.isAlt(),
1717 keypress.isShift());
1718 }
1719 default:
1720 switch (arrowKeyMode) {
1721 case ANSI:
1722 return "\033[D";
1723 case VT52:
1724 return "\033D";
1725 case VT100:
1726 return "\033OD";
1727 }
1728 }
1729 }
1730
1731 if (keypress.equalsWithoutModifiers(kbRight)) {
1732 switch (type) {
1733 case XTERM:
1734 switch (arrowKeyMode) {
1735 case ANSI:
1736 return xtermBuildKeySequence("\033[", '1', 'C',
1737 keypress.isCtrl(), keypress.isAlt(),
1738 keypress.isShift());
1739 case VT52:
1740 return xtermBuildKeySequence("\033", '1', 'C',
1741 keypress.isCtrl(), keypress.isAlt(),
1742 keypress.isShift());
1743 case VT100:
1744 return xtermBuildKeySequence("\033O", '1', 'C',
1745 keypress.isCtrl(), keypress.isAlt(),
1746 keypress.isShift());
1747 }
1748 default:
1749 switch (arrowKeyMode) {
1750 case ANSI:
1751 return "\033[C";
1752 case VT52:
1753 return "\033C";
1754 case VT100:
1755 return "\033OC";
1756 }
1757 }
1758 }
1759
1760 if (keypress.equalsWithoutModifiers(kbUp)) {
1761 switch (type) {
1762 case XTERM:
1763 switch (arrowKeyMode) {
1764 case ANSI:
1765 return xtermBuildKeySequence("\033[", '1', 'A',
1766 keypress.isCtrl(), keypress.isAlt(),
1767 keypress.isShift());
1768 case VT52:
1769 return xtermBuildKeySequence("\033", '1', 'A',
1770 keypress.isCtrl(), keypress.isAlt(),
1771 keypress.isShift());
1772 case VT100:
1773 return xtermBuildKeySequence("\033O", '1', 'A',
1774 keypress.isCtrl(), keypress.isAlt(),
1775 keypress.isShift());
1776 }
1777 default:
1778 switch (arrowKeyMode) {
1779 case ANSI:
1780 return "\033[A";
1781 case VT52:
1782 return "\033A";
1783 case VT100:
1784 return "\033OA";
1785 }
1786 }
1787 }
1788
1789 if (keypress.equalsWithoutModifiers(kbDown)) {
1790 switch (type) {
1791 case XTERM:
1792 switch (arrowKeyMode) {
1793 case ANSI:
1794 return xtermBuildKeySequence("\033[", '1', 'B',
1795 keypress.isCtrl(), keypress.isAlt(),
1796 keypress.isShift());
1797 case VT52:
1798 return xtermBuildKeySequence("\033", '1', 'B',
1799 keypress.isCtrl(), keypress.isAlt(),
1800 keypress.isShift());
1801 case VT100:
1802 return xtermBuildKeySequence("\033O", '1', 'B',
1803 keypress.isCtrl(), keypress.isAlt(),
1804 keypress.isShift());
1805 }
1806 default:
1807 switch (arrowKeyMode) {
1808 case ANSI:
1809 return "\033[B";
1810 case VT52:
1811 return "\033B";
1812 case VT100:
1813 return "\033OB";
1814 }
1815 }
1816 }
1817
1818 if (keypress.equalsWithoutModifiers(kbHome)) {
1819 switch (type) {
1820 case XTERM:
1821 switch (arrowKeyMode) {
1822 case ANSI:
1823 return xtermBuildKeySequence("\033[", '1', 'H',
1824 keypress.isCtrl(), keypress.isAlt(),
1825 keypress.isShift());
1826 case VT52:
1827 return xtermBuildKeySequence("\033", '1', 'H',
1828 keypress.isCtrl(), keypress.isAlt(),
1829 keypress.isShift());
1830 case VT100:
1831 return xtermBuildKeySequence("\033O", '1', 'H',
1832 keypress.isCtrl(), keypress.isAlt(),
1833 keypress.isShift());
1834 }
1835 default:
1836 switch (arrowKeyMode) {
1837 case ANSI:
1838 return "\033[H";
1839 case VT52:
1840 return "\033H";
1841 case VT100:
1842 return "\033OH";
1843 }
1844 }
1845 }
1846
1847 if (keypress.equalsWithoutModifiers(kbEnd)) {
1848 switch (type) {
1849 case XTERM:
1850 switch (arrowKeyMode) {
1851 case ANSI:
1852 return xtermBuildKeySequence("\033[", '1', 'F',
1853 keypress.isCtrl(), keypress.isAlt(),
1854 keypress.isShift());
1855 case VT52:
1856 return xtermBuildKeySequence("\033", '1', 'F',
1857 keypress.isCtrl(), keypress.isAlt(),
1858 keypress.isShift());
1859 case VT100:
1860 return xtermBuildKeySequence("\033O", '1', 'F',
1861 keypress.isCtrl(), keypress.isAlt(),
1862 keypress.isShift());
1863 }
1864 default:
1865 switch (arrowKeyMode) {
1866 case ANSI:
1867 return "\033[F";
1868 case VT52:
1869 return "\033F";
1870 case VT100:
1871 return "\033OF";
1872 }
1873 }
1874 }
1875
1876 if (keypress.equals(kbF1)) {
1877 // PF1
1878 if (vt52Mode) {
1879 return "\033P";
1880 }
1881 return "\033OP";
1882 }
1883
1884 if (keypress.equals(kbF2)) {
1885 // PF2
1886 if (vt52Mode) {
1887 return "\033Q";
1888 }
1889 return "\033OQ";
1890 }
1891
1892 if (keypress.equals(kbF3)) {
1893 // PF3
1894 if (vt52Mode) {
1895 return "\033R";
1896 }
1897 return "\033OR";
1898 }
1899
1900 if (keypress.equals(kbF4)) {
1901 // PF4
1902 if (vt52Mode) {
1903 return "\033S";
1904 }
1905 return "\033OS";
1906 }
1907
1908 if (keypress.equals(kbF5)) {
1909 switch (type) {
1910 case VT100:
1911 return "\033Ot";
1912 case VT102:
1913 return "\033Ot";
1914 case VT220:
1915 return "\033[15~";
1916 case XTERM:
1917 return "\033[15~";
1918 }
1919 }
1920
1921 if (keypress.equals(kbF6)) {
1922 switch (type) {
1923 case VT100:
1924 return "\033Ou";
1925 case VT102:
1926 return "\033Ou";
1927 case VT220:
1928 return "\033[17~";
1929 case XTERM:
1930 return "\033[17~";
1931 }
1932 }
1933
1934 if (keypress.equals(kbF7)) {
1935 switch (type) {
1936 case VT100:
1937 return "\033Ov";
1938 case VT102:
1939 return "\033Ov";
1940 case VT220:
1941 return "\033[18~";
1942 case XTERM:
1943 return "\033[18~";
1944 }
1945 }
1946
1947 if (keypress.equals(kbF8)) {
1948 switch (type) {
1949 case VT100:
1950 return "\033Ol";
1951 case VT102:
1952 return "\033Ol";
1953 case VT220:
1954 return "\033[19~";
1955 case XTERM:
1956 return "\033[19~";
1957 }
1958 }
1959
1960 if (keypress.equals(kbF9)) {
1961 switch (type) {
1962 case VT100:
1963 return "\033Ow";
1964 case VT102:
1965 return "\033Ow";
1966 case VT220:
1967 return "\033[20~";
1968 case XTERM:
1969 return "\033[20~";
1970 }
1971 }
1972
1973 if (keypress.equals(kbF10)) {
1974 switch (type) {
1975 case VT100:
1976 return "\033Ox";
1977 case VT102:
1978 return "\033Ox";
1979 case VT220:
1980 return "\033[21~";
1981 case XTERM:
1982 return "\033[21~";
1983 }
1984 }
1985
1986 if (keypress.equals(kbF11)) {
1987 return "\033[23~";
1988 }
1989
1990 if (keypress.equals(kbF12)) {
1991 return "\033[24~";
1992 }
1993
1994 if (keypress.equals(kbShiftF1)) {
1995 // Shifted PF1
1996 if (vt52Mode) {
1997 return "\0332P";
1998 }
1999 if (type == DeviceType.XTERM) {
2000 return "\0331;2P";
2001 }
2002 return "\033O2P";
2003 }
2004
2005 if (keypress.equals(kbShiftF2)) {
2006 // Shifted PF2
2007 if (vt52Mode) {
2008 return "\0332Q";
2009 }
2010 if (type == DeviceType.XTERM) {
2011 return "\0331;2Q";
2012 }
2013 return "\033O2Q";
2014 }
2015
2016 if (keypress.equals(kbShiftF3)) {
2017 // Shifted PF3
2018 if (vt52Mode) {
2019 return "\0332R";
2020 }
2021 if (type == DeviceType.XTERM) {
2022 return "\0331;2R";
2023 }
2024 return "\033O2R";
2025 }
2026
2027 if (keypress.equals(kbShiftF4)) {
2028 // Shifted PF4
2029 if (vt52Mode) {
2030 return "\0332S";
2031 }
2032 if (type == DeviceType.XTERM) {
2033 return "\0331;2S";
2034 }
2035 return "\033O2S";
2036 }
2037
2038 if (keypress.equals(kbShiftF5)) {
2039 // Shifted F5
2040 return "\033[15;2~";
2041 }
2042
2043 if (keypress.equals(kbShiftF6)) {
2044 // Shifted F6
2045 return "\033[17;2~";
2046 }
2047
2048 if (keypress.equals(kbShiftF7)) {
2049 // Shifted F7
2050 return "\033[18;2~";
2051 }
2052
2053 if (keypress.equals(kbShiftF8)) {
2054 // Shifted F8
2055 return "\033[19;2~";
2056 }
2057
2058 if (keypress.equals(kbShiftF9)) {
2059 // Shifted F9
2060 return "\033[20;2~";
2061 }
2062
2063 if (keypress.equals(kbShiftF10)) {
2064 // Shifted F10
2065 return "\033[21;2~";
2066 }
2067
2068 if (keypress.equals(kbShiftF11)) {
2069 // Shifted F11
2070 return "\033[23;2~";
2071 }
2072
2073 if (keypress.equals(kbShiftF12)) {
2074 // Shifted F12
2075 return "\033[24;2~";
2076 }
2077
2078 if (keypress.equals(kbCtrlF1)) {
2079 // Control PF1
2080 if (vt52Mode) {
2081 return "\0335P";
2082 }
2083 if (type == DeviceType.XTERM) {
2084 return "\0331;5P";
2085 }
2086 return "\033O5P";
2087 }
2088
2089 if (keypress.equals(kbCtrlF2)) {
2090 // Control PF2
2091 if (vt52Mode) {
2092 return "\0335Q";
2093 }
2094 if (type == DeviceType.XTERM) {
2095 return "\0331;5Q";
2096 }
2097 return "\033O5Q";
2098 }
2099
2100 if (keypress.equals(kbCtrlF3)) {
2101 // Control PF3
2102 if (vt52Mode) {
2103 return "\0335R";
2104 }
2105 if (type == DeviceType.XTERM) {
2106 return "\0331;5R";
2107 }
2108 return "\033O5R";
2109 }
2110
2111 if (keypress.equals(kbCtrlF4)) {
2112 // Control PF4
2113 if (vt52Mode) {
2114 return "\0335S";
2115 }
2116 if (type == DeviceType.XTERM) {
2117 return "\0331;5S";
2118 }
2119 return "\033O5S";
2120 }
2121
2122 if (keypress.equals(kbCtrlF5)) {
2123 // Control F5
2124 return "\033[15;5~";
2125 }
2126
2127 if (keypress.equals(kbCtrlF6)) {
2128 // Control F6
2129 return "\033[17;5~";
2130 }
2131
2132 if (keypress.equals(kbCtrlF7)) {
2133 // Control F7
2134 return "\033[18;5~";
2135 }
2136
2137 if (keypress.equals(kbCtrlF8)) {
2138 // Control F8
2139 return "\033[19;5~";
2140 }
2141
2142 if (keypress.equals(kbCtrlF9)) {
2143 // Control F9
2144 return "\033[20;5~";
2145 }
2146
2147 if (keypress.equals(kbCtrlF10)) {
2148 // Control F10
2149 return "\033[21;5~";
2150 }
2151
2152 if (keypress.equals(kbCtrlF11)) {
2153 // Control F11
2154 return "\033[23;5~";
2155 }
2156
2157 if (keypress.equals(kbCtrlF12)) {
2158 // Control F12
2159 return "\033[24;5~";
2160 }
2161
2162 if (keypress.equalsWithoutModifiers(kbPgUp)) {
2163 switch (type) {
2164 case XTERM:
2165 return xtermBuildKeySequence("\033[", '5', '~',
2166 keypress.isCtrl(), keypress.isAlt(),
2167 keypress.isShift());
2168 default:
2169 return "\033[5~";
2170 }
2171 }
2172
2173 if (keypress.equalsWithoutModifiers(kbPgDn)) {
2174 switch (type) {
2175 case XTERM:
2176 return xtermBuildKeySequence("\033[", '6', '~',
2177 keypress.isCtrl(), keypress.isAlt(),
2178 keypress.isShift());
2179 default:
2180 return "\033[6~";
2181 }
2182 }
2183
2184 if (keypress.equalsWithoutModifiers(kbIns)) {
2185 switch (type) {
2186 case XTERM:
2187 return xtermBuildKeySequence("\033[", '2', '~',
2188 keypress.isCtrl(), keypress.isAlt(),
2189 keypress.isShift());
2190 default:
2191 return "\033[2~";
2192 }
2193 }
2194
2195 if (keypress.equalsWithoutModifiers(kbDel)) {
2196 switch (type) {
2197 case XTERM:
2198 return xtermBuildKeySequence("\033[", '3', '~',
2199 keypress.isCtrl(), keypress.isAlt(),
2200 keypress.isShift());
2201 default:
2202 // Delete sends real delete for VTxxx
2203 return "\177";
2204 }
2205 }
2206
2207 if (keypress.equals(kbEnter)) {
2208 return "\015";
2209 }
2210
2211 if (keypress.equals(kbEsc)) {
2212 return "\033";
2213 }
2214
2215 if (keypress.equals(kbAltEsc)) {
2216 return "\033\033";
2217 }
2218
2219 if (keypress.equals(kbTab)) {
2220 return "\011";
2221 }
2222
2223 if ((keypress.equalsWithoutModifiers(kbBackTab)) ||
2224 (keypress.equals(kbShiftTab))
2225 ) {
2226 switch (type) {
2227 case XTERM:
2228 return "\033[Z";
2229 default:
2230 return "\011";
2231 }
2232 }
2233
2234 // Non-alt, non-ctrl characters
2235 if (!keypress.isFnKey()) {
2236 StringBuilder sb = new StringBuilder();
2237 sb.append(keypress.getChar());
2238 return sb.toString();
2239 }
2240 return "";
2241 }
2242
2243 /**
2244 * Map a symbol in any one of the VT100/VT220 character sets to a Unicode
2245 * symbol.
2246 *
2247 * @param ch 8-bit character from the remote side
2248 * @param charsetGl character set defined for GL
2249 * @param charsetGr character set defined for GR
2250 * @return character to display on the screen
2251 */
2252 private char mapCharacterCharset(final char ch,
2253 final CharacterSet charsetGl,
2254 final CharacterSet charsetGr) {
2255
2256 int lookupChar = ch;
2257 CharacterSet lookupCharset = charsetGl;
2258
2259 if (ch >= 0x80) {
2260 assert ((type == DeviceType.VT220) || (type == DeviceType.XTERM));
2261 lookupCharset = charsetGr;
2262 lookupChar &= 0x7F;
2263 }
2264
2265 switch (lookupCharset) {
2266
2267 case DRAWING:
2268 return DECCharacterSets.SPECIAL_GRAPHICS[lookupChar];
2269
2270 case UK:
2271 return DECCharacterSets.UK[lookupChar];
2272
2273 case US:
2274 return DECCharacterSets.US_ASCII[lookupChar];
2275
2276 case NRC_DUTCH:
2277 return DECCharacterSets.NL[lookupChar];
2278
2279 case NRC_FINNISH:
2280 return DECCharacterSets.FI[lookupChar];
2281
2282 case NRC_FRENCH:
2283 return DECCharacterSets.FR[lookupChar];
2284
2285 case NRC_FRENCH_CA:
2286 return DECCharacterSets.FR_CA[lookupChar];
2287
2288 case NRC_GERMAN:
2289 return DECCharacterSets.DE[lookupChar];
2290
2291 case NRC_ITALIAN:
2292 return DECCharacterSets.IT[lookupChar];
2293
2294 case NRC_NORWEGIAN:
2295 return DECCharacterSets.NO[lookupChar];
2296
2297 case NRC_SPANISH:
2298 return DECCharacterSets.ES[lookupChar];
2299
2300 case NRC_SWEDISH:
2301 return DECCharacterSets.SV[lookupChar];
2302
2303 case NRC_SWISS:
2304 return DECCharacterSets.SWISS[lookupChar];
2305
2306 case DEC_SUPPLEMENTAL:
2307 return DECCharacterSets.DEC_SUPPLEMENTAL[lookupChar];
2308
2309 case VT52_GRAPHICS:
2310 return DECCharacterSets.VT52_SPECIAL_GRAPHICS[lookupChar];
2311
2312 case ROM:
2313 return DECCharacterSets.US_ASCII[lookupChar];
2314
2315 case ROM_SPECIAL:
2316 return DECCharacterSets.US_ASCII[lookupChar];
2317
2318 default:
2319 throw new IllegalArgumentException("Invalid character set value: "
2320 + lookupCharset);
2321 }
2322 }
2323
2324 /**
2325 * Map an 8-bit byte into a printable character.
2326 *
2327 * @param ch either 8-bit or Unicode character from the remote side
2328 * @return character to display on the screen
2329 */
2330 private char mapCharacter(final char ch) {
2331 if (ch >= 0x100) {
2332 // Unicode character, just return it
2333 return ch;
2334 }
2335
2336 CharacterSet charsetGl = currentState.g0Charset;
2337 CharacterSet charsetGr = currentState.grCharset;
2338
2339 if (vt52Mode == true) {
2340 if (shiftOut == true) {
2341 // Shifted out character, pull from VT52 graphics
2342 charsetGl = currentState.g1Charset;
2343 charsetGr = CharacterSet.US;
2344 } else {
2345 // Normal
2346 charsetGl = currentState.g0Charset;
2347 charsetGr = CharacterSet.US;
2348 }
2349
2350 // Pull the character
2351 return mapCharacterCharset(ch, charsetGl, charsetGr);
2352 }
2353
2354 // shiftOout
2355 if (shiftOut == true) {
2356 // Shifted out character, pull from G1
2357 charsetGl = currentState.g1Charset;
2358 charsetGr = currentState.grCharset;
2359
2360 // Pull the character
2361 return mapCharacterCharset(ch, charsetGl, charsetGr);
2362 }
2363
2364 // SS2
2365 if (singleshift == Singleshift.SS2) {
2366
2367 singleshift = Singleshift.NONE;
2368
2369 // Shifted out character, pull from G2
2370 charsetGl = currentState.g2Charset;
2371 charsetGr = currentState.grCharset;
2372 }
2373
2374 // SS3
2375 if (singleshift == Singleshift.SS3) {
2376
2377 singleshift = Singleshift.NONE;
2378
2379 // Shifted out character, pull from G3
2380 charsetGl = currentState.g3Charset;
2381 charsetGr = currentState.grCharset;
2382 }
2383
2384 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
2385 // Check for locking shift
2386
2387 switch (currentState.glLockshift) {
2388
2389 case G1_GR:
2390 throw new IllegalArgumentException("programming bug");
2391
2392 case G2_GR:
2393 throw new IllegalArgumentException("programming bug");
2394
2395 case G3_GR:
2396 throw new IllegalArgumentException("programming bug");
2397
2398 case G2_GL:
2399 // LS2
2400 charsetGl = currentState.g2Charset;
2401 break;
2402
2403 case G3_GL:
2404 // LS3
2405 charsetGl = currentState.g3Charset;
2406 break;
2407
2408 case NONE:
2409 // Normal
2410 charsetGl = currentState.g0Charset;
2411 break;
2412 }
2413
2414 switch (currentState.grLockshift) {
2415
2416 case G2_GL:
2417 throw new IllegalArgumentException("programming bug");
2418
2419 case G3_GL:
2420 throw new IllegalArgumentException("programming bug");
2421
2422 case G1_GR:
2423 // LS1R
2424 charsetGr = currentState.g1Charset;
2425 break;
2426
2427 case G2_GR:
2428 // LS2R
2429 charsetGr = currentState.g2Charset;
2430 break;
2431
2432 case G3_GR:
2433 // LS3R
2434 charsetGr = currentState.g3Charset;
2435 break;
2436
2437 case NONE:
2438 // Normal
2439 charsetGr = CharacterSet.DEC_SUPPLEMENTAL;
2440 break;
2441 }
2442
2443
2444 }
2445
2446 // Pull the character
2447 return mapCharacterCharset(ch, charsetGl, charsetGr);
2448 }
2449
2450 /**
2451 * Scroll the text within a scrolling region up n lines.
2452 *
2453 * @param regionTop top row of the scrolling region
2454 * @param regionBottom bottom row of the scrolling region
2455 * @param n number of lines to scroll
2456 */
2457 private void scrollingRegionScrollUp(final int regionTop,
2458 final int regionBottom, final int n) {
2459
2460 if (regionTop >= regionBottom) {
2461 return;
2462 }
2463
2464 // Sanity check: see if there will be any characters left after the
2465 // scroll
2466 if (regionBottom + 1 - regionTop <= n) {
2467 // There won't be anything left in the region, so just call
2468 // eraseScreen() and return.
2469 eraseScreen(regionTop, 0, regionBottom, width - 1, false);
2470 return;
2471 }
2472
2473 int remaining = regionBottom + 1 - regionTop - n;
2474 List<DisplayLine> displayTop = display.subList(0, regionTop);
2475 List<DisplayLine> displayBottom = display.subList(regionBottom + 1,
2476 display.size());
2477 List<DisplayLine> displayMiddle = display.subList(regionBottom + 1
2478 - remaining, regionBottom + 1);
2479 display = new ArrayList<DisplayLine>(displayTop);
2480 display.addAll(displayMiddle);
2481 for (int i = 0; i < n; i++) {
2482 DisplayLine line = new DisplayLine(currentState.attr);
2483 line.setReverseColor(reverseVideo);
2484 display.add(line);
2485 }
2486 display.addAll(displayBottom);
2487
2488 assert (display.size() == height);
2489 }
2490
2491 /**
2492 * Scroll the text within a scrolling region down n lines.
2493 *
2494 * @param regionTop top row of the scrolling region
2495 * @param regionBottom bottom row of the scrolling region
2496 * @param n number of lines to scroll
2497 */
2498 private void scrollingRegionScrollDown(final int regionTop,
2499 final int regionBottom, final int n) {
2500
2501 if (regionTop >= regionBottom) {
2502 return;
2503 }
2504
2505 // Sanity check: see if there will be any characters left after the
2506 // scroll
2507 if (regionBottom + 1 - regionTop <= n) {
2508 // There won't be anything left in the region, so just call
2509 // eraseScreen() and return.
2510 eraseScreen(regionTop, 0, regionBottom, width - 1, false);
2511 return;
2512 }
2513
2514 int remaining = regionBottom + 1 - regionTop - n;
2515 List<DisplayLine> displayTop = display.subList(0, regionTop);
2516 List<DisplayLine> displayBottom = display.subList(regionBottom + 1,
2517 display.size());
2518 List<DisplayLine> displayMiddle = display.subList(regionTop,
2519 regionTop + remaining);
2520 display = new ArrayList<DisplayLine>(displayTop);
2521 for (int i = 0; i < n; i++) {
2522 DisplayLine line = new DisplayLine(currentState.attr);
2523 line.setReverseColor(reverseVideo);
2524 display.add(line);
2525 }
2526 display.addAll(displayMiddle);
2527 display.addAll(displayBottom);
2528
2529 assert (display.size() == height);
2530 }
2531
2532 /**
2533 * Process a control character.
2534 *
2535 * @param ch 8-bit character from the remote side
2536 */
2537 private void handleControlChar(final char ch) {
2538 assert ((ch <= 0x1F) || ((ch >= 0x7F) && (ch <= 0x9F)));
2539
2540 switch (ch) {
2541
2542 case 0x00:
2543 // NUL - discard
2544 return;
2545
2546 case 0x05:
2547 // ENQ
2548
2549 // Transmit the answerback message.
2550 // Not supported
2551 break;
2552
2553 case 0x07:
2554 // BEL
2555 // Not supported
2556 break;
2557
2558 case 0x08:
2559 // BS
2560 cursorLeft(1, false);
2561 break;
2562
2563 case 0x09:
2564 // HT
2565 advanceToNextTabStop();
2566 break;
2567
2568 case 0x0A:
2569 // LF
2570 linefeed();
2571 break;
2572
2573 case 0x0B:
2574 // VT
2575 linefeed();
2576 break;
2577
2578 case 0x0C:
2579 // FF
2580 linefeed();
2581 break;
2582
2583 case 0x0D:
2584 // CR
2585 carriageReturn();
2586 break;
2587
2588 case 0x0E:
2589 // SO
2590 shiftOut = true;
2591 currentState.glLockshift = LockshiftMode.NONE;
2592 break;
2593
2594 case 0x0F:
2595 // SI
2596 shiftOut = false;
2597 currentState.glLockshift = LockshiftMode.NONE;
2598 break;
2599
2600 case 0x84:
2601 // IND
2602 ind();
2603 break;
2604
2605 case 0x85:
2606 // NEL
2607 nel();
2608 break;
2609
2610 case 0x88:
2611 // HTS
2612 hts();
2613 break;
2614
2615 case 0x8D:
2616 // RI
2617 ri();
2618 break;
2619
2620 case 0x8E:
2621 // SS2
2622 singleshift = Singleshift.SS2;
2623 break;
2624
2625 case 0x8F:
2626 // SS3
2627 singleshift = Singleshift.SS3;
2628 break;
2629
2630 default:
2631 break;
2632 }
2633
2634 }
2635
2636 /**
2637 * Advance the cursor to the next tab stop.
2638 */
2639 private void advanceToNextTabStop() {
2640 if (tabStops.size() == 0) {
2641 // Go to the rightmost column
2642 cursorRight(rightMargin - currentState.cursorX, false);
2643 return;
2644 }
2645 for (Integer stop: tabStops) {
2646 if (stop > currentState.cursorX) {
2647 cursorRight(stop - currentState.cursorX, false);
2648 return;
2649 }
2650 }
2651 /*
2652 * We got here, meaning there isn't a tab stop beyond the current
2653 * cursor position. Place the cursor of the right-most edge of the
2654 * screen.
2655 */
2656 cursorRight(rightMargin - currentState.cursorX, false);
2657 }
2658
2659 /**
2660 * Save a character into the collect buffer.
2661 *
2662 * @param ch character to save
2663 */
2664 private void collect(final char ch) {
2665 collectBuffer.append(ch);
2666 }
2667
2668 /**
2669 * Save a byte into the CSI parameters buffer.
2670 *
2671 * @param ch byte to save
2672 */
2673 private void param(final byte ch) {
2674 if (csiParams.size() == 0) {
2675 csiParams.add(Integer.valueOf(0));
2676 }
2677 Integer x = csiParams.get(csiParams.size() - 1);
2678 if ((ch >= '0') && (ch <= '9')) {
2679 x *= 10;
2680 x += (ch - '0');
2681 csiParams.set(csiParams.size() - 1, x);
2682 }
2683
2684 if ((ch == ';') && (csiParams.size() < 16)) {
2685 csiParams.add(Integer.valueOf(0));
2686 }
2687 }
2688
2689 /**
2690 * Get a CSI parameter value, with a default.
2691 *
2692 * @param position parameter index. 0 is the first parameter.
2693 * @param defaultValue value to use if csiParams[position] doesn't exist
2694 * @return parameter value
2695 */
2696 private int getCsiParam(final int position, final int defaultValue) {
2697 if (csiParams.size() < position + 1) {
2698 return defaultValue;
2699 }
2700 return csiParams.get(position).intValue();
2701 }
2702
2703 /**
2704 * Get a CSI parameter value, clamped to within min/max.
2705 *
2706 * @param position parameter index. 0 is the first parameter.
2707 * @param defaultValue value to use if csiParams[position] doesn't exist
2708 * @param minValue minimum value inclusive
2709 * @param maxValue maximum value inclusive
2710 * @return parameter value
2711 */
2712 private int getCsiParam(final int position, final int defaultValue,
2713 final int minValue, final int maxValue) {
2714
2715 assert (minValue <= maxValue);
2716 int value = getCsiParam(position, defaultValue);
2717 if (value < minValue) {
2718 value = minValue;
2719 }
2720 if (value > maxValue) {
2721 value = maxValue;
2722 }
2723 return value;
2724 }
2725
2726 /**
2727 * Set or unset a toggle.
2728 *
2729 * @param value true for set ('h'), false for reset ('l')
2730 */
2731 private void setToggle(final boolean value) {
2732 boolean decPrivateModeFlag = false;
2733 for (int i = 0; i < collectBuffer.length(); i++) {
2734 if (collectBuffer.charAt(i) == '?') {
2735 decPrivateModeFlag = true;
2736 break;
2737 }
2738 }
2739
2740 for (Integer i: csiParams) {
2741
2742 switch (i) {
2743
2744 case 1:
2745 if (decPrivateModeFlag == true) {
2746 // DECCKM
2747 if (value == true) {
2748 // Use application arrow keys
2749 arrowKeyMode = ArrowKeyMode.VT100;
2750 } else {
2751 // Use ANSI arrow keys
2752 arrowKeyMode = ArrowKeyMode.ANSI;
2753 }
2754 }
2755 break;
2756 case 2:
2757 if (decPrivateModeFlag == true) {
2758 if (value == false) {
2759
2760 // DECANM
2761 vt52Mode = true;
2762 arrowKeyMode = ArrowKeyMode.VT52;
2763
2764 /*
2765 * From the VT102 docs: "You use ANSI mode to select
2766 * most terminal features; the terminal uses the same
2767 * features when it switches to VT52 mode. You
2768 * cannot, however, change most of these features in
2769 * VT52 mode."
2770 *
2771 * In other words, do not reset any other attributes
2772 * when switching between VT52 submode and ANSI.
2773 *
2774 * HOWEVER, the real vt100 does switch the character
2775 * set according to Usenet.
2776 */
2777 currentState.g0Charset = CharacterSet.US;
2778 currentState.g1Charset = CharacterSet.DRAWING;
2779 shiftOut = false;
2780
2781 if ((type == DeviceType.VT220)
2782 || (type == DeviceType.XTERM)) {
2783
2784 // VT52 mode is explicitly 7-bit
2785 s8c1t = false;
2786 singleshift = Singleshift.NONE;
2787 }
2788 }
2789 } else {
2790 // KAM
2791 if (value == true) {
2792 // Turn off keyboard
2793 // Not supported
2794 } else {
2795 // Turn on keyboard
2796 // Not supported
2797 }
2798 }
2799 break;
2800 case 3:
2801 if (decPrivateModeFlag == true) {
2802 // DECCOLM
2803 if (value == true) {
2804 // 132 columns
2805 columns132 = true;
2806 rightMargin = 131;
2807 } else {
2808 // 80 columns
2809 columns132 = false;
2810 if ((displayListener != null)
2811 && (type == DeviceType.XTERM)
2812 ) {
2813 // For xterms, reset to the actual width, not 80
2814 // columns.
2815 width = displayListener.getDisplayWidth();
2816 rightMargin = width - 1;
2817 } else {
2818 rightMargin = 79;
2819 width = rightMargin + 1;
2820 }
2821 }
2822 // Entire screen is cleared, and scrolling region is
2823 // reset
2824 eraseScreen(0, 0, height - 1, width - 1, false);
2825 scrollRegionTop = 0;
2826 scrollRegionBottom = height - 1;
2827 // Also home the cursor
2828 cursorPosition(0, 0);
2829 }
2830 break;
2831 case 4:
2832 if (decPrivateModeFlag == true) {
2833 // DECSCLM
2834 if (value == true) {
2835 // Smooth scroll
2836 // Not supported
2837 } else {
2838 // Jump scroll
2839 // Not supported
2840 }
2841 } else {
2842 // IRM
2843 if (value == true) {
2844 insertMode = true;
2845 } else {
2846 insertMode = false;
2847 }
2848 }
2849 break;
2850 case 5:
2851 if (decPrivateModeFlag == true) {
2852 // DECSCNM
2853 if (value == true) {
2854 /*
2855 * Set selects reverse screen, a white screen
2856 * background with black characters.
2857 */
2858 if (reverseVideo != true) {
2859 /*
2860 * If in normal video, switch it back
2861 */
2862 invertDisplayColors();
2863 }
2864 reverseVideo = true;
2865 } else {
2866 /*
2867 * Reset selects normal screen, a black screen
2868 * background with white characters.
2869 */
2870 if (reverseVideo == true) {
2871 /*
2872 * If in reverse video already, switch it back
2873 */
2874 invertDisplayColors();
2875 }
2876 reverseVideo = false;
2877 }
2878 }
2879 break;
2880 case 6:
2881 if (decPrivateModeFlag == true) {
2882 // DECOM
2883 if (value == true) {
2884 // Origin is relative to scroll region cursor.
2885 // Cursor can NEVER leave scrolling region.
2886 currentState.originMode = true;
2887 cursorPosition(0, 0);
2888 } else {
2889 // Origin is absolute to entire screen. Cursor can
2890 // leave the scrolling region via cup() and hvp().
2891 currentState.originMode = false;
2892 cursorPosition(0, 0);
2893 }
2894 }
2895 break;
2896 case 7:
2897 if (decPrivateModeFlag == true) {
2898 // DECAWM
2899 if (value == true) {
2900 // Turn linewrap on
2901 currentState.lineWrap = true;
2902 } else {
2903 // Turn linewrap off
2904 currentState.lineWrap = false;
2905 }
2906 }
2907 break;
2908 case 8:
2909 if (decPrivateModeFlag == true) {
2910 // DECARM
2911 if (value == true) {
2912 // Keyboard auto-repeat on
2913 // Not supported
2914 } else {
2915 // Keyboard auto-repeat off
2916 // Not supported
2917 }
2918 }
2919 break;
2920 case 12:
2921 if (decPrivateModeFlag == false) {
2922 // SRM
2923 if (value == true) {
2924 // Local echo off
2925 fullDuplex = true;
2926 } else {
2927 // Local echo on
2928 fullDuplex = false;
2929 }
2930 }
2931 break;
2932 case 18:
2933 if (decPrivateModeFlag == true) {
2934 // DECPFF
2935 // Not supported
2936 }
2937 break;
2938 case 19:
2939 if (decPrivateModeFlag == true) {
2940 // DECPEX
2941 // Not supported
2942 }
2943 break;
2944 case 20:
2945 if (decPrivateModeFlag == false) {
2946 // LNM
2947 if (value == true) {
2948 /*
2949 * Set causes a received linefeed, form feed, or
2950 * vertical tab to move cursor to first column of
2951 * next line. RETURN transmits both a carriage return
2952 * and linefeed. This selection is also called new
2953 * line option.
2954 */
2955 newLineMode = true;
2956 } else {
2957 /*
2958 * Reset causes a received linefeed, form feed, or
2959 * vertical tab to move cursor to next line in
2960 * current column. RETURN transmits a carriage
2961 * return.
2962 */
2963 newLineMode = false;
2964 }
2965 }
2966 break;
2967
2968 case 25:
2969 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
2970 if (decPrivateModeFlag == true) {
2971 // DECTCEM
2972 if (value == true) {
2973 // Visible cursor
2974 cursorVisible = true;
2975 } else {
2976 // Invisible cursor
2977 cursorVisible = false;
2978 }
2979 }
2980 }
2981 break;
2982
2983 case 42:
2984 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
2985 if (decPrivateModeFlag == true) {
2986 // DECNRCM
2987 if (value == true) {
2988 // Select national mode NRC
2989 // Not supported
2990 } else {
2991 // Select multi-national mode
2992 // Not supported
2993 }
2994 }
2995 }
2996
2997 break;
2998
2999 case 1000:
3000 if ((type == DeviceType.XTERM)
3001 && (decPrivateModeFlag == true)
3002 ) {
3003 // Mouse: normal tracking mode
3004 if (value == true) {
3005 mouseProtocol = MouseProtocol.NORMAL;
3006 } else {
3007 mouseProtocol = MouseProtocol.OFF;
3008 }
3009 }
3010 break;
3011
3012 case 1002:
3013 if ((type == DeviceType.XTERM)
3014 && (decPrivateModeFlag == true)
3015 ) {
3016 // Mouse: normal tracking mode
3017 if (value == true) {
3018 mouseProtocol = MouseProtocol.BUTTONEVENT;
3019 } else {
3020 mouseProtocol = MouseProtocol.OFF;
3021 }
3022 }
3023 break;
3024
3025 case 1003:
3026 if ((type == DeviceType.XTERM)
3027 && (decPrivateModeFlag == true)
3028 ) {
3029 // Mouse: Any-event tracking mode
3030 if (value == true) {
3031 mouseProtocol = MouseProtocol.ANYEVENT;
3032 } else {
3033 mouseProtocol = MouseProtocol.OFF;
3034 }
3035 }
3036 break;
3037
3038 case 1005:
3039 if ((type == DeviceType.XTERM)
3040 && (decPrivateModeFlag == true)
3041 ) {
3042 // Mouse: UTF-8 coordinates
3043 if (value == true) {
3044 mouseEncoding = MouseEncoding.UTF8;
3045 } else {
3046 mouseEncoding = MouseEncoding.X10;
3047 }
3048 }
3049 break;
3050
3051 case 1006:
3052 if ((type == DeviceType.XTERM)
3053 && (decPrivateModeFlag == true)
3054 ) {
3055 // Mouse: SGR coordinates
3056 if (value == true) {
3057 mouseEncoding = MouseEncoding.SGR;
3058 } else {
3059 mouseEncoding = MouseEncoding.X10;
3060 }
3061 }
3062 break;
3063
3064 default:
3065 break;
3066
3067 }
3068 }
3069 }
3070
3071 /**
3072 * DECSC - Save cursor.
3073 */
3074 private void decsc() {
3075 savedState.setTo(currentState);
3076 }
3077
3078 /**
3079 * DECRC - Restore cursor.
3080 */
3081 private void decrc() {
3082 currentState.setTo(savedState);
3083 }
3084
3085 /**
3086 * IND - Index.
3087 */
3088 private void ind() {
3089 // Move the cursor and scroll if necessary. If at the bottom line
3090 // already, a scroll up is supposed to be performed.
3091 if (currentState.cursorY == scrollRegionBottom) {
3092 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
3093 }
3094 cursorDown(1, true);
3095 }
3096
3097 /**
3098 * RI - Reverse index.
3099 */
3100 private void ri() {
3101 // Move the cursor and scroll if necessary. If at the top line
3102 // already, a scroll down is supposed to be performed.
3103 if (currentState.cursorY == scrollRegionTop) {
3104 scrollingRegionScrollDown(scrollRegionTop, scrollRegionBottom, 1);
3105 }
3106 cursorUp(1, true);
3107 }
3108
3109 /**
3110 * NEL - Next line.
3111 */
3112 private void nel() {
3113 // Move the cursor and scroll if necessary. If at the bottom line
3114 // already, a scroll up is supposed to be performed.
3115 if (currentState.cursorY == scrollRegionBottom) {
3116 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
3117 }
3118 cursorDown(1, true);
3119
3120 // Reset to the beginning of the next line
3121 currentState.cursorX = 0;
3122 }
3123
3124 /**
3125 * DECKPAM - Keypad application mode.
3126 */
3127 private void deckpam() {
3128 keypadMode = KeypadMode.Application;
3129 }
3130
3131 /**
3132 * DECKPNM - Keypad numeric mode.
3133 */
3134 private void deckpnm() {
3135 keypadMode = KeypadMode.Numeric;
3136 }
3137
3138 /**
3139 * Move up n spaces.
3140 *
3141 * @param n number of spaces to move
3142 * @param honorScrollRegion if true, then do nothing if the cursor is
3143 * outside the scrolling region
3144 */
3145 private void cursorUp(final int n, final boolean honorScrollRegion) {
3146 int top;
3147
3148 /*
3149 * Special case: if a user moves the cursor from the right margin, we
3150 * have to reset the VT100 right margin flag.
3151 */
3152 if (n > 0) {
3153 wrapLineFlag = false;
3154 }
3155
3156 for (int i = 0; i < n; i++) {
3157 if (honorScrollRegion == true) {
3158 // Honor the scrolling region
3159 if ((currentState.cursorY < scrollRegionTop)
3160 || (currentState.cursorY > scrollRegionBottom)
3161 ) {
3162 // Outside region, do nothing
3163 return;
3164 }
3165 // Inside region, go up
3166 top = scrollRegionTop;
3167 } else {
3168 // Non-scrolling case
3169 top = 0;
3170 }
3171
3172 if (currentState.cursorY > top) {
3173 currentState.cursorY--;
3174 }
3175 }
3176 }
3177
3178 /**
3179 * Move down n spaces.
3180 *
3181 * @param n number of spaces to move
3182 * @param honorScrollRegion if true, then do nothing if the cursor is
3183 * outside the scrolling region
3184 */
3185 private void cursorDown(final int n, final boolean honorScrollRegion) {
3186 int bottom;
3187
3188 /*
3189 * Special case: if a user moves the cursor from the right margin, we
3190 * have to reset the VT100 right margin flag.
3191 */
3192 if (n > 0) {
3193 wrapLineFlag = false;
3194 }
3195
3196 for (int i = 0; i < n; i++) {
3197
3198 if (honorScrollRegion == true) {
3199 // Honor the scrolling region
3200 if (currentState.cursorY > scrollRegionBottom) {
3201 // Outside region, do nothing
3202 return;
3203 }
3204 // Inside region, go down
3205 bottom = scrollRegionBottom;
3206 } else {
3207 // Non-scrolling case
3208 bottom = height - 1;
3209 }
3210
3211 if (currentState.cursorY < bottom) {
3212 currentState.cursorY++;
3213 }
3214 }
3215 }
3216
3217 /**
3218 * Move left n spaces.
3219 *
3220 * @param n number of spaces to move
3221 * @param honorScrollRegion if true, then do nothing if the cursor is
3222 * outside the scrolling region
3223 */
3224 private void cursorLeft(final int n, final boolean honorScrollRegion) {
3225 /*
3226 * Special case: if a user moves the cursor from the right margin, we
3227 * have to reset the VT100 right margin flag.
3228 */
3229 if (n > 0) {
3230 wrapLineFlag = false;
3231 }
3232
3233 for (int i = 0; i < n; i++) {
3234 if (honorScrollRegion == true) {
3235 // Honor the scrolling region
3236 if ((currentState.cursorY < scrollRegionTop)
3237 || (currentState.cursorY > scrollRegionBottom)
3238 ) {
3239 // Outside region, do nothing
3240 return;
3241 }
3242 }
3243
3244 if (currentState.cursorX > 0) {
3245 currentState.cursorX--;
3246 }
3247 }
3248 }
3249
3250 /**
3251 * Move right n spaces.
3252 *
3253 * @param n number of spaces to move
3254 * @param honorScrollRegion if true, then do nothing if the cursor is
3255 * outside the scrolling region
3256 */
3257 private void cursorRight(final int n, final boolean honorScrollRegion) {
3258 int rightMargin = this.rightMargin;
3259
3260 /*
3261 * Special case: if a user moves the cursor from the right margin, we
3262 * have to reset the VT100 right margin flag.
3263 */
3264 if (n > 0) {
3265 wrapLineFlag = false;
3266 }
3267
3268 if (display.get(currentState.cursorY).isDoubleWidth()) {
3269 rightMargin = ((rightMargin + 1) / 2) - 1;
3270 }
3271
3272 for (int i = 0; i < n; i++) {
3273 if (honorScrollRegion == true) {
3274 // Honor the scrolling region
3275 if ((currentState.cursorY < scrollRegionTop)
3276 || (currentState.cursorY > scrollRegionBottom)
3277 ) {
3278 // Outside region, do nothing
3279 return;
3280 }
3281 }
3282
3283 if (currentState.cursorX < rightMargin) {
3284 currentState.cursorX++;
3285 }
3286 }
3287 }
3288
3289 /**
3290 * Move cursor to (col, row) where (0, 0) is the top-left corner.
3291 *
3292 * @param row row to move to
3293 * @param col column to move to
3294 */
3295 private void cursorPosition(int row, final int col) {
3296 int rightMargin = this.rightMargin;
3297
3298 assert (col >= 0);
3299 assert (row >= 0);
3300
3301 if (display.get(currentState.cursorY).isDoubleWidth()) {
3302 rightMargin = ((rightMargin + 1) / 2) - 1;
3303 }
3304
3305 // Set column number
3306 currentState.cursorX = col;
3307
3308 // Sanity check, bring column back to margin.
3309 if (currentState.cursorX > rightMargin) {
3310 currentState.cursorX = rightMargin;
3311 }
3312
3313 // Set row number
3314 if (currentState.originMode == true) {
3315 row += scrollRegionTop;
3316 }
3317 if (currentState.cursorY < row) {
3318 cursorDown(row - currentState.cursorY, false);
3319 } else if (currentState.cursorY > row) {
3320 cursorUp(currentState.cursorY - row, false);
3321 }
3322
3323 wrapLineFlag = false;
3324 }
3325
3326 /**
3327 * HTS - Horizontal tabulation set.
3328 */
3329 private void hts() {
3330 for (Integer stop: tabStops) {
3331 if (stop == currentState.cursorX) {
3332 // Already have a tab stop here
3333 return;
3334 }
3335 }
3336
3337 // Append a tab stop to the end of the array and resort them
3338 tabStops.add(currentState.cursorX);
3339 Collections.sort(tabStops);
3340 }
3341
3342 /**
3343 * DECSWL - Single-width line.
3344 */
3345 private void decswl() {
3346 display.get(currentState.cursorY).setDoubleWidth(false);
3347 display.get(currentState.cursorY).setDoubleHeight(0);
3348 }
3349
3350 /**
3351 * DECDWL - Double-width line.
3352 */
3353 private void decdwl() {
3354 display.get(currentState.cursorY).setDoubleWidth(true);
3355 display.get(currentState.cursorY).setDoubleHeight(0);
3356 }
3357
3358 /**
3359 * DECHDL - Double-height + double-width line.
3360 *
3361 * @param topHalf if true, this sets the row to be the top half row of a
3362 * double-height row
3363 */
3364 private void dechdl(final boolean topHalf) {
3365 display.get(currentState.cursorY).setDoubleWidth(true);
3366 if (topHalf == true) {
3367 display.get(currentState.cursorY).setDoubleHeight(1);
3368 } else {
3369 display.get(currentState.cursorY).setDoubleHeight(2);
3370 }
3371 }
3372
3373 /**
3374 * DECALN - Screen alignment display.
3375 */
3376 private void decaln() {
3377 Cell newCell = new Cell();
3378 newCell.setChar('E');
3379 for (DisplayLine line: display) {
3380 for (int i = 0; i < line.length(); i++) {
3381 line.replace(i, newCell);
3382 }
3383 }
3384 }
3385
3386 /**
3387 * DECSCL - Compatibility level.
3388 */
3389 private void decscl() {
3390 int i = getCsiParam(0, 0);
3391 int j = getCsiParam(1, 0);
3392
3393 if (i == 61) {
3394 // Reset fonts
3395 currentState.g0Charset = CharacterSet.US;
3396 currentState.g1Charset = CharacterSet.DRAWING;
3397 s8c1t = false;
3398 } else if (i == 62) {
3399
3400 if ((j == 0) || (j == 2)) {
3401 s8c1t = true;
3402 } else if (j == 1) {
3403 s8c1t = false;
3404 }
3405 }
3406 }
3407
3408 /**
3409 * CUD - Cursor down.
3410 */
3411 private void cud() {
3412 cursorDown(getCsiParam(0, 1, 1, height), true);
3413 }
3414
3415 /**
3416 * CUF - Cursor forward.
3417 */
3418 private void cuf() {
3419 cursorRight(getCsiParam(0, 1, 1, rightMargin + 1), true);
3420 }
3421
3422 /**
3423 * CUB - Cursor backward.
3424 */
3425 private void cub() {
3426 cursorLeft(getCsiParam(0, 1, 1, currentState.cursorX + 1), true);
3427 }
3428
3429 /**
3430 * CUU - Cursor up.
3431 */
3432 private void cuu() {
3433 cursorUp(getCsiParam(0, 1, 1, currentState.cursorY + 1), true);
3434 }
3435
3436 /**
3437 * CUP - Cursor position.
3438 */
3439 private void cup() {
3440 cursorPosition(getCsiParam(0, 1, 1, height) - 1,
3441 getCsiParam(1, 1, 1, rightMargin + 1) - 1);
3442 }
3443
3444 /**
3445 * CNL - Cursor down and to column 1.
3446 */
3447 private void cnl() {
3448 cursorDown(getCsiParam(0, 1, 1, height), true);
3449 // To column 0
3450 cursorLeft(currentState.cursorX, true);
3451 }
3452
3453 /**
3454 * CPL - Cursor up and to column 1.
3455 */
3456 private void cpl() {
3457 cursorUp(getCsiParam(0, 1, 1, currentState.cursorY + 1), true);
3458 // To column 0
3459 cursorLeft(currentState.cursorX, true);
3460 }
3461
3462 /**
3463 * CHA - Cursor to column # in current row.
3464 */
3465 private void cha() {
3466 cursorPosition(currentState.cursorY,
3467 getCsiParam(0, 1, 1, rightMargin + 1) - 1);
3468 }
3469
3470 /**
3471 * VPA - Cursor to row #, same column.
3472 */
3473 private void vpa() {
3474 cursorPosition(getCsiParam(0, 1, 1, height) - 1,
3475 currentState.cursorX);
3476 }
3477
3478 /**
3479 * ED - Erase in display.
3480 */
3481 private void ed() {
3482 boolean honorProtected = false;
3483 boolean decPrivateModeFlag = false;
3484
3485 for (int i = 0; i < collectBuffer.length(); i++) {
3486 if (collectBuffer.charAt(i) == '?') {
3487 decPrivateModeFlag = true;
3488 break;
3489 }
3490 }
3491
3492 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
3493 && (decPrivateModeFlag == true)
3494 ) {
3495 honorProtected = true;
3496 }
3497
3498 int i = getCsiParam(0, 0);
3499
3500 if (i == 0) {
3501 // Erase from here to end of screen
3502 if (currentState.cursorY < height - 1) {
3503 eraseScreen(currentState.cursorY + 1, 0, height - 1, width - 1,
3504 honorProtected);
3505 }
3506 eraseLine(currentState.cursorX, width - 1, honorProtected);
3507 } else if (i == 1) {
3508 // Erase from beginning of screen to here
3509 eraseScreen(0, 0, currentState.cursorY - 1, width - 1,
3510 honorProtected);
3511 eraseLine(0, currentState.cursorX, honorProtected);
3512 } else if (i == 2) {
3513 // Erase entire screen
3514 eraseScreen(0, 0, height - 1, width - 1, honorProtected);
3515 }
3516 }
3517
3518 /**
3519 * EL - Erase in line.
3520 */
3521 private void el() {
3522 boolean honorProtected = false;
3523 boolean decPrivateModeFlag = false;
3524
3525 for (int i = 0; i < collectBuffer.length(); i++) {
3526 if (collectBuffer.charAt(i) == '?') {
3527 decPrivateModeFlag = true;
3528 break;
3529 }
3530 }
3531
3532 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
3533 && (decPrivateModeFlag == true)
3534 ) {
3535 honorProtected = true;
3536 }
3537
3538 int i = getCsiParam(0, 0);
3539
3540 if (i == 0) {
3541 // Erase from here to end of line
3542 eraseLine(currentState.cursorX, width - 1, honorProtected);
3543 } else if (i == 1) {
3544 // Erase from beginning of line to here
3545 eraseLine(0, currentState.cursorX, honorProtected);
3546 } else if (i == 2) {
3547 // Erase entire line
3548 eraseLine(0, width - 1, honorProtected);
3549 }
3550 }
3551
3552 /**
3553 * ECH - Erase # of characters in current row.
3554 */
3555 private void ech() {
3556 int i = getCsiParam(0, 1, 1, width);
3557
3558 // Erase from here to i characters
3559 eraseLine(currentState.cursorX, currentState.cursorX + i - 1, false);
3560 }
3561
3562 /**
3563 * IL - Insert line.
3564 */
3565 private void il() {
3566 int i = getCsiParam(0, 1);
3567
3568 if ((currentState.cursorY >= scrollRegionTop)
3569 && (currentState.cursorY <= scrollRegionBottom)
3570 ) {
3571
3572 // I can get the same effect with a scroll-down
3573 scrollingRegionScrollDown(currentState.cursorY,
3574 scrollRegionBottom, i);
3575 }
3576 }
3577
3578 /**
3579 * DCH - Delete char.
3580 */
3581 private void dch() {
3582 int n = getCsiParam(0, 1);
3583 DisplayLine line = display.get(currentState.cursorY);
3584 Cell blank = new Cell();
3585 for (int i = 0; i < n; i++) {
3586 line.delete(currentState.cursorX, blank);
3587 }
3588 }
3589
3590 /**
3591 * ICH - Insert blank char at cursor.
3592 */
3593 private void ich() {
3594 int n = getCsiParam(0, 1);
3595 DisplayLine line = display.get(currentState.cursorY);
3596 Cell blank = new Cell();
3597 for (int i = 0; i < n; i++) {
3598 line.insert(currentState.cursorX, blank);
3599 }
3600 }
3601
3602 /**
3603 * DL - Delete line.
3604 */
3605 private void dl() {
3606 int i = getCsiParam(0, 1);
3607
3608 if ((currentState.cursorY >= scrollRegionTop)
3609 && (currentState.cursorY <= scrollRegionBottom)) {
3610
3611 // I can get the same effect with a scroll-down
3612 scrollingRegionScrollUp(currentState.cursorY,
3613 scrollRegionBottom, i);
3614 }
3615 }
3616
3617 /**
3618 * HVP - Horizontal and vertical position.
3619 */
3620 private void hvp() {
3621 cup();
3622 }
3623
3624 /**
3625 * REP - Repeat character.
3626 */
3627 private void rep() {
3628 int n = getCsiParam(0, 1);
3629 for (int i = 0; i < n; i++) {
3630 printCharacter(repCh);
3631 }
3632 }
3633
3634 /**
3635 * SU - Scroll up.
3636 */
3637 private void su() {
3638 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom,
3639 getCsiParam(0, 1, 1, height));
3640 }
3641
3642 /**
3643 * SD - Scroll down.
3644 */
3645 private void sd() {
3646 scrollingRegionScrollDown(scrollRegionTop, scrollRegionBottom,
3647 getCsiParam(0, 1, 1, height));
3648 }
3649
3650 /**
3651 * CBT - Go back X tab stops.
3652 */
3653 private void cbt() {
3654 int tabsToMove = getCsiParam(0, 1);
3655 int tabI;
3656
3657 for (int i = 0; i < tabsToMove; i++) {
3658 int j = currentState.cursorX;
3659 for (tabI = 0; tabI < tabStops.size(); tabI++) {
3660 if (tabStops.get(tabI) >= currentState.cursorX) {
3661 break;
3662 }
3663 }
3664 tabI--;
3665 if (tabI <= 0) {
3666 j = 0;
3667 } else {
3668 j = tabStops.get(tabI);
3669 }
3670 cursorPosition(currentState.cursorY, j);
3671 }
3672 }
3673
3674 /**
3675 * CHT - Advance X tab stops.
3676 */
3677 private void cht() {
3678 int n = getCsiParam(0, 1);
3679 for (int i = 0; i < n; i++) {
3680 advanceToNextTabStop();
3681 }
3682 }
3683
3684 /**
3685 * SGR - Select graphics rendition.
3686 */
3687 private void sgr() {
3688
3689 if (csiParams.size() == 0) {
3690 currentState.attr.reset();
3691 return;
3692 }
3693
3694 int sgrColorMode = -1;
3695 boolean idx88Color = false;
3696 boolean rgbColor = false;
3697 int rgbRed = -1;
3698 int rgbGreen = -1;
3699
3700 for (Integer i: csiParams) {
3701
3702 if ((sgrColorMode == 38) || (sgrColorMode == 48)) {
3703
3704 assert (type == DeviceType.XTERM);
3705
3706 if (idx88Color) {
3707 /*
3708 * Indexed color mode, we now have the index number.
3709 */
3710 if (sgrColorMode == 38) {
3711 currentState.attr.setForeColorRGB(get88Color(i));
3712 } else {
3713 assert (sgrColorMode == 48);
3714 currentState.attr.setBackColorRGB(get88Color(i));
3715 }
3716 sgrColorMode = -1;
3717 idx88Color = false;
3718 continue;
3719 }
3720
3721 if (rgbColor) {
3722 /*
3723 * RGB color mode, we are collecting tokens.
3724 */
3725 if (rgbRed == -1) {
3726 rgbRed = i & 0xFF;
3727 } else if (rgbGreen == -1) {
3728 rgbGreen = i & 0xFF;
3729 } else {
3730 int rgb = rgbRed << 16;
3731 rgb |= rgbGreen << 8;
3732 rgb |= i & 0xFF;
3733
3734 // System.err.printf("RGB: %08x\n", rgb);
3735
3736 if (sgrColorMode == 38) {
3737 currentState.attr.setForeColorRGB(rgb);
3738 } else {
3739 assert (sgrColorMode == 48);
3740 currentState.attr.setBackColorRGB(rgb);
3741 }
3742 rgbRed = -1;
3743 rgbGreen = -1;
3744 sgrColorMode = -1;
3745 rgbColor = false;
3746 }
3747 continue;
3748 }
3749
3750 switch (i) {
3751
3752 case 2:
3753 /*
3754 * RGB color mode.
3755 */
3756 rgbColor = true;
3757 break;
3758
3759 case 5:
3760 /*
3761 * Indexed color mode.
3762 */
3763 idx88Color = true;
3764 break;
3765
3766 default:
3767 /*
3768 * This is neither indexed nor RGB color. Bail out.
3769 */
3770 return;
3771 }
3772
3773 } // if ((sgrColorMode == 38) || (sgrColorMode == 48))
3774
3775 switch (i) {
3776
3777 case 0:
3778 // Normal
3779 currentState.attr.reset();
3780 break;
3781
3782 case 1:
3783 // Bold
3784 currentState.attr.setBold(true);
3785 break;
3786
3787 case 4:
3788 // Underline
3789 currentState.attr.setUnderline(true);
3790 break;
3791
3792 case 5:
3793 // Blink
3794 currentState.attr.setBlink(true);
3795 break;
3796
3797 case 7:
3798 // Reverse
3799 currentState.attr.setReverse(true);
3800 break;
3801
3802 default:
3803 break;
3804 }
3805
3806 if (type == DeviceType.XTERM) {
3807
3808 switch (i) {
3809
3810 case 8:
3811 // Invisible
3812 // TODO
3813 break;
3814
3815 case 90:
3816 // Set black foreground
3817 currentState.attr.setForeColorRGB(get88Color(8));
3818 break;
3819 case 91:
3820 // Set red foreground
3821 currentState.attr.setForeColorRGB(get88Color(9));
3822 break;
3823 case 92:
3824 // Set green foreground
3825 currentState.attr.setForeColorRGB(get88Color(10));
3826 break;
3827 case 93:
3828 // Set yellow foreground
3829 currentState.attr.setForeColorRGB(get88Color(11));
3830 break;
3831 case 94:
3832 // Set blue foreground
3833 currentState.attr.setForeColorRGB(get88Color(12));
3834 break;
3835 case 95:
3836 // Set magenta foreground
3837 currentState.attr.setForeColorRGB(get88Color(13));
3838 break;
3839 case 96:
3840 // Set cyan foreground
3841 currentState.attr.setForeColorRGB(get88Color(14));
3842 break;
3843 case 97:
3844 // Set white foreground
3845 currentState.attr.setForeColorRGB(get88Color(15));
3846 break;
3847
3848 case 100:
3849 // Set black background
3850 currentState.attr.setBackColorRGB(get88Color(8));
3851 break;
3852 case 101:
3853 // Set red background
3854 currentState.attr.setBackColorRGB(get88Color(9));
3855 break;
3856 case 102:
3857 // Set green background
3858 currentState.attr.setBackColorRGB(get88Color(10));
3859 break;
3860 case 103:
3861 // Set yellow background
3862 currentState.attr.setBackColorRGB(get88Color(11));
3863 break;
3864 case 104:
3865 // Set blue background
3866 currentState.attr.setBackColorRGB(get88Color(12));
3867 break;
3868 case 105:
3869 // Set magenta background
3870 currentState.attr.setBackColorRGB(get88Color(13));
3871 break;
3872 case 106:
3873 // Set cyan background
3874 currentState.attr.setBackColorRGB(get88Color(14));
3875 break;
3876 case 107:
3877 // Set white background
3878 currentState.attr.setBackColorRGB(get88Color(15));
3879 break;
3880
3881 default:
3882 break;
3883 }
3884 }
3885
3886 if ((type == DeviceType.VT220)
3887 || (type == DeviceType.XTERM)) {
3888
3889 switch (i) {
3890
3891 case 22:
3892 // Normal intensity
3893 currentState.attr.setBold(false);
3894 break;
3895
3896 case 24:
3897 // No underline
3898 currentState.attr.setUnderline(false);
3899 break;
3900
3901 case 25:
3902 // No blink
3903 currentState.attr.setBlink(false);
3904 break;
3905
3906 case 27:
3907 // Un-reverse
3908 currentState.attr.setReverse(false);
3909 break;
3910
3911 default:
3912 break;
3913 }
3914 }
3915
3916 // A true VT100/102/220 does not support color, however everyone
3917 // is used to their terminal emulator supporting color so we will
3918 // unconditionally support color for all DeviceType's.
3919
3920 switch (i) {
3921
3922 case 30:
3923 // Set black foreground
3924 currentState.attr.setForeColor(Color.BLACK);
3925 currentState.attr.setForeColorRGB(-1);
3926 break;
3927 case 31:
3928 // Set red foreground
3929 currentState.attr.setForeColor(Color.RED);
3930 currentState.attr.setForeColorRGB(-1);
3931 break;
3932 case 32:
3933 // Set green foreground
3934 currentState.attr.setForeColor(Color.GREEN);
3935 currentState.attr.setForeColorRGB(-1);
3936 break;
3937 case 33:
3938 // Set yellow foreground
3939 currentState.attr.setForeColor(Color.YELLOW);
3940 currentState.attr.setForeColorRGB(-1);
3941 break;
3942 case 34:
3943 // Set blue foreground
3944 currentState.attr.setForeColor(Color.BLUE);
3945 currentState.attr.setForeColorRGB(-1);
3946 break;
3947 case 35:
3948 // Set magenta foreground
3949 currentState.attr.setForeColor(Color.MAGENTA);
3950 currentState.attr.setForeColorRGB(-1);
3951 break;
3952 case 36:
3953 // Set cyan foreground
3954 currentState.attr.setForeColor(Color.CYAN);
3955 currentState.attr.setForeColorRGB(-1);
3956 break;
3957 case 37:
3958 // Set white foreground
3959 currentState.attr.setForeColor(Color.WHITE);
3960 currentState.attr.setForeColorRGB(-1);
3961 break;
3962 case 38:
3963 if (type == DeviceType.XTERM) {
3964 /*
3965 * Xterm supports T.416 / ISO-8613-3 codes to select
3966 * either an indexed color or an RGB value. (It also
3967 * permits these ISO-8613-3 SGR sequences to be separated
3968 * by colons rather than semicolons.)
3969 *
3970 * We will support only the following:
3971 *
3972 * 1. Indexed color mode (88- or 256-color modes).
3973 *
3974 * 2. Direct RGB.
3975 *
3976 * These cover most of the use cases in the real world.
3977 *
3978 * HOWEVER, note that this is an awful broken "standard",
3979 * with no way to do it "right". See
3980 * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
3981 * for a detailed discussion of the current state of RGB
3982 * in various terminals, the point of which is that none
3983 * of them really do the same thing despite all appearing
3984 * to be "xterm".
3985 *
3986 * Also see
3987 * https://bugs.kde.org/show_bug.cgi?id=107487#c3 .
3988 * where it is assumed that supporting just the "indexed
3989 * mode" of these sequences (which could align easily
3990 * with existing SGR colors) is assumed to mean full
3991 * support of 24-bit RGB. So it is all or nothing.
3992 *
3993 * Finally, these sequences break the assumptions of
3994 * standard ECMA-48 style parsers as pointed out at
3995 * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
3996 * Therefore in order to keep a clean display, we cannot
3997 * parse anything else in this sequence.
3998 */
3999 sgrColorMode = 38;
4000 continue;
4001 } else {
4002 // Underscore on, default foreground color
4003 currentState.attr.setUnderline(true);
4004 currentState.attr.setForeColor(Color.WHITE);
4005 }
4006 break;
4007 case 39:
4008 // Underscore off, default foreground color
4009 currentState.attr.setUnderline(false);
4010 currentState.attr.setForeColor(Color.WHITE);
4011 currentState.attr.setForeColorRGB(-1);
4012 break;
4013 case 40:
4014 // Set black background
4015 currentState.attr.setBackColor(Color.BLACK);
4016 currentState.attr.setBackColorRGB(-1);
4017 break;
4018 case 41:
4019 // Set red background
4020 currentState.attr.setBackColor(Color.RED);
4021 currentState.attr.setBackColorRGB(-1);
4022 break;
4023 case 42:
4024 // Set green background
4025 currentState.attr.setBackColor(Color.GREEN);
4026 currentState.attr.setBackColorRGB(-1);
4027 break;
4028 case 43:
4029 // Set yellow background
4030 currentState.attr.setBackColor(Color.YELLOW);
4031 currentState.attr.setBackColorRGB(-1);
4032 break;
4033 case 44:
4034 // Set blue background
4035 currentState.attr.setBackColor(Color.BLUE);
4036 currentState.attr.setBackColorRGB(-1);
4037 break;
4038 case 45:
4039 // Set magenta background
4040 currentState.attr.setBackColor(Color.MAGENTA);
4041 currentState.attr.setBackColorRGB(-1);
4042 break;
4043 case 46:
4044 // Set cyan background
4045 currentState.attr.setBackColor(Color.CYAN);
4046 currentState.attr.setBackColorRGB(-1);
4047 break;
4048 case 47:
4049 // Set white background
4050 currentState.attr.setBackColor(Color.WHITE);
4051 currentState.attr.setBackColorRGB(-1);
4052 break;
4053 case 48:
4054 if (type == DeviceType.XTERM) {
4055 /*
4056 * Xterm supports T.416 / ISO-8613-3 codes to select
4057 * either an indexed color or an RGB value. (It also
4058 * permits these ISO-8613-3 SGR sequences to be separated
4059 * by colons rather than semicolons.)
4060 *
4061 * We will support only the following:
4062 *
4063 * 1. Indexed color mode (88- or 256-color modes).
4064 *
4065 * 2. Direct RGB.
4066 *
4067 * These cover most of the use cases in the real world.
4068 *
4069 * HOWEVER, note that this is an awful broken "standard",
4070 * with no way to do it "right". See
4071 * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
4072 * for a detailed discussion of the current state of RGB
4073 * in various terminals, the point of which is that none
4074 * of them really do the same thing despite all appearing
4075 * to be "xterm".
4076 *
4077 * Also see
4078 * https://bugs.kde.org/show_bug.cgi?id=107487#c3 .
4079 * where it is assumed that supporting just the "indexed
4080 * mode" of these sequences (which could align easily
4081 * with existing SGR colors) is assumed to mean full
4082 * support of 24-bit RGB. So it is all or nothing.
4083 *
4084 * Finally, these sequences break the assumptions of
4085 * standard ECMA-48 style parsers as pointed out at
4086 * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
4087 * Therefore in order to keep a clean display, we cannot
4088 * parse anything else in this sequence.
4089 */
4090 sgrColorMode = 48;
4091 continue;
4092 }
4093 break;
4094 case 49:
4095 // Default background
4096 currentState.attr.setBackColor(Color.BLACK);
4097 currentState.attr.setBackColorRGB(-1);
4098 break;
4099
4100 default:
4101 break;
4102 }
4103 }
4104 }
4105
4106 /**
4107 * DA - Device attributes.
4108 */
4109 private void da() {
4110 int extendedFlag = 0;
4111 int i = 0;
4112 if (collectBuffer.length() > 0) {
4113 String args = collectBuffer.substring(1);
4114 if (collectBuffer.charAt(0) == '>') {
4115 extendedFlag = 1;
4116 if (collectBuffer.length() >= 2) {
4117 i = Integer.parseInt(args);
4118 }
4119 } else if (collectBuffer.charAt(0) == '=') {
4120 extendedFlag = 2;
4121 if (collectBuffer.length() >= 2) {
4122 i = Integer.parseInt(args);
4123 }
4124 } else {
4125 // Unknown code, bail out
4126 return;
4127 }
4128 }
4129
4130 if ((i != 0) && (i != 1)) {
4131 return;
4132 }
4133
4134 if ((extendedFlag == 0) && (i == 0)) {
4135 // Send string directly to remote side
4136 writeRemote(deviceTypeResponse());
4137 return;
4138 }
4139
4140 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
4141
4142 if ((extendedFlag == 1) && (i == 0)) {
4143 /*
4144 * Request "What type of terminal are you, what is your
4145 * firmware version, and what hardware options do you have
4146 * installed?"
4147 *
4148 * Respond: "I am a VT220 (identification code of 1), my
4149 * firmware version is _____ (Pv), and I have _____ Po
4150 * options installed."
4151 *
4152 * (Same as xterm)
4153 *
4154 */
4155
4156 if (s8c1t == true) {
4157 writeRemote("\u009b>1;10;0c");
4158 } else {
4159 writeRemote("\033[>1;10;0c");
4160 }
4161 }
4162 }
4163
4164 // VT420 and up
4165 if ((extendedFlag == 2) && (i == 0)) {
4166
4167 /*
4168 * Request "What is your unit ID?"
4169 *
4170 * Respond: "I was manufactured at site 00 and have a unique ID
4171 * number of 123."
4172 *
4173 */
4174 writeRemote("\033P!|00010203\033\\");
4175 }
4176 }
4177
4178 /**
4179 * DECSTBM - Set top and bottom margins.
4180 */
4181 private void decstbm() {
4182 boolean decPrivateModeFlag = false;
4183
4184 for (int i = 0; i < collectBuffer.length(); i++) {
4185 if (collectBuffer.charAt(i) == '?') {
4186 decPrivateModeFlag = true;
4187 break;
4188 }
4189 }
4190 if (decPrivateModeFlag) {
4191 // This could be restore DEC private mode values.
4192 // Ignore it.
4193 } else {
4194 // DECSTBM
4195 int top = getCsiParam(0, 1, 1, height) - 1;
4196 int bottom = getCsiParam(1, height, 1, height) - 1;
4197
4198 if (top > bottom) {
4199 top = bottom;
4200 }
4201 scrollRegionTop = top;
4202 scrollRegionBottom = bottom;
4203
4204 // Home cursor
4205 cursorPosition(0, 0);
4206 }
4207 }
4208
4209 /**
4210 * DECREQTPARM - Request terminal parameters.
4211 */
4212 private void decreqtparm() {
4213 int i = getCsiParam(0, 0);
4214
4215 if ((i != 0) && (i != 1)) {
4216 return;
4217 }
4218
4219 String str = "";
4220
4221 /*
4222 * Request terminal parameters.
4223 *
4224 * Respond with:
4225 *
4226 * Parity NONE, 8 bits, xmitspeed 38400, recvspeed 38400.
4227 * (CLoCk MULtiplier = 1, STP option flags = 0)
4228 *
4229 * (Same as xterm)
4230 */
4231 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4232 && (s8c1t == true)
4233 ) {
4234 str = String.format("\u009b%d;1;1;128;128;1;0x", i + 2);
4235 } else {
4236 str = String.format("\033[%d;1;1;128;128;1;0x", i + 2);
4237 }
4238 writeRemote(str);
4239 }
4240
4241 /**
4242 * DECSCA - Select Character Attributes.
4243 */
4244 private void decsca() {
4245 int i = getCsiParam(0, 0);
4246
4247 if ((i == 0) || (i == 2)) {
4248 // Protect mode OFF
4249 currentState.attr.setProtect(false);
4250 }
4251 if (i == 1) {
4252 // Protect mode ON
4253 currentState.attr.setProtect(true);
4254 }
4255 }
4256
4257 /**
4258 * DECSTR - Soft Terminal Reset.
4259 */
4260 private void decstr() {
4261 // Do exactly like RIS - Reset to initial state
4262 reset();
4263 // Do I clear screen too? I think so...
4264 eraseScreen(0, 0, height - 1, width - 1, false);
4265 cursorPosition(0, 0);
4266 }
4267
4268 /**
4269 * DSR - Device status report.
4270 */
4271 private void dsr() {
4272 boolean decPrivateModeFlag = false;
4273 int row = currentState.cursorY;
4274
4275 for (int i = 0; i < collectBuffer.length(); i++) {
4276 if (collectBuffer.charAt(i) == '?') {
4277 decPrivateModeFlag = true;
4278 break;
4279 }
4280 }
4281
4282 int i = getCsiParam(0, 0);
4283
4284 switch (i) {
4285
4286 case 5:
4287 // Request status report. Respond with "OK, no malfunction."
4288
4289 // Send string directly to remote side
4290 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4291 && (s8c1t == true)
4292 ) {
4293 writeRemote("\u009b0n");
4294 } else {
4295 writeRemote("\033[0n");
4296 }
4297 break;
4298
4299 case 6:
4300 // Request cursor position. Respond with current position.
4301 if (currentState.originMode == true) {
4302 row -= scrollRegionTop;
4303 }
4304 String str = "";
4305 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4306 && (s8c1t == true)
4307 ) {
4308 str = String.format("\u009b%d;%dR", row + 1,
4309 currentState.cursorX + 1);
4310 } else {
4311 str = String.format("\033[%d;%dR", row + 1,
4312 currentState.cursorX + 1);
4313 }
4314
4315 // Send string directly to remote side
4316 writeRemote(str);
4317 break;
4318
4319 case 15:
4320 if (decPrivateModeFlag == true) {
4321
4322 // Request printer status report. Respond with "Printer not
4323 // connected."
4324
4325 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4326 && (s8c1t == true)) {
4327 writeRemote("\u009b?13n");
4328 } else {
4329 writeRemote("\033[?13n");
4330 }
4331 }
4332 break;
4333
4334 case 25:
4335 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4336 && (decPrivateModeFlag == true)
4337 ) {
4338
4339 // Request user-defined keys are locked or unlocked. Respond
4340 // with "User-defined keys are locked."
4341
4342 if (s8c1t == true) {
4343 writeRemote("\u009b?21n");
4344 } else {
4345 writeRemote("\033[?21n");
4346 }
4347 }
4348 break;
4349
4350 case 26:
4351 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4352 && (decPrivateModeFlag == true)
4353 ) {
4354
4355 // Request keyboard language. Respond with "Keyboard
4356 // language is North American."
4357
4358 if (s8c1t == true) {
4359 writeRemote("\u009b?27;1n");
4360 } else {
4361 writeRemote("\033[?27;1n");
4362 }
4363
4364 }
4365 break;
4366
4367 default:
4368 // Some other option, ignore
4369 break;
4370 }
4371 }
4372
4373 /**
4374 * TBC - Tabulation clear.
4375 */
4376 private void tbc() {
4377 int i = getCsiParam(0, 0);
4378 if (i == 0) {
4379 List<Integer> newStops = new ArrayList<Integer>();
4380 for (Integer stop: tabStops) {
4381 if (stop == currentState.cursorX) {
4382 continue;
4383 }
4384 newStops.add(stop);
4385 }
4386 tabStops = newStops;
4387 }
4388 if (i == 3) {
4389 tabStops.clear();
4390 }
4391 }
4392
4393 /**
4394 * Erase the characters in the current line from the start column to the
4395 * end column, inclusive.
4396 *
4397 * @param start starting column to erase (between 0 and width - 1)
4398 * @param end ending column to erase (between 0 and width - 1)
4399 * @param honorProtected if true, do not erase characters with the
4400 * protected attribute set
4401 */
4402 private void eraseLine(int start, int end, final boolean honorProtected) {
4403
4404 if (start > end) {
4405 return;
4406 }
4407 if (end > width - 1) {
4408 end = width - 1;
4409 }
4410 if (start < 0) {
4411 start = 0;
4412 }
4413
4414 for (int i = start; i <= end; i++) {
4415 DisplayLine line = display.get(currentState.cursorY);
4416 if ((!honorProtected)
4417 || ((honorProtected) && (!line.charAt(i).isProtect()))) {
4418
4419 switch (type) {
4420 case VT100:
4421 case VT102:
4422 case VT220:
4423 /*
4424 * From the VT102 manual:
4425 *
4426 * Erasing a character also erases any character
4427 * attribute of the character.
4428 */
4429 line.setBlank(i);
4430 break;
4431 case XTERM:
4432 /*
4433 * Erase with the current color a.k.a. back-color erase
4434 * (bce).
4435 */
4436 line.setChar(i, ' ');
4437 line.setAttr(i, currentState.attr);
4438 break;
4439 }
4440 }
4441 }
4442 }
4443
4444 /**
4445 * Erase a rectangular section of the screen, inclusive. end column,
4446 * inclusive.
4447 *
4448 * @param startRow starting row to erase (between 0 and height - 1)
4449 * @param startCol starting column to erase (between 0 and width - 1)
4450 * @param endRow ending row to erase (between 0 and height - 1)
4451 * @param endCol ending column to erase (between 0 and width - 1)
4452 * @param honorProtected if true, do not erase characters with the
4453 * protected attribute set
4454 */
4455 private void eraseScreen(final int startRow, final int startCol,
4456 final int endRow, final int endCol, final boolean honorProtected) {
4457
4458 int oldCursorY;
4459
4460 if ((startRow < 0)
4461 || (startCol < 0)
4462 || (endRow < 0)
4463 || (endCol < 0)
4464 || (endRow < startRow)
4465 || (endCol < startCol)
4466 ) {
4467 return;
4468 }
4469
4470 oldCursorY = currentState.cursorY;
4471 for (int i = startRow; i <= endRow; i++) {
4472 currentState.cursorY = i;
4473 eraseLine(startCol, endCol, honorProtected);
4474
4475 // Erase display clears the double attributes
4476 display.get(i).setDoubleWidth(false);
4477 display.get(i).setDoubleHeight(0);
4478 }
4479 currentState.cursorY = oldCursorY;
4480 }
4481
4482 /**
4483 * VT220 printer functions. All of these are parsed, but won't do
4484 * anything.
4485 */
4486 private void printerFunctions() {
4487 boolean decPrivateModeFlag = false;
4488 for (int i = 0; i < collectBuffer.length(); i++) {
4489 if (collectBuffer.charAt(i) == '?') {
4490 decPrivateModeFlag = true;
4491 break;
4492 }
4493 }
4494
4495 int i = getCsiParam(0, 0);
4496
4497 switch (i) {
4498
4499 case 0:
4500 if (decPrivateModeFlag == false) {
4501 // Print screen
4502 }
4503 break;
4504
4505 case 1:
4506 if (decPrivateModeFlag == true) {
4507 // Print cursor line
4508 }
4509 break;
4510
4511 case 4:
4512 if (decPrivateModeFlag == true) {
4513 // Auto print mode OFF
4514 } else {
4515 // Printer controller OFF
4516
4517 // Characters re-appear on the screen
4518 printerControllerMode = false;
4519 }
4520 break;
4521
4522 case 5:
4523 if (decPrivateModeFlag == true) {
4524 // Auto print mode
4525
4526 } else {
4527 // Printer controller
4528
4529 // Characters get sucked into oblivion
4530 printerControllerMode = true;
4531 }
4532 break;
4533
4534 default:
4535 break;
4536
4537 }
4538 }
4539
4540 /**
4541 * Handle the SCAN_OSC_STRING state. Handle this in VT100 because lots
4542 * of remote systems will send an XTerm title sequence even if TERM isn't
4543 * xterm.
4544 *
4545 * @param xtermChar the character received from the remote side
4546 */
4547 private void oscPut(final char xtermChar) {
4548 // System.err.println("oscPut: " + xtermChar);
4549
4550 // Collect first
4551 collectBuffer.append(xtermChar);
4552
4553 // Xterm cases...
4554 if ((xtermChar == 0x07)
4555 || (collectBuffer.toString().endsWith("\033\\"))
4556 ) {
4557 String args = null;
4558 if (xtermChar == 0x07) {
4559 args = collectBuffer.substring(0, collectBuffer.length() - 1);
4560 } else {
4561 args = collectBuffer.substring(0, collectBuffer.length() - 2);
4562 }
4563
4564 String [] p = args.split(";");
4565 if (p.length > 0) {
4566 if ((p[0].equals("0")) || (p[0].equals("2"))) {
4567 if (p.length > 1) {
4568 // Screen title
4569 screenTitle = p[1];
4570 }
4571 }
4572
4573 if (p[0].equals("4")) {
4574 for (int i = 1; i + 1 < p.length; i += 2) {
4575 // Set a color index value
4576 try {
4577 set88Color(Integer.parseInt(p[i]), p[i + 1]);
4578 } catch (NumberFormatException e) {
4579 // SQUASH
4580 }
4581 }
4582 }
4583 }
4584
4585 // Go to SCAN_GROUND state
4586 toGround();
4587 return;
4588 }
4589 }
4590
4591 /**
4592 * Handle the SCAN_SOSPMAPC_STRING state. This is currently only used by
4593 * Jexer ECMA48Terminal to talk to ECMA48.
4594 *
4595 * @param pmChar the character received from the remote side
4596 */
4597 private void pmPut(final char pmChar) {
4598 // System.err.println("pmPut: " + pmChar);
4599
4600 // Collect first
4601 collectBuffer.append(pmChar);
4602
4603 // Xterm cases...
4604 if (collectBuffer.toString().endsWith("\033\\")) {
4605 String arg = null;
4606 arg = collectBuffer.substring(0, collectBuffer.length() - 2);
4607
4608 // System.err.println("arg: '" + arg + "'");
4609
4610 if (arg.equals("hideMousePointer")) {
4611 hideMousePointer = true;
4612 }
4613 if (arg.equals("showMousePointer")) {
4614 hideMousePointer = false;
4615 }
4616
4617 // Go to SCAN_GROUND state
4618 toGround();
4619 return;
4620 }
4621 }
4622
4623 /**
4624 * Run this input character through the ECMA48 state machine.
4625 *
4626 * @param ch character from the remote side
4627 */
4628 private void consume(char ch) {
4629
4630 // DEBUG
4631 // System.err.printf("%c STATE = %s\n", ch, scanState);
4632
4633 // Special case for VT10x: 7-bit characters only
4634 if ((type == DeviceType.VT100) || (type == DeviceType.VT102)) {
4635 ch = (char)(ch & 0x7F);
4636 }
4637
4638 // Special "anywhere" states
4639
4640 // 18, 1A --> execute, then switch to SCAN_GROUND
4641 if ((ch == 0x18) || (ch == 0x1A)) {
4642 // CAN and SUB abort escape sequences
4643 toGround();
4644 return;
4645 }
4646
4647 // 80-8F, 91-97, 99, 9A, 9C --> execute, then switch to SCAN_GROUND
4648
4649 // 0x1B == ESCAPE
4650 if (ch == 0x1B) {
4651 if ((type == DeviceType.XTERM)
4652 && ((scanState == ScanState.OSC_STRING)
4653 || (scanState == ScanState.DCS_SIXEL)
4654 || (scanState == ScanState.SOSPMAPC_STRING))
4655 ) {
4656 // Xterm can pass ESCAPE to its OSC sequence.
4657 // Xterm can pass ESCAPE to its DCS sequence.
4658 // Jexer can pass ESCAPE to its PM sequence.
4659 } else if ((scanState != ScanState.DCS_ENTRY)
4660 && (scanState != ScanState.DCS_INTERMEDIATE)
4661 && (scanState != ScanState.DCS_IGNORE)
4662 && (scanState != ScanState.DCS_PARAM)
4663 && (scanState != ScanState.DCS_PASSTHROUGH)
4664 ) {
4665 scanState = ScanState.ESCAPE;
4666 return;
4667 }
4668 }
4669
4670 // 0x9B == CSI 8-bit sequence
4671 if (ch == 0x9B) {
4672 scanState = ScanState.CSI_ENTRY;
4673 return;
4674 }
4675
4676 // 0x9D goes to ScanState.OSC_STRING
4677 if (ch == 0x9D) {
4678 scanState = ScanState.OSC_STRING;
4679 return;
4680 }
4681
4682 // 0x90 goes to DCS_ENTRY
4683 if (ch == 0x90) {
4684 scanState = ScanState.DCS_ENTRY;
4685 return;
4686 }
4687
4688 // 0x98, 0x9E, and 0x9F go to SOSPMAPC_STRING
4689 if ((ch == 0x98) || (ch == 0x9E) || (ch == 0x9F)) {
4690 scanState = ScanState.SOSPMAPC_STRING;
4691 return;
4692 }
4693
4694 // 0x7F (DEL) is always discarded
4695 if (ch == 0x7F) {
4696 return;
4697 }
4698
4699 switch (scanState) {
4700
4701 case GROUND:
4702 // 00-17, 19, 1C-1F --> execute
4703 // 80-8F, 91-9A, 9C --> execute
4704 if ((ch <= 0x1F) || ((ch >= 0x80) && (ch <= 0x9F))) {
4705 handleControlChar(ch);
4706 }
4707
4708 // 20-7F --> print
4709 if (((ch >= 0x20) && (ch <= 0x7F))
4710 || (ch >= 0xA0)
4711 ) {
4712
4713 // VT220 printer --> trash bin
4714 if (((type == DeviceType.VT220)
4715 || (type == DeviceType.XTERM))
4716 && (printerControllerMode == true)
4717 ) {
4718 return;
4719 }
4720
4721 // Hang onto this character
4722 repCh = mapCharacter(ch);
4723
4724 // Print this character
4725 printCharacter(repCh);
4726 }
4727 return;
4728
4729 case ESCAPE:
4730 // 00-17, 19, 1C-1F --> execute
4731 if (ch <= 0x1F) {
4732 handleControlChar(ch);
4733 return;
4734 }
4735
4736 // 20-2F --> collect, then switch to ESCAPE_INTERMEDIATE
4737 if ((ch >= 0x20) && (ch <= 0x2F)) {
4738 collect(ch);
4739 scanState = ScanState.ESCAPE_INTERMEDIATE;
4740 return;
4741 }
4742
4743 // 30-4F, 51-57, 59, 5A, 5C, 60-7E --> dispatch, then switch to GROUND
4744 if ((ch >= 0x30) && (ch <= 0x4F)) {
4745 switch (ch) {
4746 case '0':
4747 case '1':
4748 case '2':
4749 case '3':
4750 case '4':
4751 case '5':
4752 case '6':
4753 break;
4754 case '7':
4755 // DECSC - Save cursor
4756 // Note this code overlaps both ANSI and VT52 mode
4757 decsc();
4758 break;
4759
4760 case '8':
4761 // DECRC - Restore cursor
4762 // Note this code overlaps both ANSI and VT52 mode
4763 decrc();
4764 break;
4765
4766 case '9':
4767 case ':':
4768 case ';':
4769 break;
4770 case '<':
4771 if (vt52Mode == true) {
4772 // DECANM - Enter ANSI mode
4773 vt52Mode = false;
4774 arrowKeyMode = ArrowKeyMode.VT100;
4775
4776 /*
4777 * From the VT102 docs: "You use ANSI mode to select
4778 * most terminal features; the terminal uses the same
4779 * features when it switches to VT52 mode. You
4780 * cannot, however, change most of these features in
4781 * VT52 mode."
4782 *
4783 * In other words, do not reset any other attributes
4784 * when switching between VT52 submode and ANSI.
4785 */
4786
4787 // Reset fonts
4788 currentState.g0Charset = CharacterSet.US;
4789 currentState.g1Charset = CharacterSet.DRAWING;
4790 s8c1t = false;
4791 singleshift = Singleshift.NONE;
4792 currentState.glLockshift = LockshiftMode.NONE;
4793 currentState.grLockshift = LockshiftMode.NONE;
4794 }
4795 break;
4796 case '=':
4797 // DECKPAM - Keypad application mode
4798 // Note this code overlaps both ANSI and VT52 mode
4799 deckpam();
4800 break;
4801 case '>':
4802 // DECKPNM - Keypad numeric mode
4803 // Note this code overlaps both ANSI and VT52 mode
4804 deckpnm();
4805 break;
4806 case '?':
4807 case '@':
4808 break;
4809 case 'A':
4810 if (vt52Mode == true) {
4811 // Cursor up, and stop at the top without scrolling
4812 cursorUp(1, false);
4813 }
4814 break;
4815 case 'B':
4816 if (vt52Mode == true) {
4817 // Cursor down, and stop at the bottom without scrolling
4818 cursorDown(1, false);
4819 }
4820 break;
4821 case 'C':
4822 if (vt52Mode == true) {
4823 // Cursor right, and stop at the right without scrolling
4824 cursorRight(1, false);
4825 }
4826 break;
4827 case 'D':
4828 if (vt52Mode == true) {
4829 // Cursor left, and stop at the left without scrolling
4830 cursorLeft(1, false);
4831 } else {
4832 // IND - Index
4833 ind();
4834 }
4835 break;
4836 case 'E':
4837 if (vt52Mode == true) {
4838 // Nothing
4839 } else {
4840 // NEL - Next line
4841 nel();
4842 }
4843 break;
4844 case 'F':
4845 if (vt52Mode == true) {
4846 // G0 --> Special graphics
4847 currentState.g0Charset = CharacterSet.VT52_GRAPHICS;
4848 }
4849 break;
4850 case 'G':
4851 if (vt52Mode == true) {
4852 // G0 --> ASCII set
4853 currentState.g0Charset = CharacterSet.US;
4854 }
4855 break;
4856 case 'H':
4857 if (vt52Mode == true) {
4858 // Cursor to home
4859 cursorPosition(0, 0);
4860 } else {
4861 // HTS - Horizontal tabulation set
4862 hts();
4863 }
4864 break;
4865 case 'I':
4866 if (vt52Mode == true) {
4867 // Reverse line feed. Same as RI.
4868 ri();
4869 }
4870 break;
4871 case 'J':
4872 if (vt52Mode == true) {
4873 // Erase to end of screen
4874 eraseLine(currentState.cursorX, width - 1, false);
4875 eraseScreen(currentState.cursorY + 1, 0, height - 1,
4876 width - 1, false);
4877 }
4878 break;
4879 case 'K':
4880 if (vt52Mode == true) {
4881 // Erase to end of line
4882 eraseLine(currentState.cursorX, width - 1, false);
4883 }
4884 break;
4885 case 'L':
4886 break;
4887 case 'M':
4888 if (vt52Mode == true) {
4889 // Nothing
4890 } else {
4891 // RI - Reverse index
4892 ri();
4893 }
4894 break;
4895 case 'N':
4896 if (vt52Mode == false) {
4897 // SS2
4898 singleshift = Singleshift.SS2;
4899 }
4900 break;
4901 case 'O':
4902 if (vt52Mode == false) {
4903 // SS3
4904 singleshift = Singleshift.SS3;
4905 }
4906 break;
4907 }
4908 toGround();
4909 return;
4910 }
4911 if ((ch >= 0x51) && (ch <= 0x57)) {
4912 switch (ch) {
4913 case 'Q':
4914 case 'R':
4915 case 'S':
4916 case 'T':
4917 case 'U':
4918 case 'V':
4919 case 'W':
4920 break;
4921 }
4922 toGround();
4923 return;
4924 }
4925 if (ch == 0x59) {
4926 // 'Y'
4927 if (vt52Mode == true) {
4928 scanState = ScanState.VT52_DIRECT_CURSOR_ADDRESS;
4929 } else {
4930 toGround();
4931 }
4932 return;
4933 }
4934 if (ch == 0x5A) {
4935 // 'Z'
4936 if (vt52Mode == true) {
4937 // Identify
4938 // Send string directly to remote side
4939 writeRemote("\033/Z");
4940 } else {
4941 // DECID
4942 // Send string directly to remote side
4943 writeRemote(deviceTypeResponse());
4944 }
4945 toGround();
4946 return;
4947 }
4948 if (ch == 0x5C) {
4949 // '\'
4950 toGround();
4951 return;
4952 }
4953
4954 // VT52 cannot get to any of these other states
4955 if (vt52Mode == true) {
4956 toGround();
4957 return;
4958 }
4959
4960 if ((ch >= 0x60) && (ch <= 0x7E)) {
4961 switch (ch) {
4962 case '`':
4963 case 'a':
4964 case 'b':
4965 break;
4966 case 'c':
4967 // RIS - Reset to initial state
4968 reset();
4969 // Do I clear screen too? I think so...
4970 eraseScreen(0, 0, height - 1, width - 1, false);
4971 cursorPosition(0, 0);
4972 break;
4973 case 'd':
4974 case 'e':
4975 case 'f':
4976 case 'g':
4977 case 'h':
4978 case 'i':
4979 case 'j':
4980 case 'k':
4981 case 'l':
4982 case 'm':
4983 break;
4984 case 'n':
4985 if ((type == DeviceType.VT220)
4986 || (type == DeviceType.XTERM)) {
4987
4988 // VT220 lockshift G2 into GL
4989 currentState.glLockshift = LockshiftMode.G2_GL;
4990 shiftOut = false;
4991 }
4992 break;
4993 case 'o':
4994 if ((type == DeviceType.VT220)
4995 || (type == DeviceType.XTERM)) {
4996
4997 // VT220 lockshift G3 into GL
4998 currentState.glLockshift = LockshiftMode.G3_GL;
4999 shiftOut = false;
5000 }
5001 break;
5002 case 'p':
5003 case 'q':
5004 case 'r':
5005 case 's':
5006 case 't':
5007 case 'u':
5008 case 'v':
5009 case 'w':
5010 case 'x':
5011 case 'y':
5012 case 'z':
5013 case '{':
5014 break;
5015 case '|':
5016 if ((type == DeviceType.VT220)
5017 || (type == DeviceType.XTERM)) {
5018
5019 // VT220 lockshift G3 into GR
5020 currentState.grLockshift = LockshiftMode.G3_GR;
5021 shiftOut = false;
5022 }
5023 break;
5024 case '}':
5025 if ((type == DeviceType.VT220)
5026 || (type == DeviceType.XTERM)) {
5027
5028 // VT220 lockshift G2 into GR
5029 currentState.grLockshift = LockshiftMode.G2_GR;
5030 shiftOut = false;
5031 }
5032 break;
5033
5034 case '~':
5035 if ((type == DeviceType.VT220)
5036 || (type == DeviceType.XTERM)) {
5037
5038 // VT220 lockshift G1 into GR
5039 currentState.grLockshift = LockshiftMode.G1_GR;
5040 shiftOut = false;
5041 }
5042 break;
5043 }
5044 toGround();
5045 }
5046
5047 // 7F --> ignore
5048
5049 // 0x5B goes to CSI_ENTRY
5050 if (ch == 0x5B) {
5051 scanState = ScanState.CSI_ENTRY;
5052 }
5053
5054 // 0x5D goes to OSC_STRING
5055 if (ch == 0x5D) {
5056 scanState = ScanState.OSC_STRING;
5057 }
5058
5059 // 0x50 goes to DCS_ENTRY
5060 if (ch == 0x50) {
5061 scanState = ScanState.DCS_ENTRY;
5062 }
5063
5064 // 0x58, 0x5E, and 0x5F go to SOSPMAPC_STRING
5065 if ((ch == 0x58) || (ch == 0x5E) || (ch == 0x5F)) {
5066 scanState = ScanState.SOSPMAPC_STRING;
5067 }
5068
5069 return;
5070
5071 case ESCAPE_INTERMEDIATE:
5072 // 00-17, 19, 1C-1F --> execute
5073 if (ch <= 0x1F) {
5074 handleControlChar(ch);
5075 }
5076
5077 // 20-2F --> collect
5078 if ((ch >= 0x20) && (ch <= 0x2F)) {
5079 collect(ch);
5080 }
5081
5082 // 30-7E --> dispatch, then switch to GROUND
5083 if ((ch >= 0x30) && (ch <= 0x7E)) {
5084 switch (ch) {
5085 case '0':
5086 if ((collectBuffer.length() == 1)
5087 && (collectBuffer.charAt(0) == '(')) {
5088 // G0 --> Special graphics
5089 currentState.g0Charset = CharacterSet.DRAWING;
5090 }
5091 if ((collectBuffer.length() == 1)
5092 && (collectBuffer.charAt(0) == ')')) {
5093 // G1 --> Special graphics
5094 currentState.g1Charset = CharacterSet.DRAWING;
5095 }
5096 if ((type == DeviceType.VT220)
5097 || (type == DeviceType.XTERM)) {
5098
5099 if ((collectBuffer.length() == 1)
5100 && (collectBuffer.charAt(0) == '*')) {
5101 // G2 --> Special graphics
5102 currentState.g2Charset = CharacterSet.DRAWING;
5103 }
5104 if ((collectBuffer.length() == 1)
5105 && (collectBuffer.charAt(0) == '+')) {
5106 // G3 --> Special graphics
5107 currentState.g3Charset = CharacterSet.DRAWING;
5108 }
5109 }
5110 break;
5111 case '1':
5112 if ((collectBuffer.length() == 1)
5113 && (collectBuffer.charAt(0) == '(')) {
5114 // G0 --> Alternate character ROM standard character set
5115 currentState.g0Charset = CharacterSet.ROM;
5116 }
5117 if ((collectBuffer.length() == 1)
5118 && (collectBuffer.charAt(0) == ')')) {
5119 // G1 --> Alternate character ROM standard character set
5120 currentState.g1Charset = CharacterSet.ROM;
5121 }
5122 break;
5123 case '2':
5124 if ((collectBuffer.length() == 1)
5125 && (collectBuffer.charAt(0) == '(')) {
5126 // G0 --> Alternate character ROM special graphics
5127 currentState.g0Charset = CharacterSet.ROM_SPECIAL;
5128 }
5129 if ((collectBuffer.length() == 1)
5130 && (collectBuffer.charAt(0) == ')')) {
5131 // G1 --> Alternate character ROM special graphics
5132 currentState.g1Charset = CharacterSet.ROM_SPECIAL;
5133 }
5134 break;
5135 case '3':
5136 if ((collectBuffer.length() == 1)
5137 && (collectBuffer.charAt(0) == '#')) {
5138 // DECDHL - Double-height line (top half)
5139 dechdl(true);
5140 }
5141 break;
5142 case '4':
5143 if ((collectBuffer.length() == 1)
5144 && (collectBuffer.charAt(0) == '#')) {
5145 // DECDHL - Double-height line (bottom half)
5146 dechdl(false);
5147 }
5148 if ((type == DeviceType.VT220)
5149 || (type == DeviceType.XTERM)) {
5150
5151 if ((collectBuffer.length() == 1)
5152 && (collectBuffer.charAt(0) == '(')) {
5153 // G0 --> DUTCH
5154 currentState.g0Charset = CharacterSet.NRC_DUTCH;
5155 }
5156 if ((collectBuffer.length() == 1)
5157 && (collectBuffer.charAt(0) == ')')) {
5158 // G1 --> DUTCH
5159 currentState.g1Charset = CharacterSet.NRC_DUTCH;
5160 }
5161 if ((collectBuffer.length() == 1)
5162 && (collectBuffer.charAt(0) == '*')) {
5163 // G2 --> DUTCH
5164 currentState.g2Charset = CharacterSet.NRC_DUTCH;
5165 }
5166 if ((collectBuffer.length() == 1)
5167 && (collectBuffer.charAt(0) == '+')) {
5168 // G3 --> DUTCH
5169 currentState.g3Charset = CharacterSet.NRC_DUTCH;
5170 }
5171 }
5172 break;
5173 case '5':
5174 if ((collectBuffer.length() == 1)
5175 && (collectBuffer.charAt(0) == '#')) {
5176 // DECSWL - Single-width line
5177 decswl();
5178 }
5179 if ((type == DeviceType.VT220)
5180 || (type == DeviceType.XTERM)) {
5181
5182 if ((collectBuffer.length() == 1)
5183 && (collectBuffer.charAt(0) == '(')) {
5184 // G0 --> FINNISH
5185 currentState.g0Charset = CharacterSet.NRC_FINNISH;
5186 }
5187 if ((collectBuffer.length() == 1)
5188 && (collectBuffer.charAt(0) == ')')) {
5189 // G1 --> FINNISH
5190 currentState.g1Charset = CharacterSet.NRC_FINNISH;
5191 }
5192 if ((collectBuffer.length() == 1)
5193 && (collectBuffer.charAt(0) == '*')) {
5194 // G2 --> FINNISH
5195 currentState.g2Charset = CharacterSet.NRC_FINNISH;
5196 }
5197 if ((collectBuffer.length() == 1)
5198 && (collectBuffer.charAt(0) == '+')) {
5199 // G3 --> FINNISH
5200 currentState.g3Charset = CharacterSet.NRC_FINNISH;
5201 }
5202 }
5203 break;
5204 case '6':
5205 if ((collectBuffer.length() == 1)
5206 && (collectBuffer.charAt(0) == '#')) {
5207 // DECDWL - Double-width line
5208 decdwl();
5209 }
5210 if ((type == DeviceType.VT220)
5211 || (type == DeviceType.XTERM)) {
5212
5213 if ((collectBuffer.length() == 1)
5214 && (collectBuffer.charAt(0) == '(')) {
5215 // G0 --> NORWEGIAN
5216 currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
5217 }
5218 if ((collectBuffer.length() == 1)
5219 && (collectBuffer.charAt(0) == ')')) {
5220 // G1 --> NORWEGIAN
5221 currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
5222 }
5223 if ((collectBuffer.length() == 1)
5224 && (collectBuffer.charAt(0) == '*')) {
5225 // G2 --> NORWEGIAN
5226 currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
5227 }
5228 if ((collectBuffer.length() == 1)
5229 && (collectBuffer.charAt(0) == '+')) {
5230 // G3 --> NORWEGIAN
5231 currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
5232 }
5233 }
5234 break;
5235 case '7':
5236 if ((type == DeviceType.VT220)
5237 || (type == DeviceType.XTERM)) {
5238
5239 if ((collectBuffer.length() == 1)
5240 && (collectBuffer.charAt(0) == '(')) {
5241 // G0 --> SWEDISH
5242 currentState.g0Charset = CharacterSet.NRC_SWEDISH;
5243 }
5244 if ((collectBuffer.length() == 1)
5245 && (collectBuffer.charAt(0) == ')')) {
5246 // G1 --> SWEDISH
5247 currentState.g1Charset = CharacterSet.NRC_SWEDISH;
5248 }
5249 if ((collectBuffer.length() == 1)
5250 && (collectBuffer.charAt(0) == '*')) {
5251 // G2 --> SWEDISH
5252 currentState.g2Charset = CharacterSet.NRC_SWEDISH;
5253 }
5254 if ((collectBuffer.length() == 1)
5255 && (collectBuffer.charAt(0) == '+')) {
5256 // G3 --> SWEDISH
5257 currentState.g3Charset = CharacterSet.NRC_SWEDISH;
5258 }
5259 }
5260 break;
5261 case '8':
5262 if ((collectBuffer.length() == 1)
5263 && (collectBuffer.charAt(0) == '#')) {
5264 // DECALN - Screen alignment display
5265 decaln();
5266 }
5267 break;
5268 case '9':
5269 case ':':
5270 case ';':
5271 break;
5272 case '<':
5273 if ((type == DeviceType.VT220)
5274 || (type == DeviceType.XTERM)) {
5275
5276 if ((collectBuffer.length() == 1)
5277 && (collectBuffer.charAt(0) == '(')) {
5278 // G0 --> DEC_SUPPLEMENTAL
5279 currentState.g0Charset = CharacterSet.DEC_SUPPLEMENTAL;
5280 }
5281 if ((collectBuffer.length() == 1)
5282 && (collectBuffer.charAt(0) == ')')) {
5283 // G1 --> DEC_SUPPLEMENTAL
5284 currentState.g1Charset = CharacterSet.DEC_SUPPLEMENTAL;
5285 }
5286 if ((collectBuffer.length() == 1)
5287 && (collectBuffer.charAt(0) == '*')) {
5288 // G2 --> DEC_SUPPLEMENTAL
5289 currentState.g2Charset = CharacterSet.DEC_SUPPLEMENTAL;
5290 }
5291 if ((collectBuffer.length() == 1)
5292 && (collectBuffer.charAt(0) == '+')) {
5293 // G3 --> DEC_SUPPLEMENTAL
5294 currentState.g3Charset = CharacterSet.DEC_SUPPLEMENTAL;
5295 }
5296 }
5297 break;
5298 case '=':
5299 if ((type == DeviceType.VT220)
5300 || (type == DeviceType.XTERM)) {
5301
5302 if ((collectBuffer.length() == 1)
5303 && (collectBuffer.charAt(0) == '(')) {
5304 // G0 --> SWISS
5305 currentState.g0Charset = CharacterSet.NRC_SWISS;
5306 }
5307 if ((collectBuffer.length() == 1)
5308 && (collectBuffer.charAt(0) == ')')) {
5309 // G1 --> SWISS
5310 currentState.g1Charset = CharacterSet.NRC_SWISS;
5311 }
5312 if ((collectBuffer.length() == 1)
5313 && (collectBuffer.charAt(0) == '*')) {
5314 // G2 --> SWISS
5315 currentState.g2Charset = CharacterSet.NRC_SWISS;
5316 }
5317 if ((collectBuffer.length() == 1)
5318 && (collectBuffer.charAt(0) == '+')) {
5319 // G3 --> SWISS
5320 currentState.g3Charset = CharacterSet.NRC_SWISS;
5321 }
5322 }
5323 break;
5324 case '>':
5325 case '?':
5326 case '@':
5327 break;
5328 case 'A':
5329 if ((collectBuffer.length() == 1)
5330 && (collectBuffer.charAt(0) == '(')) {
5331 // G0 --> United Kingdom set
5332 currentState.g0Charset = CharacterSet.UK;
5333 }
5334 if ((collectBuffer.length() == 1)
5335 && (collectBuffer.charAt(0) == ')')) {
5336 // G1 --> United Kingdom set
5337 currentState.g1Charset = CharacterSet.UK;
5338 }
5339 if ((type == DeviceType.VT220)
5340 || (type == DeviceType.XTERM)) {
5341
5342 if ((collectBuffer.length() == 1)
5343 && (collectBuffer.charAt(0) == '*')) {
5344 // G2 --> United Kingdom set
5345 currentState.g2Charset = CharacterSet.UK;
5346 }
5347 if ((collectBuffer.length() == 1)
5348 && (collectBuffer.charAt(0) == '+')) {
5349 // G3 --> United Kingdom set
5350 currentState.g3Charset = CharacterSet.UK;
5351 }
5352 }
5353 break;
5354 case 'B':
5355 if ((collectBuffer.length() == 1)
5356 && (collectBuffer.charAt(0) == '(')) {
5357 // G0 --> ASCII set
5358 currentState.g0Charset = CharacterSet.US;
5359 }
5360 if ((collectBuffer.length() == 1)
5361 && (collectBuffer.charAt(0) == ')')) {
5362 // G1 --> ASCII set
5363 currentState.g1Charset = CharacterSet.US;
5364 }
5365 if ((type == DeviceType.VT220)
5366 || (type == DeviceType.XTERM)) {
5367
5368 if ((collectBuffer.length() == 1)
5369 && (collectBuffer.charAt(0) == '*')) {
5370 // G2 --> ASCII
5371 currentState.g2Charset = CharacterSet.US;
5372 }
5373 if ((collectBuffer.length() == 1)
5374 && (collectBuffer.charAt(0) == '+')) {
5375 // G3 --> ASCII
5376 currentState.g3Charset = CharacterSet.US;
5377 }
5378 }
5379 break;
5380 case 'C':
5381 if ((type == DeviceType.VT220)
5382 || (type == DeviceType.XTERM)) {
5383
5384 if ((collectBuffer.length() == 1)
5385 && (collectBuffer.charAt(0) == '(')) {
5386 // G0 --> FINNISH
5387 currentState.g0Charset = CharacterSet.NRC_FINNISH;
5388 }
5389 if ((collectBuffer.length() == 1)
5390 && (collectBuffer.charAt(0) == ')')) {
5391 // G1 --> FINNISH
5392 currentState.g1Charset = CharacterSet.NRC_FINNISH;
5393 }
5394 if ((collectBuffer.length() == 1)
5395 && (collectBuffer.charAt(0) == '*')) {
5396 // G2 --> FINNISH
5397 currentState.g2Charset = CharacterSet.NRC_FINNISH;
5398 }
5399 if ((collectBuffer.length() == 1)
5400 && (collectBuffer.charAt(0) == '+')) {
5401 // G3 --> FINNISH
5402 currentState.g3Charset = CharacterSet.NRC_FINNISH;
5403 }
5404 }
5405 break;
5406 case 'D':
5407 break;
5408 case 'E':
5409 if ((type == DeviceType.VT220)
5410 || (type == DeviceType.XTERM)) {
5411
5412 if ((collectBuffer.length() == 1)
5413 && (collectBuffer.charAt(0) == '(')) {
5414 // G0 --> NORWEGIAN
5415 currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
5416 }
5417 if ((collectBuffer.length() == 1)
5418 && (collectBuffer.charAt(0) == ')')) {
5419 // G1 --> NORWEGIAN
5420 currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
5421 }
5422 if ((collectBuffer.length() == 1)
5423 && (collectBuffer.charAt(0) == '*')) {
5424 // G2 --> NORWEGIAN
5425 currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
5426 }
5427 if ((collectBuffer.length() == 1)
5428 && (collectBuffer.charAt(0) == '+')) {
5429 // G3 --> NORWEGIAN
5430 currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
5431 }
5432 }
5433 break;
5434 case 'F':
5435 if ((type == DeviceType.VT220)
5436 || (type == DeviceType.XTERM)) {
5437
5438 if ((collectBuffer.length() == 1)
5439 && (collectBuffer.charAt(0) == ' ')) {
5440 // S7C1T
5441 s8c1t = false;
5442 }
5443 }
5444 break;
5445 case 'G':
5446 if ((type == DeviceType.VT220)
5447 || (type == DeviceType.XTERM)) {
5448
5449 if ((collectBuffer.length() == 1)
5450 && (collectBuffer.charAt(0) == ' ')) {
5451 // S8C1T
5452 s8c1t = true;
5453 }
5454 }
5455 break;
5456 case 'H':
5457 if ((type == DeviceType.VT220)
5458 || (type == DeviceType.XTERM)) {
5459
5460 if ((collectBuffer.length() == 1)
5461 && (collectBuffer.charAt(0) == '(')) {
5462 // G0 --> SWEDISH
5463 currentState.g0Charset = CharacterSet.NRC_SWEDISH;
5464 }
5465 if ((collectBuffer.length() == 1)
5466 && (collectBuffer.charAt(0) == ')')) {
5467 // G1 --> SWEDISH
5468 currentState.g1Charset = CharacterSet.NRC_SWEDISH;
5469 }
5470 if ((collectBuffer.length() == 1)
5471 && (collectBuffer.charAt(0) == '*')) {
5472 // G2 --> SWEDISH
5473 currentState.g2Charset = CharacterSet.NRC_SWEDISH;
5474 }
5475 if ((collectBuffer.length() == 1)
5476 && (collectBuffer.charAt(0) == '+')) {
5477 // G3 --> SWEDISH
5478 currentState.g3Charset = CharacterSet.NRC_SWEDISH;
5479 }
5480 }
5481 break;
5482 case 'I':
5483 case 'J':
5484 break;
5485 case 'K':
5486 if ((type == DeviceType.VT220)
5487 || (type == DeviceType.XTERM)) {
5488
5489 if ((collectBuffer.length() == 1)
5490 && (collectBuffer.charAt(0) == '(')) {
5491 // G0 --> GERMAN
5492 currentState.g0Charset = CharacterSet.NRC_GERMAN;
5493 }
5494 if ((collectBuffer.length() == 1)
5495 && (collectBuffer.charAt(0) == ')')) {
5496 // G1 --> GERMAN
5497 currentState.g1Charset = CharacterSet.NRC_GERMAN;
5498 }
5499 if ((collectBuffer.length() == 1)
5500 && (collectBuffer.charAt(0) == '*')) {
5501 // G2 --> GERMAN
5502 currentState.g2Charset = CharacterSet.NRC_GERMAN;
5503 }
5504 if ((collectBuffer.length() == 1)
5505 && (collectBuffer.charAt(0) == '+')) {
5506 // G3 --> GERMAN
5507 currentState.g3Charset = CharacterSet.NRC_GERMAN;
5508 }
5509 }
5510 break;
5511 case 'L':
5512 case 'M':
5513 case 'N':
5514 case 'O':
5515 case 'P':
5516 break;
5517 case 'Q':
5518 if ((type == DeviceType.VT220)
5519 || (type == DeviceType.XTERM)) {
5520
5521 if ((collectBuffer.length() == 1)
5522 && (collectBuffer.charAt(0) == '(')) {
5523 // G0 --> FRENCH_CA
5524 currentState.g0Charset = CharacterSet.NRC_FRENCH_CA;
5525 }
5526 if ((collectBuffer.length() == 1)
5527 && (collectBuffer.charAt(0) == ')')) {
5528 // G1 --> FRENCH_CA
5529 currentState.g1Charset = CharacterSet.NRC_FRENCH_CA;
5530 }
5531 if ((collectBuffer.length() == 1)
5532 && (collectBuffer.charAt(0) == '*')) {
5533 // G2 --> FRENCH_CA
5534 currentState.g2Charset = CharacterSet.NRC_FRENCH_CA;
5535 }
5536 if ((collectBuffer.length() == 1)
5537 && (collectBuffer.charAt(0) == '+')) {
5538 // G3 --> FRENCH_CA
5539 currentState.g3Charset = CharacterSet.NRC_FRENCH_CA;
5540 }
5541 }
5542 break;
5543 case 'R':
5544 if ((type == DeviceType.VT220)
5545 || (type == DeviceType.XTERM)) {
5546
5547 if ((collectBuffer.length() == 1)
5548 && (collectBuffer.charAt(0) == '(')) {
5549 // G0 --> FRENCH
5550 currentState.g0Charset = CharacterSet.NRC_FRENCH;
5551 }
5552 if ((collectBuffer.length() == 1)
5553 && (collectBuffer.charAt(0) == ')')) {
5554 // G1 --> FRENCH
5555 currentState.g1Charset = CharacterSet.NRC_FRENCH;
5556 }
5557 if ((collectBuffer.length() == 1)
5558 && (collectBuffer.charAt(0) == '*')) {
5559 // G2 --> FRENCH
5560 currentState.g2Charset = CharacterSet.NRC_FRENCH;
5561 }
5562 if ((collectBuffer.length() == 1)
5563 && (collectBuffer.charAt(0) == '+')) {
5564 // G3 --> FRENCH
5565 currentState.g3Charset = CharacterSet.NRC_FRENCH;
5566 }
5567 }
5568 break;
5569 case 'S':
5570 case 'T':
5571 case 'U':
5572 case 'V':
5573 case 'W':
5574 case 'X':
5575 break;
5576 case 'Y':
5577 if ((type == DeviceType.VT220)
5578 || (type == DeviceType.XTERM)) {
5579
5580 if ((collectBuffer.length() == 1)
5581 && (collectBuffer.charAt(0) == '(')) {
5582 // G0 --> ITALIAN
5583 currentState.g0Charset = CharacterSet.NRC_ITALIAN;
5584 }
5585 if ((collectBuffer.length() == 1)
5586 && (collectBuffer.charAt(0) == ')')) {
5587 // G1 --> ITALIAN
5588 currentState.g1Charset = CharacterSet.NRC_ITALIAN;
5589 }
5590 if ((collectBuffer.length() == 1)
5591 && (collectBuffer.charAt(0) == '*')) {
5592 // G2 --> ITALIAN
5593 currentState.g2Charset = CharacterSet.NRC_ITALIAN;
5594 }
5595 if ((collectBuffer.length() == 1)
5596 && (collectBuffer.charAt(0) == '+')) {
5597 // G3 --> ITALIAN
5598 currentState.g3Charset = CharacterSet.NRC_ITALIAN;
5599 }
5600 }
5601 break;
5602 case 'Z':
5603 if ((type == DeviceType.VT220)
5604 || (type == DeviceType.XTERM)) {
5605
5606 if ((collectBuffer.length() == 1)
5607 && (collectBuffer.charAt(0) == '(')) {
5608 // G0 --> SPANISH
5609 currentState.g0Charset = CharacterSet.NRC_SPANISH;
5610 }
5611 if ((collectBuffer.length() == 1)
5612 && (collectBuffer.charAt(0) == ')')) {
5613 // G1 --> SPANISH
5614 currentState.g1Charset = CharacterSet.NRC_SPANISH;
5615 }
5616 if ((collectBuffer.length() == 1)
5617 && (collectBuffer.charAt(0) == '*')) {
5618 // G2 --> SPANISH
5619 currentState.g2Charset = CharacterSet.NRC_SPANISH;
5620 }
5621 if ((collectBuffer.length() == 1)
5622 && (collectBuffer.charAt(0) == '+')) {
5623 // G3 --> SPANISH
5624 currentState.g3Charset = CharacterSet.NRC_SPANISH;
5625 }
5626 }
5627 break;
5628 case '[':
5629 case '\\':
5630 case ']':
5631 case '^':
5632 case '_':
5633 case '`':
5634 case 'a':
5635 case 'b':
5636 case 'c':
5637 case 'd':
5638 case 'e':
5639 case 'f':
5640 case 'g':
5641 case 'h':
5642 case 'i':
5643 case 'j':
5644 case 'k':
5645 case 'l':
5646 case 'm':
5647 case 'n':
5648 case 'o':
5649 case 'p':
5650 case 'q':
5651 case 'r':
5652 case 's':
5653 case 't':
5654 case 'u':
5655 case 'v':
5656 case 'w':
5657 case 'x':
5658 case 'y':
5659 case 'z':
5660 case '{':
5661 case '|':
5662 case '}':
5663 case '~':
5664 break;
5665 }
5666 toGround();
5667 }
5668
5669 // 7F --> ignore
5670
5671 // 0x9C goes to GROUND
5672 if (ch == 0x9C) {
5673 toGround();
5674 }
5675
5676 return;
5677
5678 case CSI_ENTRY:
5679 // 00-17, 19, 1C-1F --> execute
5680 if (ch <= 0x1F) {
5681 handleControlChar(ch);
5682 }
5683
5684 // 20-2F --> collect, then switch to CSI_INTERMEDIATE
5685 if ((ch >= 0x20) && (ch <= 0x2F)) {
5686 collect(ch);
5687 scanState = ScanState.CSI_INTERMEDIATE;
5688 }
5689
5690 // 30-39, 3B --> param, then switch to CSI_PARAM
5691 if ((ch >= '0') && (ch <= '9')) {
5692 param((byte) ch);
5693 scanState = ScanState.CSI_PARAM;
5694 }
5695 if (ch == ';') {
5696 param((byte) ch);
5697 scanState = ScanState.CSI_PARAM;
5698 }
5699
5700 // 3C-3F --> collect, then switch to CSI_PARAM
5701 if ((ch >= 0x3C) && (ch <= 0x3F)) {
5702 collect(ch);
5703 scanState = ScanState.CSI_PARAM;
5704 }
5705
5706 // 40-7E --> dispatch, then switch to GROUND
5707 if ((ch >= 0x40) && (ch <= 0x7E)) {
5708 switch (ch) {
5709 case '@':
5710 // ICH - Insert character
5711 ich();
5712 break;
5713 case 'A':
5714 // CUU - Cursor up
5715 cuu();
5716 break;
5717 case 'B':
5718 // CUD - Cursor down
5719 cud();
5720 break;
5721 case 'C':
5722 // CUF - Cursor forward
5723 cuf();
5724 break;
5725 case 'D':
5726 // CUB - Cursor backward
5727 cub();
5728 break;
5729 case 'E':
5730 // CNL - Cursor down and to column 1
5731 if (type == DeviceType.XTERM) {
5732 cnl();
5733 }
5734 break;
5735 case 'F':
5736 // CPL - Cursor up and to column 1
5737 if (type == DeviceType.XTERM) {
5738 cpl();
5739 }
5740 break;
5741 case 'G':
5742 // CHA - Cursor to column # in current row
5743 if (type == DeviceType.XTERM) {
5744 cha();
5745 }
5746 break;
5747 case 'H':
5748 // CUP - Cursor position
5749 cup();
5750 break;
5751 case 'I':
5752 // CHT - Cursor forward X tab stops (default 1)
5753 if (type == DeviceType.XTERM) {
5754 cht();
5755 }
5756 break;
5757 case 'J':
5758 // ED - Erase in display
5759 ed();
5760 break;
5761 case 'K':
5762 // EL - Erase in line
5763 el();
5764 break;
5765 case 'L':
5766 // IL - Insert line
5767 il();
5768 break;
5769 case 'M':
5770 // DL - Delete line
5771 dl();
5772 break;
5773 case 'N':
5774 case 'O':
5775 break;
5776 case 'P':
5777 // DCH - Delete character
5778 dch();
5779 break;
5780 case 'Q':
5781 case 'R':
5782 break;
5783 case 'S':
5784 // Scroll up X lines (default 1)
5785 if (type == DeviceType.XTERM) {
5786 su();
5787 }
5788 break;
5789 case 'T':
5790 // Scroll down X lines (default 1)
5791 if (type == DeviceType.XTERM) {
5792 sd();
5793 }
5794 break;
5795 case 'U':
5796 case 'V':
5797 case 'W':
5798 break;
5799 case 'X':
5800 if ((type == DeviceType.VT220)
5801 || (type == DeviceType.XTERM)) {
5802
5803 // ECH - Erase character
5804 ech();
5805 }
5806 break;
5807 case 'Y':
5808 break;
5809 case 'Z':
5810 // CBT - Cursor backward X tab stops (default 1)
5811 if (type == DeviceType.XTERM) {
5812 cbt();
5813 }
5814 break;
5815 case '[':
5816 case '\\':
5817 case ']':
5818 case '^':
5819 case '_':
5820 break;
5821 case '`':
5822 // HPA - Cursor to column # in current row. Same as CHA
5823 if (type == DeviceType.XTERM) {
5824 cha();
5825 }
5826 break;
5827 case 'a':
5828 // HPR - Cursor right. Same as CUF
5829 if (type == DeviceType.XTERM) {
5830 cuf();
5831 }
5832 break;
5833 case 'b':
5834 // REP - Repeat last char X times
5835 if (type == DeviceType.XTERM) {
5836 rep();
5837 }
5838 break;
5839 case 'c':
5840 // DA - Device attributes
5841 da();
5842 break;
5843 case 'd':
5844 // VPA - Cursor to row, current column.
5845 if (type == DeviceType.XTERM) {
5846 vpa();
5847 }
5848 break;
5849 case 'e':
5850 // VPR - Cursor down. Same as CUD
5851 if (type == DeviceType.XTERM) {
5852 cud();
5853 }
5854 break;
5855 case 'f':
5856 // HVP - Horizontal and vertical position
5857 hvp();
5858 break;
5859 case 'g':
5860 // TBC - Tabulation clear
5861 tbc();
5862 break;
5863 case 'h':
5864 // Sets an ANSI or DEC private toggle
5865 setToggle(true);
5866 break;
5867 case 'i':
5868 if ((type == DeviceType.VT220)
5869 || (type == DeviceType.XTERM)) {
5870
5871 // Printer functions
5872 printerFunctions();
5873 }
5874 break;
5875 case 'j':
5876 case 'k':
5877 break;
5878 case 'l':
5879 // Sets an ANSI or DEC private toggle
5880 setToggle(false);
5881 break;
5882 case 'm':
5883 // SGR - Select graphics rendition
5884 sgr();
5885 break;
5886 case 'n':
5887 // DSR - Device status report
5888 dsr();
5889 break;
5890 case 'o':
5891 case 'p':
5892 break;
5893 case 'q':
5894 // DECLL - Load leds
5895 // Not supported
5896 break;
5897 case 'r':
5898 // DECSTBM - Set top and bottom margins
5899 decstbm();
5900 break;
5901 case 's':
5902 // Save cursor (ANSI.SYS)
5903 if (type == DeviceType.XTERM) {
5904 savedState.cursorX = currentState.cursorX;
5905 savedState.cursorY = currentState.cursorY;
5906 }
5907 break;
5908 case 't':
5909 break;
5910 case 'u':
5911 // Restore cursor (ANSI.SYS)
5912 if (type == DeviceType.XTERM) {
5913 cursorPosition(savedState.cursorY, savedState.cursorX);
5914 }
5915 break;
5916 case 'v':
5917 case 'w':
5918 break;
5919 case 'x':
5920 // DECREQTPARM - Request terminal parameters
5921 decreqtparm();
5922 break;
5923 case 'y':
5924 case 'z':
5925 case '{':
5926 case '|':
5927 case '}':
5928 case '~':
5929 break;
5930 }
5931 toGround();
5932 }
5933
5934 // 7F --> ignore
5935
5936 // 0x9C goes to GROUND
5937 if (ch == 0x9C) {
5938 toGround();
5939 }
5940
5941 // 0x3A goes to CSI_IGNORE
5942 if (ch == 0x3A) {
5943 scanState = ScanState.CSI_IGNORE;
5944 }
5945 return;
5946
5947 case CSI_PARAM:
5948 // 00-17, 19, 1C-1F --> execute
5949 if (ch <= 0x1F) {
5950 handleControlChar(ch);
5951 }
5952
5953 // 20-2F --> collect, then switch to CSI_INTERMEDIATE
5954 if ((ch >= 0x20) && (ch <= 0x2F)) {
5955 collect(ch);
5956 scanState = ScanState.CSI_INTERMEDIATE;
5957 }
5958
5959 // 30-39, 3B --> param
5960 if ((ch >= '0') && (ch <= '9')) {
5961 param((byte) ch);
5962 }
5963 if (ch == ';') {
5964 param((byte) ch);
5965 }
5966
5967 // 0x3A goes to CSI_IGNORE
5968 if (ch == 0x3A) {
5969 scanState = ScanState.CSI_IGNORE;
5970 }
5971 // 0x3C-3F goes to CSI_IGNORE
5972 if ((ch >= 0x3C) && (ch <= 0x3F)) {
5973 scanState = ScanState.CSI_IGNORE;
5974 }
5975
5976 // 40-7E --> dispatch, then switch to GROUND
5977 if ((ch >= 0x40) && (ch <= 0x7E)) {
5978 switch (ch) {
5979 case '@':
5980 // ICH - Insert character
5981 ich();
5982 break;
5983 case 'A':
5984 // CUU - Cursor up
5985 cuu();
5986 break;
5987 case 'B':
5988 // CUD - Cursor down
5989 cud();
5990 break;
5991 case 'C':
5992 // CUF - Cursor forward
5993 cuf();
5994 break;
5995 case 'D':
5996 // CUB - Cursor backward
5997 cub();
5998 break;
5999 case 'E':
6000 // CNL - Cursor down and to column 1
6001 if (type == DeviceType.XTERM) {
6002 cnl();
6003 }
6004 break;
6005 case 'F':
6006 // CPL - Cursor up and to column 1
6007 if (type == DeviceType.XTERM) {
6008 cpl();
6009 }
6010 break;
6011 case 'G':
6012 // CHA - Cursor to column # in current row
6013 if (type == DeviceType.XTERM) {
6014 cha();
6015 }
6016 break;
6017 case 'H':
6018 // CUP - Cursor position
6019 cup();
6020 break;
6021 case 'I':
6022 // CHT - Cursor forward X tab stops (default 1)
6023 if (type == DeviceType.XTERM) {
6024 cht();
6025 }
6026 break;
6027 case 'J':
6028 // ED - Erase in display
6029 ed();
6030 break;
6031 case 'K':
6032 // EL - Erase in line
6033 el();
6034 break;
6035 case 'L':
6036 // IL - Insert line
6037 il();
6038 break;
6039 case 'M':
6040 // DL - Delete line
6041 dl();
6042 break;
6043 case 'N':
6044 case 'O':
6045 break;
6046 case 'P':
6047 // DCH - Delete character
6048 dch();
6049 break;
6050 case 'Q':
6051 case 'R':
6052 break;
6053 case 'S':
6054 // Scroll up X lines (default 1)
6055 if (type == DeviceType.XTERM) {
6056 su();
6057 }
6058 break;
6059 case 'T':
6060 // Scroll down X lines (default 1)
6061 if (type == DeviceType.XTERM) {
6062 sd();
6063 }
6064 break;
6065 case 'U':
6066 case 'V':
6067 case 'W':
6068 break;
6069 case 'X':
6070 if ((type == DeviceType.VT220)
6071 || (type == DeviceType.XTERM)) {
6072
6073 // ECH - Erase character
6074 ech();
6075 }
6076 break;
6077 case 'Y':
6078 break;
6079 case 'Z':
6080 // CBT - Cursor backward X tab stops (default 1)
6081 if (type == DeviceType.XTERM) {
6082 cbt();
6083 }
6084 break;
6085 case '[':
6086 case '\\':
6087 case ']':
6088 case '^':
6089 case '_':
6090 break;
6091 case '`':
6092 // HPA - Cursor to column # in current row. Same as CHA
6093 if (type == DeviceType.XTERM) {
6094 cha();
6095 }
6096 break;
6097 case 'a':
6098 // HPR - Cursor right. Same as CUF
6099 if (type == DeviceType.XTERM) {
6100 cuf();
6101 }
6102 break;
6103 case 'b':
6104 // REP - Repeat last char X times
6105 if (type == DeviceType.XTERM) {
6106 rep();
6107 }
6108 break;
6109 case 'c':
6110 // DA - Device attributes
6111 da();
6112 break;
6113 case 'd':
6114 // VPA - Cursor to row, current column.
6115 if (type == DeviceType.XTERM) {
6116 vpa();
6117 }
6118 break;
6119 case 'e':
6120 // VPR - Cursor down. Same as CUD
6121 if (type == DeviceType.XTERM) {
6122 cud();
6123 }
6124 break;
6125 case 'f':
6126 // HVP - Horizontal and vertical position
6127 hvp();
6128 break;
6129 case 'g':
6130 // TBC - Tabulation clear
6131 tbc();
6132 break;
6133 case 'h':
6134 // Sets an ANSI or DEC private toggle
6135 setToggle(true);
6136 break;
6137 case 'i':
6138 if ((type == DeviceType.VT220)
6139 || (type == DeviceType.XTERM)) {
6140
6141 // Printer functions
6142 printerFunctions();
6143 }
6144 break;
6145 case 'j':
6146 case 'k':
6147 break;
6148 case 'l':
6149 // Sets an ANSI or DEC private toggle
6150 setToggle(false);
6151 break;
6152 case 'm':
6153 // SGR - Select graphics rendition
6154 sgr();
6155 break;
6156 case 'n':
6157 // DSR - Device status report
6158 dsr();
6159 break;
6160 case 'o':
6161 case 'p':
6162 break;
6163 case 'q':
6164 // DECLL - Load leds
6165 // Not supported
6166 break;
6167 case 'r':
6168 // DECSTBM - Set top and bottom margins
6169 decstbm();
6170 break;
6171 case 's':
6172 case 't':
6173 case 'u':
6174 case 'v':
6175 case 'w':
6176 break;
6177 case 'x':
6178 // DECREQTPARM - Request terminal parameters
6179 decreqtparm();
6180 break;
6181 case 'y':
6182 case 'z':
6183 case '{':
6184 case '|':
6185 case '}':
6186 case '~':
6187 break;
6188 }
6189 toGround();
6190 }
6191
6192 // 7F --> ignore
6193 return;
6194
6195 case CSI_INTERMEDIATE:
6196 // 00-17, 19, 1C-1F --> execute
6197 if (ch <= 0x1F) {
6198 handleControlChar(ch);
6199 }
6200
6201 // 20-2F --> collect
6202 if ((ch >= 0x20) && (ch <= 0x2F)) {
6203 collect(ch);
6204 }
6205
6206 // 0x30-3F goes to CSI_IGNORE
6207 if ((ch >= 0x30) && (ch <= 0x3F)) {
6208 scanState = ScanState.CSI_IGNORE;
6209 }
6210
6211 // 40-7E --> dispatch, then switch to GROUND
6212 if ((ch >= 0x40) && (ch <= 0x7E)) {
6213 switch (ch) {
6214 case '@':
6215 case 'A':
6216 case 'B':
6217 case 'C':
6218 case 'D':
6219 case 'E':
6220 case 'F':
6221 case 'G':
6222 case 'H':
6223 case 'I':
6224 case 'J':
6225 case 'K':
6226 case 'L':
6227 case 'M':
6228 case 'N':
6229 case 'O':
6230 case 'P':
6231 case 'Q':
6232 case 'R':
6233 case 'S':
6234 case 'T':
6235 case 'U':
6236 case 'V':
6237 case 'W':
6238 case 'X':
6239 case 'Y':
6240 case 'Z':
6241 case '[':
6242 case '\\':
6243 case ']':
6244 case '^':
6245 case '_':
6246 case '`':
6247 case 'a':
6248 case 'b':
6249 case 'c':
6250 case 'd':
6251 case 'e':
6252 case 'f':
6253 case 'g':
6254 case 'h':
6255 case 'i':
6256 case 'j':
6257 case 'k':
6258 case 'l':
6259 case 'm':
6260 case 'n':
6261 case 'o':
6262 break;
6263 case 'p':
6264 if (((type == DeviceType.VT220)
6265 || (type == DeviceType.XTERM))
6266 && (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
6267 ) {
6268 // DECSCL - compatibility level
6269 decscl();
6270 }
6271 if ((type == DeviceType.XTERM)
6272 && (collectBuffer.charAt(collectBuffer.length() - 1) == '!')
6273 ) {
6274 // DECSTR - Soft terminal reset
6275 decstr();
6276 }
6277 break;
6278 case 'q':
6279 if (((type == DeviceType.VT220)
6280 || (type == DeviceType.XTERM))
6281 && (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
6282 ) {
6283 // DECSCA
6284 decsca();
6285 }
6286 break;
6287 case 'r':
6288 case 's':
6289 case 't':
6290 case 'u':
6291 case 'v':
6292 case 'w':
6293 case 'x':
6294 case 'y':
6295 case 'z':
6296 case '{':
6297 case '|':
6298 case '}':
6299 case '~':
6300 break;
6301 }
6302 toGround();
6303 }
6304
6305 // 7F --> ignore
6306 return;
6307
6308 case CSI_IGNORE:
6309 // 00-17, 19, 1C-1F --> execute
6310 if (ch <= 0x1F) {
6311 handleControlChar(ch);
6312 }
6313
6314 // 20-2F --> collect
6315 if ((ch >= 0x20) && (ch <= 0x2F)) {
6316 collect(ch);
6317 }
6318
6319 // 40-7E --> ignore, then switch to GROUND
6320 if ((ch >= 0x40) && (ch <= 0x7E)) {
6321 toGround();
6322 }
6323
6324 // 20-3F, 7F --> ignore
6325
6326 return;
6327
6328 case DCS_ENTRY:
6329
6330 // 0x9C goes to GROUND
6331 if (ch == 0x9C) {
6332 toGround();
6333 }
6334
6335 // 0x1B 0x5C goes to GROUND
6336 if (ch == 0x1B) {
6337 collect(ch);
6338 }
6339 if (ch == 0x5C) {
6340 if ((collectBuffer.length() > 0)
6341 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6342 ) {
6343 toGround();
6344 }
6345 }
6346
6347 // 20-2F --> collect, then switch to DCS_INTERMEDIATE
6348 if ((ch >= 0x20) && (ch <= 0x2F)) {
6349 collect(ch);
6350 scanState = ScanState.DCS_INTERMEDIATE;
6351 }
6352
6353 // 30-39, 3B --> param, then switch to DCS_PARAM
6354 if ((ch >= '0') && (ch <= '9')) {
6355 param((byte) ch);
6356 scanState = ScanState.DCS_PARAM;
6357 }
6358 if (ch == ';') {
6359 param((byte) ch);
6360 scanState = ScanState.DCS_PARAM;
6361 }
6362
6363 // 3C-3F --> collect, then switch to DCS_PARAM
6364 if ((ch >= 0x3C) && (ch <= 0x3F)) {
6365 collect(ch);
6366 scanState = ScanState.DCS_PARAM;
6367 }
6368
6369 // 00-17, 19, 1C-1F, 7F --> ignore
6370
6371 // 0x3A goes to DCS_IGNORE
6372 if (ch == 0x3F) {
6373 scanState = ScanState.DCS_IGNORE;
6374 }
6375
6376 // 0x71 goes to DCS_SIXEL
6377 if (ch == 0x71) {
6378 sixelParseBuffer = new StringBuilder();
6379 scanState = ScanState.DCS_SIXEL;
6380 } else if ((ch >= 0x40) && (ch <= 0x7E)) {
6381 // 0x40-7E goes to DCS_PASSTHROUGH
6382 scanState = ScanState.DCS_PASSTHROUGH;
6383 }
6384 return;
6385
6386 case DCS_INTERMEDIATE:
6387
6388 // 0x9C goes to GROUND
6389 if (ch == 0x9C) {
6390 toGround();
6391 }
6392
6393 // 0x1B 0x5C goes to GROUND
6394 if (ch == 0x1B) {
6395 collect(ch);
6396 }
6397 if (ch == 0x5C) {
6398 if ((collectBuffer.length() > 0)
6399 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6400 ) {
6401 toGround();
6402 }
6403 }
6404
6405 // 0x30-3F goes to DCS_IGNORE
6406 if ((ch >= 0x30) && (ch <= 0x3F)) {
6407 scanState = ScanState.DCS_IGNORE;
6408 }
6409
6410 // 0x40-7E goes to DCS_PASSTHROUGH
6411 if ((ch >= 0x40) && (ch <= 0x7E)) {
6412 scanState = ScanState.DCS_PASSTHROUGH;
6413 }
6414
6415 // 00-17, 19, 1C-1F, 7F --> ignore
6416 return;
6417
6418 case DCS_PARAM:
6419
6420 // 0x9C goes to GROUND
6421 if (ch == 0x9C) {
6422 toGround();
6423 }
6424
6425 // 0x1B 0x5C goes to GROUND
6426 if (ch == 0x1B) {
6427 collect(ch);
6428 }
6429 if (ch == 0x5C) {
6430 if ((collectBuffer.length() > 0)
6431 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6432 ) {
6433 toGround();
6434 }
6435 }
6436
6437 // 20-2F --> collect, then switch to DCS_INTERMEDIATE
6438 if ((ch >= 0x20) && (ch <= 0x2F)) {
6439 collect(ch);
6440 scanState = ScanState.DCS_INTERMEDIATE;
6441 }
6442
6443 // 30-39, 3B --> param
6444 if ((ch >= '0') && (ch <= '9')) {
6445 param((byte) ch);
6446 }
6447 if (ch == ';') {
6448 param((byte) ch);
6449 }
6450
6451 // 00-17, 19, 1C-1F, 7F --> ignore
6452
6453 // 0x3A, 3C-3F goes to DCS_IGNORE
6454 if (ch == 0x3F) {
6455 scanState = ScanState.DCS_IGNORE;
6456 }
6457 if ((ch >= 0x3C) && (ch <= 0x3F)) {
6458 scanState = ScanState.DCS_IGNORE;
6459 }
6460
6461 // 0x71 goes to DCS_SIXEL
6462 if (ch == 0x71) {
6463 sixelParseBuffer = new StringBuilder();
6464 scanState = ScanState.DCS_SIXEL;
6465 } else if ((ch >= 0x40) && (ch <= 0x7E)) {
6466 // 0x40-7E goes to DCS_PASSTHROUGH
6467 scanState = ScanState.DCS_PASSTHROUGH;
6468 }
6469 return;
6470
6471 case DCS_PASSTHROUGH:
6472 // 0x9C goes to GROUND
6473 if (ch == 0x9C) {
6474 toGround();
6475 }
6476
6477 // 0x1B 0x5C goes to GROUND
6478 if (ch == 0x1B) {
6479 collect(ch);
6480 }
6481 if (ch == 0x5C) {
6482 if ((collectBuffer.length() > 0)
6483 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6484 ) {
6485 toGround();
6486 }
6487 }
6488
6489 // 00-17, 19, 1C-1F, 20-7E --> put
6490 // TODO
6491 if (ch <= 0x17) {
6492 return;
6493 }
6494 if (ch == 0x19) {
6495 return;
6496 }
6497 if ((ch >= 0x1C) && (ch <= 0x1F)) {
6498 return;
6499 }
6500 if ((ch >= 0x20) && (ch <= 0x7E)) {
6501 return;
6502 }
6503
6504 // 7F --> ignore
6505
6506 return;
6507
6508 case DCS_IGNORE:
6509 // 00-17, 19, 1C-1F, 20-7F --> ignore
6510
6511 // 0x9C goes to GROUND
6512 if (ch == 0x9C) {
6513 toGround();
6514 }
6515
6516 return;
6517
6518 case DCS_SIXEL:
6519 // 0x9C goes to GROUND
6520 if (ch == 0x9C) {
6521 parseSixel();
6522 toGround();
6523 }
6524
6525 // 0x1B 0x5C goes to GROUND
6526 if (ch == 0x1B) {
6527 collect(ch);
6528 }
6529 if (ch == 0x5C) {
6530 if ((collectBuffer.length() > 0)
6531 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6532 ) {
6533 parseSixel();
6534 toGround();
6535 }
6536 }
6537
6538 // 00-17, 19, 1C-1F, 20-7E --> put
6539 if (ch <= 0x17) {
6540 sixelParseBuffer.append(ch);
6541 return;
6542 }
6543 if (ch == 0x19) {
6544 sixelParseBuffer.append(ch);
6545 return;
6546 }
6547 if ((ch >= 0x1C) && (ch <= 0x1F)) {
6548 sixelParseBuffer.append(ch);
6549 return;
6550 }
6551 if ((ch >= 0x20) && (ch <= 0x7E)) {
6552 sixelParseBuffer.append(ch);
6553 return;
6554 }
6555
6556 // 7F --> ignore
6557
6558 return;
6559
6560 case SOSPMAPC_STRING:
6561 // 00-17, 19, 1C-1F, 20-7F --> ignore
6562
6563 // Special case for Jexer: PM can pass one control character
6564 if (ch == 0x1B) {
6565 pmPut(ch);
6566 }
6567
6568 if ((ch >= 0x20) && (ch <= 0x7F)) {
6569 pmPut(ch);
6570 }
6571
6572 // 0x9C goes to GROUND
6573 if (ch == 0x9C) {
6574 toGround();
6575 }
6576
6577 return;
6578
6579 case OSC_STRING:
6580 // Special case for Xterm: OSC can pass control characters
6581 if ((ch == 0x9C) || (ch == 0x07) || (ch == 0x1B)) {
6582 oscPut(ch);
6583 }
6584
6585 // 00-17, 19, 1C-1F --> ignore
6586
6587 // 20-7F --> osc_put
6588 if ((ch >= 0x20) && (ch <= 0x7F)) {
6589 oscPut(ch);
6590 }
6591
6592 // 0x9C goes to GROUND
6593 if (ch == 0x9C) {
6594 toGround();
6595 }
6596
6597 return;
6598
6599 case VT52_DIRECT_CURSOR_ADDRESS:
6600 // This is a special case for the VT52 sequence "ESC Y l c"
6601 if (collectBuffer.length() == 0) {
6602 collect(ch);
6603 } else if (collectBuffer.length() == 1) {
6604 // We've got the two characters, one in the buffer and the
6605 // other in ch.
6606 cursorPosition(collectBuffer.charAt(0) - '\040', ch - '\040');
6607 toGround();
6608 }
6609 return;
6610 }
6611
6612 }
6613
6614 /**
6615 * Expose current cursor X to outside world.
6616 *
6617 * @return current cursor X
6618 */
6619 public final int getCursorX() {
6620 if (display.get(currentState.cursorY).isDoubleWidth()) {
6621 return currentState.cursorX * 2;
6622 }
6623 return currentState.cursorX;
6624 }
6625
6626 /**
6627 * Expose current cursor Y to outside world.
6628 *
6629 * @return current cursor Y
6630 */
6631 public final int getCursorY() {
6632 return currentState.cursorY;
6633 }
6634
6635 /**
6636 * Returns true if this terminal has requested the mouse pointer be
6637 * hidden.
6638 *
6639 * @return true if this terminal has requested the mouse pointer be
6640 * hidden
6641 */
6642 public final boolean hasHiddenMousePointer() {
6643 return hideMousePointer;
6644 }
6645
6646 // ------------------------------------------------------------------------
6647 // Sixel support ----------------------------------------------------------
6648 // ------------------------------------------------------------------------
6649
6650 /**
6651 * Set the width of a character cell in pixels.
6652 *
6653 * @param textWidth the width in pixels of a character cell
6654 */
6655 public void setTextWidth(final int textWidth) {
6656 this.textWidth = textWidth;
6657 }
6658
6659 /**
6660 * Set the height of a character cell in pixels.
6661 *
6662 * @param textHeight the height in pixels of a character cell
6663 */
6664 public void setTextHeight(final int textHeight) {
6665 this.textHeight = textHeight;
6666 }
6667
6668 /**
6669 * Parse a sixel string into a bitmap image, and overlay that image onto
6670 * the text cells.
6671 */
6672 private void parseSixel() {
6673 System.err.println("parseSixel(): '" + sixelParseBuffer.toString() +
6674 "'");
6675
6676 Sixel sixel = new Sixel(sixelParseBuffer.toString());
6677 BufferedImage image = sixel.getImage();
6678
6679 System.err.println("parseSixel(): image " + image);
6680
6681 if (image == null) {
6682 // Sixel data was malformed in some way, bail out.
6683 return;
6684 }
6685
6686 /*
6687 * Procedure:
6688 *
6689 * Break up the image into text cell sized pieces as a new array of
6690 * Cells.
6691 *
6692 * Note original column position x0.
6693 *
6694 * For each cell:
6695 *
6696 * 1. Advance (printCharacter(' ')) for horizontal increment, or
6697 * index (linefeed() + cursorPosition(y, x0)) for vertical
6698 * increment.
6699 *
6700 * 2. Set (x, y) cell image data.
6701 *
6702 * 3. For the right and bottom edges:
6703 *
6704 * a. Render the text to pixels using Terminus font.
6705 *
6706 * b. Blit the image on top of the text, using alpha channel.
6707 */
6708 int cellColumns = image.getWidth() / textWidth;
6709 if (cellColumns * textWidth < image.getWidth()) {
6710 cellColumns++;
6711 }
6712 int cellRows = image.getHeight() / textHeight;
6713 if (cellRows * textHeight < image.getHeight()) {
6714 cellRows++;
6715 }
6716
6717 // Break the image up into an array of cells.
6718 Cell [][] cells = new Cell[cellColumns][cellRows];
6719
6720 for (int x = 0; x < cellColumns; x++) {
6721 for (int y = 0; y < cellRows; y++) {
6722
6723 int width = textWidth;
6724 if ((x + 1) * textWidth > image.getWidth()) {
6725 width = image.getWidth() - (x * textWidth);
6726 }
6727 int height = textHeight;
6728 if ((y + 1) * textHeight > image.getHeight()) {
6729 height = image.getHeight() - (y * textHeight);
6730 }
6731
6732 Cell cell = new Cell();
6733 cell.setImage(image.getSubimage(x * textWidth,
6734 y * textHeight, width, height));
6735
6736 cells[x][y] = cell;
6737 }
6738 }
6739
6740 int x0 = currentState.cursorX;
6741 for (int y = 0; y < cellRows; y++) {
6742 for (int x = 0; x < cellColumns; x++) {
6743 printCharacter(' ');
6744 cursorLeft(1, false);
6745 if ((x == cellColumns - 1) || (y == cellRows - 1)) {
6746 // TODO: render text of current cell first, then image
6747 // over it. For now, just copy the cell.
6748 DisplayLine line = display.get(currentState.cursorY);
6749 line.replace(currentState.cursorX, cells[x][y]);
6750 } else {
6751 // Copy the image cell into the display.
6752 DisplayLine line = display.get(currentState.cursorY);
6753 line.replace(currentState.cursorX, cells[x][y]);
6754 }
6755 cursorRight(1, false);
6756 }
6757 linefeed();
6758 cursorPosition(currentState.cursorY, x0);
6759 }
6760
6761 }
6762
6763 }