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