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