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