retrofit from gjexer
[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(Integer.valueOf(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 @SuppressWarnings("fallthrough")
1633 private String keypressToString(final TKeypress keypress) {
1634
1635 if ((fullDuplex == false) && (!keypress.isFnKey())) {
1636 /*
1637 * If this is a control character, process it like it came from
1638 * the remote side.
1639 */
1640 if (keypress.getChar() < 0x20) {
1641 handleControlChar(keypress.getChar());
1642 } else {
1643 // Local echo for everything else
1644 printCharacter(keypress.getChar());
1645 }
1646 }
1647
1648 if ((newLineMode == true) && (keypress.equals(kbEnter))) {
1649 // NLM: send CRLF
1650 return "\015\012";
1651 }
1652
1653 // Handle control characters
1654 if ((keypress.isCtrl()) && (!keypress.isFnKey())) {
1655 StringBuilder sb = new StringBuilder();
1656 char ch = keypress.getChar();
1657 ch -= 0x40;
1658 sb.append(ch);
1659 return sb.toString();
1660 }
1661
1662 // Handle alt characters
1663 if ((keypress.isAlt()) && (!keypress.isFnKey())) {
1664 StringBuilder sb = new StringBuilder("\033");
1665 char ch = keypress.getChar();
1666 sb.append(ch);
1667 return sb.toString();
1668 }
1669
1670 if (keypress.equals(kbBackspaceDel)) {
1671 switch (type) {
1672 case VT100:
1673 return "\010";
1674 case VT102:
1675 return "\010";
1676 case VT220:
1677 return "\177";
1678 case XTERM:
1679 return "\177";
1680 }
1681 }
1682
1683 if (keypress.equalsWithoutModifiers(kbLeft)) {
1684 switch (type) {
1685 case XTERM:
1686 switch (arrowKeyMode) {
1687 case ANSI:
1688 return xtermBuildKeySequence("\033[", '1', 'D',
1689 keypress.isCtrl(), keypress.isAlt(),
1690 keypress.isShift());
1691 case VT52:
1692 return xtermBuildKeySequence("\033", '1', 'D',
1693 keypress.isCtrl(), keypress.isAlt(),
1694 keypress.isShift());
1695 case VT100:
1696 return xtermBuildKeySequence("\033O", '1', 'D',
1697 keypress.isCtrl(), keypress.isAlt(),
1698 keypress.isShift());
1699 }
1700 default:
1701 switch (arrowKeyMode) {
1702 case ANSI:
1703 return "\033[D";
1704 case VT52:
1705 return "\033D";
1706 case VT100:
1707 return "\033OD";
1708 }
1709 }
1710 }
1711
1712 if (keypress.equalsWithoutModifiers(kbRight)) {
1713 switch (type) {
1714 case XTERM:
1715 switch (arrowKeyMode) {
1716 case ANSI:
1717 return xtermBuildKeySequence("\033[", '1', 'C',
1718 keypress.isCtrl(), keypress.isAlt(),
1719 keypress.isShift());
1720 case VT52:
1721 return xtermBuildKeySequence("\033", '1', 'C',
1722 keypress.isCtrl(), keypress.isAlt(),
1723 keypress.isShift());
1724 case VT100:
1725 return xtermBuildKeySequence("\033O", '1', 'C',
1726 keypress.isCtrl(), keypress.isAlt(),
1727 keypress.isShift());
1728 }
1729 default:
1730 switch (arrowKeyMode) {
1731 case ANSI:
1732 return "\033[C";
1733 case VT52:
1734 return "\033C";
1735 case VT100:
1736 return "\033OC";
1737 }
1738 }
1739 }
1740
1741 if (keypress.equalsWithoutModifiers(kbUp)) {
1742 switch (type) {
1743 case XTERM:
1744 switch (arrowKeyMode) {
1745 case ANSI:
1746 return xtermBuildKeySequence("\033[", '1', 'A',
1747 keypress.isCtrl(), keypress.isAlt(),
1748 keypress.isShift());
1749 case VT52:
1750 return xtermBuildKeySequence("\033", '1', 'A',
1751 keypress.isCtrl(), keypress.isAlt(),
1752 keypress.isShift());
1753 case VT100:
1754 return xtermBuildKeySequence("\033O", '1', 'A',
1755 keypress.isCtrl(), keypress.isAlt(),
1756 keypress.isShift());
1757 }
1758 default:
1759 switch (arrowKeyMode) {
1760 case ANSI:
1761 return "\033[A";
1762 case VT52:
1763 return "\033A";
1764 case VT100:
1765 return "\033OA";
1766 }
1767 }
1768 }
1769
1770 if (keypress.equalsWithoutModifiers(kbDown)) {
1771 switch (type) {
1772 case XTERM:
1773 switch (arrowKeyMode) {
1774 case ANSI:
1775 return xtermBuildKeySequence("\033[", '1', 'B',
1776 keypress.isCtrl(), keypress.isAlt(),
1777 keypress.isShift());
1778 case VT52:
1779 return xtermBuildKeySequence("\033", '1', 'B',
1780 keypress.isCtrl(), keypress.isAlt(),
1781 keypress.isShift());
1782 case VT100:
1783 return xtermBuildKeySequence("\033O", '1', 'B',
1784 keypress.isCtrl(), keypress.isAlt(),
1785 keypress.isShift());
1786 }
1787 default:
1788 switch (arrowKeyMode) {
1789 case ANSI:
1790 return "\033[B";
1791 case VT52:
1792 return "\033B";
1793 case VT100:
1794 return "\033OB";
1795 }
1796 }
1797 }
1798
1799 if (keypress.equalsWithoutModifiers(kbHome)) {
1800 switch (type) {
1801 case XTERM:
1802 switch (arrowKeyMode) {
1803 case ANSI:
1804 return xtermBuildKeySequence("\033[", '1', 'H',
1805 keypress.isCtrl(), keypress.isAlt(),
1806 keypress.isShift());
1807 case VT52:
1808 return xtermBuildKeySequence("\033", '1', 'H',
1809 keypress.isCtrl(), keypress.isAlt(),
1810 keypress.isShift());
1811 case VT100:
1812 return xtermBuildKeySequence("\033O", '1', 'H',
1813 keypress.isCtrl(), keypress.isAlt(),
1814 keypress.isShift());
1815 }
1816 default:
1817 switch (arrowKeyMode) {
1818 case ANSI:
1819 return "\033[H";
1820 case VT52:
1821 return "\033H";
1822 case VT100:
1823 return "\033OH";
1824 }
1825 }
1826 }
1827
1828 if (keypress.equalsWithoutModifiers(kbEnd)) {
1829 switch (type) {
1830 case XTERM:
1831 switch (arrowKeyMode) {
1832 case ANSI:
1833 return xtermBuildKeySequence("\033[", '1', 'F',
1834 keypress.isCtrl(), keypress.isAlt(),
1835 keypress.isShift());
1836 case VT52:
1837 return xtermBuildKeySequence("\033", '1', 'F',
1838 keypress.isCtrl(), keypress.isAlt(),
1839 keypress.isShift());
1840 case VT100:
1841 return xtermBuildKeySequence("\033O", '1', 'F',
1842 keypress.isCtrl(), keypress.isAlt(),
1843 keypress.isShift());
1844 }
1845 default:
1846 switch (arrowKeyMode) {
1847 case ANSI:
1848 return "\033[F";
1849 case VT52:
1850 return "\033F";
1851 case VT100:
1852 return "\033OF";
1853 }
1854 }
1855 }
1856
1857 if (keypress.equals(kbF1)) {
1858 // PF1
1859 if (vt52Mode) {
1860 return "\033P";
1861 }
1862 return "\033OP";
1863 }
1864
1865 if (keypress.equals(kbF2)) {
1866 // PF2
1867 if (vt52Mode) {
1868 return "\033Q";
1869 }
1870 return "\033OQ";
1871 }
1872
1873 if (keypress.equals(kbF3)) {
1874 // PF3
1875 if (vt52Mode) {
1876 return "\033R";
1877 }
1878 return "\033OR";
1879 }
1880
1881 if (keypress.equals(kbF4)) {
1882 // PF4
1883 if (vt52Mode) {
1884 return "\033S";
1885 }
1886 return "\033OS";
1887 }
1888
1889 if (keypress.equals(kbF5)) {
1890 switch (type) {
1891 case VT100:
1892 return "\033Ot";
1893 case VT102:
1894 return "\033Ot";
1895 case VT220:
1896 return "\033[15~";
1897 case XTERM:
1898 return "\033[15~";
1899 }
1900 }
1901
1902 if (keypress.equals(kbF6)) {
1903 switch (type) {
1904 case VT100:
1905 return "\033Ou";
1906 case VT102:
1907 return "\033Ou";
1908 case VT220:
1909 return "\033[17~";
1910 case XTERM:
1911 return "\033[17~";
1912 }
1913 }
1914
1915 if (keypress.equals(kbF7)) {
1916 switch (type) {
1917 case VT100:
1918 return "\033Ov";
1919 case VT102:
1920 return "\033Ov";
1921 case VT220:
1922 return "\033[18~";
1923 case XTERM:
1924 return "\033[18~";
1925 }
1926 }
1927
1928 if (keypress.equals(kbF8)) {
1929 switch (type) {
1930 case VT100:
1931 return "\033Ol";
1932 case VT102:
1933 return "\033Ol";
1934 case VT220:
1935 return "\033[19~";
1936 case XTERM:
1937 return "\033[19~";
1938 }
1939 }
1940
1941 if (keypress.equals(kbF9)) {
1942 switch (type) {
1943 case VT100:
1944 return "\033Ow";
1945 case VT102:
1946 return "\033Ow";
1947 case VT220:
1948 return "\033[20~";
1949 case XTERM:
1950 return "\033[20~";
1951 }
1952 }
1953
1954 if (keypress.equals(kbF10)) {
1955 switch (type) {
1956 case VT100:
1957 return "\033Ox";
1958 case VT102:
1959 return "\033Ox";
1960 case VT220:
1961 return "\033[21~";
1962 case XTERM:
1963 return "\033[21~";
1964 }
1965 }
1966
1967 if (keypress.equals(kbF11)) {
1968 return "\033[23~";
1969 }
1970
1971 if (keypress.equals(kbF12)) {
1972 return "\033[24~";
1973 }
1974
1975 if (keypress.equals(kbShiftF1)) {
1976 // Shifted PF1
1977 if (vt52Mode) {
1978 return "\0332P";
1979 }
1980 if (type == DeviceType.XTERM) {
1981 return "\0331;2P";
1982 }
1983 return "\033O2P";
1984 }
1985
1986 if (keypress.equals(kbShiftF2)) {
1987 // Shifted PF2
1988 if (vt52Mode) {
1989 return "\0332Q";
1990 }
1991 if (type == DeviceType.XTERM) {
1992 return "\0331;2Q";
1993 }
1994 return "\033O2Q";
1995 }
1996
1997 if (keypress.equals(kbShiftF3)) {
1998 // Shifted PF3
1999 if (vt52Mode) {
2000 return "\0332R";
2001 }
2002 if (type == DeviceType.XTERM) {
2003 return "\0331;2R";
2004 }
2005 return "\033O2R";
2006 }
2007
2008 if (keypress.equals(kbShiftF4)) {
2009 // Shifted PF4
2010 if (vt52Mode) {
2011 return "\0332S";
2012 }
2013 if (type == DeviceType.XTERM) {
2014 return "\0331;2S";
2015 }
2016 return "\033O2S";
2017 }
2018
2019 if (keypress.equals(kbShiftF5)) {
2020 // Shifted F5
2021 return "\033[15;2~";
2022 }
2023
2024 if (keypress.equals(kbShiftF6)) {
2025 // Shifted F6
2026 return "\033[17;2~";
2027 }
2028
2029 if (keypress.equals(kbShiftF7)) {
2030 // Shifted F7
2031 return "\033[18;2~";
2032 }
2033
2034 if (keypress.equals(kbShiftF8)) {
2035 // Shifted F8
2036 return "\033[19;2~";
2037 }
2038
2039 if (keypress.equals(kbShiftF9)) {
2040 // Shifted F9
2041 return "\033[20;2~";
2042 }
2043
2044 if (keypress.equals(kbShiftF10)) {
2045 // Shifted F10
2046 return "\033[21;2~";
2047 }
2048
2049 if (keypress.equals(kbShiftF11)) {
2050 // Shifted F11
2051 return "\033[23;2~";
2052 }
2053
2054 if (keypress.equals(kbShiftF12)) {
2055 // Shifted F12
2056 return "\033[24;2~";
2057 }
2058
2059 if (keypress.equals(kbCtrlF1)) {
2060 // Control PF1
2061 if (vt52Mode) {
2062 return "\0335P";
2063 }
2064 if (type == DeviceType.XTERM) {
2065 return "\0331;5P";
2066 }
2067 return "\033O5P";
2068 }
2069
2070 if (keypress.equals(kbCtrlF2)) {
2071 // Control PF2
2072 if (vt52Mode) {
2073 return "\0335Q";
2074 }
2075 if (type == DeviceType.XTERM) {
2076 return "\0331;5Q";
2077 }
2078 return "\033O5Q";
2079 }
2080
2081 if (keypress.equals(kbCtrlF3)) {
2082 // Control PF3
2083 if (vt52Mode) {
2084 return "\0335R";
2085 }
2086 if (type == DeviceType.XTERM) {
2087 return "\0331;5R";
2088 }
2089 return "\033O5R";
2090 }
2091
2092 if (keypress.equals(kbCtrlF4)) {
2093 // Control PF4
2094 if (vt52Mode) {
2095 return "\0335S";
2096 }
2097 if (type == DeviceType.XTERM) {
2098 return "\0331;5S";
2099 }
2100 return "\033O5S";
2101 }
2102
2103 if (keypress.equals(kbCtrlF5)) {
2104 // Control F5
2105 return "\033[15;5~";
2106 }
2107
2108 if (keypress.equals(kbCtrlF6)) {
2109 // Control F6
2110 return "\033[17;5~";
2111 }
2112
2113 if (keypress.equals(kbCtrlF7)) {
2114 // Control F7
2115 return "\033[18;5~";
2116 }
2117
2118 if (keypress.equals(kbCtrlF8)) {
2119 // Control F8
2120 return "\033[19;5~";
2121 }
2122
2123 if (keypress.equals(kbCtrlF9)) {
2124 // Control F9
2125 return "\033[20;5~";
2126 }
2127
2128 if (keypress.equals(kbCtrlF10)) {
2129 // Control F10
2130 return "\033[21;5~";
2131 }
2132
2133 if (keypress.equals(kbCtrlF11)) {
2134 // Control F11
2135 return "\033[23;5~";
2136 }
2137
2138 if (keypress.equals(kbCtrlF12)) {
2139 // Control F12
2140 return "\033[24;5~";
2141 }
2142
2143 if (keypress.equalsWithoutModifiers(kbPgUp)) {
2144 switch (type) {
2145 case XTERM:
2146 return xtermBuildKeySequence("\033[", '5', '~',
2147 keypress.isCtrl(), keypress.isAlt(),
2148 keypress.isShift());
2149 default:
2150 return "\033[5~";
2151 }
2152 }
2153
2154 if (keypress.equalsWithoutModifiers(kbPgDn)) {
2155 switch (type) {
2156 case XTERM:
2157 return xtermBuildKeySequence("\033[", '6', '~',
2158 keypress.isCtrl(), keypress.isAlt(),
2159 keypress.isShift());
2160 default:
2161 return "\033[6~";
2162 }
2163 }
2164
2165 if (keypress.equalsWithoutModifiers(kbIns)) {
2166 switch (type) {
2167 case XTERM:
2168 return xtermBuildKeySequence("\033[", '2', '~',
2169 keypress.isCtrl(), keypress.isAlt(),
2170 keypress.isShift());
2171 default:
2172 return "\033[2~";
2173 }
2174 }
2175
2176 if (keypress.equalsWithoutModifiers(kbDel)) {
2177 switch (type) {
2178 case XTERM:
2179 return xtermBuildKeySequence("\033[", '3', '~',
2180 keypress.isCtrl(), keypress.isAlt(),
2181 keypress.isShift());
2182 default:
2183 // Delete sends real delete for VTxxx
2184 return "\177";
2185 }
2186 }
2187
2188 if (keypress.equals(kbEnter)) {
2189 return "\015";
2190 }
2191
2192 if (keypress.equals(kbEsc)) {
2193 return "\033";
2194 }
2195
2196 if (keypress.equals(kbAltEsc)) {
2197 return "\033\033";
2198 }
2199
2200 if (keypress.equals(kbTab)) {
2201 return "\011";
2202 }
2203
2204 if ((keypress.equalsWithoutModifiers(kbBackTab)) ||
2205 (keypress.equals(kbShiftTab))
2206 ) {
2207 switch (type) {
2208 case XTERM:
2209 return "\033[Z";
2210 default:
2211 return "\011";
2212 }
2213 }
2214
2215 // Non-alt, non-ctrl characters
2216 if (!keypress.isFnKey()) {
2217 StringBuilder sb = new StringBuilder();
2218 sb.append(keypress.getChar());
2219 return sb.toString();
2220 }
2221 return "";
2222 }
2223
2224 /**
2225 * Map a symbol in any one of the VT100/VT220 character sets to a Unicode
2226 * symbol.
2227 *
2228 * @param ch 8-bit character from the remote side
2229 * @param charsetGl character set defined for GL
2230 * @param charsetGr character set defined for GR
2231 * @return character to display on the screen
2232 */
2233 private char mapCharacterCharset(final char ch,
2234 final CharacterSet charsetGl,
2235 final CharacterSet charsetGr) {
2236
2237 int lookupChar = ch;
2238 CharacterSet lookupCharset = charsetGl;
2239
2240 if (ch >= 0x80) {
2241 assert ((type == DeviceType.VT220) || (type == DeviceType.XTERM));
2242 lookupCharset = charsetGr;
2243 lookupChar &= 0x7F;
2244 }
2245
2246 switch (lookupCharset) {
2247
2248 case DRAWING:
2249 return DECCharacterSets.SPECIAL_GRAPHICS[lookupChar];
2250
2251 case UK:
2252 return DECCharacterSets.UK[lookupChar];
2253
2254 case US:
2255 return DECCharacterSets.US_ASCII[lookupChar];
2256
2257 case NRC_DUTCH:
2258 return DECCharacterSets.NL[lookupChar];
2259
2260 case NRC_FINNISH:
2261 return DECCharacterSets.FI[lookupChar];
2262
2263 case NRC_FRENCH:
2264 return DECCharacterSets.FR[lookupChar];
2265
2266 case NRC_FRENCH_CA:
2267 return DECCharacterSets.FR_CA[lookupChar];
2268
2269 case NRC_GERMAN:
2270 return DECCharacterSets.DE[lookupChar];
2271
2272 case NRC_ITALIAN:
2273 return DECCharacterSets.IT[lookupChar];
2274
2275 case NRC_NORWEGIAN:
2276 return DECCharacterSets.NO[lookupChar];
2277
2278 case NRC_SPANISH:
2279 return DECCharacterSets.ES[lookupChar];
2280
2281 case NRC_SWEDISH:
2282 return DECCharacterSets.SV[lookupChar];
2283
2284 case NRC_SWISS:
2285 return DECCharacterSets.SWISS[lookupChar];
2286
2287 case DEC_SUPPLEMENTAL:
2288 return DECCharacterSets.DEC_SUPPLEMENTAL[lookupChar];
2289
2290 case VT52_GRAPHICS:
2291 return DECCharacterSets.VT52_SPECIAL_GRAPHICS[lookupChar];
2292
2293 case ROM:
2294 return DECCharacterSets.US_ASCII[lookupChar];
2295
2296 case ROM_SPECIAL:
2297 return DECCharacterSets.US_ASCII[lookupChar];
2298
2299 default:
2300 throw new IllegalArgumentException("Invalid character set value: "
2301 + lookupCharset);
2302 }
2303 }
2304
2305 /**
2306 * Map an 8-bit byte into a printable character.
2307 *
2308 * @param ch either 8-bit or Unicode character from the remote side
2309 * @return character to display on the screen
2310 */
2311 private char mapCharacter(final char ch) {
2312 if (ch >= 0x100) {
2313 // Unicode character, just return it
2314 return ch;
2315 }
2316
2317 CharacterSet charsetGl = currentState.g0Charset;
2318 CharacterSet charsetGr = currentState.grCharset;
2319
2320 if (vt52Mode == true) {
2321 if (shiftOut == true) {
2322 // Shifted out character, pull from VT52 graphics
2323 charsetGl = currentState.g1Charset;
2324 charsetGr = CharacterSet.US;
2325 } else {
2326 // Normal
2327 charsetGl = currentState.g0Charset;
2328 charsetGr = CharacterSet.US;
2329 }
2330
2331 // Pull the character
2332 return mapCharacterCharset(ch, charsetGl, charsetGr);
2333 }
2334
2335 // shiftOout
2336 if (shiftOut == true) {
2337 // Shifted out character, pull from G1
2338 charsetGl = currentState.g1Charset;
2339 charsetGr = currentState.grCharset;
2340
2341 // Pull the character
2342 return mapCharacterCharset(ch, charsetGl, charsetGr);
2343 }
2344
2345 // SS2
2346 if (singleshift == Singleshift.SS2) {
2347
2348 singleshift = Singleshift.NONE;
2349
2350 // Shifted out character, pull from G2
2351 charsetGl = currentState.g2Charset;
2352 charsetGr = currentState.grCharset;
2353 }
2354
2355 // SS3
2356 if (singleshift == Singleshift.SS3) {
2357
2358 singleshift = Singleshift.NONE;
2359
2360 // Shifted out character, pull from G3
2361 charsetGl = currentState.g3Charset;
2362 charsetGr = currentState.grCharset;
2363 }
2364
2365 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
2366 // Check for locking shift
2367
2368 switch (currentState.glLockshift) {
2369
2370 case G1_GR:
2371 throw new IllegalArgumentException("programming bug");
2372
2373 case G2_GR:
2374 throw new IllegalArgumentException("programming bug");
2375
2376 case G3_GR:
2377 throw new IllegalArgumentException("programming bug");
2378
2379 case G2_GL:
2380 // LS2
2381 charsetGl = currentState.g2Charset;
2382 break;
2383
2384 case G3_GL:
2385 // LS3
2386 charsetGl = currentState.g3Charset;
2387 break;
2388
2389 case NONE:
2390 // Normal
2391 charsetGl = currentState.g0Charset;
2392 break;
2393 }
2394
2395 switch (currentState.grLockshift) {
2396
2397 case G2_GL:
2398 throw new IllegalArgumentException("programming bug");
2399
2400 case G3_GL:
2401 throw new IllegalArgumentException("programming bug");
2402
2403 case G1_GR:
2404 // LS1R
2405 charsetGr = currentState.g1Charset;
2406 break;
2407
2408 case G2_GR:
2409 // LS2R
2410 charsetGr = currentState.g2Charset;
2411 break;
2412
2413 case G3_GR:
2414 // LS3R
2415 charsetGr = currentState.g3Charset;
2416 break;
2417
2418 case NONE:
2419 // Normal
2420 charsetGr = CharacterSet.DEC_SUPPLEMENTAL;
2421 break;
2422 }
2423
2424
2425 }
2426
2427 // Pull the character
2428 return mapCharacterCharset(ch, charsetGl, charsetGr);
2429 }
2430
2431 /**
2432 * Scroll the text within a scrolling region up n lines.
2433 *
2434 * @param regionTop top row of the scrolling region
2435 * @param regionBottom bottom row of the scrolling region
2436 * @param n number of lines to scroll
2437 */
2438 private void scrollingRegionScrollUp(final int regionTop,
2439 final int regionBottom, final int n) {
2440
2441 if (regionTop >= regionBottom) {
2442 return;
2443 }
2444
2445 // Sanity check: see if there will be any characters left after the
2446 // scroll
2447 if (regionBottom + 1 - regionTop <= n) {
2448 // There won't be anything left in the region, so just call
2449 // eraseScreen() and return.
2450 eraseScreen(regionTop, 0, regionBottom, width - 1, false);
2451 return;
2452 }
2453
2454 int remaining = regionBottom + 1 - regionTop - n;
2455 List<DisplayLine> displayTop = display.subList(0, regionTop);
2456 List<DisplayLine> displayBottom = display.subList(regionBottom + 1,
2457 display.size());
2458 List<DisplayLine> displayMiddle = display.subList(regionBottom + 1
2459 - remaining, regionBottom + 1);
2460 display = new ArrayList<DisplayLine>(displayTop);
2461 display.addAll(displayMiddle);
2462 for (int i = 0; i < n; i++) {
2463 DisplayLine line = new DisplayLine(currentState.attr);
2464 line.setReverseColor(reverseVideo);
2465 display.add(line);
2466 }
2467 display.addAll(displayBottom);
2468
2469 assert (display.size() == height);
2470 }
2471
2472 /**
2473 * Scroll the text within a scrolling region down n lines.
2474 *
2475 * @param regionTop top row of the scrolling region
2476 * @param regionBottom bottom row of the scrolling region
2477 * @param n number of lines to scroll
2478 */
2479 private void scrollingRegionScrollDown(final int regionTop,
2480 final int regionBottom, final int n) {
2481
2482 if (regionTop >= regionBottom) {
2483 return;
2484 }
2485
2486 // Sanity check: see if there will be any characters left after the
2487 // scroll
2488 if (regionBottom + 1 - regionTop <= n) {
2489 // There won't be anything left in the region, so just call
2490 // eraseScreen() and return.
2491 eraseScreen(regionTop, 0, regionBottom, width - 1, false);
2492 return;
2493 }
2494
2495 int remaining = regionBottom + 1 - regionTop - n;
2496 List<DisplayLine> displayTop = display.subList(0, regionTop);
2497 List<DisplayLine> displayBottom = display.subList(regionBottom + 1,
2498 display.size());
2499 List<DisplayLine> displayMiddle = display.subList(regionTop,
2500 regionTop + remaining);
2501 display = new ArrayList<DisplayLine>(displayTop);
2502 for (int i = 0; i < n; i++) {
2503 DisplayLine line = new DisplayLine(currentState.attr);
2504 line.setReverseColor(reverseVideo);
2505 display.add(line);
2506 }
2507 display.addAll(displayMiddle);
2508 display.addAll(displayBottom);
2509
2510 assert (display.size() == height);
2511 }
2512
2513 /**
2514 * Process a control character.
2515 *
2516 * @param ch 8-bit character from the remote side
2517 */
2518 private void handleControlChar(final char ch) {
2519 assert ((ch <= 0x1F) || ((ch >= 0x7F) && (ch <= 0x9F)));
2520
2521 switch (ch) {
2522
2523 case 0x00:
2524 // NUL - discard
2525 return;
2526
2527 case 0x05:
2528 // ENQ
2529
2530 // Transmit the answerback message.
2531 // Not supported
2532 break;
2533
2534 case 0x07:
2535 // BEL
2536 // Not supported
2537 break;
2538
2539 case 0x08:
2540 // BS
2541 cursorLeft(1, false);
2542 break;
2543
2544 case 0x09:
2545 // HT
2546 advanceToNextTabStop();
2547 break;
2548
2549 case 0x0A:
2550 // LF
2551 linefeed();
2552 break;
2553
2554 case 0x0B:
2555 // VT
2556 linefeed();
2557 break;
2558
2559 case 0x0C:
2560 // FF
2561 linefeed();
2562 break;
2563
2564 case 0x0D:
2565 // CR
2566 carriageReturn();
2567 break;
2568
2569 case 0x0E:
2570 // SO
2571 shiftOut = true;
2572 currentState.glLockshift = LockshiftMode.NONE;
2573 break;
2574
2575 case 0x0F:
2576 // SI
2577 shiftOut = false;
2578 currentState.glLockshift = LockshiftMode.NONE;
2579 break;
2580
2581 case 0x84:
2582 // IND
2583 ind();
2584 break;
2585
2586 case 0x85:
2587 // NEL
2588 nel();
2589 break;
2590
2591 case 0x88:
2592 // HTS
2593 hts();
2594 break;
2595
2596 case 0x8D:
2597 // RI
2598 ri();
2599 break;
2600
2601 case 0x8E:
2602 // SS2
2603 singleshift = Singleshift.SS2;
2604 break;
2605
2606 case 0x8F:
2607 // SS3
2608 singleshift = Singleshift.SS3;
2609 break;
2610
2611 default:
2612 break;
2613 }
2614
2615 }
2616
2617 /**
2618 * Advance the cursor to the next tab stop.
2619 */
2620 private void advanceToNextTabStop() {
2621 if (tabStops.size() == 0) {
2622 // Go to the rightmost column
2623 cursorRight(rightMargin - currentState.cursorX, false);
2624 return;
2625 }
2626 for (Integer stop: tabStops) {
2627 if (stop > currentState.cursorX) {
2628 cursorRight(stop - currentState.cursorX, false);
2629 return;
2630 }
2631 }
2632 /*
2633 * We got here, meaning there isn't a tab stop beyond the current
2634 * cursor position. Place the cursor of the right-most edge of the
2635 * screen.
2636 */
2637 cursorRight(rightMargin - currentState.cursorX, false);
2638 }
2639
2640 /**
2641 * Save a character into the collect buffer.
2642 *
2643 * @param ch character to save
2644 */
2645 private void collect(final char ch) {
2646 collectBuffer.append(ch);
2647 }
2648
2649 /**
2650 * Save a byte into the CSI parameters buffer.
2651 *
2652 * @param ch byte to save
2653 */
2654 private void param(final byte ch) {
2655 if (csiParams.size() == 0) {
2656 csiParams.add(Integer.valueOf(0));
2657 }
2658 Integer x = csiParams.get(csiParams.size() - 1);
2659 if ((ch >= '0') && (ch <= '9')) {
2660 x *= 10;
2661 x += (ch - '0');
2662 csiParams.set(csiParams.size() - 1, x);
2663 }
2664
2665 if (ch == ';') {
2666 csiParams.add(Integer.valueOf(0));
2667 }
2668 }
2669
2670 /**
2671 * Get a CSI parameter value, with a default.
2672 *
2673 * @param position parameter index. 0 is the first parameter.
2674 * @param defaultValue value to use if csiParams[position] doesn't exist
2675 * @return parameter value
2676 */
2677 private int getCsiParam(final int position, final int defaultValue) {
2678 if (csiParams.size() < position + 1) {
2679 return defaultValue;
2680 }
2681 return csiParams.get(position).intValue();
2682 }
2683
2684 /**
2685 * Get a CSI parameter value, clamped to within min/max.
2686 *
2687 * @param position parameter index. 0 is the first parameter.
2688 * @param defaultValue value to use if csiParams[position] doesn't exist
2689 * @param minValue minimum value inclusive
2690 * @param maxValue maximum value inclusive
2691 * @return parameter value
2692 */
2693 private int getCsiParam(final int position, final int defaultValue,
2694 final int minValue, final int maxValue) {
2695
2696 assert (minValue <= maxValue);
2697 int value = getCsiParam(position, defaultValue);
2698 if (value < minValue) {
2699 value = minValue;
2700 }
2701 if (value > maxValue) {
2702 value = maxValue;
2703 }
2704 return value;
2705 }
2706
2707 /**
2708 * Set or unset a toggle.
2709 *
2710 * @param value true for set ('h'), false for reset ('l')
2711 */
2712 private void setToggle(final boolean value) {
2713 boolean decPrivateModeFlag = false;
2714 for (int i = 0; i < collectBuffer.length(); i++) {
2715 if (collectBuffer.charAt(i) == '?') {
2716 decPrivateModeFlag = true;
2717 break;
2718 }
2719 }
2720
2721 for (Integer i: csiParams) {
2722
2723 switch (i) {
2724
2725 case 1:
2726 if (decPrivateModeFlag == true) {
2727 // DECCKM
2728 if (value == true) {
2729 // Use application arrow keys
2730 arrowKeyMode = ArrowKeyMode.VT100;
2731 } else {
2732 // Use ANSI arrow keys
2733 arrowKeyMode = ArrowKeyMode.ANSI;
2734 }
2735 }
2736 break;
2737 case 2:
2738 if (decPrivateModeFlag == true) {
2739 if (value == false) {
2740
2741 // DECANM
2742 vt52Mode = true;
2743 arrowKeyMode = ArrowKeyMode.VT52;
2744
2745 /*
2746 * From the VT102 docs: "You use ANSI mode to select
2747 * most terminal features; the terminal uses the same
2748 * features when it switches to VT52 mode. You
2749 * cannot, however, change most of these features in
2750 * VT52 mode."
2751 *
2752 * In other words, do not reset any other attributes
2753 * when switching between VT52 submode and ANSI.
2754 *
2755 * HOWEVER, the real vt100 does switch the character
2756 * set according to Usenet.
2757 */
2758 currentState.g0Charset = CharacterSet.US;
2759 currentState.g1Charset = CharacterSet.DRAWING;
2760 shiftOut = false;
2761
2762 if ((type == DeviceType.VT220)
2763 || (type == DeviceType.XTERM)) {
2764
2765 // VT52 mode is explicitly 7-bit
2766 s8c1t = false;
2767 singleshift = Singleshift.NONE;
2768 }
2769 }
2770 } else {
2771 // KAM
2772 if (value == true) {
2773 // Turn off keyboard
2774 // Not supported
2775 } else {
2776 // Turn on keyboard
2777 // Not supported
2778 }
2779 }
2780 break;
2781 case 3:
2782 if (decPrivateModeFlag == true) {
2783 // DECCOLM
2784 if (value == true) {
2785 // 132 columns
2786 columns132 = true;
2787 rightMargin = 131;
2788 } else {
2789 // 80 columns
2790 columns132 = false;
2791 if ((displayListener != null)
2792 && (type == DeviceType.XTERM)
2793 ) {
2794 // For xterms, reset to the actual width, not 80
2795 // columns.
2796 width = displayListener.getDisplayWidth();
2797 rightMargin = width - 1;
2798 } else {
2799 rightMargin = 79;
2800 width = rightMargin + 1;
2801 }
2802 }
2803 // Entire screen is cleared, and scrolling region is
2804 // reset
2805 eraseScreen(0, 0, height - 1, width - 1, false);
2806 scrollRegionTop = 0;
2807 scrollRegionBottom = height - 1;
2808 // Also home the cursor
2809 cursorPosition(0, 0);
2810 }
2811 break;
2812 case 4:
2813 if (decPrivateModeFlag == true) {
2814 // DECSCLM
2815 if (value == true) {
2816 // Smooth scroll
2817 // Not supported
2818 } else {
2819 // Jump scroll
2820 // Not supported
2821 }
2822 } else {
2823 // IRM
2824 if (value == true) {
2825 insertMode = true;
2826 } else {
2827 insertMode = false;
2828 }
2829 }
2830 break;
2831 case 5:
2832 if (decPrivateModeFlag == true) {
2833 // DECSCNM
2834 if (value == true) {
2835 /*
2836 * Set selects reverse screen, a white screen
2837 * background with black characters.
2838 */
2839 if (reverseVideo != true) {
2840 /*
2841 * If in normal video, switch it back
2842 */
2843 invertDisplayColors();
2844 }
2845 reverseVideo = true;
2846 } else {
2847 /*
2848 * Reset selects normal screen, a black screen
2849 * background with white characters.
2850 */
2851 if (reverseVideo == true) {
2852 /*
2853 * If in reverse video already, switch it back
2854 */
2855 invertDisplayColors();
2856 }
2857 reverseVideo = false;
2858 }
2859 }
2860 break;
2861 case 6:
2862 if (decPrivateModeFlag == true) {
2863 // DECOM
2864 if (value == true) {
2865 // Origin is relative to scroll region cursor.
2866 // Cursor can NEVER leave scrolling region.
2867 currentState.originMode = true;
2868 cursorPosition(0, 0);
2869 } else {
2870 // Origin is absolute to entire screen. Cursor can
2871 // leave the scrolling region via cup() and hvp().
2872 currentState.originMode = false;
2873 cursorPosition(0, 0);
2874 }
2875 }
2876 break;
2877 case 7:
2878 if (decPrivateModeFlag == true) {
2879 // DECAWM
2880 if (value == true) {
2881 // Turn linewrap on
2882 currentState.lineWrap = true;
2883 } else {
2884 // Turn linewrap off
2885 currentState.lineWrap = false;
2886 }
2887 }
2888 break;
2889 case 8:
2890 if (decPrivateModeFlag == true) {
2891 // DECARM
2892 if (value == true) {
2893 // Keyboard auto-repeat on
2894 // Not supported
2895 } else {
2896 // Keyboard auto-repeat off
2897 // Not supported
2898 }
2899 }
2900 break;
2901 case 12:
2902 if (decPrivateModeFlag == false) {
2903 // SRM
2904 if (value == true) {
2905 // Local echo off
2906 fullDuplex = true;
2907 } else {
2908 // Local echo on
2909 fullDuplex = false;
2910 }
2911 }
2912 break;
2913 case 18:
2914 if (decPrivateModeFlag == true) {
2915 // DECPFF
2916 // Not supported
2917 }
2918 break;
2919 case 19:
2920 if (decPrivateModeFlag == true) {
2921 // DECPEX
2922 // Not supported
2923 }
2924 break;
2925 case 20:
2926 if (decPrivateModeFlag == false) {
2927 // LNM
2928 if (value == true) {
2929 /*
2930 * Set causes a received linefeed, form feed, or
2931 * vertical tab to move cursor to first column of
2932 * next line. RETURN transmits both a carriage return
2933 * and linefeed. This selection is also called new
2934 * line option.
2935 */
2936 newLineMode = true;
2937 } else {
2938 /*
2939 * Reset causes a received linefeed, form feed, or
2940 * vertical tab to move cursor to next line in
2941 * current column. RETURN transmits a carriage
2942 * return.
2943 */
2944 newLineMode = false;
2945 }
2946 }
2947 break;
2948
2949 case 25:
2950 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
2951 if (decPrivateModeFlag == true) {
2952 // DECTCEM
2953 if (value == true) {
2954 // Visible cursor
2955 cursorVisible = true;
2956 } else {
2957 // Invisible cursor
2958 cursorVisible = false;
2959 }
2960 }
2961 }
2962 break;
2963
2964 case 42:
2965 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
2966 if (decPrivateModeFlag == true) {
2967 // DECNRCM
2968 if (value == true) {
2969 // Select national mode NRC
2970 // Not supported
2971 } else {
2972 // Select multi-national mode
2973 // Not supported
2974 }
2975 }
2976 }
2977
2978 break;
2979
2980 case 1000:
2981 if ((type == DeviceType.XTERM)
2982 && (decPrivateModeFlag == true)
2983 ) {
2984 // Mouse: normal tracking mode
2985 if (value == true) {
2986 mouseProtocol = MouseProtocol.NORMAL;
2987 } else {
2988 mouseProtocol = MouseProtocol.OFF;
2989 }
2990 }
2991 break;
2992
2993 case 1002:
2994 if ((type == DeviceType.XTERM)
2995 && (decPrivateModeFlag == true)
2996 ) {
2997 // Mouse: normal tracking mode
2998 if (value == true) {
2999 mouseProtocol = MouseProtocol.BUTTONEVENT;
3000 } else {
3001 mouseProtocol = MouseProtocol.OFF;
3002 }
3003 }
3004 break;
3005
3006 case 1003:
3007 if ((type == DeviceType.XTERM)
3008 && (decPrivateModeFlag == true)
3009 ) {
3010 // Mouse: Any-event tracking mode
3011 if (value == true) {
3012 mouseProtocol = MouseProtocol.ANYEVENT;
3013 } else {
3014 mouseProtocol = MouseProtocol.OFF;
3015 }
3016 }
3017 break;
3018
3019 case 1005:
3020 if ((type == DeviceType.XTERM)
3021 && (decPrivateModeFlag == true)
3022 ) {
3023 // Mouse: UTF-8 coordinates
3024 if (value == true) {
3025 mouseEncoding = MouseEncoding.UTF8;
3026 } else {
3027 mouseEncoding = MouseEncoding.X10;
3028 }
3029 }
3030 break;
3031
3032 case 1006:
3033 if ((type == DeviceType.XTERM)
3034 && (decPrivateModeFlag == true)
3035 ) {
3036 // Mouse: SGR coordinates
3037 if (value == true) {
3038 mouseEncoding = MouseEncoding.SGR;
3039 } else {
3040 mouseEncoding = MouseEncoding.X10;
3041 }
3042 }
3043 break;
3044
3045 default:
3046 break;
3047
3048 }
3049 }
3050 }
3051
3052 /**
3053 * DECSC - Save cursor.
3054 */
3055 private void decsc() {
3056 savedState.setTo(currentState);
3057 }
3058
3059 /**
3060 * DECRC - Restore cursor.
3061 */
3062 private void decrc() {
3063 currentState.setTo(savedState);
3064 }
3065
3066 /**
3067 * IND - Index.
3068 */
3069 private void ind() {
3070 // Move the cursor and scroll if necessary. If at the bottom line
3071 // already, a scroll up is supposed to be performed.
3072 if (currentState.cursorY == scrollRegionBottom) {
3073 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
3074 }
3075 cursorDown(1, true);
3076 }
3077
3078 /**
3079 * RI - Reverse index.
3080 */
3081 private void ri() {
3082 // Move the cursor and scroll if necessary. If at the top line
3083 // already, a scroll down is supposed to be performed.
3084 if (currentState.cursorY == scrollRegionTop) {
3085 scrollingRegionScrollDown(scrollRegionTop, scrollRegionBottom, 1);
3086 }
3087 cursorUp(1, true);
3088 }
3089
3090 /**
3091 * NEL - Next line.
3092 */
3093 private void nel() {
3094 // Move the cursor and scroll if necessary. If at the bottom line
3095 // already, a scroll up is supposed to be performed.
3096 if (currentState.cursorY == scrollRegionBottom) {
3097 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom, 1);
3098 }
3099 cursorDown(1, true);
3100
3101 // Reset to the beginning of the next line
3102 currentState.cursorX = 0;
3103 }
3104
3105 /**
3106 * DECKPAM - Keypad application mode.
3107 */
3108 private void deckpam() {
3109 keypadMode = KeypadMode.Application;
3110 }
3111
3112 /**
3113 * DECKPNM - Keypad numeric mode.
3114 */
3115 private void deckpnm() {
3116 keypadMode = KeypadMode.Numeric;
3117 }
3118
3119 /**
3120 * Move up n spaces.
3121 *
3122 * @param n number of spaces to move
3123 * @param honorScrollRegion if true, then do nothing if the cursor is
3124 * outside the scrolling region
3125 */
3126 private void cursorUp(final int n, final boolean honorScrollRegion) {
3127 int top;
3128
3129 /*
3130 * Special case: if a user moves the cursor from the right margin, we
3131 * have to reset the VT100 right margin flag.
3132 */
3133 if (n > 0) {
3134 wrapLineFlag = false;
3135 }
3136
3137 for (int i = 0; i < n; i++) {
3138 if (honorScrollRegion == true) {
3139 // Honor the scrolling region
3140 if ((currentState.cursorY < scrollRegionTop)
3141 || (currentState.cursorY > scrollRegionBottom)
3142 ) {
3143 // Outside region, do nothing
3144 return;
3145 }
3146 // Inside region, go up
3147 top = scrollRegionTop;
3148 } else {
3149 // Non-scrolling case
3150 top = 0;
3151 }
3152
3153 if (currentState.cursorY > top) {
3154 currentState.cursorY--;
3155 }
3156 }
3157 }
3158
3159 /**
3160 * Move down n spaces.
3161 *
3162 * @param n number of spaces to move
3163 * @param honorScrollRegion if true, then do nothing if the cursor is
3164 * outside the scrolling region
3165 */
3166 private void cursorDown(final int n, final boolean honorScrollRegion) {
3167 int bottom;
3168
3169 /*
3170 * Special case: if a user moves the cursor from the right margin, we
3171 * have to reset the VT100 right margin flag.
3172 */
3173 if (n > 0) {
3174 wrapLineFlag = false;
3175 }
3176
3177 for (int i = 0; i < n; i++) {
3178
3179 if (honorScrollRegion == true) {
3180 // Honor the scrolling region
3181 if (currentState.cursorY > scrollRegionBottom) {
3182 // Outside region, do nothing
3183 return;
3184 }
3185 // Inside region, go down
3186 bottom = scrollRegionBottom;
3187 } else {
3188 // Non-scrolling case
3189 bottom = height - 1;
3190 }
3191
3192 if (currentState.cursorY < bottom) {
3193 currentState.cursorY++;
3194 }
3195 }
3196 }
3197
3198 /**
3199 * Move left n spaces.
3200 *
3201 * @param n number of spaces to move
3202 * @param honorScrollRegion if true, then do nothing if the cursor is
3203 * outside the scrolling region
3204 */
3205 private void cursorLeft(final int n, final boolean honorScrollRegion) {
3206 /*
3207 * Special case: if a user moves the cursor from the right margin, we
3208 * have to reset the VT100 right margin flag.
3209 */
3210 if (n > 0) {
3211 wrapLineFlag = false;
3212 }
3213
3214 for (int i = 0; i < n; i++) {
3215 if (honorScrollRegion == true) {
3216 // Honor the scrolling region
3217 if ((currentState.cursorY < scrollRegionTop)
3218 || (currentState.cursorY > scrollRegionBottom)
3219 ) {
3220 // Outside region, do nothing
3221 return;
3222 }
3223 }
3224
3225 if (currentState.cursorX > 0) {
3226 currentState.cursorX--;
3227 }
3228 }
3229 }
3230
3231 /**
3232 * Move right n spaces.
3233 *
3234 * @param n number of spaces to move
3235 * @param honorScrollRegion if true, then do nothing if the cursor is
3236 * outside the scrolling region
3237 */
3238 private void cursorRight(final int n, final boolean honorScrollRegion) {
3239 int rightMargin = this.rightMargin;
3240
3241 /*
3242 * Special case: if a user moves the cursor from the right margin, we
3243 * have to reset the VT100 right margin flag.
3244 */
3245 if (n > 0) {
3246 wrapLineFlag = false;
3247 }
3248
3249 if (display.get(currentState.cursorY).isDoubleWidth()) {
3250 rightMargin = ((rightMargin + 1) / 2) - 1;
3251 }
3252
3253 for (int i = 0; i < n; i++) {
3254 if (honorScrollRegion == true) {
3255 // Honor the scrolling region
3256 if ((currentState.cursorY < scrollRegionTop)
3257 || (currentState.cursorY > scrollRegionBottom)
3258 ) {
3259 // Outside region, do nothing
3260 return;
3261 }
3262 }
3263
3264 if (currentState.cursorX < rightMargin) {
3265 currentState.cursorX++;
3266 }
3267 }
3268 }
3269
3270 /**
3271 * Move cursor to (col, row) where (0, 0) is the top-left corner.
3272 *
3273 * @param row row to move to
3274 * @param col column to move to
3275 */
3276 private void cursorPosition(int row, final int col) {
3277 int rightMargin = this.rightMargin;
3278
3279 assert (col >= 0);
3280 assert (row >= 0);
3281
3282 if (display.get(currentState.cursorY).isDoubleWidth()) {
3283 rightMargin = ((rightMargin + 1) / 2) - 1;
3284 }
3285
3286 // Set column number
3287 currentState.cursorX = col;
3288
3289 // Sanity check, bring column back to margin.
3290 if (currentState.cursorX > rightMargin) {
3291 currentState.cursorX = rightMargin;
3292 }
3293
3294 // Set row number
3295 if (currentState.originMode == true) {
3296 row += scrollRegionTop;
3297 }
3298 if (currentState.cursorY < row) {
3299 cursorDown(row - currentState.cursorY, false);
3300 } else if (currentState.cursorY > row) {
3301 cursorUp(currentState.cursorY - row, false);
3302 }
3303
3304 wrapLineFlag = false;
3305 }
3306
3307 /**
3308 * HTS - Horizontal tabulation set.
3309 */
3310 private void hts() {
3311 for (Integer stop: tabStops) {
3312 if (stop == currentState.cursorX) {
3313 // Already have a tab stop here
3314 return;
3315 }
3316 }
3317
3318 // Append a tab stop to the end of the array and resort them
3319 tabStops.add(currentState.cursorX);
3320 Collections.sort(tabStops);
3321 }
3322
3323 /**
3324 * DECSWL - Single-width line.
3325 */
3326 private void decswl() {
3327 display.get(currentState.cursorY).setDoubleWidth(false);
3328 display.get(currentState.cursorY).setDoubleHeight(0);
3329 }
3330
3331 /**
3332 * DECDWL - Double-width line.
3333 */
3334 private void decdwl() {
3335 display.get(currentState.cursorY).setDoubleWidth(true);
3336 display.get(currentState.cursorY).setDoubleHeight(0);
3337 }
3338
3339 /**
3340 * DECHDL - Double-height + double-width line.
3341 *
3342 * @param topHalf if true, this sets the row to be the top half row of a
3343 * double-height row
3344 */
3345 private void dechdl(final boolean topHalf) {
3346 display.get(currentState.cursorY).setDoubleWidth(true);
3347 if (topHalf == true) {
3348 display.get(currentState.cursorY).setDoubleHeight(1);
3349 } else {
3350 display.get(currentState.cursorY).setDoubleHeight(2);
3351 }
3352 }
3353
3354 /**
3355 * DECALN - Screen alignment display.
3356 */
3357 private void decaln() {
3358 Cell newCell = new Cell();
3359 newCell.setChar('E');
3360 for (DisplayLine line: display) {
3361 for (int i = 0; i < line.length(); i++) {
3362 line.replace(i, newCell);
3363 }
3364 }
3365 }
3366
3367 /**
3368 * DECSCL - Compatibility level.
3369 */
3370 private void decscl() {
3371 int i = getCsiParam(0, 0);
3372 int j = getCsiParam(1, 0);
3373
3374 if (i == 61) {
3375 // Reset fonts
3376 currentState.g0Charset = CharacterSet.US;
3377 currentState.g1Charset = CharacterSet.DRAWING;
3378 s8c1t = false;
3379 } else if (i == 62) {
3380
3381 if ((j == 0) || (j == 2)) {
3382 s8c1t = true;
3383 } else if (j == 1) {
3384 s8c1t = false;
3385 }
3386 }
3387 }
3388
3389 /**
3390 * CUD - Cursor down.
3391 */
3392 private void cud() {
3393 cursorDown(getCsiParam(0, 1, 1, height), true);
3394 }
3395
3396 /**
3397 * CUF - Cursor forward.
3398 */
3399 private void cuf() {
3400 cursorRight(getCsiParam(0, 1, 1, rightMargin + 1), true);
3401 }
3402
3403 /**
3404 * CUB - Cursor backward.
3405 */
3406 private void cub() {
3407 cursorLeft(getCsiParam(0, 1, 1, currentState.cursorX + 1), true);
3408 }
3409
3410 /**
3411 * CUU - Cursor up.
3412 */
3413 private void cuu() {
3414 cursorUp(getCsiParam(0, 1, 1, currentState.cursorY + 1), true);
3415 }
3416
3417 /**
3418 * CUP - Cursor position.
3419 */
3420 private void cup() {
3421 cursorPosition(getCsiParam(0, 1, 1, height) - 1,
3422 getCsiParam(1, 1, 1, rightMargin + 1) - 1);
3423 }
3424
3425 /**
3426 * CNL - Cursor down and to column 1.
3427 */
3428 private void cnl() {
3429 cursorDown(getCsiParam(0, 1, 1, height), true);
3430 // To column 0
3431 cursorLeft(currentState.cursorX, true);
3432 }
3433
3434 /**
3435 * CPL - Cursor up and to column 1.
3436 */
3437 private void cpl() {
3438 cursorUp(getCsiParam(0, 1, 1, currentState.cursorY + 1), true);
3439 // To column 0
3440 cursorLeft(currentState.cursorX, true);
3441 }
3442
3443 /**
3444 * CHA - Cursor to column # in current row.
3445 */
3446 private void cha() {
3447 cursorPosition(currentState.cursorY,
3448 getCsiParam(0, 1, 1, rightMargin + 1) - 1);
3449 }
3450
3451 /**
3452 * VPA - Cursor to row #, same column.
3453 */
3454 private void vpa() {
3455 cursorPosition(getCsiParam(0, 1, 1, height) - 1,
3456 currentState.cursorX);
3457 }
3458
3459 /**
3460 * ED - Erase in display.
3461 */
3462 private void ed() {
3463 boolean honorProtected = false;
3464 boolean decPrivateModeFlag = false;
3465
3466 for (int i = 0; i < collectBuffer.length(); i++) {
3467 if (collectBuffer.charAt(i) == '?') {
3468 decPrivateModeFlag = true;
3469 break;
3470 }
3471 }
3472
3473 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
3474 && (decPrivateModeFlag == true)
3475 ) {
3476 honorProtected = true;
3477 }
3478
3479 int i = getCsiParam(0, 0);
3480
3481 if (i == 0) {
3482 // Erase from here to end of screen
3483 if (currentState.cursorY < height - 1) {
3484 eraseScreen(currentState.cursorY + 1, 0, height - 1, width - 1,
3485 honorProtected);
3486 }
3487 eraseLine(currentState.cursorX, width - 1, honorProtected);
3488 } else if (i == 1) {
3489 // Erase from beginning of screen to here
3490 eraseScreen(0, 0, currentState.cursorY - 1, width - 1,
3491 honorProtected);
3492 eraseLine(0, currentState.cursorX, honorProtected);
3493 } else if (i == 2) {
3494 // Erase entire screen
3495 eraseScreen(0, 0, height - 1, width - 1, honorProtected);
3496 }
3497 }
3498
3499 /**
3500 * EL - Erase in line.
3501 */
3502 private void el() {
3503 boolean honorProtected = false;
3504 boolean decPrivateModeFlag = false;
3505
3506 for (int i = 0; i < collectBuffer.length(); i++) {
3507 if (collectBuffer.charAt(i) == '?') {
3508 decPrivateModeFlag = true;
3509 break;
3510 }
3511 }
3512
3513 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
3514 && (decPrivateModeFlag == true)
3515 ) {
3516 honorProtected = true;
3517 }
3518
3519 int i = getCsiParam(0, 0);
3520
3521 if (i == 0) {
3522 // Erase from here to end of line
3523 eraseLine(currentState.cursorX, width - 1, honorProtected);
3524 } else if (i == 1) {
3525 // Erase from beginning of line to here
3526 eraseLine(0, currentState.cursorX, honorProtected);
3527 } else if (i == 2) {
3528 // Erase entire line
3529 eraseLine(0, width - 1, honorProtected);
3530 }
3531 }
3532
3533 /**
3534 * ECH - Erase # of characters in current row.
3535 */
3536 private void ech() {
3537 int i = getCsiParam(0, 1, 1, width);
3538
3539 // Erase from here to i characters
3540 eraseLine(currentState.cursorX, currentState.cursorX + i - 1, false);
3541 }
3542
3543 /**
3544 * IL - Insert line.
3545 */
3546 private void il() {
3547 int i = getCsiParam(0, 1);
3548
3549 if ((currentState.cursorY >= scrollRegionTop)
3550 && (currentState.cursorY <= scrollRegionBottom)
3551 ) {
3552
3553 // I can get the same effect with a scroll-down
3554 scrollingRegionScrollDown(currentState.cursorY,
3555 scrollRegionBottom, i);
3556 }
3557 }
3558
3559 /**
3560 * DCH - Delete char.
3561 */
3562 private void dch() {
3563 int n = getCsiParam(0, 1);
3564 DisplayLine line = display.get(currentState.cursorY);
3565 Cell blank = new Cell();
3566 for (int i = 0; i < n; i++) {
3567 line.delete(currentState.cursorX, blank);
3568 }
3569 }
3570
3571 /**
3572 * ICH - Insert blank char at cursor.
3573 */
3574 private void ich() {
3575 int n = getCsiParam(0, 1);
3576 DisplayLine line = display.get(currentState.cursorY);
3577 Cell blank = new Cell();
3578 for (int i = 0; i < n; i++) {
3579 line.insert(currentState.cursorX, blank);
3580 }
3581 }
3582
3583 /**
3584 * DL - Delete line.
3585 */
3586 private void dl() {
3587 int i = getCsiParam(0, 1);
3588
3589 if ((currentState.cursorY >= scrollRegionTop)
3590 && (currentState.cursorY <= scrollRegionBottom)) {
3591
3592 // I can get the same effect with a scroll-down
3593 scrollingRegionScrollUp(currentState.cursorY,
3594 scrollRegionBottom, i);
3595 }
3596 }
3597
3598 /**
3599 * HVP - Horizontal and vertical position.
3600 */
3601 private void hvp() {
3602 cup();
3603 }
3604
3605 /**
3606 * REP - Repeat character.
3607 */
3608 private void rep() {
3609 int n = getCsiParam(0, 1);
3610 for (int i = 0; i < n; i++) {
3611 printCharacter(repCh);
3612 }
3613 }
3614
3615 /**
3616 * SU - Scroll up.
3617 */
3618 private void su() {
3619 scrollingRegionScrollUp(scrollRegionTop, scrollRegionBottom,
3620 getCsiParam(0, 1, 1, height));
3621 }
3622
3623 /**
3624 * SD - Scroll down.
3625 */
3626 private void sd() {
3627 scrollingRegionScrollDown(scrollRegionTop, scrollRegionBottom,
3628 getCsiParam(0, 1, 1, height));
3629 }
3630
3631 /**
3632 * CBT - Go back X tab stops.
3633 */
3634 private void cbt() {
3635 int tabsToMove = getCsiParam(0, 1);
3636 int tabI;
3637
3638 for (int i = 0; i < tabsToMove; i++) {
3639 int j = currentState.cursorX;
3640 for (tabI = 0; tabI < tabStops.size(); tabI++) {
3641 if (tabStops.get(tabI) >= currentState.cursorX) {
3642 break;
3643 }
3644 }
3645 tabI--;
3646 if (tabI <= 0) {
3647 j = 0;
3648 } else {
3649 j = tabStops.get(tabI);
3650 }
3651 cursorPosition(currentState.cursorY, j);
3652 }
3653 }
3654
3655 /**
3656 * CHT - Advance X tab stops.
3657 */
3658 private void cht() {
3659 int n = getCsiParam(0, 1);
3660 for (int i = 0; i < n; i++) {
3661 advanceToNextTabStop();
3662 }
3663 }
3664
3665 /**
3666 * SGR - Select graphics rendition.
3667 */
3668 private void sgr() {
3669
3670 if (csiParams.size() == 0) {
3671 currentState.attr.reset();
3672 return;
3673 }
3674
3675 int sgrColorMode = -1;
3676 boolean idx88Color = false;
3677 boolean rgbColor = false;
3678 int rgbRed = -1;
3679 int rgbGreen = -1;
3680
3681 for (Integer i: csiParams) {
3682
3683 if ((sgrColorMode == 38) || (sgrColorMode == 48)) {
3684
3685 assert (type == DeviceType.XTERM);
3686
3687 if (idx88Color) {
3688 /*
3689 * Indexed color mode, we now have the index number.
3690 */
3691 if (sgrColorMode == 38) {
3692 currentState.attr.setForeColorRGB(get88Color(i));
3693 } else {
3694 assert (sgrColorMode == 48);
3695 currentState.attr.setBackColorRGB(get88Color(i));
3696 }
3697 sgrColorMode = -1;
3698 idx88Color = false;
3699 continue;
3700 }
3701
3702 if (rgbColor) {
3703 /*
3704 * RGB color mode, we are collecting tokens.
3705 */
3706 if (rgbRed == -1) {
3707 rgbRed = i & 0xFF;
3708 } else if (rgbGreen == -1) {
3709 rgbGreen = i & 0xFF;
3710 } else {
3711 int rgb = rgbRed << 16;
3712 rgb |= rgbGreen << 8;
3713 rgb |= i & 0xFF;
3714
3715 // System.err.printf("RGB: %08x\n", rgb);
3716
3717 if (sgrColorMode == 38) {
3718 currentState.attr.setForeColorRGB(rgb);
3719 } else {
3720 assert (sgrColorMode == 48);
3721 currentState.attr.setBackColorRGB(rgb);
3722 }
3723 rgbRed = -1;
3724 rgbGreen = -1;
3725 sgrColorMode = -1;
3726 rgbColor = false;
3727 }
3728 continue;
3729 }
3730
3731 switch (i) {
3732
3733 case 2:
3734 /*
3735 * RGB color mode.
3736 */
3737 rgbColor = true;
3738 break;
3739
3740 case 5:
3741 /*
3742 * Indexed color mode.
3743 */
3744 idx88Color = true;
3745 break;
3746
3747 default:
3748 /*
3749 * This is neither indexed nor RGB color. Bail out.
3750 */
3751 return;
3752 }
3753
3754 } // if ((sgrColorMode == 38) || (sgrColorMode == 48))
3755
3756 switch (i) {
3757
3758 case 0:
3759 // Normal
3760 currentState.attr.reset();
3761 break;
3762
3763 case 1:
3764 // Bold
3765 currentState.attr.setBold(true);
3766 break;
3767
3768 case 4:
3769 // Underline
3770 currentState.attr.setUnderline(true);
3771 break;
3772
3773 case 5:
3774 // Blink
3775 currentState.attr.setBlink(true);
3776 break;
3777
3778 case 7:
3779 // Reverse
3780 currentState.attr.setReverse(true);
3781 break;
3782
3783 default:
3784 break;
3785 }
3786
3787 if (type == DeviceType.XTERM) {
3788
3789 switch (i) {
3790
3791 case 8:
3792 // Invisible
3793 // TODO
3794 break;
3795
3796 case 90:
3797 // Set black foreground
3798 currentState.attr.setForeColorRGB(get88Color(8));
3799 break;
3800 case 91:
3801 // Set red foreground
3802 currentState.attr.setForeColorRGB(get88Color(9));
3803 break;
3804 case 92:
3805 // Set green foreground
3806 currentState.attr.setForeColorRGB(get88Color(10));
3807 break;
3808 case 93:
3809 // Set yellow foreground
3810 currentState.attr.setForeColorRGB(get88Color(11));
3811 break;
3812 case 94:
3813 // Set blue foreground
3814 currentState.attr.setForeColorRGB(get88Color(12));
3815 break;
3816 case 95:
3817 // Set magenta foreground
3818 currentState.attr.setForeColorRGB(get88Color(13));
3819 break;
3820 case 96:
3821 // Set cyan foreground
3822 currentState.attr.setForeColorRGB(get88Color(14));
3823 break;
3824 case 97:
3825 // Set white foreground
3826 currentState.attr.setForeColorRGB(get88Color(15));
3827 break;
3828
3829 case 100:
3830 // Set black background
3831 currentState.attr.setBackColorRGB(get88Color(8));
3832 break;
3833 case 101:
3834 // Set red background
3835 currentState.attr.setBackColorRGB(get88Color(9));
3836 break;
3837 case 102:
3838 // Set green background
3839 currentState.attr.setBackColorRGB(get88Color(10));
3840 break;
3841 case 103:
3842 // Set yellow background
3843 currentState.attr.setBackColorRGB(get88Color(11));
3844 break;
3845 case 104:
3846 // Set blue background
3847 currentState.attr.setBackColorRGB(get88Color(12));
3848 break;
3849 case 105:
3850 // Set magenta background
3851 currentState.attr.setBackColorRGB(get88Color(13));
3852 break;
3853 case 106:
3854 // Set cyan background
3855 currentState.attr.setBackColorRGB(get88Color(14));
3856 break;
3857 case 107:
3858 // Set white background
3859 currentState.attr.setBackColorRGB(get88Color(15));
3860 break;
3861
3862 default:
3863 break;
3864 }
3865 }
3866
3867 if ((type == DeviceType.VT220)
3868 || (type == DeviceType.XTERM)) {
3869
3870 switch (i) {
3871
3872 case 22:
3873 // Normal intensity
3874 currentState.attr.setBold(false);
3875 break;
3876
3877 case 24:
3878 // No underline
3879 currentState.attr.setUnderline(false);
3880 break;
3881
3882 case 25:
3883 // No blink
3884 currentState.attr.setBlink(false);
3885 break;
3886
3887 case 27:
3888 // Un-reverse
3889 currentState.attr.setReverse(false);
3890 break;
3891
3892 default:
3893 break;
3894 }
3895 }
3896
3897 // A true VT100/102/220 does not support color, however everyone
3898 // is used to their terminal emulator supporting color so we will
3899 // unconditionally support color for all DeviceType's.
3900
3901 switch (i) {
3902
3903 case 30:
3904 // Set black foreground
3905 currentState.attr.setForeColor(Color.BLACK);
3906 currentState.attr.setForeColorRGB(-1);
3907 break;
3908 case 31:
3909 // Set red foreground
3910 currentState.attr.setForeColor(Color.RED);
3911 currentState.attr.setForeColorRGB(-1);
3912 break;
3913 case 32:
3914 // Set green foreground
3915 currentState.attr.setForeColor(Color.GREEN);
3916 currentState.attr.setForeColorRGB(-1);
3917 break;
3918 case 33:
3919 // Set yellow foreground
3920 currentState.attr.setForeColor(Color.YELLOW);
3921 currentState.attr.setForeColorRGB(-1);
3922 break;
3923 case 34:
3924 // Set blue foreground
3925 currentState.attr.setForeColor(Color.BLUE);
3926 currentState.attr.setForeColorRGB(-1);
3927 break;
3928 case 35:
3929 // Set magenta foreground
3930 currentState.attr.setForeColor(Color.MAGENTA);
3931 currentState.attr.setForeColorRGB(-1);
3932 break;
3933 case 36:
3934 // Set cyan foreground
3935 currentState.attr.setForeColor(Color.CYAN);
3936 currentState.attr.setForeColorRGB(-1);
3937 break;
3938 case 37:
3939 // Set white foreground
3940 currentState.attr.setForeColor(Color.WHITE);
3941 currentState.attr.setForeColorRGB(-1);
3942 break;
3943 case 38:
3944 if (type == DeviceType.XTERM) {
3945 /*
3946 * Xterm supports T.416 / ISO-8613-3 codes to select
3947 * either an indexed color or an RGB value. (It also
3948 * permits these ISO-8613-3 SGR sequences to be separated
3949 * by colons rather than semicolons.)
3950 *
3951 * We will support only the following:
3952 *
3953 * 1. Indexed color mode (88- or 256-color modes).
3954 *
3955 * 2. Direct RGB.
3956 *
3957 * These cover most of the use cases in the real world.
3958 *
3959 * HOWEVER, note that this is an awful broken "standard",
3960 * with no way to do it "right". See
3961 * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
3962 * for a detailed discussion of the current state of RGB
3963 * in various terminals, the point of which is that none
3964 * of them really do the same thing despite all appearing
3965 * to be "xterm".
3966 *
3967 * Also see
3968 * https://bugs.kde.org/show_bug.cgi?id=107487#c3 .
3969 * where it is assumed that supporting just the "indexed
3970 * mode" of these sequences (which could align easily
3971 * with existing SGR colors) is assumed to mean full
3972 * support of 24-bit RGB. So it is all or nothing.
3973 *
3974 * Finally, these sequences break the assumptions of
3975 * standard ECMA-48 style parsers as pointed out at
3976 * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
3977 * Therefore in order to keep a clean display, we cannot
3978 * parse anything else in this sequence.
3979 */
3980 sgrColorMode = 38;
3981 continue;
3982 } else {
3983 // Underscore on, default foreground color
3984 currentState.attr.setUnderline(true);
3985 currentState.attr.setForeColor(Color.WHITE);
3986 }
3987 break;
3988 case 39:
3989 // Underscore off, default foreground color
3990 currentState.attr.setUnderline(false);
3991 currentState.attr.setForeColor(Color.WHITE);
3992 currentState.attr.setForeColorRGB(-1);
3993 break;
3994 case 40:
3995 // Set black background
3996 currentState.attr.setBackColor(Color.BLACK);
3997 currentState.attr.setBackColorRGB(-1);
3998 break;
3999 case 41:
4000 // Set red background
4001 currentState.attr.setBackColor(Color.RED);
4002 currentState.attr.setBackColorRGB(-1);
4003 break;
4004 case 42:
4005 // Set green background
4006 currentState.attr.setBackColor(Color.GREEN);
4007 currentState.attr.setBackColorRGB(-1);
4008 break;
4009 case 43:
4010 // Set yellow background
4011 currentState.attr.setBackColor(Color.YELLOW);
4012 currentState.attr.setBackColorRGB(-1);
4013 break;
4014 case 44:
4015 // Set blue background
4016 currentState.attr.setBackColor(Color.BLUE);
4017 currentState.attr.setBackColorRGB(-1);
4018 break;
4019 case 45:
4020 // Set magenta background
4021 currentState.attr.setBackColor(Color.MAGENTA);
4022 currentState.attr.setBackColorRGB(-1);
4023 break;
4024 case 46:
4025 // Set cyan background
4026 currentState.attr.setBackColor(Color.CYAN);
4027 currentState.attr.setBackColorRGB(-1);
4028 break;
4029 case 47:
4030 // Set white background
4031 currentState.attr.setBackColor(Color.WHITE);
4032 currentState.attr.setBackColorRGB(-1);
4033 break;
4034 case 48:
4035 if (type == DeviceType.XTERM) {
4036 /*
4037 * Xterm supports T.416 / ISO-8613-3 codes to select
4038 * either an indexed color or an RGB value. (It also
4039 * permits these ISO-8613-3 SGR sequences to be separated
4040 * by colons rather than semicolons.)
4041 *
4042 * We will support only the following:
4043 *
4044 * 1. Indexed color mode (88- or 256-color modes).
4045 *
4046 * 2. Direct RGB.
4047 *
4048 * These cover most of the use cases in the real world.
4049 *
4050 * HOWEVER, note that this is an awful broken "standard",
4051 * with no way to do it "right". See
4052 * http://invisible-island.net/ncurses/ncurses.faq.html#xterm_16MegaColors
4053 * for a detailed discussion of the current state of RGB
4054 * in various terminals, the point of which is that none
4055 * of them really do the same thing despite all appearing
4056 * to be "xterm".
4057 *
4058 * Also see
4059 * https://bugs.kde.org/show_bug.cgi?id=107487#c3 .
4060 * where it is assumed that supporting just the "indexed
4061 * mode" of these sequences (which could align easily
4062 * with existing SGR colors) is assumed to mean full
4063 * support of 24-bit RGB. So it is all or nothing.
4064 *
4065 * Finally, these sequences break the assumptions of
4066 * standard ECMA-48 style parsers as pointed out at
4067 * https://bugs.kde.org/show_bug.cgi?id=107487#c11 .
4068 * Therefore in order to keep a clean display, we cannot
4069 * parse anything else in this sequence.
4070 */
4071 sgrColorMode = 48;
4072 continue;
4073 }
4074 break;
4075 case 49:
4076 // Default background
4077 currentState.attr.setBackColor(Color.BLACK);
4078 currentState.attr.setBackColorRGB(-1);
4079 break;
4080
4081 default:
4082 break;
4083 }
4084 }
4085 }
4086
4087 /**
4088 * DA - Device attributes.
4089 */
4090 private void da() {
4091 int extendedFlag = 0;
4092 int i = 0;
4093 if (collectBuffer.length() > 0) {
4094 String args = collectBuffer.substring(1);
4095 if (collectBuffer.charAt(0) == '>') {
4096 extendedFlag = 1;
4097 if (collectBuffer.length() >= 2) {
4098 i = Integer.parseInt(args.toString());
4099 }
4100 } else if (collectBuffer.charAt(0) == '=') {
4101 extendedFlag = 2;
4102 if (collectBuffer.length() >= 2) {
4103 i = Integer.parseInt(args.toString());
4104 }
4105 } else {
4106 // Unknown code, bail out
4107 return;
4108 }
4109 }
4110
4111 if ((i != 0) && (i != 1)) {
4112 return;
4113 }
4114
4115 if ((extendedFlag == 0) && (i == 0)) {
4116 // Send string directly to remote side
4117 writeRemote(deviceTypeResponse());
4118 return;
4119 }
4120
4121 if ((type == DeviceType.VT220) || (type == DeviceType.XTERM)) {
4122
4123 if ((extendedFlag == 1) && (i == 0)) {
4124 /*
4125 * Request "What type of terminal are you, what is your
4126 * firmware version, and what hardware options do you have
4127 * installed?"
4128 *
4129 * Respond: "I am a VT220 (identification code of 1), my
4130 * firmware version is _____ (Pv), and I have _____ Po
4131 * options installed."
4132 *
4133 * (Same as xterm)
4134 *
4135 */
4136
4137 if (s8c1t == true) {
4138 writeRemote("\u009b>1;10;0c");
4139 } else {
4140 writeRemote("\033[>1;10;0c");
4141 }
4142 }
4143 }
4144
4145 // VT420 and up
4146 if ((extendedFlag == 2) && (i == 0)) {
4147
4148 /*
4149 * Request "What is your unit ID?"
4150 *
4151 * Respond: "I was manufactured at site 00 and have a unique ID
4152 * number of 123."
4153 *
4154 */
4155 writeRemote("\033P!|00010203\033\\");
4156 }
4157 }
4158
4159 /**
4160 * DECSTBM - Set top and bottom margins.
4161 */
4162 private void decstbm() {
4163 boolean decPrivateModeFlag = false;
4164
4165 for (int i = 0; i < collectBuffer.length(); i++) {
4166 if (collectBuffer.charAt(i) == '?') {
4167 decPrivateModeFlag = true;
4168 break;
4169 }
4170 }
4171 if (decPrivateModeFlag) {
4172 // This could be restore DEC private mode values.
4173 // Ignore it.
4174 } else {
4175 // DECSTBM
4176 int top = getCsiParam(0, 1, 1, height) - 1;
4177 int bottom = getCsiParam(1, height, 1, height) - 1;
4178
4179 if (top > bottom) {
4180 top = bottom;
4181 }
4182 scrollRegionTop = top;
4183 scrollRegionBottom = bottom;
4184
4185 // Home cursor
4186 cursorPosition(0, 0);
4187 }
4188 }
4189
4190 /**
4191 * DECREQTPARM - Request terminal parameters.
4192 */
4193 private void decreqtparm() {
4194 int i = getCsiParam(0, 0);
4195
4196 if ((i != 0) && (i != 1)) {
4197 return;
4198 }
4199
4200 String str = "";
4201
4202 /*
4203 * Request terminal parameters.
4204 *
4205 * Respond with:
4206 *
4207 * Parity NONE, 8 bits, xmitspeed 38400, recvspeed 38400.
4208 * (CLoCk MULtiplier = 1, STP option flags = 0)
4209 *
4210 * (Same as xterm)
4211 */
4212 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4213 && (s8c1t == true)
4214 ) {
4215 str = String.format("\u009b%d;1;1;128;128;1;0x", i + 2);
4216 } else {
4217 str = String.format("\033[%d;1;1;128;128;1;0x", i + 2);
4218 }
4219 writeRemote(str);
4220 }
4221
4222 /**
4223 * DECSCA - Select Character Attributes.
4224 */
4225 private void decsca() {
4226 int i = getCsiParam(0, 0);
4227
4228 if ((i == 0) || (i == 2)) {
4229 // Protect mode OFF
4230 currentState.attr.setProtect(false);
4231 }
4232 if (i == 1) {
4233 // Protect mode ON
4234 currentState.attr.setProtect(true);
4235 }
4236 }
4237
4238 /**
4239 * DECSTR - Soft Terminal Reset.
4240 */
4241 private void decstr() {
4242 // Do exactly like RIS - Reset to initial state
4243 reset();
4244 // Do I clear screen too? I think so...
4245 eraseScreen(0, 0, height - 1, width - 1, false);
4246 cursorPosition(0, 0);
4247 }
4248
4249 /**
4250 * DSR - Device status report.
4251 */
4252 private void dsr() {
4253 boolean decPrivateModeFlag = false;
4254 int row = currentState.cursorY;
4255
4256 for (int i = 0; i < collectBuffer.length(); i++) {
4257 if (collectBuffer.charAt(i) == '?') {
4258 decPrivateModeFlag = true;
4259 break;
4260 }
4261 }
4262
4263 int i = getCsiParam(0, 0);
4264
4265 switch (i) {
4266
4267 case 5:
4268 // Request status report. Respond with "OK, no malfunction."
4269
4270 // Send string directly to remote side
4271 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4272 && (s8c1t == true)
4273 ) {
4274 writeRemote("\u009b0n");
4275 } else {
4276 writeRemote("\033[0n");
4277 }
4278 break;
4279
4280 case 6:
4281 // Request cursor position. Respond with current position.
4282 if (currentState.originMode == true) {
4283 row -= scrollRegionTop;
4284 }
4285 String str = "";
4286 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4287 && (s8c1t == true)
4288 ) {
4289 str = String.format("\u009b%d;%dR", row + 1,
4290 currentState.cursorX + 1);
4291 } else {
4292 str = String.format("\033[%d;%dR", row + 1,
4293 currentState.cursorX + 1);
4294 }
4295
4296 // Send string directly to remote side
4297 writeRemote(str);
4298 break;
4299
4300 case 15:
4301 if (decPrivateModeFlag == true) {
4302
4303 // Request printer status report. Respond with "Printer not
4304 // connected."
4305
4306 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4307 && (s8c1t == true)) {
4308 writeRemote("\u009b?13n");
4309 } else {
4310 writeRemote("\033[?13n");
4311 }
4312 }
4313 break;
4314
4315 case 25:
4316 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4317 && (decPrivateModeFlag == true)
4318 ) {
4319
4320 // Request user-defined keys are locked or unlocked. Respond
4321 // with "User-defined keys are locked."
4322
4323 if (s8c1t == true) {
4324 writeRemote("\u009b?21n");
4325 } else {
4326 writeRemote("\033[?21n");
4327 }
4328 }
4329 break;
4330
4331 case 26:
4332 if (((type == DeviceType.VT220) || (type == DeviceType.XTERM))
4333 && (decPrivateModeFlag == true)
4334 ) {
4335
4336 // Request keyboard language. Respond with "Keyboard
4337 // language is North American."
4338
4339 if (s8c1t == true) {
4340 writeRemote("\u009b?27;1n");
4341 } else {
4342 writeRemote("\033[?27;1n");
4343 }
4344
4345 }
4346 break;
4347
4348 default:
4349 // Some other option, ignore
4350 break;
4351 }
4352 }
4353
4354 /**
4355 * TBC - Tabulation clear.
4356 */
4357 private void tbc() {
4358 int i = getCsiParam(0, 0);
4359 if (i == 0) {
4360 List<Integer> newStops = new ArrayList<Integer>();
4361 for (Integer stop: tabStops) {
4362 if (stop == currentState.cursorX) {
4363 continue;
4364 }
4365 newStops.add(stop);
4366 }
4367 tabStops = newStops;
4368 }
4369 if (i == 3) {
4370 tabStops.clear();
4371 }
4372 }
4373
4374 /**
4375 * Erase the characters in the current line from the start column to the
4376 * end column, inclusive.
4377 *
4378 * @param start starting column to erase (between 0 and width - 1)
4379 * @param end ending column to erase (between 0 and width - 1)
4380 * @param honorProtected if true, do not erase characters with the
4381 * protected attribute set
4382 */
4383 private void eraseLine(int start, int end, final boolean honorProtected) {
4384
4385 if (start > end) {
4386 return;
4387 }
4388 if (end > width - 1) {
4389 end = width - 1;
4390 }
4391 if (start < 0) {
4392 start = 0;
4393 }
4394
4395 for (int i = start; i <= end; i++) {
4396 DisplayLine line = display.get(currentState.cursorY);
4397 if ((!honorProtected)
4398 || ((honorProtected) && (!line.charAt(i).isProtect()))) {
4399
4400 switch (type) {
4401 case VT100:
4402 case VT102:
4403 case VT220:
4404 /*
4405 * From the VT102 manual:
4406 *
4407 * Erasing a character also erases any character
4408 * attribute of the character.
4409 */
4410 line.setBlank(i);
4411 break;
4412 case XTERM:
4413 /*
4414 * Erase with the current color a.k.a. back-color erase
4415 * (bce).
4416 */
4417 line.setChar(i, ' ');
4418 line.setAttr(i, currentState.attr);
4419 break;
4420 }
4421 }
4422 }
4423 }
4424
4425 /**
4426 * Erase a rectangular section of the screen, inclusive. end column,
4427 * inclusive.
4428 *
4429 * @param startRow starting row to erase (between 0 and height - 1)
4430 * @param startCol starting column to erase (between 0 and width - 1)
4431 * @param endRow ending row to erase (between 0 and height - 1)
4432 * @param endCol ending column to erase (between 0 and width - 1)
4433 * @param honorProtected if true, do not erase characters with the
4434 * protected attribute set
4435 */
4436 private void eraseScreen(final int startRow, final int startCol,
4437 final int endRow, final int endCol, final boolean honorProtected) {
4438
4439 int oldCursorY;
4440
4441 if ((startRow < 0)
4442 || (startCol < 0)
4443 || (endRow < 0)
4444 || (endCol < 0)
4445 || (endRow < startRow)
4446 || (endCol < startCol)
4447 ) {
4448 return;
4449 }
4450
4451 oldCursorY = currentState.cursorY;
4452 for (int i = startRow; i <= endRow; i++) {
4453 currentState.cursorY = i;
4454 eraseLine(startCol, endCol, honorProtected);
4455
4456 // Erase display clears the double attributes
4457 display.get(i).setDoubleWidth(false);
4458 display.get(i).setDoubleHeight(0);
4459 }
4460 currentState.cursorY = oldCursorY;
4461 }
4462
4463 /**
4464 * VT220 printer functions. All of these are parsed, but won't do
4465 * anything.
4466 */
4467 private void printerFunctions() {
4468 boolean decPrivateModeFlag = false;
4469 for (int i = 0; i < collectBuffer.length(); i++) {
4470 if (collectBuffer.charAt(i) == '?') {
4471 decPrivateModeFlag = true;
4472 break;
4473 }
4474 }
4475
4476 int i = getCsiParam(0, 0);
4477
4478 switch (i) {
4479
4480 case 0:
4481 if (decPrivateModeFlag == false) {
4482 // Print screen
4483 }
4484 break;
4485
4486 case 1:
4487 if (decPrivateModeFlag == true) {
4488 // Print cursor line
4489 }
4490 break;
4491
4492 case 4:
4493 if (decPrivateModeFlag == true) {
4494 // Auto print mode OFF
4495 } else {
4496 // Printer controller OFF
4497
4498 // Characters re-appear on the screen
4499 printerControllerMode = false;
4500 }
4501 break;
4502
4503 case 5:
4504 if (decPrivateModeFlag == true) {
4505 // Auto print mode
4506
4507 } else {
4508 // Printer controller
4509
4510 // Characters get sucked into oblivion
4511 printerControllerMode = true;
4512 }
4513 break;
4514
4515 default:
4516 break;
4517
4518 }
4519 }
4520
4521 /**
4522 * Handle the SCAN_OSC_STRING state. Handle this in VT100 because lots
4523 * of remote systems will send an XTerm title sequence even if TERM isn't
4524 * xterm.
4525 *
4526 * @param xtermChar the character received from the remote side
4527 */
4528 private void oscPut(final char xtermChar) {
4529 // System.err.println("oscPut: " + xtermChar);
4530
4531 // Collect first
4532 collectBuffer.append(xtermChar);
4533
4534 // Xterm cases...
4535 if ((xtermChar == 0x07)
4536 || (collectBuffer.toString().endsWith("\033\\"))
4537 ) {
4538 String args = null;
4539 if (xtermChar == 0x07) {
4540 args = collectBuffer.substring(0, collectBuffer.length() - 1);
4541 } else {
4542 args = collectBuffer.substring(0, collectBuffer.length() - 2);
4543 }
4544
4545 String [] p = args.toString().split(";");
4546 if (p.length > 0) {
4547 if ((p[0].equals("0")) || (p[0].equals("2"))) {
4548 if (p.length > 1) {
4549 // Screen title
4550 screenTitle = p[1];
4551 }
4552 }
4553
4554 if (p[0].equals("4")) {
4555 for (int i = 1; i + 1 < p.length; i += 2) {
4556 // Set a color index value
4557 try {
4558 set88Color(Integer.parseInt(p[i]), p[i + 1]);
4559 } catch (NumberFormatException e) {
4560 // SQUASH
4561 }
4562 }
4563 }
4564 }
4565
4566 // Go to SCAN_GROUND state
4567 toGround();
4568 return;
4569 }
4570 }
4571
4572 /**
4573 * Handle the SCAN_SOSPMAPC_STRING state. This is currently only used by
4574 * Jexer ECMA48Terminal to talk to ECMA48.
4575 *
4576 * @param pmChar the character received from the remote side
4577 */
4578 private void pmPut(final char pmChar) {
4579 // System.err.println("pmPut: " + pmChar);
4580
4581 // Collect first
4582 collectBuffer.append(pmChar);
4583
4584 // Xterm cases...
4585 if (collectBuffer.toString().endsWith("\033\\")) {
4586 String arg = null;
4587 arg = collectBuffer.substring(0, collectBuffer.length() - 2);
4588
4589 // System.err.println("arg: '" + arg + "'");
4590
4591 if (arg.equals("hideMousePointer")) {
4592 hideMousePointer = true;
4593 }
4594 if (arg.equals("showMousePointer")) {
4595 hideMousePointer = false;
4596 }
4597
4598 // Go to SCAN_GROUND state
4599 toGround();
4600 return;
4601 }
4602 }
4603
4604 /**
4605 * Run this input character through the ECMA48 state machine.
4606 *
4607 * @param ch character from the remote side
4608 */
4609 private void consume(char ch) {
4610
4611 // DEBUG
4612 // System.err.printf("%c", ch);
4613
4614 // Special case for VT10x: 7-bit characters only
4615 if ((type == DeviceType.VT100) || (type == DeviceType.VT102)) {
4616 ch = (char)(ch & 0x7F);
4617 }
4618
4619 // Special "anywhere" states
4620
4621 // 18, 1A --> execute, then switch to SCAN_GROUND
4622 if ((ch == 0x18) || (ch == 0x1A)) {
4623 // CAN and SUB abort escape sequences
4624 toGround();
4625 return;
4626 }
4627
4628 // 80-8F, 91-97, 99, 9A, 9C --> execute, then switch to SCAN_GROUND
4629
4630 // 0x1B == ESCAPE
4631 if (ch == 0x1B) {
4632 if ((type == DeviceType.XTERM)
4633 && ((scanState == ScanState.OSC_STRING)
4634 || (scanState == ScanState.SOSPMAPC_STRING))
4635 ) {
4636 // Xterm can pass ESCAPE to its OSC sequence.
4637 // Jexer can pass ESCAPE to its PM sequence.
4638 } else if ((scanState != ScanState.DCS_ENTRY)
4639 && (scanState != ScanState.DCS_INTERMEDIATE)
4640 && (scanState != ScanState.DCS_IGNORE)
4641 && (scanState != ScanState.DCS_PARAM)
4642 && (scanState != ScanState.DCS_PASSTHROUGH)
4643 ) {
4644
4645 scanState = ScanState.ESCAPE;
4646 return;
4647 }
4648 }
4649
4650 // 0x9B == CSI 8-bit sequence
4651 if (ch == 0x9B) {
4652 scanState = ScanState.CSI_ENTRY;
4653 return;
4654 }
4655
4656 // 0x9D goes to ScanState.OSC_STRING
4657 if (ch == 0x9D) {
4658 scanState = ScanState.OSC_STRING;
4659 return;
4660 }
4661
4662 // 0x90 goes to DCS_ENTRY
4663 if (ch == 0x90) {
4664 scanState = ScanState.DCS_ENTRY;
4665 return;
4666 }
4667
4668 // 0x98, 0x9E, and 0x9F go to SOSPMAPC_STRING
4669 if ((ch == 0x98) || (ch == 0x9E) || (ch == 0x9F)) {
4670 scanState = ScanState.SOSPMAPC_STRING;
4671 return;
4672 }
4673
4674 // 0x7F (DEL) is always discarded
4675 if (ch == 0x7F) {
4676 return;
4677 }
4678
4679 switch (scanState) {
4680
4681 case GROUND:
4682 // 00-17, 19, 1C-1F --> execute
4683 // 80-8F, 91-9A, 9C --> execute
4684 if ((ch <= 0x1F) || ((ch >= 0x80) && (ch <= 0x9F))) {
4685 handleControlChar(ch);
4686 }
4687
4688 // 20-7F --> print
4689 if (((ch >= 0x20) && (ch <= 0x7F))
4690 || (ch >= 0xA0)
4691 ) {
4692
4693 // VT220 printer --> trash bin
4694 if (((type == DeviceType.VT220)
4695 || (type == DeviceType.XTERM))
4696 && (printerControllerMode == true)
4697 ) {
4698 return;
4699 }
4700
4701 // Hang onto this character
4702 repCh = mapCharacter(ch);
4703
4704 // Print this character
4705 printCharacter(repCh);
4706 }
4707 return;
4708
4709 case ESCAPE:
4710 // 00-17, 19, 1C-1F --> execute
4711 if (ch <= 0x1F) {
4712 handleControlChar(ch);
4713 return;
4714 }
4715
4716 // 20-2F --> collect, then switch to ESCAPE_INTERMEDIATE
4717 if ((ch >= 0x20) && (ch <= 0x2F)) {
4718 collect(ch);
4719 scanState = ScanState.ESCAPE_INTERMEDIATE;
4720 return;
4721 }
4722
4723 // 30-4F, 51-57, 59, 5A, 5C, 60-7E --> dispatch, then switch to GROUND
4724 if ((ch >= 0x30) && (ch <= 0x4F)) {
4725 switch (ch) {
4726 case '0':
4727 case '1':
4728 case '2':
4729 case '3':
4730 case '4':
4731 case '5':
4732 case '6':
4733 break;
4734 case '7':
4735 // DECSC - Save cursor
4736 // Note this code overlaps both ANSI and VT52 mode
4737 decsc();
4738 break;
4739
4740 case '8':
4741 // DECRC - Restore cursor
4742 // Note this code overlaps both ANSI and VT52 mode
4743 decrc();
4744 break;
4745
4746 case '9':
4747 case ':':
4748 case ';':
4749 break;
4750 case '<':
4751 if (vt52Mode == true) {
4752 // DECANM - Enter ANSI mode
4753 vt52Mode = false;
4754 arrowKeyMode = ArrowKeyMode.VT100;
4755
4756 /*
4757 * From the VT102 docs: "You use ANSI mode to select
4758 * most terminal features; the terminal uses the same
4759 * features when it switches to VT52 mode. You
4760 * cannot, however, change most of these features in
4761 * VT52 mode."
4762 *
4763 * In other words, do not reset any other attributes
4764 * when switching between VT52 submode and ANSI.
4765 */
4766
4767 // Reset fonts
4768 currentState.g0Charset = CharacterSet.US;
4769 currentState.g1Charset = CharacterSet.DRAWING;
4770 s8c1t = false;
4771 singleshift = Singleshift.NONE;
4772 currentState.glLockshift = LockshiftMode.NONE;
4773 currentState.grLockshift = LockshiftMode.NONE;
4774 }
4775 break;
4776 case '=':
4777 // DECKPAM - Keypad application mode
4778 // Note this code overlaps both ANSI and VT52 mode
4779 deckpam();
4780 break;
4781 case '>':
4782 // DECKPNM - Keypad numeric mode
4783 // Note this code overlaps both ANSI and VT52 mode
4784 deckpnm();
4785 break;
4786 case '?':
4787 case '@':
4788 break;
4789 case 'A':
4790 if (vt52Mode == true) {
4791 // Cursor up, and stop at the top without scrolling
4792 cursorUp(1, false);
4793 }
4794 break;
4795 case 'B':
4796 if (vt52Mode == true) {
4797 // Cursor down, and stop at the bottom without scrolling
4798 cursorDown(1, false);
4799 }
4800 break;
4801 case 'C':
4802 if (vt52Mode == true) {
4803 // Cursor right, and stop at the right without scrolling
4804 cursorRight(1, false);
4805 }
4806 break;
4807 case 'D':
4808 if (vt52Mode == true) {
4809 // Cursor left, and stop at the left without scrolling
4810 cursorLeft(1, false);
4811 } else {
4812 // IND - Index
4813 ind();
4814 }
4815 break;
4816 case 'E':
4817 if (vt52Mode == true) {
4818 // Nothing
4819 } else {
4820 // NEL - Next line
4821 nel();
4822 }
4823 break;
4824 case 'F':
4825 if (vt52Mode == true) {
4826 // G0 --> Special graphics
4827 currentState.g0Charset = CharacterSet.VT52_GRAPHICS;
4828 }
4829 break;
4830 case 'G':
4831 if (vt52Mode == true) {
4832 // G0 --> ASCII set
4833 currentState.g0Charset = CharacterSet.US;
4834 }
4835 break;
4836 case 'H':
4837 if (vt52Mode == true) {
4838 // Cursor to home
4839 cursorPosition(0, 0);
4840 } else {
4841 // HTS - Horizontal tabulation set
4842 hts();
4843 }
4844 break;
4845 case 'I':
4846 if (vt52Mode == true) {
4847 // Reverse line feed. Same as RI.
4848 ri();
4849 }
4850 break;
4851 case 'J':
4852 if (vt52Mode == true) {
4853 // Erase to end of screen
4854 eraseLine(currentState.cursorX, width - 1, false);
4855 eraseScreen(currentState.cursorY + 1, 0, height - 1,
4856 width - 1, false);
4857 }
4858 break;
4859 case 'K':
4860 if (vt52Mode == true) {
4861 // Erase to end of line
4862 eraseLine(currentState.cursorX, width - 1, false);
4863 }
4864 break;
4865 case 'L':
4866 break;
4867 case 'M':
4868 if (vt52Mode == true) {
4869 // Nothing
4870 } else {
4871 // RI - Reverse index
4872 ri();
4873 }
4874 break;
4875 case 'N':
4876 if (vt52Mode == false) {
4877 // SS2
4878 singleshift = Singleshift.SS2;
4879 }
4880 break;
4881 case 'O':
4882 if (vt52Mode == false) {
4883 // SS3
4884 singleshift = Singleshift.SS3;
4885 }
4886 break;
4887 }
4888 toGround();
4889 return;
4890 }
4891 if ((ch >= 0x51) && (ch <= 0x57)) {
4892 switch (ch) {
4893 case 'Q':
4894 case 'R':
4895 case 'S':
4896 case 'T':
4897 case 'U':
4898 case 'V':
4899 case 'W':
4900 break;
4901 }
4902 toGround();
4903 return;
4904 }
4905 if (ch == 0x59) {
4906 // 'Y'
4907 if (vt52Mode == true) {
4908 scanState = ScanState.VT52_DIRECT_CURSOR_ADDRESS;
4909 } else {
4910 toGround();
4911 }
4912 return;
4913 }
4914 if (ch == 0x5A) {
4915 // 'Z'
4916 if (vt52Mode == true) {
4917 // Identify
4918 // Send string directly to remote side
4919 writeRemote("\033/Z");
4920 } else {
4921 // DECID
4922 // Send string directly to remote side
4923 writeRemote(deviceTypeResponse());
4924 }
4925 toGround();
4926 return;
4927 }
4928 if (ch == 0x5C) {
4929 // '\'
4930 toGround();
4931 return;
4932 }
4933
4934 // VT52 cannot get to any of these other states
4935 if (vt52Mode == true) {
4936 toGround();
4937 return;
4938 }
4939
4940 if ((ch >= 0x60) && (ch <= 0x7E)) {
4941 switch (ch) {
4942 case '`':
4943 case 'a':
4944 case 'b':
4945 break;
4946 case 'c':
4947 // RIS - Reset to initial state
4948 reset();
4949 // Do I clear screen too? I think so...
4950 eraseScreen(0, 0, height - 1, width - 1, false);
4951 cursorPosition(0, 0);
4952 break;
4953 case 'd':
4954 case 'e':
4955 case 'f':
4956 case 'g':
4957 case 'h':
4958 case 'i':
4959 case 'j':
4960 case 'k':
4961 case 'l':
4962 case 'm':
4963 break;
4964 case 'n':
4965 if ((type == DeviceType.VT220)
4966 || (type == DeviceType.XTERM)) {
4967
4968 // VT220 lockshift G2 into GL
4969 currentState.glLockshift = LockshiftMode.G2_GL;
4970 shiftOut = false;
4971 }
4972 break;
4973 case 'o':
4974 if ((type == DeviceType.VT220)
4975 || (type == DeviceType.XTERM)) {
4976
4977 // VT220 lockshift G3 into GL
4978 currentState.glLockshift = LockshiftMode.G3_GL;
4979 shiftOut = false;
4980 }
4981 break;
4982 case 'p':
4983 case 'q':
4984 case 'r':
4985 case 's':
4986 case 't':
4987 case 'u':
4988 case 'v':
4989 case 'w':
4990 case 'x':
4991 case 'y':
4992 case 'z':
4993 case '{':
4994 break;
4995 case '|':
4996 if ((type == DeviceType.VT220)
4997 || (type == DeviceType.XTERM)) {
4998
4999 // VT220 lockshift G3 into GR
5000 currentState.grLockshift = LockshiftMode.G3_GR;
5001 shiftOut = false;
5002 }
5003 break;
5004 case '}':
5005 if ((type == DeviceType.VT220)
5006 || (type == DeviceType.XTERM)) {
5007
5008 // VT220 lockshift G2 into GR
5009 currentState.grLockshift = LockshiftMode.G2_GR;
5010 shiftOut = false;
5011 }
5012 break;
5013
5014 case '~':
5015 if ((type == DeviceType.VT220)
5016 || (type == DeviceType.XTERM)) {
5017
5018 // VT220 lockshift G1 into GR
5019 currentState.grLockshift = LockshiftMode.G1_GR;
5020 shiftOut = false;
5021 }
5022 break;
5023 }
5024 toGround();
5025 }
5026
5027 // 7F --> ignore
5028
5029 // 0x5B goes to CSI_ENTRY
5030 if (ch == 0x5B) {
5031 scanState = ScanState.CSI_ENTRY;
5032 }
5033
5034 // 0x5D goes to OSC_STRING
5035 if (ch == 0x5D) {
5036 scanState = ScanState.OSC_STRING;
5037 }
5038
5039 // 0x50 goes to DCS_ENTRY
5040 if (ch == 0x50) {
5041 scanState = ScanState.DCS_ENTRY;
5042 }
5043
5044 // 0x58, 0x5E, and 0x5F go to SOSPMAPC_STRING
5045 if ((ch == 0x58) || (ch == 0x5E) || (ch == 0x5F)) {
5046 scanState = ScanState.SOSPMAPC_STRING;
5047 }
5048
5049 return;
5050
5051 case ESCAPE_INTERMEDIATE:
5052 // 00-17, 19, 1C-1F --> execute
5053 if (ch <= 0x1F) {
5054 handleControlChar(ch);
5055 }
5056
5057 // 20-2F --> collect
5058 if ((ch >= 0x20) && (ch <= 0x2F)) {
5059 collect(ch);
5060 }
5061
5062 // 30-7E --> dispatch, then switch to GROUND
5063 if ((ch >= 0x30) && (ch <= 0x7E)) {
5064 switch (ch) {
5065 case '0':
5066 if ((collectBuffer.length() == 1)
5067 && (collectBuffer.charAt(0) == '(')) {
5068 // G0 --> Special graphics
5069 currentState.g0Charset = CharacterSet.DRAWING;
5070 }
5071 if ((collectBuffer.length() == 1)
5072 && (collectBuffer.charAt(0) == ')')) {
5073 // G1 --> Special graphics
5074 currentState.g1Charset = CharacterSet.DRAWING;
5075 }
5076 if ((type == DeviceType.VT220)
5077 || (type == DeviceType.XTERM)) {
5078
5079 if ((collectBuffer.length() == 1)
5080 && (collectBuffer.charAt(0) == '*')) {
5081 // G2 --> Special graphics
5082 currentState.g2Charset = CharacterSet.DRAWING;
5083 }
5084 if ((collectBuffer.length() == 1)
5085 && (collectBuffer.charAt(0) == '+')) {
5086 // G3 --> Special graphics
5087 currentState.g3Charset = CharacterSet.DRAWING;
5088 }
5089 }
5090 break;
5091 case '1':
5092 if ((collectBuffer.length() == 1)
5093 && (collectBuffer.charAt(0) == '(')) {
5094 // G0 --> Alternate character ROM standard character set
5095 currentState.g0Charset = CharacterSet.ROM;
5096 }
5097 if ((collectBuffer.length() == 1)
5098 && (collectBuffer.charAt(0) == ')')) {
5099 // G1 --> Alternate character ROM standard character set
5100 currentState.g1Charset = CharacterSet.ROM;
5101 }
5102 break;
5103 case '2':
5104 if ((collectBuffer.length() == 1)
5105 && (collectBuffer.charAt(0) == '(')) {
5106 // G0 --> Alternate character ROM special graphics
5107 currentState.g0Charset = CharacterSet.ROM_SPECIAL;
5108 }
5109 if ((collectBuffer.length() == 1)
5110 && (collectBuffer.charAt(0) == ')')) {
5111 // G1 --> Alternate character ROM special graphics
5112 currentState.g1Charset = CharacterSet.ROM_SPECIAL;
5113 }
5114 break;
5115 case '3':
5116 if ((collectBuffer.length() == 1)
5117 && (collectBuffer.charAt(0) == '#')) {
5118 // DECDHL - Double-height line (top half)
5119 dechdl(true);
5120 }
5121 break;
5122 case '4':
5123 if ((collectBuffer.length() == 1)
5124 && (collectBuffer.charAt(0) == '#')) {
5125 // DECDHL - Double-height line (bottom half)
5126 dechdl(false);
5127 }
5128 if ((type == DeviceType.VT220)
5129 || (type == DeviceType.XTERM)) {
5130
5131 if ((collectBuffer.length() == 1)
5132 && (collectBuffer.charAt(0) == '(')) {
5133 // G0 --> DUTCH
5134 currentState.g0Charset = CharacterSet.NRC_DUTCH;
5135 }
5136 if ((collectBuffer.length() == 1)
5137 && (collectBuffer.charAt(0) == ')')) {
5138 // G1 --> DUTCH
5139 currentState.g1Charset = CharacterSet.NRC_DUTCH;
5140 }
5141 if ((collectBuffer.length() == 1)
5142 && (collectBuffer.charAt(0) == '*')) {
5143 // G2 --> DUTCH
5144 currentState.g2Charset = CharacterSet.NRC_DUTCH;
5145 }
5146 if ((collectBuffer.length() == 1)
5147 && (collectBuffer.charAt(0) == '+')) {
5148 // G3 --> DUTCH
5149 currentState.g3Charset = CharacterSet.NRC_DUTCH;
5150 }
5151 }
5152 break;
5153 case '5':
5154 if ((collectBuffer.length() == 1)
5155 && (collectBuffer.charAt(0) == '#')) {
5156 // DECSWL - Single-width line
5157 decswl();
5158 }
5159 if ((type == DeviceType.VT220)
5160 || (type == DeviceType.XTERM)) {
5161
5162 if ((collectBuffer.length() == 1)
5163 && (collectBuffer.charAt(0) == '(')) {
5164 // G0 --> FINNISH
5165 currentState.g0Charset = CharacterSet.NRC_FINNISH;
5166 }
5167 if ((collectBuffer.length() == 1)
5168 && (collectBuffer.charAt(0) == ')')) {
5169 // G1 --> FINNISH
5170 currentState.g1Charset = CharacterSet.NRC_FINNISH;
5171 }
5172 if ((collectBuffer.length() == 1)
5173 && (collectBuffer.charAt(0) == '*')) {
5174 // G2 --> FINNISH
5175 currentState.g2Charset = CharacterSet.NRC_FINNISH;
5176 }
5177 if ((collectBuffer.length() == 1)
5178 && (collectBuffer.charAt(0) == '+')) {
5179 // G3 --> FINNISH
5180 currentState.g3Charset = CharacterSet.NRC_FINNISH;
5181 }
5182 }
5183 break;
5184 case '6':
5185 if ((collectBuffer.length() == 1)
5186 && (collectBuffer.charAt(0) == '#')) {
5187 // DECDWL - Double-width line
5188 decdwl();
5189 }
5190 if ((type == DeviceType.VT220)
5191 || (type == DeviceType.XTERM)) {
5192
5193 if ((collectBuffer.length() == 1)
5194 && (collectBuffer.charAt(0) == '(')) {
5195 // G0 --> NORWEGIAN
5196 currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
5197 }
5198 if ((collectBuffer.length() == 1)
5199 && (collectBuffer.charAt(0) == ')')) {
5200 // G1 --> NORWEGIAN
5201 currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
5202 }
5203 if ((collectBuffer.length() == 1)
5204 && (collectBuffer.charAt(0) == '*')) {
5205 // G2 --> NORWEGIAN
5206 currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
5207 }
5208 if ((collectBuffer.length() == 1)
5209 && (collectBuffer.charAt(0) == '+')) {
5210 // G3 --> NORWEGIAN
5211 currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
5212 }
5213 }
5214 break;
5215 case '7':
5216 if ((type == DeviceType.VT220)
5217 || (type == DeviceType.XTERM)) {
5218
5219 if ((collectBuffer.length() == 1)
5220 && (collectBuffer.charAt(0) == '(')) {
5221 // G0 --> SWEDISH
5222 currentState.g0Charset = CharacterSet.NRC_SWEDISH;
5223 }
5224 if ((collectBuffer.length() == 1)
5225 && (collectBuffer.charAt(0) == ')')) {
5226 // G1 --> SWEDISH
5227 currentState.g1Charset = CharacterSet.NRC_SWEDISH;
5228 }
5229 if ((collectBuffer.length() == 1)
5230 && (collectBuffer.charAt(0) == '*')) {
5231 // G2 --> SWEDISH
5232 currentState.g2Charset = CharacterSet.NRC_SWEDISH;
5233 }
5234 if ((collectBuffer.length() == 1)
5235 && (collectBuffer.charAt(0) == '+')) {
5236 // G3 --> SWEDISH
5237 currentState.g3Charset = CharacterSet.NRC_SWEDISH;
5238 }
5239 }
5240 break;
5241 case '8':
5242 if ((collectBuffer.length() == 1)
5243 && (collectBuffer.charAt(0) == '#')) {
5244 // DECALN - Screen alignment display
5245 decaln();
5246 }
5247 break;
5248 case '9':
5249 case ':':
5250 case ';':
5251 break;
5252 case '<':
5253 if ((type == DeviceType.VT220)
5254 || (type == DeviceType.XTERM)) {
5255
5256 if ((collectBuffer.length() == 1)
5257 && (collectBuffer.charAt(0) == '(')) {
5258 // G0 --> DEC_SUPPLEMENTAL
5259 currentState.g0Charset = CharacterSet.DEC_SUPPLEMENTAL;
5260 }
5261 if ((collectBuffer.length() == 1)
5262 && (collectBuffer.charAt(0) == ')')) {
5263 // G1 --> DEC_SUPPLEMENTAL
5264 currentState.g1Charset = CharacterSet.DEC_SUPPLEMENTAL;
5265 }
5266 if ((collectBuffer.length() == 1)
5267 && (collectBuffer.charAt(0) == '*')) {
5268 // G2 --> DEC_SUPPLEMENTAL
5269 currentState.g2Charset = CharacterSet.DEC_SUPPLEMENTAL;
5270 }
5271 if ((collectBuffer.length() == 1)
5272 && (collectBuffer.charAt(0) == '+')) {
5273 // G3 --> DEC_SUPPLEMENTAL
5274 currentState.g3Charset = CharacterSet.DEC_SUPPLEMENTAL;
5275 }
5276 }
5277 break;
5278 case '=':
5279 if ((type == DeviceType.VT220)
5280 || (type == DeviceType.XTERM)) {
5281
5282 if ((collectBuffer.length() == 1)
5283 && (collectBuffer.charAt(0) == '(')) {
5284 // G0 --> SWISS
5285 currentState.g0Charset = CharacterSet.NRC_SWISS;
5286 }
5287 if ((collectBuffer.length() == 1)
5288 && (collectBuffer.charAt(0) == ')')) {
5289 // G1 --> SWISS
5290 currentState.g1Charset = CharacterSet.NRC_SWISS;
5291 }
5292 if ((collectBuffer.length() == 1)
5293 && (collectBuffer.charAt(0) == '*')) {
5294 // G2 --> SWISS
5295 currentState.g2Charset = CharacterSet.NRC_SWISS;
5296 }
5297 if ((collectBuffer.length() == 1)
5298 && (collectBuffer.charAt(0) == '+')) {
5299 // G3 --> SWISS
5300 currentState.g3Charset = CharacterSet.NRC_SWISS;
5301 }
5302 }
5303 break;
5304 case '>':
5305 case '?':
5306 case '@':
5307 break;
5308 case 'A':
5309 if ((collectBuffer.length() == 1)
5310 && (collectBuffer.charAt(0) == '(')) {
5311 // G0 --> United Kingdom set
5312 currentState.g0Charset = CharacterSet.UK;
5313 }
5314 if ((collectBuffer.length() == 1)
5315 && (collectBuffer.charAt(0) == ')')) {
5316 // G1 --> United Kingdom set
5317 currentState.g1Charset = CharacterSet.UK;
5318 }
5319 if ((type == DeviceType.VT220)
5320 || (type == DeviceType.XTERM)) {
5321
5322 if ((collectBuffer.length() == 1)
5323 && (collectBuffer.charAt(0) == '*')) {
5324 // G2 --> United Kingdom set
5325 currentState.g2Charset = CharacterSet.UK;
5326 }
5327 if ((collectBuffer.length() == 1)
5328 && (collectBuffer.charAt(0) == '+')) {
5329 // G3 --> United Kingdom set
5330 currentState.g3Charset = CharacterSet.UK;
5331 }
5332 }
5333 break;
5334 case 'B':
5335 if ((collectBuffer.length() == 1)
5336 && (collectBuffer.charAt(0) == '(')) {
5337 // G0 --> ASCII set
5338 currentState.g0Charset = CharacterSet.US;
5339 }
5340 if ((collectBuffer.length() == 1)
5341 && (collectBuffer.charAt(0) == ')')) {
5342 // G1 --> ASCII set
5343 currentState.g1Charset = CharacterSet.US;
5344 }
5345 if ((type == DeviceType.VT220)
5346 || (type == DeviceType.XTERM)) {
5347
5348 if ((collectBuffer.length() == 1)
5349 && (collectBuffer.charAt(0) == '*')) {
5350 // G2 --> ASCII
5351 currentState.g2Charset = CharacterSet.US;
5352 }
5353 if ((collectBuffer.length() == 1)
5354 && (collectBuffer.charAt(0) == '+')) {
5355 // G3 --> ASCII
5356 currentState.g3Charset = CharacterSet.US;
5357 }
5358 }
5359 break;
5360 case 'C':
5361 if ((type == DeviceType.VT220)
5362 || (type == DeviceType.XTERM)) {
5363
5364 if ((collectBuffer.length() == 1)
5365 && (collectBuffer.charAt(0) == '(')) {
5366 // G0 --> FINNISH
5367 currentState.g0Charset = CharacterSet.NRC_FINNISH;
5368 }
5369 if ((collectBuffer.length() == 1)
5370 && (collectBuffer.charAt(0) == ')')) {
5371 // G1 --> FINNISH
5372 currentState.g1Charset = CharacterSet.NRC_FINNISH;
5373 }
5374 if ((collectBuffer.length() == 1)
5375 && (collectBuffer.charAt(0) == '*')) {
5376 // G2 --> FINNISH
5377 currentState.g2Charset = CharacterSet.NRC_FINNISH;
5378 }
5379 if ((collectBuffer.length() == 1)
5380 && (collectBuffer.charAt(0) == '+')) {
5381 // G3 --> FINNISH
5382 currentState.g3Charset = CharacterSet.NRC_FINNISH;
5383 }
5384 }
5385 break;
5386 case 'D':
5387 break;
5388 case 'E':
5389 if ((type == DeviceType.VT220)
5390 || (type == DeviceType.XTERM)) {
5391
5392 if ((collectBuffer.length() == 1)
5393 && (collectBuffer.charAt(0) == '(')) {
5394 // G0 --> NORWEGIAN
5395 currentState.g0Charset = CharacterSet.NRC_NORWEGIAN;
5396 }
5397 if ((collectBuffer.length() == 1)
5398 && (collectBuffer.charAt(0) == ')')) {
5399 // G1 --> NORWEGIAN
5400 currentState.g1Charset = CharacterSet.NRC_NORWEGIAN;
5401 }
5402 if ((collectBuffer.length() == 1)
5403 && (collectBuffer.charAt(0) == '*')) {
5404 // G2 --> NORWEGIAN
5405 currentState.g2Charset = CharacterSet.NRC_NORWEGIAN;
5406 }
5407 if ((collectBuffer.length() == 1)
5408 && (collectBuffer.charAt(0) == '+')) {
5409 // G3 --> NORWEGIAN
5410 currentState.g3Charset = CharacterSet.NRC_NORWEGIAN;
5411 }
5412 }
5413 break;
5414 case 'F':
5415 if ((type == DeviceType.VT220)
5416 || (type == DeviceType.XTERM)) {
5417
5418 if ((collectBuffer.length() == 1)
5419 && (collectBuffer.charAt(0) == ' ')) {
5420 // S7C1T
5421 s8c1t = false;
5422 }
5423 }
5424 break;
5425 case 'G':
5426 if ((type == DeviceType.VT220)
5427 || (type == DeviceType.XTERM)) {
5428
5429 if ((collectBuffer.length() == 1)
5430 && (collectBuffer.charAt(0) == ' ')) {
5431 // S8C1T
5432 s8c1t = true;
5433 }
5434 }
5435 break;
5436 case 'H':
5437 if ((type == DeviceType.VT220)
5438 || (type == DeviceType.XTERM)) {
5439
5440 if ((collectBuffer.length() == 1)
5441 && (collectBuffer.charAt(0) == '(')) {
5442 // G0 --> SWEDISH
5443 currentState.g0Charset = CharacterSet.NRC_SWEDISH;
5444 }
5445 if ((collectBuffer.length() == 1)
5446 && (collectBuffer.charAt(0) == ')')) {
5447 // G1 --> SWEDISH
5448 currentState.g1Charset = CharacterSet.NRC_SWEDISH;
5449 }
5450 if ((collectBuffer.length() == 1)
5451 && (collectBuffer.charAt(0) == '*')) {
5452 // G2 --> SWEDISH
5453 currentState.g2Charset = CharacterSet.NRC_SWEDISH;
5454 }
5455 if ((collectBuffer.length() == 1)
5456 && (collectBuffer.charAt(0) == '+')) {
5457 // G3 --> SWEDISH
5458 currentState.g3Charset = CharacterSet.NRC_SWEDISH;
5459 }
5460 }
5461 break;
5462 case 'I':
5463 case 'J':
5464 break;
5465 case 'K':
5466 if ((type == DeviceType.VT220)
5467 || (type == DeviceType.XTERM)) {
5468
5469 if ((collectBuffer.length() == 1)
5470 && (collectBuffer.charAt(0) == '(')) {
5471 // G0 --> GERMAN
5472 currentState.g0Charset = CharacterSet.NRC_GERMAN;
5473 }
5474 if ((collectBuffer.length() == 1)
5475 && (collectBuffer.charAt(0) == ')')) {
5476 // G1 --> GERMAN
5477 currentState.g1Charset = CharacterSet.NRC_GERMAN;
5478 }
5479 if ((collectBuffer.length() == 1)
5480 && (collectBuffer.charAt(0) == '*')) {
5481 // G2 --> GERMAN
5482 currentState.g2Charset = CharacterSet.NRC_GERMAN;
5483 }
5484 if ((collectBuffer.length() == 1)
5485 && (collectBuffer.charAt(0) == '+')) {
5486 // G3 --> GERMAN
5487 currentState.g3Charset = CharacterSet.NRC_GERMAN;
5488 }
5489 }
5490 break;
5491 case 'L':
5492 case 'M':
5493 case 'N':
5494 case 'O':
5495 case 'P':
5496 break;
5497 case 'Q':
5498 if ((type == DeviceType.VT220)
5499 || (type == DeviceType.XTERM)) {
5500
5501 if ((collectBuffer.length() == 1)
5502 && (collectBuffer.charAt(0) == '(')) {
5503 // G0 --> FRENCH_CA
5504 currentState.g0Charset = CharacterSet.NRC_FRENCH_CA;
5505 }
5506 if ((collectBuffer.length() == 1)
5507 && (collectBuffer.charAt(0) == ')')) {
5508 // G1 --> FRENCH_CA
5509 currentState.g1Charset = CharacterSet.NRC_FRENCH_CA;
5510 }
5511 if ((collectBuffer.length() == 1)
5512 && (collectBuffer.charAt(0) == '*')) {
5513 // G2 --> FRENCH_CA
5514 currentState.g2Charset = CharacterSet.NRC_FRENCH_CA;
5515 }
5516 if ((collectBuffer.length() == 1)
5517 && (collectBuffer.charAt(0) == '+')) {
5518 // G3 --> FRENCH_CA
5519 currentState.g3Charset = CharacterSet.NRC_FRENCH_CA;
5520 }
5521 }
5522 break;
5523 case 'R':
5524 if ((type == DeviceType.VT220)
5525 || (type == DeviceType.XTERM)) {
5526
5527 if ((collectBuffer.length() == 1)
5528 && (collectBuffer.charAt(0) == '(')) {
5529 // G0 --> FRENCH
5530 currentState.g0Charset = CharacterSet.NRC_FRENCH;
5531 }
5532 if ((collectBuffer.length() == 1)
5533 && (collectBuffer.charAt(0) == ')')) {
5534 // G1 --> FRENCH
5535 currentState.g1Charset = CharacterSet.NRC_FRENCH;
5536 }
5537 if ((collectBuffer.length() == 1)
5538 && (collectBuffer.charAt(0) == '*')) {
5539 // G2 --> FRENCH
5540 currentState.g2Charset = CharacterSet.NRC_FRENCH;
5541 }
5542 if ((collectBuffer.length() == 1)
5543 && (collectBuffer.charAt(0) == '+')) {
5544 // G3 --> FRENCH
5545 currentState.g3Charset = CharacterSet.NRC_FRENCH;
5546 }
5547 }
5548 break;
5549 case 'S':
5550 case 'T':
5551 case 'U':
5552 case 'V':
5553 case 'W':
5554 case 'X':
5555 break;
5556 case 'Y':
5557 if ((type == DeviceType.VT220)
5558 || (type == DeviceType.XTERM)) {
5559
5560 if ((collectBuffer.length() == 1)
5561 && (collectBuffer.charAt(0) == '(')) {
5562 // G0 --> ITALIAN
5563 currentState.g0Charset = CharacterSet.NRC_ITALIAN;
5564 }
5565 if ((collectBuffer.length() == 1)
5566 && (collectBuffer.charAt(0) == ')')) {
5567 // G1 --> ITALIAN
5568 currentState.g1Charset = CharacterSet.NRC_ITALIAN;
5569 }
5570 if ((collectBuffer.length() == 1)
5571 && (collectBuffer.charAt(0) == '*')) {
5572 // G2 --> ITALIAN
5573 currentState.g2Charset = CharacterSet.NRC_ITALIAN;
5574 }
5575 if ((collectBuffer.length() == 1)
5576 && (collectBuffer.charAt(0) == '+')) {
5577 // G3 --> ITALIAN
5578 currentState.g3Charset = CharacterSet.NRC_ITALIAN;
5579 }
5580 }
5581 break;
5582 case 'Z':
5583 if ((type == DeviceType.VT220)
5584 || (type == DeviceType.XTERM)) {
5585
5586 if ((collectBuffer.length() == 1)
5587 && (collectBuffer.charAt(0) == '(')) {
5588 // G0 --> SPANISH
5589 currentState.g0Charset = CharacterSet.NRC_SPANISH;
5590 }
5591 if ((collectBuffer.length() == 1)
5592 && (collectBuffer.charAt(0) == ')')) {
5593 // G1 --> SPANISH
5594 currentState.g1Charset = CharacterSet.NRC_SPANISH;
5595 }
5596 if ((collectBuffer.length() == 1)
5597 && (collectBuffer.charAt(0) == '*')) {
5598 // G2 --> SPANISH
5599 currentState.g2Charset = CharacterSet.NRC_SPANISH;
5600 }
5601 if ((collectBuffer.length() == 1)
5602 && (collectBuffer.charAt(0) == '+')) {
5603 // G3 --> SPANISH
5604 currentState.g3Charset = CharacterSet.NRC_SPANISH;
5605 }
5606 }
5607 break;
5608 case '[':
5609 case '\\':
5610 case ']':
5611 case '^':
5612 case '_':
5613 case '`':
5614 case 'a':
5615 case 'b':
5616 case 'c':
5617 case 'd':
5618 case 'e':
5619 case 'f':
5620 case 'g':
5621 case 'h':
5622 case 'i':
5623 case 'j':
5624 case 'k':
5625 case 'l':
5626 case 'm':
5627 case 'n':
5628 case 'o':
5629 case 'p':
5630 case 'q':
5631 case 'r':
5632 case 's':
5633 case 't':
5634 case 'u':
5635 case 'v':
5636 case 'w':
5637 case 'x':
5638 case 'y':
5639 case 'z':
5640 case '{':
5641 case '|':
5642 case '}':
5643 case '~':
5644 break;
5645 }
5646 toGround();
5647 }
5648
5649 // 7F --> ignore
5650
5651 // 0x9C goes to GROUND
5652 if (ch == 0x9C) {
5653 toGround();
5654 }
5655
5656 return;
5657
5658 case CSI_ENTRY:
5659 // 00-17, 19, 1C-1F --> execute
5660 if (ch <= 0x1F) {
5661 handleControlChar(ch);
5662 }
5663
5664 // 20-2F --> collect, then switch to CSI_INTERMEDIATE
5665 if ((ch >= 0x20) && (ch <= 0x2F)) {
5666 collect(ch);
5667 scanState = ScanState.CSI_INTERMEDIATE;
5668 }
5669
5670 // 30-39, 3B --> param, then switch to CSI_PARAM
5671 if ((ch >= '0') && (ch <= '9')) {
5672 param((byte) ch);
5673 scanState = ScanState.CSI_PARAM;
5674 }
5675 if (ch == ';') {
5676 param((byte) ch);
5677 scanState = ScanState.CSI_PARAM;
5678 }
5679
5680 // 3C-3F --> collect, then switch to CSI_PARAM
5681 if ((ch >= 0x3C) && (ch <= 0x3F)) {
5682 collect(ch);
5683 scanState = ScanState.CSI_PARAM;
5684 }
5685
5686 // 40-7E --> dispatch, then switch to GROUND
5687 if ((ch >= 0x40) && (ch <= 0x7E)) {
5688 switch (ch) {
5689 case '@':
5690 // ICH - Insert character
5691 ich();
5692 break;
5693 case 'A':
5694 // CUU - Cursor up
5695 cuu();
5696 break;
5697 case 'B':
5698 // CUD - Cursor down
5699 cud();
5700 break;
5701 case 'C':
5702 // CUF - Cursor forward
5703 cuf();
5704 break;
5705 case 'D':
5706 // CUB - Cursor backward
5707 cub();
5708 break;
5709 case 'E':
5710 // CNL - Cursor down and to column 1
5711 if (type == DeviceType.XTERM) {
5712 cnl();
5713 }
5714 break;
5715 case 'F':
5716 // CPL - Cursor up and to column 1
5717 if (type == DeviceType.XTERM) {
5718 cpl();
5719 }
5720 break;
5721 case 'G':
5722 // CHA - Cursor to column # in current row
5723 if (type == DeviceType.XTERM) {
5724 cha();
5725 }
5726 break;
5727 case 'H':
5728 // CUP - Cursor position
5729 cup();
5730 break;
5731 case 'I':
5732 // CHT - Cursor forward X tab stops (default 1)
5733 if (type == DeviceType.XTERM) {
5734 cht();
5735 }
5736 break;
5737 case 'J':
5738 // ED - Erase in display
5739 ed();
5740 break;
5741 case 'K':
5742 // EL - Erase in line
5743 el();
5744 break;
5745 case 'L':
5746 // IL - Insert line
5747 il();
5748 break;
5749 case 'M':
5750 // DL - Delete line
5751 dl();
5752 break;
5753 case 'N':
5754 case 'O':
5755 break;
5756 case 'P':
5757 // DCH - Delete character
5758 dch();
5759 break;
5760 case 'Q':
5761 case 'R':
5762 break;
5763 case 'S':
5764 // Scroll up X lines (default 1)
5765 if (type == DeviceType.XTERM) {
5766 su();
5767 }
5768 break;
5769 case 'T':
5770 // Scroll down X lines (default 1)
5771 if (type == DeviceType.XTERM) {
5772 sd();
5773 }
5774 break;
5775 case 'U':
5776 case 'V':
5777 case 'W':
5778 break;
5779 case 'X':
5780 if ((type == DeviceType.VT220)
5781 || (type == DeviceType.XTERM)) {
5782
5783 // ECH - Erase character
5784 ech();
5785 }
5786 break;
5787 case 'Y':
5788 break;
5789 case 'Z':
5790 // CBT - Cursor backward X tab stops (default 1)
5791 if (type == DeviceType.XTERM) {
5792 cbt();
5793 }
5794 break;
5795 case '[':
5796 case '\\':
5797 case ']':
5798 case '^':
5799 case '_':
5800 break;
5801 case '`':
5802 // HPA - Cursor to column # in current row. Same as CHA
5803 if (type == DeviceType.XTERM) {
5804 cha();
5805 }
5806 break;
5807 case 'a':
5808 // HPR - Cursor right. Same as CUF
5809 if (type == DeviceType.XTERM) {
5810 cuf();
5811 }
5812 break;
5813 case 'b':
5814 // REP - Repeat last char X times
5815 if (type == DeviceType.XTERM) {
5816 rep();
5817 }
5818 break;
5819 case 'c':
5820 // DA - Device attributes
5821 da();
5822 break;
5823 case 'd':
5824 // VPA - Cursor to row, current column.
5825 if (type == DeviceType.XTERM) {
5826 vpa();
5827 }
5828 break;
5829 case 'e':
5830 // VPR - Cursor down. Same as CUD
5831 if (type == DeviceType.XTERM) {
5832 cud();
5833 }
5834 break;
5835 case 'f':
5836 // HVP - Horizontal and vertical position
5837 hvp();
5838 break;
5839 case 'g':
5840 // TBC - Tabulation clear
5841 tbc();
5842 break;
5843 case 'h':
5844 // Sets an ANSI or DEC private toggle
5845 setToggle(true);
5846 break;
5847 case 'i':
5848 if ((type == DeviceType.VT220)
5849 || (type == DeviceType.XTERM)) {
5850
5851 // Printer functions
5852 printerFunctions();
5853 }
5854 break;
5855 case 'j':
5856 case 'k':
5857 break;
5858 case 'l':
5859 // Sets an ANSI or DEC private toggle
5860 setToggle(false);
5861 break;
5862 case 'm':
5863 // SGR - Select graphics rendition
5864 sgr();
5865 break;
5866 case 'n':
5867 // DSR - Device status report
5868 dsr();
5869 break;
5870 case 'o':
5871 case 'p':
5872 break;
5873 case 'q':
5874 // DECLL - Load leds
5875 // Not supported
5876 break;
5877 case 'r':
5878 // DECSTBM - Set top and bottom margins
5879 decstbm();
5880 break;
5881 case 's':
5882 // Save cursor (ANSI.SYS)
5883 if (type == DeviceType.XTERM) {
5884 savedState.cursorX = currentState.cursorX;
5885 savedState.cursorY = currentState.cursorY;
5886 }
5887 break;
5888 case 't':
5889 break;
5890 case 'u':
5891 // Restore cursor (ANSI.SYS)
5892 if (type == DeviceType.XTERM) {
5893 cursorPosition(savedState.cursorY, savedState.cursorX);
5894 }
5895 break;
5896 case 'v':
5897 case 'w':
5898 break;
5899 case 'x':
5900 // DECREQTPARM - Request terminal parameters
5901 decreqtparm();
5902 break;
5903 case 'y':
5904 case 'z':
5905 case '{':
5906 case '|':
5907 case '}':
5908 case '~':
5909 break;
5910 }
5911 toGround();
5912 }
5913
5914 // 7F --> ignore
5915
5916 // 0x9C goes to GROUND
5917 if (ch == 0x9C) {
5918 toGround();
5919 }
5920
5921 // 0x3A goes to CSI_IGNORE
5922 if (ch == 0x3A) {
5923 scanState = ScanState.CSI_IGNORE;
5924 }
5925 return;
5926
5927 case CSI_PARAM:
5928 // 00-17, 19, 1C-1F --> execute
5929 if (ch <= 0x1F) {
5930 handleControlChar(ch);
5931 }
5932
5933 // 20-2F --> collect, then switch to CSI_INTERMEDIATE
5934 if ((ch >= 0x20) && (ch <= 0x2F)) {
5935 collect(ch);
5936 scanState = ScanState.CSI_INTERMEDIATE;
5937 }
5938
5939 // 30-39, 3B --> param
5940 if ((ch >= '0') && (ch <= '9')) {
5941 param((byte) ch);
5942 }
5943 if (ch == ';') {
5944 param((byte) ch);
5945 }
5946
5947 // 0x3A goes to CSI_IGNORE
5948 if (ch == 0x3A) {
5949 scanState = ScanState.CSI_IGNORE;
5950 }
5951 // 0x3C-3F goes to CSI_IGNORE
5952 if ((ch >= 0x3C) && (ch <= 0x3F)) {
5953 scanState = ScanState.CSI_IGNORE;
5954 }
5955
5956 // 40-7E --> dispatch, then switch to GROUND
5957 if ((ch >= 0x40) && (ch <= 0x7E)) {
5958 switch (ch) {
5959 case '@':
5960 // ICH - Insert character
5961 ich();
5962 break;
5963 case 'A':
5964 // CUU - Cursor up
5965 cuu();
5966 break;
5967 case 'B':
5968 // CUD - Cursor down
5969 cud();
5970 break;
5971 case 'C':
5972 // CUF - Cursor forward
5973 cuf();
5974 break;
5975 case 'D':
5976 // CUB - Cursor backward
5977 cub();
5978 break;
5979 case 'E':
5980 // CNL - Cursor down and to column 1
5981 if (type == DeviceType.XTERM) {
5982 cnl();
5983 }
5984 break;
5985 case 'F':
5986 // CPL - Cursor up and to column 1
5987 if (type == DeviceType.XTERM) {
5988 cpl();
5989 }
5990 break;
5991 case 'G':
5992 // CHA - Cursor to column # in current row
5993 if (type == DeviceType.XTERM) {
5994 cha();
5995 }
5996 break;
5997 case 'H':
5998 // CUP - Cursor position
5999 cup();
6000 break;
6001 case 'I':
6002 // CHT - Cursor forward X tab stops (default 1)
6003 if (type == DeviceType.XTERM) {
6004 cht();
6005 }
6006 break;
6007 case 'J':
6008 // ED - Erase in display
6009 ed();
6010 break;
6011 case 'K':
6012 // EL - Erase in line
6013 el();
6014 break;
6015 case 'L':
6016 // IL - Insert line
6017 il();
6018 break;
6019 case 'M':
6020 // DL - Delete line
6021 dl();
6022 break;
6023 case 'N':
6024 case 'O':
6025 break;
6026 case 'P':
6027 // DCH - Delete character
6028 dch();
6029 break;
6030 case 'Q':
6031 case 'R':
6032 break;
6033 case 'S':
6034 // Scroll up X lines (default 1)
6035 if (type == DeviceType.XTERM) {
6036 su();
6037 }
6038 break;
6039 case 'T':
6040 // Scroll down X lines (default 1)
6041 if (type == DeviceType.XTERM) {
6042 sd();
6043 }
6044 break;
6045 case 'U':
6046 case 'V':
6047 case 'W':
6048 break;
6049 case 'X':
6050 if ((type == DeviceType.VT220)
6051 || (type == DeviceType.XTERM)) {
6052
6053 // ECH - Erase character
6054 ech();
6055 }
6056 break;
6057 case 'Y':
6058 break;
6059 case 'Z':
6060 // CBT - Cursor backward X tab stops (default 1)
6061 if (type == DeviceType.XTERM) {
6062 cbt();
6063 }
6064 break;
6065 case '[':
6066 case '\\':
6067 case ']':
6068 case '^':
6069 case '_':
6070 break;
6071 case '`':
6072 // HPA - Cursor to column # in current row. Same as CHA
6073 if (type == DeviceType.XTERM) {
6074 cha();
6075 }
6076 break;
6077 case 'a':
6078 // HPR - Cursor right. Same as CUF
6079 if (type == DeviceType.XTERM) {
6080 cuf();
6081 }
6082 break;
6083 case 'b':
6084 // REP - Repeat last char X times
6085 if (type == DeviceType.XTERM) {
6086 rep();
6087 }
6088 break;
6089 case 'c':
6090 // DA - Device attributes
6091 da();
6092 break;
6093 case 'd':
6094 // VPA - Cursor to row, current column.
6095 if (type == DeviceType.XTERM) {
6096 vpa();
6097 }
6098 break;
6099 case 'e':
6100 // VPR - Cursor down. Same as CUD
6101 if (type == DeviceType.XTERM) {
6102 cud();
6103 }
6104 break;
6105 case 'f':
6106 // HVP - Horizontal and vertical position
6107 hvp();
6108 break;
6109 case 'g':
6110 // TBC - Tabulation clear
6111 tbc();
6112 break;
6113 case 'h':
6114 // Sets an ANSI or DEC private toggle
6115 setToggle(true);
6116 break;
6117 case 'i':
6118 if ((type == DeviceType.VT220)
6119 || (type == DeviceType.XTERM)) {
6120
6121 // Printer functions
6122 printerFunctions();
6123 }
6124 break;
6125 case 'j':
6126 case 'k':
6127 break;
6128 case 'l':
6129 // Sets an ANSI or DEC private toggle
6130 setToggle(false);
6131 break;
6132 case 'm':
6133 // SGR - Select graphics rendition
6134 sgr();
6135 break;
6136 case 'n':
6137 // DSR - Device status report
6138 dsr();
6139 break;
6140 case 'o':
6141 case 'p':
6142 break;
6143 case 'q':
6144 // DECLL - Load leds
6145 // Not supported
6146 break;
6147 case 'r':
6148 // DECSTBM - Set top and bottom margins
6149 decstbm();
6150 break;
6151 case 's':
6152 case 't':
6153 case 'u':
6154 case 'v':
6155 case 'w':
6156 break;
6157 case 'x':
6158 // DECREQTPARM - Request terminal parameters
6159 decreqtparm();
6160 break;
6161 case 'y':
6162 case 'z':
6163 case '{':
6164 case '|':
6165 case '}':
6166 case '~':
6167 break;
6168 }
6169 toGround();
6170 }
6171
6172 // 7F --> ignore
6173 return;
6174
6175 case CSI_INTERMEDIATE:
6176 // 00-17, 19, 1C-1F --> execute
6177 if (ch <= 0x1F) {
6178 handleControlChar(ch);
6179 }
6180
6181 // 20-2F --> collect
6182 if ((ch >= 0x20) && (ch <= 0x2F)) {
6183 collect(ch);
6184 }
6185
6186 // 0x30-3F goes to CSI_IGNORE
6187 if ((ch >= 0x30) && (ch <= 0x3F)) {
6188 scanState = ScanState.CSI_IGNORE;
6189 }
6190
6191 // 40-7E --> dispatch, then switch to GROUND
6192 if ((ch >= 0x40) && (ch <= 0x7E)) {
6193 switch (ch) {
6194 case '@':
6195 case 'A':
6196 case 'B':
6197 case 'C':
6198 case 'D':
6199 case 'E':
6200 case 'F':
6201 case 'G':
6202 case 'H':
6203 case 'I':
6204 case 'J':
6205 case 'K':
6206 case 'L':
6207 case 'M':
6208 case 'N':
6209 case 'O':
6210 case 'P':
6211 case 'Q':
6212 case 'R':
6213 case 'S':
6214 case 'T':
6215 case 'U':
6216 case 'V':
6217 case 'W':
6218 case 'X':
6219 case 'Y':
6220 case 'Z':
6221 case '[':
6222 case '\\':
6223 case ']':
6224 case '^':
6225 case '_':
6226 case '`':
6227 case 'a':
6228 case 'b':
6229 case 'c':
6230 case 'd':
6231 case 'e':
6232 case 'f':
6233 case 'g':
6234 case 'h':
6235 case 'i':
6236 case 'j':
6237 case 'k':
6238 case 'l':
6239 case 'm':
6240 case 'n':
6241 case 'o':
6242 break;
6243 case 'p':
6244 if (((type == DeviceType.VT220)
6245 || (type == DeviceType.XTERM))
6246 && (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
6247 ) {
6248 // DECSCL - compatibility level
6249 decscl();
6250 }
6251 if ((type == DeviceType.XTERM)
6252 && (collectBuffer.charAt(collectBuffer.length() - 1) == '!')
6253 ) {
6254 // DECSTR - Soft terminal reset
6255 decstr();
6256 }
6257 break;
6258 case 'q':
6259 if (((type == DeviceType.VT220)
6260 || (type == DeviceType.XTERM))
6261 && (collectBuffer.charAt(collectBuffer.length() - 1) == '\"')
6262 ) {
6263 // DECSCA
6264 decsca();
6265 }
6266 break;
6267 case 'r':
6268 case 's':
6269 case 't':
6270 case 'u':
6271 case 'v':
6272 case 'w':
6273 case 'x':
6274 case 'y':
6275 case 'z':
6276 case '{':
6277 case '|':
6278 case '}':
6279 case '~':
6280 break;
6281 }
6282 toGround();
6283 }
6284
6285 // 7F --> ignore
6286 return;
6287
6288 case CSI_IGNORE:
6289 // 00-17, 19, 1C-1F --> execute
6290 if (ch <= 0x1F) {
6291 handleControlChar(ch);
6292 }
6293
6294 // 20-2F --> collect
6295 if ((ch >= 0x20) && (ch <= 0x2F)) {
6296 collect(ch);
6297 }
6298
6299 // 40-7E --> ignore, then switch to GROUND
6300 if ((ch >= 0x40) && (ch <= 0x7E)) {
6301 toGround();
6302 }
6303
6304 // 20-3F, 7F --> ignore
6305
6306 return;
6307
6308 case DCS_ENTRY:
6309
6310 // 0x9C goes to GROUND
6311 if (ch == 0x9C) {
6312 toGround();
6313 }
6314
6315 // 0x1B 0x5C goes to GROUND
6316 if (ch == 0x1B) {
6317 collect(ch);
6318 }
6319 if (ch == 0x5C) {
6320 if ((collectBuffer.length() > 0)
6321 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6322 ) {
6323 toGround();
6324 }
6325 }
6326
6327 // 20-2F --> collect, then switch to DCS_INTERMEDIATE
6328 if ((ch >= 0x20) && (ch <= 0x2F)) {
6329 collect(ch);
6330 scanState = ScanState.DCS_INTERMEDIATE;
6331 }
6332
6333 // 30-39, 3B --> param, then switch to DCS_PARAM
6334 if ((ch >= '0') && (ch <= '9')) {
6335 param((byte) ch);
6336 scanState = ScanState.DCS_PARAM;
6337 }
6338 if (ch == ';') {
6339 param((byte) ch);
6340 scanState = ScanState.DCS_PARAM;
6341 }
6342
6343 // 3C-3F --> collect, then switch to DCS_PARAM
6344 if ((ch >= 0x3C) && (ch <= 0x3F)) {
6345 collect(ch);
6346 scanState = ScanState.DCS_PARAM;
6347 }
6348
6349 // 00-17, 19, 1C-1F, 7F --> ignore
6350
6351 // 0x3A goes to DCS_IGNORE
6352 if (ch == 0x3F) {
6353 scanState = ScanState.DCS_IGNORE;
6354 }
6355
6356 // 0x40-7E goes to DCS_PASSTHROUGH
6357 if ((ch >= 0x40) && (ch <= 0x7E)) {
6358 scanState = ScanState.DCS_PASSTHROUGH;
6359 }
6360 return;
6361
6362 case DCS_INTERMEDIATE:
6363
6364 // 0x9C goes to GROUND
6365 if (ch == 0x9C) {
6366 toGround();
6367 }
6368
6369 // 0x1B 0x5C goes to GROUND
6370 if (ch == 0x1B) {
6371 collect(ch);
6372 }
6373 if (ch == 0x5C) {
6374 if ((collectBuffer.length() > 0)
6375 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6376 ) {
6377 toGround();
6378 }
6379 }
6380
6381 // 0x30-3F goes to DCS_IGNORE
6382 if ((ch >= 0x30) && (ch <= 0x3F)) {
6383 scanState = ScanState.DCS_IGNORE;
6384 }
6385
6386 // 0x40-7E goes to DCS_PASSTHROUGH
6387 if ((ch >= 0x40) && (ch <= 0x7E)) {
6388 scanState = ScanState.DCS_PASSTHROUGH;
6389 }
6390
6391 // 00-17, 19, 1C-1F, 7F --> ignore
6392 return;
6393
6394 case DCS_PARAM:
6395
6396 // 0x9C goes to GROUND
6397 if (ch == 0x9C) {
6398 toGround();
6399 }
6400
6401 // 0x1B 0x5C goes to GROUND
6402 if (ch == 0x1B) {
6403 collect(ch);
6404 }
6405 if (ch == 0x5C) {
6406 if ((collectBuffer.length() > 0)
6407 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6408 ) {
6409 toGround();
6410 }
6411 }
6412
6413 // 20-2F --> collect, then switch to DCS_INTERMEDIATE
6414 if ((ch >= 0x20) && (ch <= 0x2F)) {
6415 collect(ch);
6416 scanState = ScanState.DCS_INTERMEDIATE;
6417 }
6418
6419 // 30-39, 3B --> param
6420 if ((ch >= '0') && (ch <= '9')) {
6421 param((byte) ch);
6422 }
6423 if (ch == ';') {
6424 param((byte) ch);
6425 }
6426
6427 // 00-17, 19, 1C-1F, 7F --> ignore
6428
6429 // 0x3A, 3C-3F goes to DCS_IGNORE
6430 if (ch == 0x3F) {
6431 scanState = ScanState.DCS_IGNORE;
6432 }
6433 if ((ch >= 0x3C) && (ch <= 0x3F)) {
6434 scanState = ScanState.DCS_IGNORE;
6435 }
6436
6437 // 0x40-7E goes to DCS_PASSTHROUGH
6438 if ((ch >= 0x40) && (ch <= 0x7E)) {
6439 scanState = ScanState.DCS_PASSTHROUGH;
6440 }
6441 return;
6442
6443 case DCS_PASSTHROUGH:
6444 // 0x9C goes to GROUND
6445 if (ch == 0x9C) {
6446 toGround();
6447 }
6448
6449 // 0x1B 0x5C goes to GROUND
6450 if (ch == 0x1B) {
6451 collect(ch);
6452 }
6453 if (ch == 0x5C) {
6454 if ((collectBuffer.length() > 0)
6455 && (collectBuffer.charAt(collectBuffer.length() - 1) == 0x1B)
6456 ) {
6457 toGround();
6458 }
6459 }
6460
6461 // 00-17, 19, 1C-1F, 20-7E --> put
6462 // TODO
6463 if (ch <= 0x17) {
6464 return;
6465 }
6466 if (ch == 0x19) {
6467 return;
6468 }
6469 if ((ch >= 0x1C) && (ch <= 0x1F)) {
6470 return;
6471 }
6472 if ((ch >= 0x20) && (ch <= 0x7E)) {
6473 return;
6474 }
6475
6476 // 7F --> ignore
6477
6478 return;
6479
6480 case DCS_IGNORE:
6481 // 00-17, 19, 1C-1F, 20-7F --> ignore
6482
6483 // 0x9C goes to GROUND
6484 if (ch == 0x9C) {
6485 toGround();
6486 }
6487
6488 return;
6489
6490 case SOSPMAPC_STRING:
6491 // 00-17, 19, 1C-1F, 20-7F --> ignore
6492
6493 // Special case for Jexer: PM can pass one control character
6494 if (ch == 0x1B) {
6495 pmPut(ch);
6496 }
6497
6498 if ((ch >= 0x20) && (ch <= 0x7F)) {
6499 pmPut(ch);
6500 }
6501
6502 // 0x9C goes to GROUND
6503 if (ch == 0x9C) {
6504 toGround();
6505 }
6506
6507 return;
6508
6509 case OSC_STRING:
6510 // Special case for Xterm: OSC can pass control characters
6511 if ((ch == 0x9C) || (ch == 0x07) || (ch == 0x1B)) {
6512 oscPut(ch);
6513 }
6514
6515 // 00-17, 19, 1C-1F --> ignore
6516
6517 // 20-7F --> osc_put
6518 if ((ch >= 0x20) && (ch <= 0x7F)) {
6519 oscPut(ch);
6520 }
6521
6522 // 0x9C goes to GROUND
6523 if (ch == 0x9C) {
6524 toGround();
6525 }
6526
6527 return;
6528
6529 case VT52_DIRECT_CURSOR_ADDRESS:
6530 // This is a special case for the VT52 sequence "ESC Y l c"
6531 if (collectBuffer.length() == 0) {
6532 collect(ch);
6533 } else if (collectBuffer.length() == 1) {
6534 // We've got the two characters, one in the buffer and the
6535 // other in ch.
6536 cursorPosition(collectBuffer.charAt(0) - '\040', ch - '\040');
6537 toGround();
6538 }
6539 return;
6540 }
6541
6542 }
6543
6544 /**
6545 * Expose current cursor X to outside world.
6546 *
6547 * @return current cursor X
6548 */
6549 public final int getCursorX() {
6550 if (display.get(currentState.cursorY).isDoubleWidth()) {
6551 return currentState.cursorX * 2;
6552 }
6553 return currentState.cursorX;
6554 }
6555
6556 /**
6557 * Expose current cursor Y to outside world.
6558 *
6559 * @return current cursor Y
6560 */
6561 public final int getCursorY() {
6562 return currentState.cursorY;
6563 }
6564
6565 /**
6566 * Returns true if this terminal has requested the mouse pointer be
6567 * hidden.
6568 *
6569 * @return true if this terminal has requested the mouse pointer be
6570 * hidden
6571 */
6572 public final boolean hasHiddenMousePointer() {
6573 return hideMousePointer;
6574 }
6575
6576 }