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