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