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