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