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