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