HELLO WORLD...
[nikiroo-utils.git] / src / jexer / io / ECMA48Terminal.java
CommitLineData
b1589621
KL
1/**
2 * Jexer - Java Text User Interface
3 *
4 * Version: $Id$
5 *
6 * Author: Kevin Lamonte, <a href="mailto:kevin.lamonte@gmail.com">kevin.lamonte@gmail.com</a>
7 *
8 * License: LGPLv3 or later
9 *
10 * Copyright: This module is licensed under the GNU Lesser General
11 * Public License Version 3. Please see the file "COPYING" in this
12 * directory for more information about the GNU Lesser General Public
13 * License Version 3.
14 *
15 * Copyright (C) 2015 Kevin Lamonte
16 *
17 * This program is free software; you can redistribute it and/or
18 * modify it under the terms of the GNU Lesser General Public License
19 * as published by the Free Software Foundation; either version 3 of
20 * the License, or (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful, but
23 * WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 * General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this program; if not, see
29 * http://www.gnu.org/licenses/, or write to the Free Software
30 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31 * 02110-1301 USA
32 */
33package jexer.io;
34
35import java.io.BufferedReader;
4328bb42
KL
36import java.io.FileDescriptor;
37import java.io.FileInputStream;
b1589621
KL
38import java.io.InputStream;
39import java.io.InputStreamReader;
40import java.io.IOException;
41import java.io.OutputStream;
42import java.io.OutputStreamWriter;
43import java.io.PrintWriter;
44import java.io.Reader;
45import java.io.UnsupportedEncodingException;
46import java.util.ArrayList;
47import java.util.Date;
48import java.util.List;
49import java.util.LinkedList;
50
51import jexer.TKeypress;
52import jexer.bits.Color;
53import jexer.event.TInputEvent;
54import jexer.event.TKeypressEvent;
55import jexer.event.TMouseEvent;
56import jexer.event.TResizeEvent;
57import jexer.session.SessionInfo;
58import jexer.session.TSessionInfo;
59import jexer.session.TTYSessionInfo;
60import static jexer.TKeypress.*;
61
62/**
63 * This class has convenience methods for emitting output to ANSI
64 * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys,
65 * etc.
66 */
4328bb42 67public class ECMA48Terminal implements Runnable {
b1589621
KL
68
69 /**
70 * The session information
71 */
72 public SessionInfo session;
73
4328bb42
KL
74 /**
75 * The event queue, filled up by a thread reading on input
76 */
77 private List<TInputEvent> eventQueue;
78
79 /**
80 * If true, we want the reader thread to exit gracefully.
81 */
82 private boolean stopReaderThread;
83
84 /**
85 * The reader thread
86 */
87 private Thread readerThread;
88
b1589621
KL
89 /**
90 * Parameters being collected. E.g. if the string is \033[1;3m, then
91 * params[0] will be 1 and params[1] will be 3.
92 */
93 private ArrayList<String> params;
94
95 /**
96 * params[paramI] is being appended to.
97 */
98 private int paramI;
99
100 /**
101 * States in the input parser
102 */
103 private enum ParseState {
104 GROUND,
105 ESCAPE,
106 ESCAPE_INTERMEDIATE,
107 CSI_ENTRY,
108 CSI_PARAM,
109 // CSI_INTERMEDIATE,
110 MOUSE
111 }
112
113 /**
114 * Current parsing state
115 */
116 private ParseState state;
117
118 /**
119 * The time we entered ESCAPE. If we get a bare escape
120 * without a code following it, this is used to return that bare
121 * escape.
122 */
123 private long escapeTime;
124
125 /**
126 * true if mouse1 was down. Used to report mouse1 on the release event.
127 */
128 private boolean mouse1;
129
130 /**
131 * true if mouse2 was down. Used to report mouse2 on the release event.
132 */
133 private boolean mouse2;
134
135 /**
136 * true if mouse3 was down. Used to report mouse3 on the release event.
137 */
138 private boolean mouse3;
139
140 /**
141 * Cache the cursor visibility value so we only emit the sequence when we
142 * need to.
143 */
144 private boolean cursorOn = true;
145
146 /**
147 * Cache the last window size to figure out if a TResizeEvent needs to be
148 * generated.
149 */
150 private TResizeEvent windowResize = null;
151
152 /**
153 * If true, then we changed System.in and need to change it back.
154 */
155 private boolean setRawMode;
156
157 /**
158 * The terminal's input. If an InputStream is not specified in the
4328bb42
KL
159 * constructor, then this InputStreamReader will be bound to System.in
160 * with UTF-8 encoding.
b1589621
KL
161 */
162 private Reader input;
163
4328bb42
KL
164 /**
165 * The terminal's raw InputStream. If an InputStream is not specified in
166 * the constructor, then this InputReader will be bound to System.in.
167 * This is used by run() to see if bytes are available() before calling
168 * (Reader)input.read().
169 */
170 private InputStream inputStream;
171
b1589621
KL
172 /**
173 * The terminal's output. If an OutputStream is not specified in the
174 * constructor, then this PrintWriter will be bound to System.out with
175 * UTF-8 encoding.
176 */
177 private PrintWriter output;
178
179 /**
180 * When true, the terminal is sending non-UTF8 bytes when reporting mouse
181 * events.
182 *
183 * TODO: Add broken mouse detection back into the reader.
184 */
185 private boolean brokenTerminalUTFMouse = false;
186
217c6107
KL
187 /**
188 * Get the output writer.
189 *
190 * @return the Writer
191 */
192 public PrintWriter getOutput() {
193 return output;
194 }
195
b1589621
KL
196 /**
197 * Call 'stty cooked' to set cooked mode.
198 */
199 private void sttyCooked() {
200 doStty(false);
201 }
202
203 /**
204 * Call 'stty raw' to set raw mode.
205 */
206 private void sttyRaw() {
207 doStty(true);
208 }
209
210 /**
211 * Call 'stty' to set raw or cooked mode.
212 *
213 * @param mode if true, set raw mode, otherwise set cooked mode
214 */
215 private void doStty(boolean mode) {
216 String [] cmdRaw = {
4328bb42 217 "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
b1589621
KL
218 };
219 String [] cmdCooked = {
4328bb42 220 "/bin/sh", "-c", "stty sane cooked < /dev/tty"
b1589621
KL
221 };
222 try {
b1589621
KL
223 Process process;
224 if (mode == true) {
225 process = Runtime.getRuntime().exec(cmdRaw);
226 } else {
227 process = Runtime.getRuntime().exec(cmdCooked);
228 }
229 BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
230 String line = in.readLine();
231 if ((line != null) && (line.length() > 0)) {
232 System.err.println("WEIRD?! Normal output from stty: " + line);
233 }
234 while (true) {
235 BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
236 line = err.readLine();
237 if ((line != null) && (line.length() > 0)) {
238 System.err.println("Error output from stty: " + line);
239 }
240 try{
241 process.waitFor();
242 break;
243 } catch (InterruptedException e) {
244 e.printStackTrace();
245 }
246 }
247 int rc = process.exitValue();
248 if (rc != 0) {
249 System.err.println("stty returned error code: " + rc);
250 }
251 } catch (IOException e) {
252 e.printStackTrace();
253 }
254 }
255
256 /**
257 * Constructor sets up state for getEvent()
258 *
259 * @param input an InputStream connected to the remote user, or null for
260 * System.in. If System.in is used, then on non-Windows systems it will
261 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
262 * mode. input is always converted to a Reader with UTF-8 encoding.
263 * @param output an OutputStream connected to the remote user, or null
264 * for System.out. output is always converted to a Writer with UTF-8
265 * encoding.
266 */
267 public ECMA48Terminal(InputStream input, OutputStream output) throws UnsupportedEncodingException {
268
269 reset();
4328bb42
KL
270 mouse1 = false;
271 mouse2 = false;
272 mouse3 = false;
273 stopReaderThread = false;
b1589621
KL
274
275 if (input == null) {
4328bb42
KL
276 // inputStream = System.in;
277 inputStream = new FileInputStream(FileDescriptor.in);
b1589621
KL
278 sttyRaw();
279 setRawMode = true;
280 } else {
4328bb42 281 inputStream = input;
b1589621 282 }
4328bb42
KL
283 this.input = new InputStreamReader(inputStream, "UTF-8");
284
b1589621
KL
285 // TODO: include TelnetSocket from NIB and have it implement
286 // SessionInfo
287 if (input instanceof SessionInfo) {
288 session = (SessionInfo)input;
289 }
290 if (session == null) {
291 if (input == null) {
292 // Reading right off the tty
293 session = new TTYSessionInfo();
294 } else {
295 session = new TSessionInfo();
296 }
297 }
298
299 if (output == null) {
300 this.output = new PrintWriter(new OutputStreamWriter(System.out,
301 "UTF-8"));
302 } else {
303 this.output = new PrintWriter(new OutputStreamWriter(output,
304 "UTF-8"));
305 }
306
307 // Enable mouse reporting and metaSendsEscape
308 this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
309
310 // Hang onto the window size
311 windowResize = new TResizeEvent(TResizeEvent.Type.Screen,
312 session.getWindowWidth(), session.getWindowHeight());
4328bb42
KL
313
314 // Spin up the input reader
315 eventQueue = new LinkedList<TInputEvent>();
316 readerThread = new Thread(this);
317 readerThread.start();
b1589621
KL
318 }
319
320 /**
321 * Restore terminal to normal state
322 */
323 public void shutdown() {
4328bb42
KL
324
325 // System.err.println("=== shutdown() ==="); System.err.flush();
326
327 // Tell the reader thread to stop looking at input
328 stopReaderThread = true;
329 try {
330 readerThread.join();
331 } catch (InterruptedException e) {
332 e.printStackTrace();
333 }
334
335 // Disable mouse reporting and show cursor
336 output.printf("%s%s%s", mouse(false), cursor(true), normal());
337 output.flush();
338
b1589621
KL
339 if (setRawMode) {
340 sttyCooked();
341 setRawMode = false;
4328bb42
KL
342 // We don't close System.in/out
343 } else {
344 // Shut down the streams, this should wake up the reader thread
345 // and make it exit.
346 try {
347 if (input != null) {
348 input.close();
349 input = null;
350 }
351 if (output != null) {
352 output.close();
353 output = null;
354 }
355 } catch (IOException e) {
356 e.printStackTrace();
357 }
b1589621 358 }
b1589621
KL
359 }
360
361 /**
362 * Flush output
363 */
364 public void flush() {
365 output.flush();
366 }
367
368 /**
369 * Reset keyboard/mouse input parser
370 */
371 private void reset() {
372 state = ParseState.GROUND;
4328bb42 373 params = new ArrayList<String>();
b1589621
KL
374 paramI = 0;
375 params.clear();
376 params.add("");
377 }
378
379 /**
380 * Produce a control character or one of the special ones (ENTER, TAB,
381 * etc.)
382 *
383 * @param ch Unicode code point
384 * @return one KEYPRESS event, either a control character (e.g. isKey ==
385 * false, ch == 'A', ctrl == true), or a special key (e.g. isKey == true,
386 * fnKey == ESC)
387 */
388 private TKeypressEvent controlChar(char ch) {
389 TKeypressEvent event = new TKeypressEvent();
390
391 // System.err.printf("controlChar: %02x\n", ch);
392
393 switch (ch) {
4328bb42
KL
394 case 0x0D:
395 // Carriage return --> ENTER
396 event.key = kbEnter;
397 break;
398 case 0x0A:
399 // Linefeed --> ENTER
b1589621
KL
400 event.key = kbEnter;
401 break;
402 case 0x1B:
403 // ESC
404 event.key = kbEsc;
405 break;
406 case '\t':
407 // TAB
408 event.key = kbTab;
409 break;
410 default:
411 // Make all other control characters come back as the alphabetic
412 // character with the ctrl field set. So SOH would be 'A' +
413 // ctrl.
414 event.key = new TKeypress(false, 0, (char)(ch + 0x40),
415 false, true, false);
416 break;
417 }
418 return event;
419 }
420
421 /**
422 * Produce special key from CSI Pn ; Pm ; ... ~
423 *
424 * @return one KEYPRESS event representing a special key
425 */
426 private TInputEvent csiFnKey() {
427 int key = 0;
428 int modifier = 0;
429 if (params.size() > 0) {
430 key = Integer.parseInt(params.get(0));
431 }
432 if (params.size() > 1) {
433 modifier = Integer.parseInt(params.get(1));
434 }
435 TKeypressEvent event = new TKeypressEvent();
436
437 switch (modifier) {
438 case 0:
439 // No modifier
440 switch (key) {
441 case 1:
442 event.key = kbHome;
443 break;
444 case 2:
445 event.key = kbIns;
446 break;
447 case 3:
448 event.key = kbDel;
449 break;
450 case 4:
451 event.key = kbEnd;
452 break;
453 case 5:
454 event.key = kbPgUp;
455 break;
456 case 6:
457 event.key = kbPgDn;
458 break;
459 case 15:
460 event.key = kbF5;
461 break;
462 case 17:
463 event.key = kbF6;
464 break;
465 case 18:
466 event.key = kbF7;
467 break;
468 case 19:
469 event.key = kbF8;
470 break;
471 case 20:
472 event.key = kbF9;
473 break;
474 case 21:
475 event.key = kbF10;
476 break;
477 case 23:
478 event.key = kbF11;
479 break;
480 case 24:
481 event.key = kbF12;
482 break;
483 default:
484 // Unknown
485 return null;
486 }
487
488 break;
489 case 2:
490 // Shift
491 switch (key) {
492 case 1:
493 event.key = kbShiftHome;
494 break;
495 case 2:
496 event.key = kbShiftIns;
497 break;
498 case 3:
499 event.key = kbShiftDel;
500 break;
501 case 4:
502 event.key = kbShiftEnd;
503 break;
504 case 5:
505 event.key = kbShiftPgUp;
506 break;
507 case 6:
508 event.key = kbShiftPgDn;
509 break;
510 case 15:
511 event.key = kbShiftF5;
512 break;
513 case 17:
514 event.key = kbShiftF6;
515 break;
516 case 18:
517 event.key = kbShiftF7;
518 break;
519 case 19:
520 event.key = kbShiftF8;
521 break;
522 case 20:
523 event.key = kbShiftF9;
524 break;
525 case 21:
526 event.key = kbShiftF10;
527 break;
528 case 23:
529 event.key = kbShiftF11;
530 break;
531 case 24:
532 event.key = kbShiftF12;
533 break;
534 default:
535 // Unknown
536 return null;
537 }
538 break;
539
540 case 3:
541 // Alt
542 switch (key) {
543 case 1:
544 event.key = kbAltHome;
545 break;
546 case 2:
547 event.key = kbAltIns;
548 break;
549 case 3:
550 event.key = kbAltDel;
551 break;
552 case 4:
553 event.key = kbAltEnd;
554 break;
555 case 5:
556 event.key = kbAltPgUp;
557 break;
558 case 6:
559 event.key = kbAltPgDn;
560 break;
561 case 15:
562 event.key = kbAltF5;
563 break;
564 case 17:
565 event.key = kbAltF6;
566 break;
567 case 18:
568 event.key = kbAltF7;
569 break;
570 case 19:
571 event.key = kbAltF8;
572 break;
573 case 20:
574 event.key = kbAltF9;
575 break;
576 case 21:
577 event.key = kbAltF10;
578 break;
579 case 23:
580 event.key = kbAltF11;
581 break;
582 case 24:
583 event.key = kbAltF12;
584 break;
585 default:
586 // Unknown
587 return null;
588 }
589 break;
590
591 case 5:
592 // Ctrl
593 switch (key) {
594 case 1:
595 event.key = kbCtrlHome;
596 break;
597 case 2:
598 event.key = kbCtrlIns;
599 break;
600 case 3:
601 event.key = kbCtrlDel;
602 break;
603 case 4:
604 event.key = kbCtrlEnd;
605 break;
606 case 5:
607 event.key = kbCtrlPgUp;
608 break;
609 case 6:
610 event.key = kbCtrlPgDn;
611 break;
612 case 15:
613 event.key = kbCtrlF5;
614 break;
615 case 17:
616 event.key = kbCtrlF6;
617 break;
618 case 18:
619 event.key = kbCtrlF7;
620 break;
621 case 19:
622 event.key = kbCtrlF8;
623 break;
624 case 20:
625 event.key = kbCtrlF9;
626 break;
627 case 21:
628 event.key = kbCtrlF10;
629 break;
630 case 23:
631 event.key = kbCtrlF11;
632 break;
633 case 24:
634 event.key = kbCtrlF12;
635 break;
636 default:
637 // Unknown
638 return null;
639 }
640 break;
641
642 default:
643 // Unknown
644 return null;
645 }
646
647 // All OK, return a keypress
648 return event;
649 }
650
651 /**
652 * Produce mouse events based on "Any event tracking" and UTF-8
653 * coordinates. See
654 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
655 *
656 * @return a MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN event
657 */
658 private TInputEvent parseMouse() {
659 int buttons = params.get(0).charAt(0) - 32;
660 int x = params.get(0).charAt(1) - 32 - 1;
661 int y = params.get(0).charAt(2) - 32 - 1;
662
663 // Clamp X and Y to the physical screen coordinates.
664 if (x >= windowResize.width) {
665 x = windowResize.width - 1;
666 }
667 if (y >= windowResize.height) {
668 y = windowResize.height - 1;
669 }
670
671 TMouseEvent event = new TMouseEvent(TMouseEvent.Type.MOUSE_DOWN);
672 event.x = x;
673 event.y = y;
674 event.absoluteX = x;
675 event.absoluteY = y;
676
677 // System.err.printf("buttons: %04x\r\n", buttons);
678
679 switch (buttons) {
680 case 0:
681 event.mouse1 = true;
682 mouse1 = true;
683 break;
684 case 1:
685 event.mouse2 = true;
686 mouse2 = true;
687 break;
688 case 2:
689 event.mouse3 = true;
690 mouse3 = true;
691 break;
692 case 3:
693 // Release or Move
694 if (!mouse1 && !mouse2 && !mouse3) {
695 event.type = TMouseEvent.Type.MOUSE_MOTION;
696 } else {
697 event.type = TMouseEvent.Type.MOUSE_UP;
698 }
699 if (mouse1) {
700 mouse1 = false;
701 event.mouse1 = true;
702 }
703 if (mouse2) {
704 mouse2 = false;
705 event.mouse2 = true;
706 }
707 if (mouse3) {
708 mouse3 = false;
709 event.mouse3 = true;
710 }
711 break;
712
713 case 32:
714 // Dragging with mouse1 down
715 event.mouse1 = true;
716 mouse1 = true;
717 event.type = TMouseEvent.Type.MOUSE_MOTION;
718 break;
719
720 case 33:
721 // Dragging with mouse2 down
722 event.mouse2 = true;
723 mouse2 = true;
724 event.type = TMouseEvent.Type.MOUSE_MOTION;
725 break;
726
727 case 34:
728 // Dragging with mouse3 down
729 event.mouse3 = true;
730 mouse3 = true;
731 event.type = TMouseEvent.Type.MOUSE_MOTION;
732 break;
733
734 case 96:
735 // Dragging with mouse2 down after wheelUp
736 event.mouse2 = true;
737 mouse2 = true;
738 event.type = TMouseEvent.Type.MOUSE_MOTION;
739 break;
740
741 case 97:
742 // Dragging with mouse2 down after wheelDown
743 event.mouse2 = true;
744 mouse2 = true;
745 event.type = TMouseEvent.Type.MOUSE_MOTION;
746 break;
747
748 case 64:
749 event.mouseWheelUp = true;
750 break;
751
752 case 65:
753 event.mouseWheelDown = true;
754 break;
755
756 default:
757 // Unknown, just make it motion
758 event.type = TMouseEvent.Type.MOUSE_MOTION;
759 break;
760 }
761 return event;
762 }
763
764 /**
05dbb28d 765 * Return any events in the IO queue.
b1589621 766 *
05dbb28d
KL
767 * @return list of new events (which may be empty)
768 */
769 public List<TInputEvent> getEvents() {
770 List<TInputEvent> events = new LinkedList<TInputEvent>();
4328bb42
KL
771
772 synchronized(this) {
773 if (eventQueue.size() > 0) {
774 events.addAll(eventQueue);
775 eventQueue.clear();
776 }
777 }
778
779 // TEST: drop a cmAbort
780 // events.add(new jexer.event.TCommandEvent(jexer.TCommand.cmAbort));
781 // events.add(new jexer.event.TKeypressEvent(kbAltX));
782
05dbb28d
KL
783 return events;
784 }
785
786 /**
787 * Parses the next character of input to see if an InputEvent is fully
788 * here.
b1589621 789 *
05dbb28d
KL
790 * @param ch Unicode code point
791 * @return list of new events (which may be empty)
b1589621
KL
792 */
793 public List<TInputEvent> getEvents(char ch) {
794 return getEvents(ch, false);
795 }
796
797 /**
798 * Parses the next character of input to see if an InputEvent is
799 * fully here.
800 *
05dbb28d
KL
801 * @param ch Unicode code point
802 * @param noChar if true, ignore ch. This is currently used to return a
803 * bare ESC and RESIZE events.
804 * @return list of new events (which may be empty)
b1589621
KL
805 */
806 public List<TInputEvent> getEvents(char ch, boolean noChar) {
807 List<TInputEvent> events = new LinkedList<TInputEvent>();
808
809 TKeypressEvent keypress;
810 Date now = new Date();
811
4328bb42 812 /*
b1589621
KL
813 // ESCDELAY type timeout
814 if (state == ParseState.ESCAPE) {
815 long escDelay = now.getTime() - escapeTime;
816 if (escDelay > 250) {
817 // After 0.25 seconds, assume a true escape character
818 events.add(controlChar((char)0x1B));
819 reset();
820 }
821 }
4328bb42 822 */
b1589621
KL
823
824 if (noChar == true) {
825 int newWidth = session.getWindowWidth();
826 int newHeight = session.getWindowHeight();
827 if ((newWidth != windowResize.width) ||
828 (newHeight != windowResize.height)) {
829 TResizeEvent event = new TResizeEvent(TResizeEvent.Type.Screen,
830 newWidth, newHeight);
831 windowResize.width = newWidth;
832 windowResize.height = newHeight;
833 events.add(event);
834 }
835
836 // Nothing else to do, bail out
837 return events;
838 }
839
840 // System.err.printf("state: %s ch %c\r\n", state, ch);
841
842 switch (state) {
843 case GROUND:
844
845 if (ch == 0x1B) {
846 state = ParseState.ESCAPE;
847 escapeTime = now.getTime();
848 return events;
849 }
850
851 if (ch <= 0x1F) {
852 // Control character
853 events.add(controlChar(ch));
854 reset();
855 return events;
856 }
857
858 if (ch >= 0x20) {
859 // Normal character
860 keypress = new TKeypressEvent();
861 keypress.key.isKey = false;
862 keypress.key.ch = ch;
863 events.add(keypress);
864 reset();
865 return events;
866 }
867
868 break;
869
870 case ESCAPE:
871 if (ch <= 0x1F) {
872 // ALT-Control character
873 keypress = controlChar(ch);
874 keypress.key.alt = true;
875 events.add(keypress);
876 reset();
877 return events;
878 }
879
880 if (ch == 'O') {
881 // This will be one of the function keys
882 state = ParseState.ESCAPE_INTERMEDIATE;
883 return events;
884 }
885
886 // '[' goes to CSI_ENTRY
887 if (ch == '[') {
888 state = ParseState.CSI_ENTRY;
889 return events;
890 }
891
892 // Everything else is assumed to be Alt-keystroke
893 keypress = new TKeypressEvent();
894 keypress.key.isKey = false;
895 keypress.key.ch = ch;
896 keypress.key.alt = true;
897 if ((ch >= 'A') && (ch <= 'Z')) {
898 keypress.key.shift = true;
899 }
900 events.add(keypress);
901 reset();
902 return events;
903
904 case ESCAPE_INTERMEDIATE:
905 if ((ch >= 'P') && (ch <= 'S')) {
906 // Function key
907 keypress = new TKeypressEvent();
908 keypress.key.isKey = true;
909 switch (ch) {
910 case 'P':
911 keypress.key.fnKey = TKeypress.F1;
912 break;
913 case 'Q':
914 keypress.key.fnKey = TKeypress.F2;
915 break;
916 case 'R':
917 keypress.key.fnKey = TKeypress.F3;
918 break;
919 case 'S':
920 keypress.key.fnKey = TKeypress.F4;
921 break;
922 default:
923 break;
924 }
925 events.add(keypress);
926 reset();
927 return events;
928 }
929
930 // Unknown keystroke, ignore
931 reset();
932 return events;
933
934 case CSI_ENTRY:
935 // Numbers - parameter values
936 if ((ch >= '0') && (ch <= '9')) {
937 params.set(paramI, params.get(paramI) + ch);
938 state = ParseState.CSI_PARAM;
939 return events;
940 }
941 // Parameter separator
942 if (ch == ';') {
943 paramI++;
944 params.set(paramI, "");
945 return events;
946 }
947
948 if ((ch >= 0x30) && (ch <= 0x7E)) {
949 switch (ch) {
950 case 'A':
951 // Up
952 keypress = new TKeypressEvent();
953 keypress.key.isKey = true;
954 keypress.key.fnKey = TKeypress.UP;
955 if (params.size() > 1) {
956 if (params.get(1).equals("2")) {
957 keypress.key.shift = true;
958 }
959 if (params.get(1).equals("5")) {
960 keypress.key.ctrl = true;
961 }
962 if (params.get(1).equals("3")) {
963 keypress.key.alt = true;
964 }
965 }
966 events.add(keypress);
967 reset();
968 return events;
969 case 'B':
970 // Down
971 keypress = new TKeypressEvent();
972 keypress.key.isKey = true;
973 keypress.key.fnKey = TKeypress.DOWN;
974 if (params.size() > 1) {
975 if (params.get(1).equals("2")) {
976 keypress.key.shift = true;
977 }
978 if (params.get(1).equals("5")) {
979 keypress.key.ctrl = true;
980 }
981 if (params.get(1).equals("3")) {
982 keypress.key.alt = true;
983 }
984 }
985 events.add(keypress);
986 reset();
987 return events;
988 case 'C':
989 // Right
990 keypress = new TKeypressEvent();
991 keypress.key.isKey = true;
992 keypress.key.fnKey = TKeypress.RIGHT;
993 if (params.size() > 1) {
994 if (params.get(1).equals("2")) {
995 keypress.key.shift = true;
996 }
997 if (params.get(1).equals("5")) {
998 keypress.key.ctrl = true;
999 }
1000 if (params.get(1).equals("3")) {
1001 keypress.key.alt = true;
1002 }
1003 }
1004 events.add(keypress);
1005 reset();
1006 return events;
1007 case 'D':
1008 // Left
1009 keypress = new TKeypressEvent();
1010 keypress.key.isKey = true;
1011 keypress.key.fnKey = TKeypress.LEFT;
1012 if (params.size() > 1) {
1013 if (params.get(1).equals("2")) {
1014 keypress.key.shift = true;
1015 }
1016 if (params.get(1).equals("5")) {
1017 keypress.key.ctrl = true;
1018 }
1019 if (params.get(1).equals("3")) {
1020 keypress.key.alt = true;
1021 }
1022 }
1023 events.add(keypress);
1024 reset();
1025 return events;
1026 case 'H':
1027 // Home
1028 keypress = new TKeypressEvent();
1029 keypress.key.isKey = true;
1030 keypress.key.fnKey = TKeypress.HOME;
1031 events.add(keypress);
1032 reset();
1033 return events;
1034 case 'F':
1035 // End
1036 keypress = new TKeypressEvent();
1037 keypress.key.isKey = true;
1038 keypress.key.fnKey = TKeypress.END;
1039 events.add(keypress);
1040 reset();
1041 return events;
1042 case 'Z':
1043 // CBT - Cursor backward X tab stops (default 1)
1044 keypress = new TKeypressEvent();
1045 keypress.key.isKey = true;
1046 keypress.key.fnKey = TKeypress.BTAB;
1047 events.add(keypress);
1048 reset();
1049 return events;
1050 case 'M':
1051 // Mouse position
1052 state = ParseState.MOUSE;
1053 return events;
1054 default:
1055 break;
1056 }
1057 }
1058
1059 // Unknown keystroke, ignore
1060 reset();
1061 return events;
1062
1063 case CSI_PARAM:
1064 // Numbers - parameter values
1065 if ((ch >= '0') && (ch <= '9')) {
1066 params.set(paramI, params.get(paramI) + ch);
1067 state = ParseState.CSI_PARAM;
1068 return events;
1069 }
1070 // Parameter separator
1071 if (ch == ';') {
1072 paramI++;
1073 params.set(paramI, "");
1074 return events;
1075 }
1076
1077 if (ch == '~') {
1078 events.add(csiFnKey());
1079 reset();
1080 return events;
1081 }
1082
1083 if ((ch >= 0x30) && (ch <= 0x7E)) {
1084 switch (ch) {
1085 case 'A':
1086 // Up
1087 keypress = new TKeypressEvent();
1088 keypress.key.isKey = true;
1089 keypress.key.fnKey = TKeypress.UP;
1090 if (params.size() > 1) {
1091 if (params.get(1).equals("2")) {
1092 keypress.key.shift = true;
1093 }
1094 if (params.get(1).equals("5")) {
1095 keypress.key.ctrl = true;
1096 }
1097 if (params.get(1).equals("3")) {
1098 keypress.key.alt = true;
1099 }
1100 }
1101 events.add(keypress);
1102 reset();
1103 return events;
1104 case 'B':
1105 // Down
1106 keypress = new TKeypressEvent();
1107 keypress.key.isKey = true;
1108 keypress.key.fnKey = TKeypress.DOWN;
1109 if (params.size() > 1) {
1110 if (params.get(1).equals("2")) {
1111 keypress.key.shift = true;
1112 }
1113 if (params.get(1).equals("5")) {
1114 keypress.key.ctrl = true;
1115 }
1116 if (params.get(1).equals("3")) {
1117 keypress.key.alt = true;
1118 }
1119 }
1120 events.add(keypress);
1121 reset();
1122 return events;
1123 case 'C':
1124 // Right
1125 keypress = new TKeypressEvent();
1126 keypress.key.isKey = true;
1127 keypress.key.fnKey = TKeypress.RIGHT;
1128 if (params.size() > 1) {
1129 if (params.get(1).equals("2")) {
1130 keypress.key.shift = true;
1131 }
1132 if (params.get(1).equals("5")) {
1133 keypress.key.ctrl = true;
1134 }
1135 if (params.get(1).equals("3")) {
1136 keypress.key.alt = true;
1137 }
1138 }
1139 events.add(keypress);
1140 reset();
1141 return events;
1142 case 'D':
1143 // Left
1144 keypress = new TKeypressEvent();
1145 keypress.key.isKey = true;
1146 keypress.key.fnKey = TKeypress.LEFT;
1147 if (params.size() > 1) {
1148 if (params.get(1).equals("2")) {
1149 keypress.key.shift = true;
1150 }
1151 if (params.get(1).equals("5")) {
1152 keypress.key.ctrl = true;
1153 }
1154 if (params.get(1).equals("3")) {
1155 keypress.key.alt = true;
1156 }
1157 }
1158 events.add(keypress);
1159 reset();
1160 return events;
1161 default:
1162 break;
1163 }
1164 }
1165
1166 // Unknown keystroke, ignore
1167 reset();
1168 return events;
1169
1170 case MOUSE:
1171 params.set(0, params.get(paramI) + ch);
1172 if (params.get(0).length() == 3) {
1173 // We have enough to generate a mouse event
1174 events.add(parseMouse());
1175 reset();
1176 }
1177 return events;
1178
1179 default:
1180 break;
1181 }
1182
1183 // This "should" be impossible to reach
1184 return events;
1185 }
1186
1187 /**
05dbb28d
KL
1188 * Tell (u)xterm that we want alt- keystrokes to send escape + character
1189 * rather than set the 8th bit. Anyone who wants UTF8 should want this
1190 * enabled.
b1589621 1191 *
05dbb28d
KL
1192 * @param on if true, enable metaSendsEscape
1193 * @return the string to emit to xterm
b1589621
KL
1194 */
1195 static public String xtermMetaSendsEscape(boolean on) {
1196 if (on) {
1197 return "\033[?1036h\033[?1034l";
1198 }
1199 return "\033[?1036l";
1200 }
1201
1202 /**
05dbb28d
KL
1203 * Convert a list of SGR parameters into a full escape sequence. This
1204 * also eliminates a trailing ';' which would otherwise reset everything
1205 * to white-on-black not-bold.
b1589621 1206 *
05dbb28d
KL
1207 * @param str string of parameters, e.g. "31;1;"
1208 * @return the string to emit to an ANSI / ECMA-style terminal,
1209 * e.g. "\033[31;1m"
b1589621
KL
1210 */
1211 static public String addHeaderSGR(String str) {
1212 if (str.length() > 0) {
1213 // Nix any trailing ';' because that resets all attributes
1214 while (str.endsWith(":")) {
1215 str = str.substring(0, str.length() - 1);
1216 }
1217 }
1218 return "\033[" + str + "m";
1219 }
1220
1221 /**
1222 * Create a SGR parameter sequence for a single color change.
1223 *
05dbb28d
KL
1224 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1225 * @param foreground if true, this is a foreground color
1226 * @return the string to emit to an ANSI / ECMA-style terminal,
1227 * e.g. "\033[42m"
b1589621
KL
1228 */
1229 static public String color(Color color, boolean foreground) {
1230 return color(color, foreground, true);
1231 }
1232
1233 /**
1234 * Create a SGR parameter sequence for a single color change.
1235 *
05dbb28d
KL
1236 * @param color one of the Color.WHITE, Color.BLUE, etc. constants
1237 * @param foreground if true, this is a foreground color
1238 * @param header if true, make the full header, otherwise just emit the
1239 * color parameter e.g. "42;"
1240 * @return the string to emit to an ANSI / ECMA-style terminal,
1241 * e.g. "\033[42m"
b1589621
KL
1242 */
1243 static public String color(Color color, boolean foreground,
1244 boolean header) {
1245
1246 int ecmaColor = color.value;
1247
1248 // Convert Color.* values to SGR numerics
1249 if (foreground == true) {
1250 ecmaColor += 30;
1251 } else {
1252 ecmaColor += 40;
1253 }
1254
1255 if (header) {
1256 return String.format("\033[%dm", ecmaColor);
1257 } else {
1258 return String.format("%d;", ecmaColor);
1259 }
1260 }
1261
1262 /**
1263 * Create a SGR parameter sequence for both foreground and
1264 * background color change.
1265 *
05dbb28d
KL
1266 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1267 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1268 * @return the string to emit to an ANSI / ECMA-style terminal,
1269 * e.g. "\033[31;42m"
b1589621
KL
1270 */
1271 static public String color(Color foreColor, Color backColor) {
1272 return color(foreColor, backColor, true);
1273 }
1274
1275 /**
1276 * Create a SGR parameter sequence for both foreground and
1277 * background color change.
1278 *
05dbb28d
KL
1279 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1280 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1281 * @param header if true, make the full header, otherwise just emit the
1282 * color parameter e.g. "31;42;"
1283 * @return the string to emit to an ANSI / ECMA-style terminal,
1284 * e.g. "\033[31;42m"
b1589621
KL
1285 */
1286 static public String color(Color foreColor, Color backColor,
1287 boolean header) {
1288
1289 int ecmaForeColor = foreColor.value;
1290 int ecmaBackColor = backColor.value;
1291
1292 // Convert Color.* values to SGR numerics
1293 ecmaBackColor += 40;
1294 ecmaForeColor += 30;
1295
1296 if (header) {
1297 return String.format("\033[%d;%dm", ecmaForeColor, ecmaBackColor);
1298 } else {
1299 return String.format("%d;%d;", ecmaForeColor, ecmaBackColor);
1300 }
1301 }
1302
1303 /**
1304 * Create a SGR parameter sequence for foreground, background, and
05dbb28d
KL
1305 * several attributes. This sequence first resets all attributes to
1306 * default, then sets attributes as per the parameters.
b1589621 1307 *
05dbb28d
KL
1308 * @param foreColor one of the Color.WHITE, Color.BLUE, etc. constants
1309 * @param backColor one of the Color.WHITE, Color.BLUE, etc. constants
1310 * @param bold if true, set bold
1311 * @param reverse if true, set reverse
1312 * @param blink if true, set blink
1313 * @param underline if true, set underline
1314 * @return the string to emit to an ANSI / ECMA-style terminal,
1315 * e.g. "\033[0;1;31;42m"
b1589621
KL
1316 */
1317 static public String color(Color foreColor, Color backColor, boolean bold,
1318 boolean reverse, boolean blink, boolean underline) {
1319
1320 int ecmaForeColor = foreColor.value;
1321 int ecmaBackColor = backColor.value;
1322
1323 // Convert Color.* values to SGR numerics
1324 ecmaBackColor += 40;
1325 ecmaForeColor += 30;
1326
1327 StringBuilder sb = new StringBuilder();
1328 if ( bold && reverse && blink && !underline ) {
1329 sb.append("\033[0;1;7;5;");
1330 } else if ( bold && reverse && !blink && !underline ) {
1331 sb.append("\033[0;1;7;");
1332 } else if ( !bold && reverse && blink && !underline ) {
1333 sb.append("\033[0;7;5;");
1334 } else if ( bold && !reverse && blink && !underline ) {
1335 sb.append("\033[0;1;5;");
1336 } else if ( bold && !reverse && !blink && !underline ) {
1337 sb.append("\033[0;1;");
1338 } else if ( !bold && reverse && !blink && !underline ) {
1339 sb.append("\033[0;7;");
1340 } else if ( !bold && !reverse && blink && !underline) {
1341 sb.append("\033[0;5;");
1342 } else if ( bold && reverse && blink && underline ) {
1343 sb.append("\033[0;1;7;5;4;");
1344 } else if ( bold && reverse && !blink && underline ) {
1345 sb.append("\033[0;1;7;4;");
1346 } else if ( !bold && reverse && blink && underline ) {
1347 sb.append("\033[0;7;5;4;");
1348 } else if ( bold && !reverse && blink && underline ) {
1349 sb.append("\033[0;1;5;4;");
1350 } else if ( bold && !reverse && !blink && underline ) {
1351 sb.append("\033[0;1;4;");
1352 } else if ( !bold && reverse && !blink && underline ) {
1353 sb.append("\033[0;7;4;");
1354 } else if ( !bold && !reverse && blink && underline) {
1355 sb.append("\033[0;5;4;");
1356 } else if ( !bold && !reverse && !blink && underline) {
1357 sb.append("\033[0;4;");
1358 } else {
1359 assert(!bold && !reverse && !blink && !underline);
1360 sb.append("\033[0;");
1361 }
1362 sb.append(String.format("%d;%dm", ecmaForeColor, ecmaBackColor));
1363 return sb.toString();
1364 }
1365
1366 /**
1367 * Create a SGR parameter sequence for enabling reverse color.
1368 *
05dbb28d
KL
1369 * @param on if true, turn on reverse
1370 * @return the string to emit to an ANSI / ECMA-style terminal,
1371 * e.g. "\033[7m"
b1589621
KL
1372 */
1373 static public String reverse(boolean on) {
1374 if (on) {
1375 return "\033[7m";
1376 }
1377 return "\033[27m";
1378 }
1379
1380 /**
1381 * Create a SGR parameter sequence to reset to defaults.
1382 *
05dbb28d
KL
1383 * @return the string to emit to an ANSI / ECMA-style terminal,
1384 * e.g. "\033[0m"
b1589621
KL
1385 */
1386 static public String normal() {
1387 return normal(true);
1388 }
1389
1390 /**
1391 * Create a SGR parameter sequence to reset to defaults.
1392 *
05dbb28d
KL
1393 * @param header if true, make the full header, otherwise just emit the
1394 * bare parameter e.g. "0;"
1395 * @return the string to emit to an ANSI / ECMA-style terminal,
1396 * e.g. "\033[0m"
b1589621
KL
1397 */
1398 static public String normal(boolean header) {
1399 if (header) {
1400 return "\033[0;37;40m";
1401 }
1402 return "0;37;40";
1403 }
1404
1405 /**
1406 * Create a SGR parameter sequence for enabling boldface.
1407 *
05dbb28d
KL
1408 * @param on if true, turn on bold
1409 * @return the string to emit to an ANSI / ECMA-style terminal,
1410 * e.g. "\033[1m"
b1589621
KL
1411 */
1412 static public String bold(boolean on) {
1413 return bold(on, true);
1414 }
1415
1416 /**
1417 * Create a SGR parameter sequence for enabling boldface.
1418 *
05dbb28d
KL
1419 * @param on if true, turn on bold
1420 * @param header if true, make the full header, otherwise just emit the
1421 * bare parameter e.g. "1;"
1422 * @return the string to emit to an ANSI / ECMA-style terminal,
1423 * e.g. "\033[1m"
b1589621
KL
1424 */
1425 static public String bold(boolean on, boolean header) {
1426 if (header) {
1427 if (on) {
1428 return "\033[1m";
1429 }
1430 return "\033[22m";
1431 }
1432 if (on) {
1433 return "1;";
1434 }
1435 return "22;";
1436 }
1437
1438 /**
1439 * Create a SGR parameter sequence for enabling blinking text.
1440 *
05dbb28d
KL
1441 * @param on if true, turn on blink
1442 * @return the string to emit to an ANSI / ECMA-style terminal,
1443 * e.g. "\033[5m"
b1589621
KL
1444 */
1445 static public String blink(boolean on) {
1446 return blink(on, true);
1447 }
1448
1449 /**
1450 * Create a SGR parameter sequence for enabling blinking text.
1451 *
05dbb28d
KL
1452 * @param on if true, turn on blink
1453 * @param header if true, make the full header, otherwise just emit the
1454 * bare parameter e.g. "5;"
1455 * @return the string to emit to an ANSI / ECMA-style terminal,
1456 * e.g. "\033[5m"
b1589621
KL
1457 */
1458 static public String blink(boolean on, boolean header) {
1459 if (header) {
1460 if (on) {
1461 return "\033[5m";
1462 }
1463 return "\033[25m";
1464 }
1465 if (on) {
1466 return "5;";
1467 }
1468 return "25;";
1469 }
1470
1471 /**
05dbb28d
KL
1472 * Create a SGR parameter sequence for enabling underline / underscored
1473 * text.
b1589621 1474 *
05dbb28d
KL
1475 * @param on if true, turn on underline
1476 * @return the string to emit to an ANSI / ECMA-style terminal,
1477 * e.g. "\033[4m"
b1589621
KL
1478 */
1479 static public String underline(boolean on) {
1480 if (on) {
1481 return "\033[4m";
1482 }
1483 return "\033[24m";
1484 }
1485
1486 /**
1487 * Create a SGR parameter sequence for enabling the visible cursor.
1488 *
05dbb28d
KL
1489 * @param on if true, turn on cursor
1490 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1491 */
1492 public String cursor(boolean on) {
1493 if (on && (cursorOn == false)) {
1494 cursorOn = true;
1495 return "\033[?25h";
1496 }
1497 if (!on && (cursorOn == true)) {
1498 cursorOn = false;
1499 return "\033[?25l";
1500 }
1501 return "";
1502 }
1503
1504 /**
1505 * Clear the entire screen. Because some terminals use back-color-erase,
1506 * set the color to white-on-black beforehand.
1507 *
05dbb28d 1508 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1509 */
1510 static public String clearAll() {
1511 return "\033[0;37;40m\033[2J";
1512 }
1513
1514 /**
1515 * Clear the line from the cursor (inclusive) to the end of the screen.
1516 * Because some terminals use back-color-erase, set the color to
1517 * white-on-black beforehand.
1518 *
05dbb28d 1519 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1520 */
1521 static public String clearRemainingLine() {
1522 return "\033[0;37;40m\033[K";
1523 }
1524
1525 /**
1526 * Clear the line up the cursor (inclusive). Because some terminals use
1527 * back-color-erase, set the color to white-on-black beforehand.
1528 *
05dbb28d 1529 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1530 */
1531 static public String clearPreceedingLine() {
1532 return "\033[0;37;40m\033[1K";
1533 }
1534
1535 /**
1536 * Clear the line. Because some terminals use back-color-erase, set the
1537 * color to white-on-black beforehand.
1538 *
05dbb28d 1539 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1540 */
1541 static public String clearLine() {
1542 return "\033[0;37;40m\033[2K";
1543 }
1544
1545 /**
1546 * Move the cursor to the top-left corner.
1547 *
05dbb28d 1548 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1549 */
1550 static public String home() {
1551 return "\033[H";
1552 }
1553
1554 /**
1555 * Move the cursor to (x, y).
1556 *
05dbb28d
KL
1557 * @param x column coordinate. 0 is the left-most column.
1558 * @param y row coordinate. 0 is the top-most row.
1559 * @return the string to emit to an ANSI / ECMA-style terminal
b1589621
KL
1560 */
1561 static public String gotoXY(int x, int y) {
1562 return String.format("\033[%d;%dH", y + 1, x + 1);
1563 }
1564
1565 /**
05dbb28d
KL
1566 * Tell (u)xterm that we want to receive mouse events based on "Any event
1567 * tracking" and UTF-8 coordinates. See
b1589621
KL
1568 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
1569 *
05dbb28d 1570 * Note that this also sets the alternate/primary screen buffer.
b1589621 1571 *
05dbb28d
KL
1572 * @param on If true, enable mouse report and use the alternate screen
1573 * buffer. If false disable mouse reporting and use the primary screen
1574 * buffer.
1575 * @return the string to emit to xterm
b1589621
KL
1576 */
1577 static public String mouse(boolean on) {
1578 if (on) {
1579 return "\033[?1003;1005h\033[?1049h";
1580 }
1581 return "\033[?1003;1005l\033[?1049l";
1582 }
1583
4328bb42
KL
1584 /**
1585 * Read function runs on a separate thread.
1586 */
1587 public void run() {
1588 boolean done = false;
1589 // available() will often return > 1, so we need to read in chunks to
1590 // stay caught up.
1591 char [] readBuffer = new char[128];
1592
1593 while ((done == false) && (stopReaderThread == false)) {
1594 try {
1595 // We assume that if inputStream has bytes available, then
1596 // input won't block on read().
1597 int n = inputStream.available();
1598 if (n > 0) {
1599 if (readBuffer.length < n) {
1600 // The buffer wasn't big enough, make it huger
1601 readBuffer = new char[readBuffer.length * 2];
1602 }
1603
1604 int rc = input.read(readBuffer, 0, n);
1605 // System.err.printf("read() %d", rc); System.err.flush();
1606 if (rc == -1) {
1607 // This is EOF
1608 done = true;
1609 } else {
1610 for (int i = 0; i < rc; i++) {
1611 int ch = readBuffer[i];
1612
1613 // System.err.printf("** READ 0x%x '%c'", ch, ch);
1614 List<TInputEvent> events = getEvents((char)ch);
1615 synchronized (this) {
1616 /*
1617 System.err.printf("adding %d events\n",
1618 events.size());
1619 */
1620 eventQueue.addAll(events);
1621 }
1622 }
1623 }
1624 } else {
1625 // Wait 5 millis for more data
1626 Thread.sleep(5);
1627 }
1628 // System.err.println("end while loop"); System.err.flush();
1629 } catch (InterruptedException e) {
1630 // SQUASH
1631 } catch (IOException e) {
1632 e.printStackTrace();
1633 done = true;
1634 }
1635 } // while ((done == false) && (stopReaderThread == false))
1636 // System.err.println("*** run() exiting..."); System.err.flush();
1637 }
1638
b1589621 1639}