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