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