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