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