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