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