2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
31 import java
.io
.BufferedReader
;
32 import java
.io
.FileDescriptor
;
33 import java
.io
.FileInputStream
;
34 import java
.io
.InputStream
;
35 import java
.io
.InputStreamReader
;
36 import java
.io
.IOException
;
37 import java
.io
.OutputStream
;
38 import java
.io
.OutputStreamWriter
;
39 import java
.io
.PrintWriter
;
40 import java
.io
.Reader
;
41 import java
.io
.UnsupportedEncodingException
;
42 import java
.util
.ArrayList
;
43 import java
.util
.List
;
44 import java
.util
.LinkedList
;
46 import jexer
.bits
.Cell
;
47 import jexer
.bits
.CellAttributes
;
48 import jexer
.bits
.Color
;
49 import jexer
.event
.TInputEvent
;
50 import jexer
.event
.TKeypressEvent
;
51 import jexer
.event
.TMouseEvent
;
52 import jexer
.event
.TResizeEvent
;
53 import static jexer
.TKeypress
.*;
56 * This class reads keystrokes and mouse events and emits output to ANSI
57 * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, etc.
59 public final class ECMA48Terminal
extends LogicalScreen
60 implements TerminalReader
, Runnable
{
62 // ------------------------------------------------------------------------
63 // Constants --------------------------------------------------------------
64 // ------------------------------------------------------------------------
67 * States in the input parser.
69 private enum ParseState
{
79 // ------------------------------------------------------------------------
80 // Variables --------------------------------------------------------------
81 // ------------------------------------------------------------------------
84 * Emit debugging to stderr.
86 private boolean debugToStderr
= false;
89 * If true, emit T.416-style RGB colors. This is a) expensive in
90 * bandwidth, and b) potentially terrible looking for non-xterms.
92 private static boolean doRgbColor
= false;
95 * The session information.
97 private SessionInfo sessionInfo
;
100 * The event queue, filled up by a thread reading on input.
102 private List
<TInputEvent
> eventQueue
;
105 * If true, we want the reader thread to exit gracefully.
107 private boolean stopReaderThread
;
112 private Thread readerThread
;
115 * Parameters being collected. E.g. if the string is \033[1;3m, then
116 * params[0] will be 1 and params[1] will be 3.
118 private List
<String
> params
;
121 * Current parsing state.
123 private ParseState state
;
126 * The time we entered ESCAPE. If we get a bare escape without a code
127 * following it, this is used to return that bare escape.
129 private long escapeTime
;
132 * The time we last checked the window size. We try not to spawn stty
133 * more than once per second.
135 private long windowSizeTime
;
138 * true if mouse1 was down. Used to report mouse1 on the release event.
140 private boolean mouse1
;
143 * true if mouse2 was down. Used to report mouse2 on the release event.
145 private boolean mouse2
;
148 * true if mouse3 was down. Used to report mouse3 on the release event.
150 private boolean mouse3
;
153 * Cache the cursor visibility value so we only emit the sequence when we
156 private boolean cursorOn
= true;
159 * Cache the last window size to figure out if a TResizeEvent needs to be
162 private TResizeEvent windowResize
= null;
165 * If true, then we changed System.in and need to change it back.
167 private boolean setRawMode
;
170 * The terminal's input. If an InputStream is not specified in the
171 * constructor, then this InputStreamReader will be bound to System.in
172 * with UTF-8 encoding.
174 private Reader input
;
177 * The terminal's raw InputStream. If an InputStream is not specified in
178 * the constructor, then this InputReader will be bound to System.in.
179 * This is used by run() to see if bytes are available() before calling
180 * (Reader)input.read().
182 private InputStream inputStream
;
185 * The terminal's output. If an OutputStream is not specified in the
186 * constructor, then this PrintWriter will be bound to System.out with
189 private PrintWriter output
;
192 * The listening object that run() wakes up on new input.
194 private Object listener
;
196 // ------------------------------------------------------------------------
197 // Constructors -----------------------------------------------------------
198 // ------------------------------------------------------------------------
201 * Constructor sets up state for getEvent().
203 * @param listener the object this backend needs to wake up when new
205 * @param input an InputStream connected to the remote user, or null for
206 * System.in. If System.in is used, then on non-Windows systems it will
207 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
208 * mode. input is always converted to a Reader with UTF-8 encoding.
209 * @param output an OutputStream connected to the remote user, or null
210 * for System.out. output is always converted to a Writer with UTF-8
212 * @param windowWidth the number of text columns to start with
213 * @param windowHeight the number of text rows to start with
214 * @throws UnsupportedEncodingException if an exception is thrown when
215 * creating the InputStreamReader
217 public ECMA48Terminal(final Object listener
, final InputStream input
,
218 final OutputStream output
, final int windowWidth
,
219 final int windowHeight
) throws UnsupportedEncodingException
{
221 this(listener
, input
, output
);
223 // Send dtterm/xterm sequences, which will probably not work because
224 // allowWindowOps is defaulted to false.
225 String resizeString
= String
.format("\033[8;%d;%dt", windowHeight
,
227 this.output
.write(resizeString
);
232 * Constructor sets up state for getEvent().
234 * @param listener the object this backend needs to wake up when new
236 * @param input an InputStream connected to the remote user, or null for
237 * System.in. If System.in is used, then on non-Windows systems it will
238 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
239 * mode. input is always converted to a Reader with UTF-8 encoding.
240 * @param output an OutputStream connected to the remote user, or null
241 * for System.out. output is always converted to a Writer with UTF-8
243 * @throws UnsupportedEncodingException if an exception is thrown when
244 * creating the InputStreamReader
246 public ECMA48Terminal(final Object listener
, final InputStream input
,
247 final OutputStream output
) throws UnsupportedEncodingException
{
253 stopReaderThread
= false;
254 this.listener
= listener
;
257 // inputStream = System.in;
258 inputStream
= new FileInputStream(FileDescriptor
.in
);
264 this.input
= new InputStreamReader(inputStream
, "UTF-8");
266 if (input
instanceof SessionInfo
) {
267 // This is a TelnetInputStream that exposes window size and
268 // environment variables from the telnet layer.
269 sessionInfo
= (SessionInfo
) input
;
271 if (sessionInfo
== null) {
273 // Reading right off the tty
274 sessionInfo
= new TTYSessionInfo();
276 sessionInfo
= new TSessionInfo();
280 if (output
== null) {
281 this.output
= new PrintWriter(new OutputStreamWriter(System
.out
,
284 this.output
= new PrintWriter(new OutputStreamWriter(output
,
288 // Enable mouse reporting and metaSendsEscape
289 this.output
.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
292 // Query the screen size
293 sessionInfo
.queryWindowSize();
294 setDimensions(sessionInfo
.getWindowWidth(),
295 sessionInfo
.getWindowHeight());
297 // Hang onto the window size
298 windowResize
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
299 sessionInfo
.getWindowWidth(), sessionInfo
.getWindowHeight());
301 // Permit RGB colors only if externally requested
302 if (System
.getProperty("jexer.ECMA48.rgbColor") != null) {
303 if (System
.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
310 // Spin up the input reader
311 eventQueue
= new LinkedList
<TInputEvent
>();
312 readerThread
= new Thread(this);
313 readerThread
.start();
316 this.output
.write(clearAll());
321 * Constructor sets up state for getEvent().
323 * @param listener the object this backend needs to wake up when new
325 * @param input the InputStream underlying 'reader'. Its available()
326 * method is used to determine if reader.read() will block or not.
327 * @param reader a Reader connected to the remote user.
328 * @param writer a PrintWriter connected to the remote user.
329 * @param setRawMode if true, set System.in into raw mode with stty.
330 * This should in general not be used. It is here solely for Demo3,
331 * which uses System.in.
332 * @throws IllegalArgumentException if input, reader, or writer are null.
334 public ECMA48Terminal(final Object listener
, final InputStream input
,
335 final Reader reader
, final PrintWriter writer
,
336 final boolean setRawMode
) {
339 throw new IllegalArgumentException("InputStream must be specified");
341 if (reader
== null) {
342 throw new IllegalArgumentException("Reader must be specified");
344 if (writer
== null) {
345 throw new IllegalArgumentException("Writer must be specified");
351 stopReaderThread
= false;
352 this.listener
= listener
;
357 if (setRawMode
== true) {
360 this.setRawMode
= setRawMode
;
362 if (input
instanceof SessionInfo
) {
363 // This is a TelnetInputStream that exposes window size and
364 // environment variables from the telnet layer.
365 sessionInfo
= (SessionInfo
) input
;
367 if (sessionInfo
== null) {
368 if (setRawMode
== true) {
369 // Reading right off the tty
370 sessionInfo
= new TTYSessionInfo();
372 sessionInfo
= new TSessionInfo();
376 this.output
= writer
;
378 // Enable mouse reporting and metaSendsEscape
379 this.output
.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
382 // Query the screen size
383 sessionInfo
.queryWindowSize();
384 setDimensions(sessionInfo
.getWindowWidth(),
385 sessionInfo
.getWindowHeight());
387 // Hang onto the window size
388 windowResize
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
389 sessionInfo
.getWindowWidth(), sessionInfo
.getWindowHeight());
391 // Permit RGB colors only if externally requested
392 if (System
.getProperty("jexer.ECMA48.rgbColor") != null) {
393 if (System
.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
400 // Spin up the input reader
401 eventQueue
= new LinkedList
<TInputEvent
>();
402 readerThread
= new Thread(this);
403 readerThread
.start();
406 this.output
.write(clearAll());
411 * Constructor sets up state for getEvent().
413 * @param listener the object this backend needs to wake up when new
415 * @param input the InputStream underlying 'reader'. Its available()
416 * method is used to determine if reader.read() will block or not.
417 * @param reader a Reader connected to the remote user.
418 * @param writer a PrintWriter connected to the remote user.
419 * @throws IllegalArgumentException if input, reader, or writer are null.
421 public ECMA48Terminal(final Object listener
, final InputStream input
,
422 final Reader reader
, final PrintWriter writer
) {
424 this(listener
, input
, reader
, writer
, false);
427 // ------------------------------------------------------------------------
428 // LogicalScreen ----------------------------------------------------------
429 // ------------------------------------------------------------------------
432 * Set the window title.
434 * @param title the new title
437 public void setTitle(final String title
) {
438 output
.write(getSetTitleString(title
));
443 * Push the logical screen to the physical device.
446 public void flushPhysical() {
447 String result
= flushString();
451 && (cursorY
<= height
- 1)
452 && (cursorX
<= width
- 1)
454 result
+= cursor(true);
455 result
+= gotoXY(cursorX
, cursorY
);
457 result
+= cursor(false);
459 output
.write(result
);
463 // ------------------------------------------------------------------------
464 // TerminalReader ---------------------------------------------------------
465 // ------------------------------------------------------------------------
468 * Check if there are events in the queue.
470 * @return if true, getEvents() has something to return to the backend
472 public boolean hasEvents() {
473 synchronized (eventQueue
) {
474 return (eventQueue
.size() > 0);
479 * Return any events in the IO queue.
481 * @param queue list to append new events to
483 public void getEvents(final List
<TInputEvent
> queue
) {
484 synchronized (eventQueue
) {
485 if (eventQueue
.size() > 0) {
486 synchronized (queue
) {
487 queue
.addAll(eventQueue
);
495 * Restore terminal to normal state.
497 public void closeTerminal() {
499 // System.err.println("=== shutdown() ==="); System.err.flush();
501 // Tell the reader thread to stop looking at input
502 stopReaderThread
= true;
505 } catch (InterruptedException e
) {
509 // Disable mouse reporting and show cursor
510 output
.printf("%s%s%s", mouse(false), cursor(true), normal());
516 // We don't close System.in/out
518 // Shut down the streams, this should wake up the reader thread
525 if (output
!= null) {
529 } catch (IOException e
) {
536 * Set listener to a different Object.
538 * @param listener the new listening object that run() wakes up on new
541 public void setListener(final Object listener
) {
542 this.listener
= listener
;
545 // ------------------------------------------------------------------------
546 // Runnable ---------------------------------------------------------------
547 // ------------------------------------------------------------------------
550 * Read function runs on a separate thread.
553 boolean done
= false;
554 // available() will often return > 1, so we need to read in chunks to
556 char [] readBuffer
= new char[128];
557 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
559 while (!done
&& !stopReaderThread
) {
561 // We assume that if inputStream has bytes available, then
562 // input won't block on read().
563 int n
= inputStream
.available();
566 System.err.printf("inputStream.available(): %d\n", n);
571 if (readBuffer
.length
< n
) {
572 // The buffer wasn't big enough, make it huger
573 readBuffer
= new char[readBuffer
.length
* 2];
576 // System.err.printf("BEFORE read()\n"); System.err.flush();
578 int rc
= input
.read(readBuffer
, 0, readBuffer
.length
);
581 System.err.printf("AFTER read() %d\n", rc);
589 for (int i
= 0; i
< rc
; i
++) {
590 int ch
= readBuffer
[i
];
591 processChar(events
, (char)ch
);
593 getIdleEvents(events
);
594 if (events
.size() > 0) {
595 // Add to the queue for the backend thread to
596 // be able to obtain.
597 synchronized (eventQueue
) {
598 eventQueue
.addAll(events
);
600 if (listener
!= null) {
601 synchronized (listener
) {
602 listener
.notifyAll();
609 getIdleEvents(events
);
610 if (events
.size() > 0) {
611 synchronized (eventQueue
) {
612 eventQueue
.addAll(events
);
614 if (listener
!= null) {
615 synchronized (listener
) {
616 listener
.notifyAll();
622 // Wait 20 millis for more data
625 // System.err.println("end while loop"); System.err.flush();
626 } catch (InterruptedException e
) {
628 } catch (IOException e
) {
632 } // while ((done == false) && (stopReaderThread == false))
633 // System.err.println("*** run() exiting..."); System.err.flush();
636 // ------------------------------------------------------------------------
637 // ECMA48Terminal ---------------------------------------------------------
638 // ------------------------------------------------------------------------
641 * Getter for sessionInfo.
643 * @return the SessionInfo
645 public SessionInfo
getSessionInfo() {
650 * Get the output writer.
654 public PrintWriter
getOutput() {
659 * Call 'stty' to set cooked mode.
661 * <p>Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
663 private void sttyCooked() {
668 * Call 'stty' to set raw mode.
670 * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
671 * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
672 * -parenb cs8 min 1 < /dev/tty'
674 private void sttyRaw() {
679 * Call 'stty' to set raw or cooked mode.
681 * @param mode if true, set raw mode, otherwise set cooked mode
683 private void doStty(final boolean mode
) {
685 "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
687 String
[] cmdCooked
= {
688 "/bin/sh", "-c", "stty sane cooked < /dev/tty"
693 process
= Runtime
.getRuntime().exec(cmdRaw
);
695 process
= Runtime
.getRuntime().exec(cmdCooked
);
697 BufferedReader in
= new BufferedReader(new InputStreamReader(process
.getInputStream(), "UTF-8"));
698 String line
= in
.readLine();
699 if ((line
!= null) && (line
.length() > 0)) {
700 System
.err
.println("WEIRD?! Normal output from stty: " + line
);
703 BufferedReader err
= new BufferedReader(new InputStreamReader(process
.getErrorStream(), "UTF-8"));
704 line
= err
.readLine();
705 if ((line
!= null) && (line
.length() > 0)) {
706 System
.err
.println("Error output from stty: " + line
);
711 } catch (InterruptedException e
) {
715 int rc
= process
.exitValue();
717 System
.err
.println("stty returned error code: " + rc
);
719 } catch (IOException e
) {
727 public void flush() {
732 * Perform a somewhat-optimal rendering of a line.
734 * @param y row coordinate. 0 is the top-most row.
735 * @param sb StringBuilder to write escape sequences to
736 * @param lastAttr cell attributes from the last call to flushLine
738 private void flushLine(final int y
, final StringBuilder sb
,
739 CellAttributes lastAttr
) {
743 for (int x
= 0; x
< width
; x
++) {
744 Cell lCell
= logical
[x
][y
];
745 if (!lCell
.isBlank()) {
749 // Push textEnd to first column beyond the text area
753 // reallyCleared = true;
755 for (int x
= 0; x
< width
; x
++) {
756 Cell lCell
= logical
[x
][y
];
757 Cell pCell
= physical
[x
][y
];
759 if (!lCell
.equals(pCell
) || reallyCleared
) {
762 System
.err
.printf("\n--\n");
763 System
.err
.printf(" Y: %d X: %d\n", y
, x
);
764 System
.err
.printf(" lCell: %s\n", lCell
);
765 System
.err
.printf(" pCell: %s\n", pCell
);
766 System
.err
.printf(" ==== \n");
769 if (lastAttr
== null) {
770 lastAttr
= new CellAttributes();
775 if ((lastX
!= (x
- 1)) || (lastX
== -1)) {
776 // Advancing at least one cell, or the first gotoXY
777 sb
.append(gotoXY(x
, y
));
780 assert (lastAttr
!= null);
782 if ((x
== textEnd
) && (textEnd
< width
- 1)) {
783 assert (lCell
.isBlank());
785 for (int i
= x
; i
< width
; i
++) {
786 assert (logical
[i
][y
].isBlank());
787 // Physical is always updated
788 physical
[i
][y
].reset();
791 // Clear remaining line
792 sb
.append(clearRemainingLine());
797 // Now emit only the modified attributes
798 if ((lCell
.getForeColor() != lastAttr
.getForeColor())
799 && (lCell
.getBackColor() != lastAttr
.getBackColor())
800 && (lCell
.isBold() == lastAttr
.isBold())
801 && (lCell
.isReverse() == lastAttr
.isReverse())
802 && (lCell
.isUnderline() == lastAttr
.isUnderline())
803 && (lCell
.isBlink() == lastAttr
.isBlink())
805 // Both colors changed, attributes the same
806 sb
.append(color(lCell
.isBold(),
807 lCell
.getForeColor(), lCell
.getBackColor()));
810 System
.err
.printf("1 Change only fore/back colors\n");
812 } else if ((lCell
.getForeColor() != lastAttr
.getForeColor())
813 && (lCell
.getBackColor() != lastAttr
.getBackColor())
814 && (lCell
.isBold() != lastAttr
.isBold())
815 && (lCell
.isReverse() != lastAttr
.isReverse())
816 && (lCell
.isUnderline() != lastAttr
.isUnderline())
817 && (lCell
.isBlink() != lastAttr
.isBlink())
819 // Everything is different
820 sb
.append(color(lCell
.getForeColor(),
821 lCell
.getBackColor(),
822 lCell
.isBold(), lCell
.isReverse(),
824 lCell
.isUnderline()));
827 System
.err
.printf("2 Set all attributes\n");
829 } else if ((lCell
.getForeColor() != lastAttr
.getForeColor())
830 && (lCell
.getBackColor() == lastAttr
.getBackColor())
831 && (lCell
.isBold() == lastAttr
.isBold())
832 && (lCell
.isReverse() == lastAttr
.isReverse())
833 && (lCell
.isUnderline() == lastAttr
.isUnderline())
834 && (lCell
.isBlink() == lastAttr
.isBlink())
837 // Attributes same, foreColor different
838 sb
.append(color(lCell
.isBold(),
839 lCell
.getForeColor(), true));
842 System
.err
.printf("3 Change foreColor\n");
844 } else if ((lCell
.getForeColor() == lastAttr
.getForeColor())
845 && (lCell
.getBackColor() != lastAttr
.getBackColor())
846 && (lCell
.isBold() == lastAttr
.isBold())
847 && (lCell
.isReverse() == lastAttr
.isReverse())
848 && (lCell
.isUnderline() == lastAttr
.isUnderline())
849 && (lCell
.isBlink() == lastAttr
.isBlink())
851 // Attributes same, backColor different
852 sb
.append(color(lCell
.isBold(),
853 lCell
.getBackColor(), false));
856 System
.err
.printf("4 Change backColor\n");
858 } else if ((lCell
.getForeColor() == lastAttr
.getForeColor())
859 && (lCell
.getBackColor() == lastAttr
.getBackColor())
860 && (lCell
.isBold() == lastAttr
.isBold())
861 && (lCell
.isReverse() == lastAttr
.isReverse())
862 && (lCell
.isUnderline() == lastAttr
.isUnderline())
863 && (lCell
.isBlink() == lastAttr
.isBlink())
866 // All attributes the same, just print the char
870 System
.err
.printf("5 Only emit character\n");
873 // Just reset everything again
874 sb
.append(color(lCell
.getForeColor(),
875 lCell
.getBackColor(),
879 lCell
.isUnderline()));
882 System
.err
.printf("6 Change all attributes\n");
885 // Emit the character
886 sb
.append(lCell
.getChar());
888 // Save the last rendered cell
890 lastAttr
.setTo(lCell
);
892 // Physical is always updated
893 physical
[x
][y
].setTo(lCell
);
895 } // if (!lCell.equals(pCell) || (reallyCleared == true))
897 } // for (int x = 0; x < width; x++)
901 * Render the screen to a string that can be emitted to something that
902 * knows how to process ECMA-48/ANSI X3.64 escape sequences.
904 * @return escape sequences string that provides the updates to the
907 private String
flushString() {
908 CellAttributes attr
= null;
910 StringBuilder sb
= new StringBuilder();
912 attr
= new CellAttributes();
913 sb
.append(clearAll());
916 for (int y
= 0; y
< height
; y
++) {
917 flushLine(y
, sb
, attr
);
920 reallyCleared
= false;
922 String result
= sb
.toString();
924 System
.err
.printf("flushString(): %s\n", result
);
930 * Reset keyboard/mouse input parser.
932 private void resetParser() {
933 state
= ParseState
.GROUND
;
934 params
= new ArrayList
<String
>();
940 * Produce a control character or one of the special ones (ENTER, TAB,
943 * @param ch Unicode code point
944 * @param alt if true, set alt on the TKeypress
945 * @return one TKeypress event, either a control character (e.g. isKey ==
946 * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
949 private TKeypressEvent
controlChar(final char ch
, final boolean alt
) {
950 // System.err.printf("controlChar: %02x\n", ch);
954 // Carriage return --> ENTER
955 return new TKeypressEvent(kbEnter
, alt
, false, false);
957 // Linefeed --> ENTER
958 return new TKeypressEvent(kbEnter
, alt
, false, false);
961 return new TKeypressEvent(kbEsc
, alt
, false, false);
964 return new TKeypressEvent(kbTab
, alt
, false, false);
966 // Make all other control characters come back as the alphabetic
967 // character with the ctrl field set. So SOH would be 'A' +
969 return new TKeypressEvent(false, 0, (char)(ch
+ 0x40),
975 * Produce special key from CSI Pn ; Pm ; ... ~
977 * @return one KEYPRESS event representing a special key
979 private TInputEvent
csiFnKey() {
981 if (params
.size() > 0) {
982 key
= Integer
.parseInt(params
.get(0));
985 boolean ctrl
= false;
986 boolean shift
= false;
987 if (params
.size() > 1) {
988 shift
= csiIsShift(params
.get(1));
989 alt
= csiIsAlt(params
.get(1));
990 ctrl
= csiIsCtrl(params
.get(1));
995 return new TKeypressEvent(kbHome
, alt
, ctrl
, shift
);
997 return new TKeypressEvent(kbIns
, alt
, ctrl
, shift
);
999 return new TKeypressEvent(kbDel
, alt
, ctrl
, shift
);
1001 return new TKeypressEvent(kbEnd
, alt
, ctrl
, shift
);
1003 return new TKeypressEvent(kbPgUp
, alt
, ctrl
, shift
);
1005 return new TKeypressEvent(kbPgDn
, alt
, ctrl
, shift
);
1007 return new TKeypressEvent(kbF5
, alt
, ctrl
, shift
);
1009 return new TKeypressEvent(kbF6
, alt
, ctrl
, shift
);
1011 return new TKeypressEvent(kbF7
, alt
, ctrl
, shift
);
1013 return new TKeypressEvent(kbF8
, alt
, ctrl
, shift
);
1015 return new TKeypressEvent(kbF9
, alt
, ctrl
, shift
);
1017 return new TKeypressEvent(kbF10
, alt
, ctrl
, shift
);
1019 return new TKeypressEvent(kbF11
, alt
, ctrl
, shift
);
1021 return new TKeypressEvent(kbF12
, alt
, ctrl
, shift
);
1029 * Produce mouse events based on "Any event tracking" and UTF-8
1031 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1033 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
1035 private TInputEvent
parseMouse() {
1036 int buttons
= params
.get(0).charAt(0) - 32;
1037 int x
= params
.get(0).charAt(1) - 32 - 1;
1038 int y
= params
.get(0).charAt(2) - 32 - 1;
1040 // Clamp X and Y to the physical screen coordinates.
1041 if (x
>= windowResize
.getWidth()) {
1042 x
= windowResize
.getWidth() - 1;
1044 if (y
>= windowResize
.getHeight()) {
1045 y
= windowResize
.getHeight() - 1;
1048 TMouseEvent
.Type eventType
= TMouseEvent
.Type
.MOUSE_DOWN
;
1049 boolean eventMouse1
= false;
1050 boolean eventMouse2
= false;
1051 boolean eventMouse3
= false;
1052 boolean eventMouseWheelUp
= false;
1053 boolean eventMouseWheelDown
= false;
1055 // System.err.printf("buttons: %04x\r\n", buttons);
1072 if (!mouse1
&& !mouse2
&& !mouse3
) {
1073 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1075 eventType
= TMouseEvent
.Type
.MOUSE_UP
;
1092 // Dragging with mouse1 down
1095 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1099 // Dragging with mouse2 down
1102 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1106 // Dragging with mouse3 down
1109 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1113 // Dragging with mouse2 down after wheelUp
1116 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1120 // Dragging with mouse2 down after wheelDown
1123 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1127 eventMouseWheelUp
= true;
1131 eventMouseWheelDown
= true;
1135 // Unknown, just make it motion
1136 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1139 return new TMouseEvent(eventType
, x
, y
, x
, y
,
1140 eventMouse1
, eventMouse2
, eventMouse3
,
1141 eventMouseWheelUp
, eventMouseWheelDown
);
1145 * Produce mouse events based on "Any event tracking" and SGR
1147 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1149 * @param release if true, this was a release ('m')
1150 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
1152 private TInputEvent
parseMouseSGR(final boolean release
) {
1153 // SGR extended coordinates - mode 1006
1154 if (params
.size() < 3) {
1155 // Invalid position, bail out.
1158 int buttons
= Integer
.parseInt(params
.get(0));
1159 int x
= Integer
.parseInt(params
.get(1)) - 1;
1160 int y
= Integer
.parseInt(params
.get(2)) - 1;
1162 // Clamp X and Y to the physical screen coordinates.
1163 if (x
>= windowResize
.getWidth()) {
1164 x
= windowResize
.getWidth() - 1;
1166 if (y
>= windowResize
.getHeight()) {
1167 y
= windowResize
.getHeight() - 1;
1170 TMouseEvent
.Type eventType
= TMouseEvent
.Type
.MOUSE_DOWN
;
1171 boolean eventMouse1
= false;
1172 boolean eventMouse2
= false;
1173 boolean eventMouse3
= false;
1174 boolean eventMouseWheelUp
= false;
1175 boolean eventMouseWheelDown
= false;
1178 eventType
= TMouseEvent
.Type
.MOUSE_UP
;
1192 // Motion only, no buttons down
1193 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1197 // Dragging with mouse1 down
1199 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1203 // Dragging with mouse2 down
1205 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1209 // Dragging with mouse3 down
1211 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1215 // Dragging with mouse2 down after wheelUp
1217 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1221 // Dragging with mouse2 down after wheelDown
1223 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1227 eventMouseWheelUp
= true;
1231 eventMouseWheelDown
= true;
1235 // Unknown, bail out
1238 return new TMouseEvent(eventType
, x
, y
, x
, y
,
1239 eventMouse1
, eventMouse2
, eventMouse3
,
1240 eventMouseWheelUp
, eventMouseWheelDown
);
1244 * Return any events in the IO queue due to timeout.
1246 * @param queue list to append new events to
1248 private void getIdleEvents(final List
<TInputEvent
> queue
) {
1249 long nowTime
= System
.currentTimeMillis();
1251 // Check for new window size
1252 long windowSizeDelay
= nowTime
- windowSizeTime
;
1253 if (windowSizeDelay
> 1000) {
1254 sessionInfo
.queryWindowSize();
1255 int newWidth
= sessionInfo
.getWindowWidth();
1256 int newHeight
= sessionInfo
.getWindowHeight();
1258 if ((newWidth
!= windowResize
.getWidth())
1259 || (newHeight
!= windowResize
.getHeight())
1262 if (debugToStderr
) {
1263 System
.err
.println("Screen size changed, old size " +
1265 System
.err
.println(" new size " +
1266 newWidth
+ " x " + newHeight
);
1269 TResizeEvent event
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
1270 newWidth
, newHeight
);
1271 windowResize
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
1272 newWidth
, newHeight
);
1275 windowSizeTime
= nowTime
;
1278 // ESCDELAY type timeout
1279 if (state
== ParseState
.ESCAPE
) {
1280 long escDelay
= nowTime
- escapeTime
;
1281 if (escDelay
> 100) {
1282 // After 0.1 seconds, assume a true escape character
1283 queue
.add(controlChar((char)0x1B, false));
1290 * Returns true if the CSI parameter for a keyboard command means that
1293 private boolean csiIsShift(final String x
) {
1305 * Returns true if the CSI parameter for a keyboard command means that
1308 private boolean csiIsAlt(final String x
) {
1320 * Returns true if the CSI parameter for a keyboard command means that
1323 private boolean csiIsCtrl(final String x
) {
1335 * Parses the next character of input to see if an InputEvent is
1338 * @param events list to append new events to
1339 * @param ch Unicode code point
1341 private void processChar(final List
<TInputEvent
> events
, final char ch
) {
1343 // ESCDELAY type timeout
1344 long nowTime
= System
.currentTimeMillis();
1345 if (state
== ParseState
.ESCAPE
) {
1346 long escDelay
= nowTime
- escapeTime
;
1347 if (escDelay
> 250) {
1348 // After 0.25 seconds, assume a true escape character
1349 events
.add(controlChar((char)0x1B, false));
1355 boolean ctrl
= false;
1356 boolean alt
= false;
1357 boolean shift
= false;
1359 // System.err.printf("state: %s ch %c\r\n", state, ch);
1365 state
= ParseState
.ESCAPE
;
1366 escapeTime
= nowTime
;
1371 // Control character
1372 events
.add(controlChar(ch
, false));
1379 events
.add(new TKeypressEvent(false, 0, ch
,
1380 false, false, false));
1389 // ALT-Control character
1390 events
.add(controlChar(ch
, true));
1396 // This will be one of the function keys
1397 state
= ParseState
.ESCAPE_INTERMEDIATE
;
1401 // '[' goes to CSI_ENTRY
1403 state
= ParseState
.CSI_ENTRY
;
1407 // Everything else is assumed to be Alt-keystroke
1408 if ((ch
>= 'A') && (ch
<= 'Z')) {
1412 events
.add(new TKeypressEvent(false, 0, ch
, alt
, ctrl
, shift
));
1416 case ESCAPE_INTERMEDIATE
:
1417 if ((ch
>= 'P') && (ch
<= 'S')) {
1421 events
.add(new TKeypressEvent(kbF1
));
1424 events
.add(new TKeypressEvent(kbF2
));
1427 events
.add(new TKeypressEvent(kbF3
));
1430 events
.add(new TKeypressEvent(kbF4
));
1439 // Unknown keystroke, ignore
1444 // Numbers - parameter values
1445 if ((ch
>= '0') && (ch
<= '9')) {
1446 params
.set(params
.size() - 1,
1447 params
.get(params
.size() - 1) + ch
);
1448 state
= ParseState
.CSI_PARAM
;
1451 // Parameter separator
1457 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
1461 events
.add(new TKeypressEvent(kbUp
, alt
, ctrl
, shift
));
1466 events
.add(new TKeypressEvent(kbDown
, alt
, ctrl
, shift
));
1471 events
.add(new TKeypressEvent(kbRight
, alt
, ctrl
, shift
));
1476 events
.add(new TKeypressEvent(kbLeft
, alt
, ctrl
, shift
));
1481 events
.add(new TKeypressEvent(kbHome
));
1486 events
.add(new TKeypressEvent(kbEnd
));
1490 // CBT - Cursor backward X tab stops (default 1)
1491 events
.add(new TKeypressEvent(kbBackTab
));
1496 state
= ParseState
.MOUSE
;
1499 // Mouse position, SGR (1006) coordinates
1500 state
= ParseState
.MOUSE_SGR
;
1507 // Unknown keystroke, ignore
1512 // Numbers - parameter values
1513 if ((ch
>= '0') && (ch
<= '9')) {
1514 params
.set(params
.size() - 1,
1515 params
.get(params
.size() - 1) + ch
);
1518 // Parameter separator
1526 // Generate a mouse press event
1527 TInputEvent event
= parseMouseSGR(false);
1528 if (event
!= null) {
1534 // Generate a mouse release event
1535 event
= parseMouseSGR(true);
1536 if (event
!= null) {
1545 // Unknown keystroke, ignore
1550 // Numbers - parameter values
1551 if ((ch
>= '0') && (ch
<= '9')) {
1552 params
.set(params
.size() - 1,
1553 params
.get(params
.size() - 1) + ch
);
1554 state
= ParseState
.CSI_PARAM
;
1557 // Parameter separator
1564 events
.add(csiFnKey());
1569 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
1573 if (params
.size() > 1) {
1574 shift
= csiIsShift(params
.get(1));
1575 alt
= csiIsAlt(params
.get(1));
1576 ctrl
= csiIsCtrl(params
.get(1));
1578 events
.add(new TKeypressEvent(kbUp
, alt
, ctrl
, shift
));
1583 if (params
.size() > 1) {
1584 shift
= csiIsShift(params
.get(1));
1585 alt
= csiIsAlt(params
.get(1));
1586 ctrl
= csiIsCtrl(params
.get(1));
1588 events
.add(new TKeypressEvent(kbDown
, alt
, ctrl
, shift
));
1593 if (params
.size() > 1) {
1594 shift
= csiIsShift(params
.get(1));
1595 alt
= csiIsAlt(params
.get(1));
1596 ctrl
= csiIsCtrl(params
.get(1));
1598 events
.add(new TKeypressEvent(kbRight
, alt
, ctrl
, shift
));
1603 if (params
.size() > 1) {
1604 shift
= csiIsShift(params
.get(1));
1605 alt
= csiIsAlt(params
.get(1));
1606 ctrl
= csiIsCtrl(params
.get(1));
1608 events
.add(new TKeypressEvent(kbLeft
, alt
, ctrl
, shift
));
1613 if (params
.size() > 1) {
1614 shift
= csiIsShift(params
.get(1));
1615 alt
= csiIsAlt(params
.get(1));
1616 ctrl
= csiIsCtrl(params
.get(1));
1618 events
.add(new TKeypressEvent(kbHome
, alt
, ctrl
, shift
));
1623 if (params
.size() > 1) {
1624 shift
= csiIsShift(params
.get(1));
1625 alt
= csiIsAlt(params
.get(1));
1626 ctrl
= csiIsCtrl(params
.get(1));
1628 events
.add(new TKeypressEvent(kbEnd
, alt
, ctrl
, shift
));
1636 // Unknown keystroke, ignore
1641 params
.set(0, params
.get(params
.size() - 1) + ch
);
1642 if (params
.get(0).length() == 3) {
1643 // We have enough to generate a mouse event
1644 events
.add(parseMouse());
1653 // This "should" be impossible to reach
1658 * Tell (u)xterm that we want alt- keystrokes to send escape + character
1659 * rather than set the 8th bit. Anyone who wants UTF8 should want this
1662 * @param on if true, enable metaSendsEscape
1663 * @return the string to emit to xterm
1665 private String
xtermMetaSendsEscape(final boolean on
) {
1667 return "\033[?1036h\033[?1034l";
1669 return "\033[?1036l";
1673 * Create an xterm OSC sequence to change the window title.
1675 * @param title the new title
1676 * @return the string to emit to xterm
1678 private String
getSetTitleString(final String title
) {
1679 return "\033]2;" + title
+ "\007";
1683 * Create a SGR parameter sequence for a single color change.
1685 * @param bold if true, set bold
1686 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1687 * @param foreground if true, this is a foreground color
1688 * @return the string to emit to an ANSI / ECMA-style terminal,
1691 private String
color(final boolean bold
, final Color color
,
1692 final boolean foreground
) {
1693 return color(color
, foreground
, true) +
1694 rgbColor(bold
, color
, foreground
);
1698 * Create a T.416 RGB parameter sequence for a single color change.
1700 * @param bold if true, set bold
1701 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1702 * @param foreground if true, this is a foreground color
1703 * @return the string to emit to an xterm terminal with RGB support,
1704 * e.g. "\033[38;2;RR;GG;BBm"
1706 private String
rgbColor(final boolean bold
, final Color color
,
1707 final boolean foreground
) {
1708 if (doRgbColor
== false) {
1711 StringBuilder sb
= new StringBuilder("\033[");
1713 // Bold implies foreground only
1715 if (color
.equals(Color
.BLACK
)) {
1716 sb
.append("84;84;84");
1717 } else if (color
.equals(Color
.RED
)) {
1718 sb
.append("252;84;84");
1719 } else if (color
.equals(Color
.GREEN
)) {
1720 sb
.append("84;252;84");
1721 } else if (color
.equals(Color
.YELLOW
)) {
1722 sb
.append("252;252;84");
1723 } else if (color
.equals(Color
.BLUE
)) {
1724 sb
.append("84;84;252");
1725 } else if (color
.equals(Color
.MAGENTA
)) {
1726 sb
.append("252;84;252");
1727 } else if (color
.equals(Color
.CYAN
)) {
1728 sb
.append("84;252;252");
1729 } else if (color
.equals(Color
.WHITE
)) {
1730 sb
.append("252;252;252");
1738 if (color
.equals(Color
.BLACK
)) {
1740 } else if (color
.equals(Color
.RED
)) {
1741 sb
.append("168;0;0");
1742 } else if (color
.equals(Color
.GREEN
)) {
1743 sb
.append("0;168;0");
1744 } else if (color
.equals(Color
.YELLOW
)) {
1745 sb
.append("168;84;0");
1746 } else if (color
.equals(Color
.BLUE
)) {
1747 sb
.append("0;0;168");
1748 } else if (color
.equals(Color
.MAGENTA
)) {
1749 sb
.append("168;0;168");
1750 } else if (color
.equals(Color
.CYAN
)) {
1751 sb
.append("0;168;168");
1752 } else if (color
.equals(Color
.WHITE
)) {
1753 sb
.append("168;168;168");
1757 return sb
.toString();
1761 * Create a T.416 RGB parameter sequence for both foreground and
1762 * background color change.
1764 * @param bold if true, set bold
1765 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1766 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1767 * @return the string to emit to an xterm terminal with RGB support,
1768 * e.g. "\033[38;2;RR;GG;BB;48;2;RR;GG;BBm"
1770 private String
rgbColor(final boolean bold
, final Color foreColor
,
1771 final Color backColor
) {
1772 if (doRgbColor
== false) {
1776 return rgbColor(bold
, foreColor
, true) +
1777 rgbColor(false, backColor
, false);
1781 * Create a SGR parameter sequence for a single color change.
1783 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1784 * @param foreground if true, this is a foreground color
1785 * @param header if true, make the full header, otherwise just emit the
1786 * color parameter e.g. "42;"
1787 * @return the string to emit to an ANSI / ECMA-style terminal,
1790 private String
color(final Color color
, final boolean foreground
,
1791 final boolean header
) {
1793 int ecmaColor
= color
.getValue();
1795 // Convert Color.* values to SGR numerics
1803 return String
.format("\033[%dm", ecmaColor
);
1805 return String
.format("%d;", ecmaColor
);
1810 * Create a SGR parameter sequence for both foreground and background
1813 * @param bold if true, set bold
1814 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1815 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1816 * @return the string to emit to an ANSI / ECMA-style terminal,
1817 * e.g. "\033[31;42m"
1819 private String
color(final boolean bold
, final Color foreColor
,
1820 final Color backColor
) {
1821 return color(foreColor
, backColor
, true) +
1822 rgbColor(bold
, foreColor
, backColor
);
1826 * Create a SGR parameter sequence for both foreground and
1827 * background color change.
1829 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1830 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1831 * @param header if true, make the full header, otherwise just emit the
1832 * color parameter e.g. "31;42;"
1833 * @return the string to emit to an ANSI / ECMA-style terminal,
1834 * e.g. "\033[31;42m"
1836 private String
color(final Color foreColor
, final Color backColor
,
1837 final boolean header
) {
1839 int ecmaForeColor
= foreColor
.getValue();
1840 int ecmaBackColor
= backColor
.getValue();
1842 // Convert Color.* values to SGR numerics
1843 ecmaBackColor
+= 40;
1844 ecmaForeColor
+= 30;
1847 return String
.format("\033[%d;%dm", ecmaForeColor
, ecmaBackColor
);
1849 return String
.format("%d;%d;", ecmaForeColor
, ecmaBackColor
);
1854 * Create a SGR parameter sequence for foreground, background, and
1855 * several attributes. This sequence first resets all attributes to
1856 * default, then sets attributes as per the parameters.
1858 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1859 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1860 * @param bold if true, set bold
1861 * @param reverse if true, set reverse
1862 * @param blink if true, set blink
1863 * @param underline if true, set underline
1864 * @return the string to emit to an ANSI / ECMA-style terminal,
1865 * e.g. "\033[0;1;31;42m"
1867 private String
color(final Color foreColor
, final Color backColor
,
1868 final boolean bold
, final boolean reverse
, final boolean blink
,
1869 final boolean underline
) {
1871 int ecmaForeColor
= foreColor
.getValue();
1872 int ecmaBackColor
= backColor
.getValue();
1874 // Convert Color.* values to SGR numerics
1875 ecmaBackColor
+= 40;
1876 ecmaForeColor
+= 30;
1878 StringBuilder sb
= new StringBuilder();
1879 if ( bold
&& reverse
&& blink
&& !underline
) {
1880 sb
.append("\033[0;1;7;5;");
1881 } else if ( bold
&& reverse
&& !blink
&& !underline
) {
1882 sb
.append("\033[0;1;7;");
1883 } else if ( !bold
&& reverse
&& blink
&& !underline
) {
1884 sb
.append("\033[0;7;5;");
1885 } else if ( bold
&& !reverse
&& blink
&& !underline
) {
1886 sb
.append("\033[0;1;5;");
1887 } else if ( bold
&& !reverse
&& !blink
&& !underline
) {
1888 sb
.append("\033[0;1;");
1889 } else if ( !bold
&& reverse
&& !blink
&& !underline
) {
1890 sb
.append("\033[0;7;");
1891 } else if ( !bold
&& !reverse
&& blink
&& !underline
) {
1892 sb
.append("\033[0;5;");
1893 } else if ( bold
&& reverse
&& blink
&& underline
) {
1894 sb
.append("\033[0;1;7;5;4;");
1895 } else if ( bold
&& reverse
&& !blink
&& underline
) {
1896 sb
.append("\033[0;1;7;4;");
1897 } else if ( !bold
&& reverse
&& blink
&& underline
) {
1898 sb
.append("\033[0;7;5;4;");
1899 } else if ( bold
&& !reverse
&& blink
&& underline
) {
1900 sb
.append("\033[0;1;5;4;");
1901 } else if ( bold
&& !reverse
&& !blink
&& underline
) {
1902 sb
.append("\033[0;1;4;");
1903 } else if ( !bold
&& reverse
&& !blink
&& underline
) {
1904 sb
.append("\033[0;7;4;");
1905 } else if ( !bold
&& !reverse
&& blink
&& underline
) {
1906 sb
.append("\033[0;5;4;");
1907 } else if ( !bold
&& !reverse
&& !blink
&& underline
) {
1908 sb
.append("\033[0;4;");
1910 assert (!bold
&& !reverse
&& !blink
&& !underline
);
1911 sb
.append("\033[0;");
1913 sb
.append(String
.format("%d;%dm", ecmaForeColor
, ecmaBackColor
));
1914 sb
.append(rgbColor(bold
, foreColor
, backColor
));
1915 return sb
.toString();
1919 * Create a SGR parameter sequence to reset to defaults.
1921 * @return the string to emit to an ANSI / ECMA-style terminal,
1924 private String
normal() {
1925 return normal(true) + rgbColor(false, Color
.WHITE
, Color
.BLACK
);
1929 * Create a SGR parameter sequence to reset to defaults.
1931 * @param header if true, make the full header, otherwise just emit the
1932 * bare parameter e.g. "0;"
1933 * @return the string to emit to an ANSI / ECMA-style terminal,
1936 private String
normal(final boolean header
) {
1938 return "\033[0;37;40m";
1944 * Create a SGR parameter sequence for enabling the visible cursor.
1946 * @param on if true, turn on cursor
1947 * @return the string to emit to an ANSI / ECMA-style terminal
1949 private String
cursor(final boolean on
) {
1950 if (on
&& !cursorOn
) {
1954 if (!on
&& cursorOn
) {
1962 * Clear the entire screen. Because some terminals use back-color-erase,
1963 * set the color to white-on-black beforehand.
1965 * @return the string to emit to an ANSI / ECMA-style terminal
1967 private String
clearAll() {
1968 return "\033[0;37;40m\033[2J";
1972 * Clear the line from the cursor (inclusive) to the end of the screen.
1973 * Because some terminals use back-color-erase, set the color to
1974 * white-on-black beforehand.
1976 * @return the string to emit to an ANSI / ECMA-style terminal
1978 private String
clearRemainingLine() {
1979 return "\033[0;37;40m\033[K";
1983 * Move the cursor to (x, y).
1985 * @param x column coordinate. 0 is the left-most column.
1986 * @param y row coordinate. 0 is the top-most row.
1987 * @return the string to emit to an ANSI / ECMA-style terminal
1989 private String
gotoXY(final int x
, final int y
) {
1990 return String
.format("\033[%d;%dH", y
+ 1, x
+ 1);
1994 * Tell (u)xterm that we want to receive mouse events based on "Any event
1995 * tracking", UTF-8 coordinates, and then SGR coordinates. Ideally we
1996 * will end up with SGR coordinates with UTF-8 coordinates as a fallback.
1998 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
2000 * Note that this also sets the alternate/primary screen buffer.
2002 * @param on If true, enable mouse report and use the alternate screen
2003 * buffer. If false disable mouse reporting and use the primary screen
2005 * @return the string to emit to xterm
2007 private String
mouse(final boolean on
) {
2009 return "\033[?1002;1003;1005;1006h\033[?1049h";
2011 return "\033[?1002;1003;1006;1005l\033[?1049l";