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