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
{
63 * Emit debugging to stderr.
65 private boolean debugToStderr
= false;
68 * If true, emit T.416-style RGB colors. This is a) expensive in
69 * bandwidth, and b) potentially terrible looking for non-xterms.
71 private static boolean doRgbColor
= false;
74 * The session information.
76 private SessionInfo sessionInfo
;
79 * Getter for sessionInfo.
81 * @return the SessionInfo
83 public SessionInfo
getSessionInfo() {
88 * The event queue, filled up by a thread reading on input.
90 private List
<TInputEvent
> eventQueue
;
93 * If true, we want the reader thread to exit gracefully.
95 private boolean stopReaderThread
;
100 private Thread readerThread
;
103 * Parameters being collected. E.g. if the string is \033[1;3m, then
104 * params[0] will be 1 and params[1] will be 3.
106 private ArrayList
<String
> params
;
109 * States in the input parser.
111 private enum ParseState
{
122 * Current parsing state.
124 private ParseState state
;
127 * The time we entered ESCAPE. If we get a bare escape without a code
128 * following it, this is used to return that bare escape.
130 private long escapeTime
;
133 * The time we last checked the window size. We try not to spawn stty
134 * more than once per second.
136 private long windowSizeTime
;
139 * true if mouse1 was down. Used to report mouse1 on the release event.
141 private boolean mouse1
;
144 * true if mouse2 was down. Used to report mouse2 on the release event.
146 private boolean mouse2
;
149 * true if mouse3 was down. Used to report mouse3 on the release event.
151 private boolean mouse3
;
154 * Cache the cursor visibility value so we only emit the sequence when we
157 private boolean cursorOn
= true;
160 * Cache the last window size to figure out if a TResizeEvent needs to be
163 private TResizeEvent windowResize
= null;
166 * If true, then we changed System.in and need to change it back.
168 private boolean setRawMode
;
171 * The terminal's input. If an InputStream is not specified in the
172 * constructor, then this InputStreamReader will be bound to System.in
173 * with UTF-8 encoding.
175 private Reader input
;
178 * The terminal's raw InputStream. If an InputStream is not specified in
179 * the constructor, then this InputReader will be bound to System.in.
180 * This is used by run() to see if bytes are available() before calling
181 * (Reader)input.read().
183 private InputStream inputStream
;
186 * The terminal's output. If an OutputStream is not specified in the
187 * constructor, then this PrintWriter will be bound to System.out with
190 private PrintWriter output
;
193 * The listening object that run() wakes up on new input.
195 private Object listener
;
198 * Set listener to a different Object.
200 * @param listener the new listening object that run() wakes up on new
203 public void setListener(final Object listener
) {
204 this.listener
= listener
;
208 * Get the output writer.
212 public PrintWriter
getOutput() {
217 * Check if there are events in the queue.
219 * @return if true, getEvents() has something to return to the backend
221 public boolean hasEvents() {
222 synchronized (eventQueue
) {
223 return (eventQueue
.size() > 0);
228 * Call 'stty' to set cooked mode.
230 * <p>Actually executes '/bin/sh -c stty sane cooked < /dev/tty'
232 private void sttyCooked() {
237 * Call 'stty' to set raw mode.
239 * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
240 * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
241 * -parenb cs8 min 1 < /dev/tty'
243 private void sttyRaw() {
248 * Call 'stty' to set raw or cooked mode.
250 * @param mode if true, set raw mode, otherwise set cooked mode
252 private void doStty(final boolean mode
) {
254 "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
256 String
[] cmdCooked
= {
257 "/bin/sh", "-c", "stty sane cooked < /dev/tty"
262 process
= Runtime
.getRuntime().exec(cmdRaw
);
264 process
= Runtime
.getRuntime().exec(cmdCooked
);
266 BufferedReader in
= new BufferedReader(new InputStreamReader(process
.getInputStream(), "UTF-8"));
267 String line
= in
.readLine();
268 if ((line
!= null) && (line
.length() > 0)) {
269 System
.err
.println("WEIRD?! Normal output from stty: " + line
);
272 BufferedReader err
= new BufferedReader(new InputStreamReader(process
.getErrorStream(), "UTF-8"));
273 line
= err
.readLine();
274 if ((line
!= null) && (line
.length() > 0)) {
275 System
.err
.println("Error output from stty: " + line
);
280 } catch (InterruptedException e
) {
284 int rc
= process
.exitValue();
286 System
.err
.println("stty returned error code: " + rc
);
288 } catch (IOException e
) {
294 * Constructor sets up state for getEvent().
296 * @param listener the object this backend needs to wake up when new
298 * @param input an InputStream connected to the remote user, or null for
299 * System.in. If System.in is used, then on non-Windows systems it will
300 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
301 * mode. input is always converted to a Reader with UTF-8 encoding.
302 * @param output an OutputStream connected to the remote user, or null
303 * for System.out. output is always converted to a Writer with UTF-8
305 * @throws UnsupportedEncodingException if an exception is thrown when
306 * creating the InputStreamReader
308 public ECMA48Terminal(final Object listener
, final InputStream input
,
309 final OutputStream output
) throws UnsupportedEncodingException
{
315 stopReaderThread
= false;
316 this.listener
= listener
;
319 // inputStream = System.in;
320 inputStream
= new FileInputStream(FileDescriptor
.in
);
326 this.input
= new InputStreamReader(inputStream
, "UTF-8");
328 if (input
instanceof SessionInfo
) {
329 // This is a TelnetInputStream that exposes window size and
330 // environment variables from the telnet layer.
331 sessionInfo
= (SessionInfo
) input
;
333 if (sessionInfo
== null) {
335 // Reading right off the tty
336 sessionInfo
= new TTYSessionInfo();
338 sessionInfo
= new TSessionInfo();
342 if (output
== null) {
343 this.output
= new PrintWriter(new OutputStreamWriter(System
.out
,
346 this.output
= new PrintWriter(new OutputStreamWriter(output
,
350 // Enable mouse reporting and metaSendsEscape
351 this.output
.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
354 // Query the screen size
355 sessionInfo
.queryWindowSize();
356 setDimensions(sessionInfo
.getWindowWidth(),
357 sessionInfo
.getWindowHeight());
359 // Hang onto the window size
360 windowResize
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
361 sessionInfo
.getWindowWidth(), sessionInfo
.getWindowHeight());
363 // Permit RGB colors only if externally requested
364 if (System
.getProperty("jexer.ECMA48.rgbColor") != null) {
365 if (System
.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
372 // Spin up the input reader
373 eventQueue
= new LinkedList
<TInputEvent
>();
374 readerThread
= new Thread(this);
375 readerThread
.start();
378 this.output
.write(clearAll());
383 * Constructor sets up state for getEvent().
385 * @param listener the object this backend needs to wake up when new
387 * @param input the InputStream underlying 'reader'. Its available()
388 * method is used to determine if reader.read() will block or not.
389 * @param reader a Reader connected to the remote user.
390 * @param writer a PrintWriter connected to the remote user.
391 * @param setRawMode if true, set System.in into raw mode with stty.
392 * This should in general not be used. It is here solely for Demo3,
393 * which uses System.in.
394 * @throws IllegalArgumentException if input, reader, or writer are null.
396 public ECMA48Terminal(final Object listener
, final InputStream input
,
397 final Reader reader
, final PrintWriter writer
,
398 final boolean setRawMode
) {
401 throw new IllegalArgumentException("InputStream must be specified");
403 if (reader
== null) {
404 throw new IllegalArgumentException("Reader must be specified");
406 if (writer
== null) {
407 throw new IllegalArgumentException("Writer must be specified");
413 stopReaderThread
= false;
414 this.listener
= listener
;
419 if (setRawMode
== true) {
422 this.setRawMode
= setRawMode
;
424 if (input
instanceof SessionInfo
) {
425 // This is a TelnetInputStream that exposes window size and
426 // environment variables from the telnet layer.
427 sessionInfo
= (SessionInfo
) input
;
429 if (sessionInfo
== null) {
430 if (setRawMode
== true) {
431 // Reading right off the tty
432 sessionInfo
= new TTYSessionInfo();
434 sessionInfo
= new TSessionInfo();
438 this.output
= writer
;
440 // Enable mouse reporting and metaSendsEscape
441 this.output
.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
444 // Query the screen size
445 sessionInfo
.queryWindowSize();
446 setDimensions(sessionInfo
.getWindowWidth(),
447 sessionInfo
.getWindowHeight());
449 // Hang onto the window size
450 windowResize
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
451 sessionInfo
.getWindowWidth(), sessionInfo
.getWindowHeight());
453 // Permit RGB colors only if externally requested
454 if (System
.getProperty("jexer.ECMA48.rgbColor") != null) {
455 if (System
.getProperty("jexer.ECMA48.rgbColor").equals("true")) {
462 // Spin up the input reader
463 eventQueue
= new LinkedList
<TInputEvent
>();
464 readerThread
= new Thread(this);
465 readerThread
.start();
468 this.output
.write(clearAll());
473 * Constructor sets up state for getEvent().
475 * @param listener the object this backend needs to wake up when new
477 * @param input the InputStream underlying 'reader'. Its available()
478 * method is used to determine if reader.read() will block or not.
479 * @param reader a Reader connected to the remote user.
480 * @param writer a PrintWriter connected to the remote user.
481 * @throws IllegalArgumentException if input, reader, or writer are null.
483 public ECMA48Terminal(final Object listener
, final InputStream input
,
484 final Reader reader
, final PrintWriter writer
) {
486 this(listener
, input
, reader
, writer
, false);
490 * Restore terminal to normal state.
492 public void closeTerminal() {
494 // System.err.println("=== shutdown() ==="); System.err.flush();
496 // Tell the reader thread to stop looking at input
497 stopReaderThread
= true;
500 } catch (InterruptedException e
) {
504 // Disable mouse reporting and show cursor
505 output
.printf("%s%s%s", mouse(false), cursor(true), normal());
511 // We don't close System.in/out
513 // Shut down the streams, this should wake up the reader thread
520 if (output
!= null) {
524 } catch (IOException e
) {
533 public void flush() {
538 * Perform a somewhat-optimal rendering of a line.
540 * @param y row coordinate. 0 is the top-most row.
541 * @param sb StringBuilder to write escape sequences to
542 * @param lastAttr cell attributes from the last call to flushLine
544 private void flushLine(final int y
, final StringBuilder sb
,
545 CellAttributes lastAttr
) {
549 for (int x
= 0; x
< width
; x
++) {
550 Cell lCell
= logical
[x
][y
];
551 if (!lCell
.isBlank()) {
555 // Push textEnd to first column beyond the text area
559 // reallyCleared = true;
561 for (int x
= 0; x
< width
; x
++) {
562 Cell lCell
= logical
[x
][y
];
563 Cell pCell
= physical
[x
][y
];
565 if (!lCell
.equals(pCell
) || reallyCleared
) {
568 System
.err
.printf("\n--\n");
569 System
.err
.printf(" Y: %d X: %d\n", y
, x
);
570 System
.err
.printf(" lCell: %s\n", lCell
);
571 System
.err
.printf(" pCell: %s\n", pCell
);
572 System
.err
.printf(" ==== \n");
575 if (lastAttr
== null) {
576 lastAttr
= new CellAttributes();
581 if ((lastX
!= (x
- 1)) || (lastX
== -1)) {
582 // Advancing at least one cell, or the first gotoXY
583 sb
.append(gotoXY(x
, y
));
586 assert (lastAttr
!= null);
588 if ((x
== textEnd
) && (textEnd
< width
- 1)) {
589 assert (lCell
.isBlank());
591 for (int i
= x
; i
< width
; i
++) {
592 assert (logical
[i
][y
].isBlank());
593 // Physical is always updated
594 physical
[i
][y
].reset();
597 // Clear remaining line
598 sb
.append(clearRemainingLine());
603 // Now emit only the modified attributes
604 if ((lCell
.getForeColor() != lastAttr
.getForeColor())
605 && (lCell
.getBackColor() != lastAttr
.getBackColor())
606 && (lCell
.isBold() == lastAttr
.isBold())
607 && (lCell
.isReverse() == lastAttr
.isReverse())
608 && (lCell
.isUnderline() == lastAttr
.isUnderline())
609 && (lCell
.isBlink() == lastAttr
.isBlink())
611 // Both colors changed, attributes the same
612 sb
.append(color(lCell
.isBold(),
613 lCell
.getForeColor(), lCell
.getBackColor()));
616 System
.err
.printf("1 Change only fore/back colors\n");
618 } else if ((lCell
.getForeColor() != lastAttr
.getForeColor())
619 && (lCell
.getBackColor() != lastAttr
.getBackColor())
620 && (lCell
.isBold() != lastAttr
.isBold())
621 && (lCell
.isReverse() != lastAttr
.isReverse())
622 && (lCell
.isUnderline() != lastAttr
.isUnderline())
623 && (lCell
.isBlink() != lastAttr
.isBlink())
625 // Everything is different
626 sb
.append(color(lCell
.getForeColor(),
627 lCell
.getBackColor(),
628 lCell
.isBold(), lCell
.isReverse(),
630 lCell
.isUnderline()));
633 System
.err
.printf("2 Set all attributes\n");
635 } else if ((lCell
.getForeColor() != lastAttr
.getForeColor())
636 && (lCell
.getBackColor() == lastAttr
.getBackColor())
637 && (lCell
.isBold() == lastAttr
.isBold())
638 && (lCell
.isReverse() == lastAttr
.isReverse())
639 && (lCell
.isUnderline() == lastAttr
.isUnderline())
640 && (lCell
.isBlink() == lastAttr
.isBlink())
643 // Attributes same, foreColor different
644 sb
.append(color(lCell
.isBold(),
645 lCell
.getForeColor(), true));
648 System
.err
.printf("3 Change foreColor\n");
650 } else if ((lCell
.getForeColor() == lastAttr
.getForeColor())
651 && (lCell
.getBackColor() != lastAttr
.getBackColor())
652 && (lCell
.isBold() == lastAttr
.isBold())
653 && (lCell
.isReverse() == lastAttr
.isReverse())
654 && (lCell
.isUnderline() == lastAttr
.isUnderline())
655 && (lCell
.isBlink() == lastAttr
.isBlink())
657 // Attributes same, backColor different
658 sb
.append(color(lCell
.isBold(),
659 lCell
.getBackColor(), false));
662 System
.err
.printf("4 Change backColor\n");
664 } else if ((lCell
.getForeColor() == lastAttr
.getForeColor())
665 && (lCell
.getBackColor() == lastAttr
.getBackColor())
666 && (lCell
.isBold() == lastAttr
.isBold())
667 && (lCell
.isReverse() == lastAttr
.isReverse())
668 && (lCell
.isUnderline() == lastAttr
.isUnderline())
669 && (lCell
.isBlink() == lastAttr
.isBlink())
672 // All attributes the same, just print the char
676 System
.err
.printf("5 Only emit character\n");
679 // Just reset everything again
680 sb
.append(color(lCell
.getForeColor(),
681 lCell
.getBackColor(),
685 lCell
.isUnderline()));
688 System
.err
.printf("6 Change all attributes\n");
691 // Emit the character
692 sb
.append(lCell
.getChar());
694 // Save the last rendered cell
696 lastAttr
.setTo(lCell
);
698 // Physical is always updated
699 physical
[x
][y
].setTo(lCell
);
701 } // if (!lCell.equals(pCell) || (reallyCleared == true))
703 } // for (int x = 0; x < width; x++)
707 * Render the screen to a string that can be emitted to something that
708 * knows how to process ECMA-48/ANSI X3.64 escape sequences.
710 * @return escape sequences string that provides the updates to the
713 private String
flushString() {
714 CellAttributes attr
= null;
716 StringBuilder sb
= new StringBuilder();
718 attr
= new CellAttributes();
719 sb
.append(clearAll());
722 for (int y
= 0; y
< height
; y
++) {
723 flushLine(y
, sb
, attr
);
726 reallyCleared
= false;
728 String result
= sb
.toString();
730 System
.err
.printf("flushString(): %s\n", result
);
736 * Push the logical screen to the physical device.
739 public void flushPhysical() {
740 String result
= flushString();
744 && (cursorY
<= height
- 1)
745 && (cursorX
<= width
- 1)
747 result
+= cursor(true);
748 result
+= gotoXY(cursorX
, cursorY
);
750 result
+= cursor(false);
752 output
.write(result
);
757 * Set the window title.
759 * @param title the new title
761 public void setTitle(final String title
) {
762 output
.write(getSetTitleString(title
));
767 * Reset keyboard/mouse input parser.
769 private void resetParser() {
770 state
= ParseState
.GROUND
;
771 params
= new ArrayList
<String
>();
777 * Produce a control character or one of the special ones (ENTER, TAB,
780 * @param ch Unicode code point
781 * @param alt if true, set alt on the TKeypress
782 * @return one TKeypress event, either a control character (e.g. isKey ==
783 * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
786 private TKeypressEvent
controlChar(final char ch
, final boolean alt
) {
787 // System.err.printf("controlChar: %02x\n", ch);
791 // Carriage return --> ENTER
792 return new TKeypressEvent(kbEnter
, alt
, false, false);
794 // Linefeed --> ENTER
795 return new TKeypressEvent(kbEnter
, alt
, false, false);
798 return new TKeypressEvent(kbEsc
, alt
, false, false);
801 return new TKeypressEvent(kbTab
, alt
, false, false);
803 // Make all other control characters come back as the alphabetic
804 // character with the ctrl field set. So SOH would be 'A' +
806 return new TKeypressEvent(false, 0, (char)(ch
+ 0x40),
812 * Produce special key from CSI Pn ; Pm ; ... ~
814 * @return one KEYPRESS event representing a special key
816 private TInputEvent
csiFnKey() {
818 if (params
.size() > 0) {
819 key
= Integer
.parseInt(params
.get(0));
822 boolean ctrl
= false;
823 boolean shift
= false;
824 if (params
.size() > 1) {
825 shift
= csiIsShift(params
.get(1));
826 alt
= csiIsAlt(params
.get(1));
827 ctrl
= csiIsCtrl(params
.get(1));
832 return new TKeypressEvent(kbHome
, alt
, ctrl
, shift
);
834 return new TKeypressEvent(kbIns
, alt
, ctrl
, shift
);
836 return new TKeypressEvent(kbDel
, alt
, ctrl
, shift
);
838 return new TKeypressEvent(kbEnd
, alt
, ctrl
, shift
);
840 return new TKeypressEvent(kbPgUp
, alt
, ctrl
, shift
);
842 return new TKeypressEvent(kbPgDn
, alt
, ctrl
, shift
);
844 return new TKeypressEvent(kbF5
, alt
, ctrl
, shift
);
846 return new TKeypressEvent(kbF6
, alt
, ctrl
, shift
);
848 return new TKeypressEvent(kbF7
, alt
, ctrl
, shift
);
850 return new TKeypressEvent(kbF8
, alt
, ctrl
, shift
);
852 return new TKeypressEvent(kbF9
, alt
, ctrl
, shift
);
854 return new TKeypressEvent(kbF10
, alt
, ctrl
, shift
);
856 return new TKeypressEvent(kbF11
, alt
, ctrl
, shift
);
858 return new TKeypressEvent(kbF12
, alt
, ctrl
, shift
);
866 * Produce mouse events based on "Any event tracking" and UTF-8
868 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
870 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
872 private TInputEvent
parseMouse() {
873 int buttons
= params
.get(0).charAt(0) - 32;
874 int x
= params
.get(0).charAt(1) - 32 - 1;
875 int y
= params
.get(0).charAt(2) - 32 - 1;
877 // Clamp X and Y to the physical screen coordinates.
878 if (x
>= windowResize
.getWidth()) {
879 x
= windowResize
.getWidth() - 1;
881 if (y
>= windowResize
.getHeight()) {
882 y
= windowResize
.getHeight() - 1;
885 TMouseEvent
.Type eventType
= TMouseEvent
.Type
.MOUSE_DOWN
;
886 boolean eventMouse1
= false;
887 boolean eventMouse2
= false;
888 boolean eventMouse3
= false;
889 boolean eventMouseWheelUp
= false;
890 boolean eventMouseWheelDown
= false;
892 // System.err.printf("buttons: %04x\r\n", buttons);
909 if (!mouse1
&& !mouse2
&& !mouse3
) {
910 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
912 eventType
= TMouseEvent
.Type
.MOUSE_UP
;
929 // Dragging with mouse1 down
932 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
936 // Dragging with mouse2 down
939 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
943 // Dragging with mouse3 down
946 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
950 // Dragging with mouse2 down after wheelUp
953 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
957 // Dragging with mouse2 down after wheelDown
960 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
964 eventMouseWheelUp
= true;
968 eventMouseWheelDown
= true;
972 // Unknown, just make it motion
973 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
976 return new TMouseEvent(eventType
, x
, y
, x
, y
,
977 eventMouse1
, eventMouse2
, eventMouse3
,
978 eventMouseWheelUp
, eventMouseWheelDown
);
982 * Produce mouse events based on "Any event tracking" and SGR
984 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
986 * @param release if true, this was a release ('m')
987 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
989 private TInputEvent
parseMouseSGR(final boolean release
) {
990 // SGR extended coordinates - mode 1006
991 if (params
.size() < 3) {
992 // Invalid position, bail out.
995 int buttons
= Integer
.parseInt(params
.get(0));
996 int x
= Integer
.parseInt(params
.get(1)) - 1;
997 int y
= Integer
.parseInt(params
.get(2)) - 1;
999 // Clamp X and Y to the physical screen coordinates.
1000 if (x
>= windowResize
.getWidth()) {
1001 x
= windowResize
.getWidth() - 1;
1003 if (y
>= windowResize
.getHeight()) {
1004 y
= windowResize
.getHeight() - 1;
1007 TMouseEvent
.Type eventType
= TMouseEvent
.Type
.MOUSE_DOWN
;
1008 boolean eventMouse1
= false;
1009 boolean eventMouse2
= false;
1010 boolean eventMouse3
= false;
1011 boolean eventMouseWheelUp
= false;
1012 boolean eventMouseWheelDown
= false;
1015 eventType
= TMouseEvent
.Type
.MOUSE_UP
;
1029 // Motion only, no buttons down
1030 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1034 // Dragging with mouse1 down
1036 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1040 // Dragging with mouse2 down
1042 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1046 // Dragging with mouse3 down
1048 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1052 // Dragging with mouse2 down after wheelUp
1054 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1058 // Dragging with mouse2 down after wheelDown
1060 eventType
= TMouseEvent
.Type
.MOUSE_MOTION
;
1064 eventMouseWheelUp
= true;
1068 eventMouseWheelDown
= true;
1072 // Unknown, bail out
1075 return new TMouseEvent(eventType
, x
, y
, x
, y
,
1076 eventMouse1
, eventMouse2
, eventMouse3
,
1077 eventMouseWheelUp
, eventMouseWheelDown
);
1081 * Return any events in the IO queue.
1083 * @param queue list to append new events to
1085 public void getEvents(final List
<TInputEvent
> queue
) {
1086 synchronized (eventQueue
) {
1087 if (eventQueue
.size() > 0) {
1088 synchronized (queue
) {
1089 queue
.addAll(eventQueue
);
1097 * Return any events in the IO queue due to timeout.
1099 * @param queue list to append new events to
1101 private void getIdleEvents(final List
<TInputEvent
> queue
) {
1102 long nowTime
= System
.currentTimeMillis();
1104 // Check for new window size
1105 long windowSizeDelay
= nowTime
- windowSizeTime
;
1106 if (windowSizeDelay
> 1000) {
1107 sessionInfo
.queryWindowSize();
1108 int newWidth
= sessionInfo
.getWindowWidth();
1109 int newHeight
= sessionInfo
.getWindowHeight();
1111 if ((newWidth
!= windowResize
.getWidth())
1112 || (newHeight
!= windowResize
.getHeight())
1115 if (debugToStderr
) {
1116 System
.err
.println("Screen size changed, old size " +
1118 System
.err
.println(" new size " +
1119 newWidth
+ " x " + newHeight
);
1122 TResizeEvent event
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
1123 newWidth
, newHeight
);
1124 windowResize
= new TResizeEvent(TResizeEvent
.Type
.SCREEN
,
1125 newWidth
, newHeight
);
1128 windowSizeTime
= nowTime
;
1131 // ESCDELAY type timeout
1132 if (state
== ParseState
.ESCAPE
) {
1133 long escDelay
= nowTime
- escapeTime
;
1134 if (escDelay
> 100) {
1135 // After 0.1 seconds, assume a true escape character
1136 queue
.add(controlChar((char)0x1B, false));
1143 * Returns true if the CSI parameter for a keyboard command means that
1146 private boolean csiIsShift(final String x
) {
1158 * Returns true if the CSI parameter for a keyboard command means that
1161 private boolean csiIsAlt(final String x
) {
1173 * Returns true if the CSI parameter for a keyboard command means that
1176 private boolean csiIsCtrl(final String x
) {
1188 * Parses the next character of input to see if an InputEvent is
1191 * @param events list to append new events to
1192 * @param ch Unicode code point
1194 private void processChar(final List
<TInputEvent
> events
, final char ch
) {
1196 // ESCDELAY type timeout
1197 long nowTime
= System
.currentTimeMillis();
1198 if (state
== ParseState
.ESCAPE
) {
1199 long escDelay
= nowTime
- escapeTime
;
1200 if (escDelay
> 250) {
1201 // After 0.25 seconds, assume a true escape character
1202 events
.add(controlChar((char)0x1B, false));
1208 boolean ctrl
= false;
1209 boolean alt
= false;
1210 boolean shift
= false;
1212 // System.err.printf("state: %s ch %c\r\n", state, ch);
1218 state
= ParseState
.ESCAPE
;
1219 escapeTime
= nowTime
;
1224 // Control character
1225 events
.add(controlChar(ch
, false));
1232 events
.add(new TKeypressEvent(false, 0, ch
,
1233 false, false, false));
1242 // ALT-Control character
1243 events
.add(controlChar(ch
, true));
1249 // This will be one of the function keys
1250 state
= ParseState
.ESCAPE_INTERMEDIATE
;
1254 // '[' goes to CSI_ENTRY
1256 state
= ParseState
.CSI_ENTRY
;
1260 // Everything else is assumed to be Alt-keystroke
1261 if ((ch
>= 'A') && (ch
<= 'Z')) {
1265 events
.add(new TKeypressEvent(false, 0, ch
, alt
, ctrl
, shift
));
1269 case ESCAPE_INTERMEDIATE
:
1270 if ((ch
>= 'P') && (ch
<= 'S')) {
1274 events
.add(new TKeypressEvent(kbF1
));
1277 events
.add(new TKeypressEvent(kbF2
));
1280 events
.add(new TKeypressEvent(kbF3
));
1283 events
.add(new TKeypressEvent(kbF4
));
1292 // Unknown keystroke, ignore
1297 // Numbers - parameter values
1298 if ((ch
>= '0') && (ch
<= '9')) {
1299 params
.set(params
.size() - 1,
1300 params
.get(params
.size() - 1) + ch
);
1301 state
= ParseState
.CSI_PARAM
;
1304 // Parameter separator
1310 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
1314 events
.add(new TKeypressEvent(kbUp
, alt
, ctrl
, shift
));
1319 events
.add(new TKeypressEvent(kbDown
, alt
, ctrl
, shift
));
1324 events
.add(new TKeypressEvent(kbRight
, alt
, ctrl
, shift
));
1329 events
.add(new TKeypressEvent(kbLeft
, alt
, ctrl
, shift
));
1334 events
.add(new TKeypressEvent(kbHome
));
1339 events
.add(new TKeypressEvent(kbEnd
));
1343 // CBT - Cursor backward X tab stops (default 1)
1344 events
.add(new TKeypressEvent(kbBackTab
));
1349 state
= ParseState
.MOUSE
;
1352 // Mouse position, SGR (1006) coordinates
1353 state
= ParseState
.MOUSE_SGR
;
1360 // Unknown keystroke, ignore
1365 // Numbers - parameter values
1366 if ((ch
>= '0') && (ch
<= '9')) {
1367 params
.set(params
.size() - 1,
1368 params
.get(params
.size() - 1) + ch
);
1371 // Parameter separator
1379 // Generate a mouse press event
1380 TInputEvent event
= parseMouseSGR(false);
1381 if (event
!= null) {
1387 // Generate a mouse release event
1388 event
= parseMouseSGR(true);
1389 if (event
!= null) {
1398 // Unknown keystroke, ignore
1403 // Numbers - parameter values
1404 if ((ch
>= '0') && (ch
<= '9')) {
1405 params
.set(params
.size() - 1,
1406 params
.get(params
.size() - 1) + ch
);
1407 state
= ParseState
.CSI_PARAM
;
1410 // Parameter separator
1417 events
.add(csiFnKey());
1422 if ((ch
>= 0x30) && (ch
<= 0x7E)) {
1426 if (params
.size() > 1) {
1427 shift
= csiIsShift(params
.get(1));
1428 alt
= csiIsAlt(params
.get(1));
1429 ctrl
= csiIsCtrl(params
.get(1));
1431 events
.add(new TKeypressEvent(kbUp
, alt
, ctrl
, shift
));
1436 if (params
.size() > 1) {
1437 shift
= csiIsShift(params
.get(1));
1438 alt
= csiIsAlt(params
.get(1));
1439 ctrl
= csiIsCtrl(params
.get(1));
1441 events
.add(new TKeypressEvent(kbDown
, alt
, ctrl
, shift
));
1446 if (params
.size() > 1) {
1447 shift
= csiIsShift(params
.get(1));
1448 alt
= csiIsAlt(params
.get(1));
1449 ctrl
= csiIsCtrl(params
.get(1));
1451 events
.add(new TKeypressEvent(kbRight
, alt
, ctrl
, shift
));
1456 if (params
.size() > 1) {
1457 shift
= csiIsShift(params
.get(1));
1458 alt
= csiIsAlt(params
.get(1));
1459 ctrl
= csiIsCtrl(params
.get(1));
1461 events
.add(new TKeypressEvent(kbLeft
, alt
, ctrl
, shift
));
1466 if (params
.size() > 1) {
1467 shift
= csiIsShift(params
.get(1));
1468 alt
= csiIsAlt(params
.get(1));
1469 ctrl
= csiIsCtrl(params
.get(1));
1471 events
.add(new TKeypressEvent(kbHome
, alt
, ctrl
, shift
));
1476 if (params
.size() > 1) {
1477 shift
= csiIsShift(params
.get(1));
1478 alt
= csiIsAlt(params
.get(1));
1479 ctrl
= csiIsCtrl(params
.get(1));
1481 events
.add(new TKeypressEvent(kbEnd
, alt
, ctrl
, shift
));
1489 // Unknown keystroke, ignore
1494 params
.set(0, params
.get(params
.size() - 1) + ch
);
1495 if (params
.get(0).length() == 3) {
1496 // We have enough to generate a mouse event
1497 events
.add(parseMouse());
1506 // This "should" be impossible to reach
1511 * Tell (u)xterm that we want alt- keystrokes to send escape + character
1512 * rather than set the 8th bit. Anyone who wants UTF8 should want this
1515 * @param on if true, enable metaSendsEscape
1516 * @return the string to emit to xterm
1518 private String
xtermMetaSendsEscape(final boolean on
) {
1520 return "\033[?1036h\033[?1034l";
1522 return "\033[?1036l";
1526 * Create an xterm OSC sequence to change the window title.
1528 * @param title the new title
1529 * @return the string to emit to xterm
1531 private String
getSetTitleString(final String title
) {
1532 return "\033]2;" + title
+ "\007";
1536 * Create a SGR parameter sequence for a single color change.
1538 * @param bold if true, set bold
1539 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1540 * @param foreground if true, this is a foreground color
1541 * @return the string to emit to an ANSI / ECMA-style terminal,
1544 private String
color(final boolean bold
, final Color color
,
1545 final boolean foreground
) {
1546 return color(color
, foreground
, true) +
1547 rgbColor(bold
, color
, foreground
);
1551 * Create a T.416 RGB parameter sequence for a single color change.
1553 * @param bold if true, set bold
1554 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1555 * @param foreground if true, this is a foreground color
1556 * @return the string to emit to an xterm terminal with RGB support,
1557 * e.g. "\033[38;2;RR;GG;BBm"
1559 private String
rgbColor(final boolean bold
, final Color color
,
1560 final boolean foreground
) {
1561 if (doRgbColor
== false) {
1564 StringBuilder sb
= new StringBuilder("\033[");
1566 // Bold implies foreground only
1568 if (color
.equals(Color
.BLACK
)) {
1569 sb
.append("84;84;84");
1570 } else if (color
.equals(Color
.RED
)) {
1571 sb
.append("252;84;84");
1572 } else if (color
.equals(Color
.GREEN
)) {
1573 sb
.append("84;252;84");
1574 } else if (color
.equals(Color
.YELLOW
)) {
1575 sb
.append("252;252;84");
1576 } else if (color
.equals(Color
.BLUE
)) {
1577 sb
.append("84;84;252");
1578 } else if (color
.equals(Color
.MAGENTA
)) {
1579 sb
.append("252;84;252");
1580 } else if (color
.equals(Color
.CYAN
)) {
1581 sb
.append("84;252;252");
1582 } else if (color
.equals(Color
.WHITE
)) {
1583 sb
.append("252;252;252");
1591 if (color
.equals(Color
.BLACK
)) {
1593 } else if (color
.equals(Color
.RED
)) {
1594 sb
.append("168;0;0");
1595 } else if (color
.equals(Color
.GREEN
)) {
1596 sb
.append("0;168;0");
1597 } else if (color
.equals(Color
.YELLOW
)) {
1598 sb
.append("168;84;0");
1599 } else if (color
.equals(Color
.BLUE
)) {
1600 sb
.append("0;0;168");
1601 } else if (color
.equals(Color
.MAGENTA
)) {
1602 sb
.append("168;0;168");
1603 } else if (color
.equals(Color
.CYAN
)) {
1604 sb
.append("0;168;168");
1605 } else if (color
.equals(Color
.WHITE
)) {
1606 sb
.append("168;168;168");
1610 return sb
.toString();
1614 * Create a T.416 RGB parameter sequence for both foreground and
1615 * background color change.
1617 * @param bold if true, set bold
1618 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1619 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1620 * @return the string to emit to an xterm terminal with RGB support,
1621 * e.g. "\033[38;2;RR;GG;BB;48;2;RR;GG;BBm"
1623 private String
rgbColor(final boolean bold
, final Color foreColor
,
1624 final Color backColor
) {
1625 if (doRgbColor
== false) {
1629 return rgbColor(bold
, foreColor
, true) +
1630 rgbColor(false, backColor
, false);
1634 * Create a SGR parameter sequence for a single color change.
1636 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1637 * @param foreground if true, this is a foreground color
1638 * @param header if true, make the full header, otherwise just emit the
1639 * color parameter e.g. "42;"
1640 * @return the string to emit to an ANSI / ECMA-style terminal,
1643 private String
color(final Color color
, final boolean foreground
,
1644 final boolean header
) {
1646 int ecmaColor
= color
.getValue();
1648 // Convert Color.* values to SGR numerics
1656 return String
.format("\033[%dm", ecmaColor
);
1658 return String
.format("%d;", ecmaColor
);
1663 * Create a SGR parameter sequence for both foreground and background
1666 * @param bold if true, set bold
1667 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1668 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1669 * @return the string to emit to an ANSI / ECMA-style terminal,
1670 * e.g. "\033[31;42m"
1672 private String
color(final boolean bold
, final Color foreColor
,
1673 final Color backColor
) {
1674 return color(foreColor
, backColor
, true) +
1675 rgbColor(bold
, foreColor
, backColor
);
1679 * Create a SGR parameter sequence for both foreground and
1680 * background color change.
1682 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1683 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1684 * @param header if true, make the full header, otherwise just emit the
1685 * color parameter e.g. "31;42;"
1686 * @return the string to emit to an ANSI / ECMA-style terminal,
1687 * e.g. "\033[31;42m"
1689 private String
color(final Color foreColor
, final Color backColor
,
1690 final boolean header
) {
1692 int ecmaForeColor
= foreColor
.getValue();
1693 int ecmaBackColor
= backColor
.getValue();
1695 // Convert Color.* values to SGR numerics
1696 ecmaBackColor
+= 40;
1697 ecmaForeColor
+= 30;
1700 return String
.format("\033[%d;%dm", ecmaForeColor
, ecmaBackColor
);
1702 return String
.format("%d;%d;", ecmaForeColor
, ecmaBackColor
);
1707 * Create a SGR parameter sequence for foreground, background, and
1708 * several attributes. This sequence first resets all attributes to
1709 * default, then sets attributes as per the parameters.
1711 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1712 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1713 * @param bold if true, set bold
1714 * @param reverse if true, set reverse
1715 * @param blink if true, set blink
1716 * @param underline if true, set underline
1717 * @return the string to emit to an ANSI / ECMA-style terminal,
1718 * e.g. "\033[0;1;31;42m"
1720 private String
color(final Color foreColor
, final Color backColor
,
1721 final boolean bold
, final boolean reverse
, final boolean blink
,
1722 final boolean underline
) {
1724 int ecmaForeColor
= foreColor
.getValue();
1725 int ecmaBackColor
= backColor
.getValue();
1727 // Convert Color.* values to SGR numerics
1728 ecmaBackColor
+= 40;
1729 ecmaForeColor
+= 30;
1731 StringBuilder sb
= new StringBuilder();
1732 if ( bold
&& reverse
&& blink
&& !underline
) {
1733 sb
.append("\033[0;1;7;5;");
1734 } else if ( bold
&& reverse
&& !blink
&& !underline
) {
1735 sb
.append("\033[0;1;7;");
1736 } else if ( !bold
&& reverse
&& blink
&& !underline
) {
1737 sb
.append("\033[0;7;5;");
1738 } else if ( bold
&& !reverse
&& blink
&& !underline
) {
1739 sb
.append("\033[0;1;5;");
1740 } else if ( bold
&& !reverse
&& !blink
&& !underline
) {
1741 sb
.append("\033[0;1;");
1742 } else if ( !bold
&& reverse
&& !blink
&& !underline
) {
1743 sb
.append("\033[0;7;");
1744 } else if ( !bold
&& !reverse
&& blink
&& !underline
) {
1745 sb
.append("\033[0;5;");
1746 } else if ( bold
&& reverse
&& blink
&& underline
) {
1747 sb
.append("\033[0;1;7;5;4;");
1748 } else if ( bold
&& reverse
&& !blink
&& underline
) {
1749 sb
.append("\033[0;1;7;4;");
1750 } else if ( !bold
&& reverse
&& blink
&& underline
) {
1751 sb
.append("\033[0;7;5;4;");
1752 } else if ( bold
&& !reverse
&& blink
&& underline
) {
1753 sb
.append("\033[0;1;5;4;");
1754 } else if ( bold
&& !reverse
&& !blink
&& underline
) {
1755 sb
.append("\033[0;1;4;");
1756 } else if ( !bold
&& reverse
&& !blink
&& underline
) {
1757 sb
.append("\033[0;7;4;");
1758 } else if ( !bold
&& !reverse
&& blink
&& underline
) {
1759 sb
.append("\033[0;5;4;");
1760 } else if ( !bold
&& !reverse
&& !blink
&& underline
) {
1761 sb
.append("\033[0;4;");
1763 assert (!bold
&& !reverse
&& !blink
&& !underline
);
1764 sb
.append("\033[0;");
1766 sb
.append(String
.format("%d;%dm", ecmaForeColor
, ecmaBackColor
));
1767 sb
.append(rgbColor(bold
, foreColor
, backColor
));
1768 return sb
.toString();
1772 * Create a SGR parameter sequence to reset to defaults.
1774 * @return the string to emit to an ANSI / ECMA-style terminal,
1777 private String
normal() {
1778 return normal(true) + rgbColor(false, Color
.WHITE
, Color
.BLACK
);
1782 * Create a SGR parameter sequence to reset to defaults.
1784 * @param header if true, make the full header, otherwise just emit the
1785 * bare parameter e.g. "0;"
1786 * @return the string to emit to an ANSI / ECMA-style terminal,
1789 private String
normal(final boolean header
) {
1791 return "\033[0;37;40m";
1797 * Create a SGR parameter sequence for enabling the visible cursor.
1799 * @param on if true, turn on cursor
1800 * @return the string to emit to an ANSI / ECMA-style terminal
1802 private String
cursor(final boolean on
) {
1803 if (on
&& !cursorOn
) {
1807 if (!on
&& cursorOn
) {
1815 * Clear the entire screen. Because some terminals use back-color-erase,
1816 * set the color to white-on-black beforehand.
1818 * @return the string to emit to an ANSI / ECMA-style terminal
1820 private String
clearAll() {
1821 return "\033[0;37;40m\033[2J";
1825 * Clear the line from the cursor (inclusive) to the end of the screen.
1826 * Because some terminals use back-color-erase, set the color to
1827 * white-on-black beforehand.
1829 * @return the string to emit to an ANSI / ECMA-style terminal
1831 private String
clearRemainingLine() {
1832 return "\033[0;37;40m\033[K";
1836 * Move the cursor to (x, y).
1838 * @param x column coordinate. 0 is the left-most column.
1839 * @param y row coordinate. 0 is the top-most row.
1840 * @return the string to emit to an ANSI / ECMA-style terminal
1842 private String
gotoXY(final int x
, final int y
) {
1843 return String
.format("\033[%d;%dH", y
+ 1, x
+ 1);
1847 * Tell (u)xterm that we want to receive mouse events based on "Any event
1848 * tracking", UTF-8 coordinates, and then SGR coordinates. Ideally we
1849 * will end up with SGR coordinates with UTF-8 coordinates as a fallback.
1851 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1853 * Note that this also sets the alternate/primary screen buffer.
1855 * @param on If true, enable mouse report and use the alternate screen
1856 * buffer. If false disable mouse reporting and use the primary screen
1858 * @return the string to emit to xterm
1860 private String
mouse(final boolean on
) {
1862 return "\033[?1002;1003;1005;1006h\033[?1049h";
1864 return "\033[?1002;1003;1006;1005l\033[?1049l";
1868 * Read function runs on a separate thread.
1871 boolean done
= false;
1872 // available() will often return > 1, so we need to read in chunks to
1874 char [] readBuffer
= new char[128];
1875 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
1877 while (!done
&& !stopReaderThread
) {
1879 // We assume that if inputStream has bytes available, then
1880 // input won't block on read().
1881 int n
= inputStream
.available();
1884 System.err.printf("inputStream.available(): %d\n", n);
1889 if (readBuffer
.length
< n
) {
1890 // The buffer wasn't big enough, make it huger
1891 readBuffer
= new char[readBuffer
.length
* 2];
1894 // System.err.printf("BEFORE read()\n"); System.err.flush();
1896 int rc
= input
.read(readBuffer
, 0, readBuffer
.length
);
1899 System.err.printf("AFTER read() %d\n", rc);
1907 for (int i
= 0; i
< rc
; i
++) {
1908 int ch
= readBuffer
[i
];
1909 processChar(events
, (char)ch
);
1911 getIdleEvents(events
);
1912 if (events
.size() > 0) {
1913 // Add to the queue for the backend thread to
1914 // be able to obtain.
1915 synchronized (eventQueue
) {
1916 eventQueue
.addAll(events
);
1918 if (listener
!= null) {
1919 synchronized (listener
) {
1920 listener
.notifyAll();
1927 getIdleEvents(events
);
1928 if (events
.size() > 0) {
1929 synchronized (eventQueue
) {
1930 eventQueue
.addAll(events
);
1932 if (listener
!= null) {
1933 synchronized (listener
) {
1934 listener
.notifyAll();
1940 // Wait 20 millis for more data
1943 // System.err.println("end while loop"); System.err.flush();
1944 } catch (InterruptedException e
) {
1946 } catch (IOException e
) {
1947 e
.printStackTrace();
1950 } // while ((done == false) && (stopReaderThread == false))
1951 // System.err.println("*** run() exiting..."); System.err.flush();