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