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