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