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