From 4328bb42c10743287dad5cf045f059ad109eb540 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 10 Mar 2015 16:37:50 -0400 Subject: [PATCH] HELLO WORLD... --- demos/Demo1.java | 13 +- src/jexer/TApplication.java | 365 +++++++++++++++++++++++++- src/jexer/TCommand.java | 13 + src/jexer/TKeypress.java | 18 ++ src/jexer/bits/Color.java | 21 +- src/jexer/bits/GraphicsChars.java | 84 +++--- src/jexer/io/ECMA48Terminal.java | 167 ++++++++++-- src/jexer/session/TTYSessionInfo.java | 2 - 8 files changed, 597 insertions(+), 86 deletions(-) diff --git a/demos/Demo1.java b/demos/Demo1.java index ada690d..62fce24 100644 --- a/demos/Demo1.java +++ b/demos/Demo1.java @@ -42,7 +42,9 @@ class DemoApplication extends TApplication { /** * Public constructor */ - public DemoApplication() { + public DemoApplication() throws Exception { + super(null, null); + /* try { ColorTheme theme = new ColorTheme(); TTYSessionInfo tty = new TTYSessionInfo(); @@ -51,6 +53,7 @@ class DemoApplication extends TApplication { } catch (Exception e) { e.printStackTrace(); } + */ } } @@ -64,8 +67,12 @@ public class Demo1 { * @param args Command line arguments */ public static void main(String [] args) { - DemoApplication app = new DemoApplication(); - app.run(); + try { + DemoApplication app = new DemoApplication(); + app.run(); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 3f74803..5df59d9 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -32,32 +32,238 @@ */ package jexer; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; + +import jexer.bits.CellAttributes; +import jexer.bits.ColorTheme; +import jexer.bits.GraphicsChars; +import jexer.event.TCommandEvent; +import jexer.event.TInputEvent; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import jexer.event.TResizeEvent; +import jexer.backend.Backend; +import jexer.backend.ECMA48Backend; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; + /** * TApplication sets up a full Text User Interface application. */ public class TApplication { /** - * Run this application until it exits, using stdin and stdout + * Access to the physical screen, keyboard, and mouse. + */ + public Backend backend; + + /** + * Actual mouse coordinate X + */ + private int mouseX; + + /** + * Actual mouse coordinate Y + */ + private int mouseY; + + /** + * Event queue that will be drained by either primary or secondary Fiber + */ + private List eventQueue; + + /** + * Windows and widgets pull colors from this ColorTheme. + */ + public ColorTheme theme; + + /** + * When true, exit the application. + */ + public boolean quit = false; + + /** + * When true, repaint the entire screen. + */ + public boolean repaint = true; + + /** + * When true, just flush updates from the screen. */ - final public void run() { + public boolean flush = false; + + /** + * Y coordinate of the top edge of the desktop. + */ + static public final int desktopTop = 1; + + /** + * Y coordinate of the bottom edge of the desktop. + */ + public int desktopBottom; + + /** + * Public constructor. + * + * @param input an InputStream connected to the remote user, or null for + * System.in. If System.in is used, then on non-Windows systems it will + * be put in raw mode; shutdown() will (blindly!) put System.in in cooked + * mode. input is always converted to a Reader with UTF-8 encoding. + * @param output an OutputStream connected to the remote user, or null + * for System.out. output is always converted to a Writer with UTF-8 + * encoding. + */ + public TApplication(InputStream input, OutputStream output) throws UnsupportedEncodingException { + + backend = new ECMA48Backend(input, output); + theme = new ColorTheme(); + desktopBottom = backend.screen.getHeight() - 1; + eventQueue = new LinkedList(); + } + + /** + * Invert the cell at the mouse pointer position. + */ + private void drawMouse() { + CellAttributes attr = backend.screen.getAttrXY(mouseX, mouseY); + attr.foreColor = attr.foreColor.invert(); + attr.backColor = attr.backColor.invert(); + backend.screen.putAttrXY(mouseX, mouseY, attr, false); + flush = true; /* - while (quit == false) { + if (windows.length == 0) { + repaint = true; + } + */ + // TODO: remove this repaint after the above if (windows.length == 0) + // can be used again. + repaint = true; + } + + /** + * Draw everything. + */ + final public void drawAll() { + if ((flush) && (!repaint)) { + backend.flushScreen(); + flush = false; + return; + } + + if (!repaint) { + return; + } + + // If true, the cursor is not visible + boolean cursor = false; + + // Start with a clean screen + backend.screen.clear(); + + // Draw the background + CellAttributes background = theme.getColor("tapplication.background"); + backend.screen.putAll(GraphicsChars.HATCH, background); + + /* + // Draw each window in reverse Z order + TWindow [] sorted = windows.dup; + sorted.sort.reverse; + foreach (w; sorted) { + w.drawChildren(); + } + + // Draw the blank menubar line - reset the screen clipping first so + // it won't trim it out. + backend.screen.resetClipping(); + backend.screen.hLineXY(0, 0, backend.screen.getWidth(), ' ', + theme.getColor("tmenu")); + // Now draw the menus. + int x = 1; + foreach (m; menus) { + CellAttributes menuColor; + CellAttributes menuMnemonicColor; + if (m.active) { + menuColor = theme.getColor("tmenu.highlighted"); + menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted"); + } else { + menuColor = theme.getColor("tmenu"); + menuMnemonicColor = theme.getColor("tmenu.mnemonic"); + } + // Draw the menu title + backend.screen.hLineXY(x, 0, cast(int)m.title.length + 2, ' ', + menuColor); + backend.screen.putStrXY(x + 1, 0, m.title, menuColor); + // Draw the highlight character + backend.screen.putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0, + m.mnemonic.shortcut, menuMnemonicColor); + + if (m.active) { + m.drawChildren(); + // Reset the screen clipping so we can draw the next title. + backend.screen.resetClipping(); + } + x += m.title.length + 2; + } + + foreach (m; subMenus) { + // Reset the screen clipping so we can draw the next sub-menu. + backend.screen.resetClipping(); + m.drawChildren(); + } + */ + + // Draw the mouse pointer + drawMouse(); - // Timeout is in milliseconds, so default timeout after 1 - // second of inactivity. - uint timeout = getSleepTime(1000); + /* + // Place the cursor if it is visible + TWidget activeWidget = null; + if (sorted.length > 0) { + activeWidget = sorted[$ - 1].getActiveChild(); + if (activeWidget.hasCursor) { + backend.screen.putCursor(true, activeWidget.getCursorAbsoluteX(), + activeWidget.getCursorAbsoluteY()); + cursor = true; + } + } + + // Kill the cursor + if (cursor == false) { + backend.screen.hideCursor(); + } + */ + + // Flush the screen contents + backend.flushScreen(); + + repaint = false; + flush = false; + } + + /** + * Run this application until it exits, using stdin and stdout + */ + public final void run() { + + while (quit == false) { + // Timeout is in milliseconds, so default timeout after 1 second + // of inactivity. + int timeout = getSleepTime(1000); // std.stdio.stderr.writefln("poll() timeout: %d", timeout); - if (eventQueue.length > 0) { + if (eventQueue.size() > 0) { // Do not wait if there are definitely events waiting to be // processed or a screen redraw to do. timeout = 0; } // Pull any pending input events - TInputEvent [] events = backend.getEvents(timeout); + List events = backend.getEvents(timeout); metaHandleEvents(events); // Process timers and call doIdle()'s @@ -67,6 +273,8 @@ public class TApplication { drawAll(); } + /* + // Shutdown the fibers eventQueue.length = 0; if (secondaryEventFiber !is null) { @@ -82,10 +290,149 @@ public class TApplication { // Wake up the primary handler so that it can exit. primaryEventFiber.call(); } + */ backend.shutdown(); + } + + /** + * Peek at certain application-level events, add to eventQueue, and wake + * up the consuming Fiber. + * + * @param events the input events to consume + */ + private void metaHandleEvents(List events) { + + for (TInputEvent event: events) { + + /* + System.err.printf(String.format("metaHandleEvents event: %s\n", + event)); System.err.flush(); + */ + + if (quit == true) { + // Do no more processing if the application is already trying + // to exit. + return; + } + + // DEBUG + if (event instanceof TKeypressEvent) { + TKeypressEvent keypress = (TKeypressEvent)event; + if (keypress.key.equals(kbAltX)) { + quit = true; + return; + } + } + // DEBUG + + // Special application-wide events ------------------------------- + + // Abort everything + if (event instanceof TCommandEvent) { + TCommandEvent command = (TCommandEvent)event; + if (command.cmd.equals(cmAbort)) { + quit = true; + return; + } + } + + // Screen resize + if (event instanceof TResizeEvent) { + TResizeEvent resize = (TResizeEvent)event; + backend.screen.setDimensions(resize.width, resize.height); + desktopBottom = backend.screen.getHeight() - 1; + repaint = true; + mouseX = 0; + mouseY = 0; + continue; + } + + // Peek at the mouse position + if (event instanceof TMouseEvent) { + TMouseEvent mouse = (TMouseEvent)event; + if ((mouseX != mouse.x) || (mouseY != mouse.y)) { + mouseX = mouse.x; + mouseY = mouse.y; + drawMouse(); + } + } + + /* + + // Put into the main queue + addEvent(event); + + // Have one of the two consumer Fibers peel the events off + // the queue. + if (secondaryEventFiber !is null) { + assert(secondaryEventFiber.state == Fiber.State.HOLD); + + // Wake up the secondary handler for these events + secondaryEventFiber.call(); + } else { + assert(primaryEventFiber.state == Fiber.State.HOLD); + + // Wake up the primary handler for these events + primaryEventFiber.call(); + } + */ + + } // for (TInputEvent event: events) + + } + + /** + * Do stuff when there is no user input. + */ + private void doIdle() { + /* + // Now run any timers that have timed out + auto now = Clock.currTime; + TTimer [] keepTimers; + foreach (t; timers) { + if (t.nextTick < now) { + t.tick(); + if (t.recurring == true) { + keepTimers ~= t; + } + } else { + keepTimers ~= t; + } + } + timers = keepTimers; + + // Call onIdle's + foreach (w; windows) { + w.onIdle(); + } */ + } - System.out.println("Hello"); + /** + * Get the amount of time I can sleep before missing a Timer tick. + * + * @param timeout = initial (maximum) timeout + * @return number of milliseconds between now and the next timer event + */ + protected int getSleepTime(int timeout) { + /* + auto now = Clock.currTime; + auto sleepTime = dur!("msecs")(timeout); + foreach (t; timers) { + if (t.nextTick < now) { + return 0; + } + if ((t.nextTick > now) && + ((t.nextTick - now) < sleepTime) + ) { + sleepTime = t.nextTick - now; + } + } + assert(sleepTime.total!("msecs")() >= 0); + return cast(uint)sleepTime.total!("msecs")(); + */ + return 0; } + } diff --git a/src/jexer/TCommand.java b/src/jexer/TCommand.java index 0a38903..ce19e2a 100644 --- a/src/jexer/TCommand.java +++ b/src/jexer/TCommand.java @@ -144,6 +144,19 @@ public class TCommand { return String.format("%s", type); } + /** + * Comparison. All fields must match to return true. + */ + @Override + public boolean equals(Object rhs) { + if (!(rhs instanceof TCommand)) { + return false; + } + + TCommand that = (TCommand)rhs; + return (type == that.type); + } + static public final TCommand cmAbort = new TCommand(TCommand.Type.ABORT); static public final TCommand cmExit = new TCommand(TCommand.Type.EXIT); static public final TCommand cmQuit = new TCommand(TCommand.Type.EXIT); diff --git a/src/jexer/TKeypress.java b/src/jexer/TKeypress.java index 4f5f0af..c4fd8b4 100644 --- a/src/jexer/TKeypress.java +++ b/src/jexer/TKeypress.java @@ -225,6 +225,24 @@ public class TKeypress { this.shift = shift; } + /** + * Comparison. All fields must match to return true. + */ + @Override + public boolean equals(Object rhs) { + if (!(rhs instanceof TKeypress)) { + return false; + } + + TKeypress that = (TKeypress)rhs; + return ((isKey == that.isKey) && + (fnKey == that.fnKey) && + (ch == that.ch) && + (alt == that.alt) && + (ctrl == that.ctrl) && + (shift == that.shift)); + } + /** * Make human-readable description of this Keystroke. */ diff --git a/src/jexer/bits/Color.java b/src/jexer/bits/Color.java index 88cc584..dd3b93d 100644 --- a/src/jexer/bits/Color.java +++ b/src/jexer/bits/Color.java @@ -101,12 +101,10 @@ public class Color { /** * Invert a color in the same way as (CGA/VGA color XOR 0x7). - * - * @param color color to change * @return the inverted color */ - static public Color invert(Color color) { - switch (color.value) { + public Color invert() { + switch (value) { case black: return Color.WHITE; case white: @@ -125,6 +123,19 @@ public class Color { return Color.BLUE; } throw new IllegalArgumentException("Invalid Color value: " + - color.value); + value); + } + + /** + * Comparison. All fields must match to return true. + */ + @Override + public boolean equals(Object rhs) { + if (!(rhs instanceof Color)) { + return false; + } + + Color that = (Color)rhs; + return (value == that.value); } } diff --git a/src/jexer/bits/GraphicsChars.java b/src/jexer/bits/GraphicsChars.java index 12e6343..1929631 100644 --- a/src/jexer/bits/GraphicsChars.java +++ b/src/jexer/bits/GraphicsChars.java @@ -32,24 +32,6 @@ */ package jexer.bits; -// Commonly used ASCII characters - -/* -public static final byte C_NUL = 0x00; // NUL -public static final byte C_SOH = 0x01; // SOH -public static final byte C_STX = 0x02; // STX -public static final byte C_EOT = 0x04; // EOT -public static final byte C_ACK = 0x06; // ACK -public static final byte C_LF = 0x0A; // Line feed '\n' -public static final byte C_CR = 0x0D; // Carriage return '\r' -public static final byte C_XON = 0x11; // XON, also known as DC1 -public static final byte C_XOFF = 0x13; // XOFF, also known as DC3 -public static final byte C_NAK = 0x15; // NAK -public static final byte C_CAN = 0x18; // CAN -public static final byte C_SUB = 0x1A; // SUB -public static final byte C_ESC = 0x1B; // ESC - */ - /** * Collection of special characters used by the windowing system. */ @@ -58,7 +40,7 @@ public class GraphicsChars { /** * CP437 translation map */ - private static final char cp437_chars[] = { + static private final char cp437_chars[] = { '\u2007', '\u263A', '\u263B', '\u2665', '\u2666', '\u2663', '\u2660', '\u2022', '\u25D8', '\u25CB', '\u25D9', '\u2642', '\u2640', '\u266A', '\u266B', '\u263C', @@ -96,37 +78,37 @@ public class GraphicsChars { }; - public static final char HATCH = cp437_chars[0xB0]; - public static final char DOUBLE_BAR = cp437_chars[0xCD]; - public static final char BOX = cp437_chars[0xFE]; - public static final char CHECK = cp437_chars[0xFB]; - public static final char TRIPLET = cp437_chars[0xF0]; - public static final char OMEGA = cp437_chars[0xEA]; - public static final char PI = cp437_chars[0xE3]; - public static final char UPARROW = cp437_chars[0x18]; - public static final char DOWNARROW = cp437_chars[0x19]; - public static final char RIGHTARROW = cp437_chars[0x1A]; - public static final char LEFTARROW = cp437_chars[0x1B]; - public static final char SINGLE_BAR = cp437_chars[0xC4]; - public static final char BACK_ARROWHEAD = cp437_chars[0x11]; - public static final char LRCORNER = cp437_chars[0xD9]; - public static final char URCORNER = cp437_chars[0xBF]; - public static final char LLCORNER = cp437_chars[0xC0]; - public static final char ULCORNER = cp437_chars[0xDA]; - public static final char DEGREE = cp437_chars[0xF8]; - public static final char PLUSMINUS = cp437_chars[0xF1]; - public static final char WINDOW_TOP = cp437_chars[0xCD]; - public static final char WINDOW_LEFT_TOP = cp437_chars[0xD5]; - public static final char WINDOW_RIGHT_TOP = cp437_chars[0xB8]; - public static final char WINDOW_SIDE = cp437_chars[0xB3]; - public static final char WINDOW_LEFT_BOTTOM = cp437_chars[0xD4]; - public static final char WINDOW_RIGHT_BOTTOM = cp437_chars[0xBE]; - public static final char WINDOW_LEFT_TEE = cp437_chars[0xC6]; - public static final char WINDOW_RIGHT_TEE = cp437_chars[0xB5]; - public static final char WINDOW_SIDE_DOUBLE = cp437_chars[0xBA]; - public static final char WINDOW_LEFT_TOP_DOUBLE = cp437_chars[0xC9]; - public static final char WINDOW_RIGHT_TOP_DOUBLE = cp437_chars[0xBB]; - public static final char WINDOW_LEFT_BOTTOM_DOUBLE = cp437_chars[0xC8]; - public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = cp437_chars[0xBC]; + static public final char HATCH = cp437_chars[0xB0]; + static public final char DOUBLE_BAR = cp437_chars[0xCD]; + static public final char BOX = cp437_chars[0xFE]; + static public final char CHECK = cp437_chars[0xFB]; + static public final char TRIPLET = cp437_chars[0xF0]; + static public final char OMEGA = cp437_chars[0xEA]; + static public final char PI = cp437_chars[0xE3]; + static public final char UPARROW = cp437_chars[0x18]; + static public final char DOWNARROW = cp437_chars[0x19]; + static public final char RIGHTARROW = cp437_chars[0x1A]; + static public final char LEFTARROW = cp437_chars[0x1B]; + static public final char SINGLE_BAR = cp437_chars[0xC4]; + static public final char BACK_ARROWHEAD = cp437_chars[0x11]; + static public final char LRCORNER = cp437_chars[0xD9]; + static public final char URCORNER = cp437_chars[0xBF]; + static public final char LLCORNER = cp437_chars[0xC0]; + static public final char ULCORNER = cp437_chars[0xDA]; + static public final char DEGREE = cp437_chars[0xF8]; + static public final char PLUSMINUS = cp437_chars[0xF1]; + static public final char WINDOW_TOP = cp437_chars[0xCD]; + static public final char WINDOW_LEFT_TOP = cp437_chars[0xD5]; + static public final char WINDOW_RIGHT_TOP = cp437_chars[0xB8]; + static public final char WINDOW_SIDE = cp437_chars[0xB3]; + static public final char WINDOW_LEFT_BOTTOM = cp437_chars[0xD4]; + static public final char WINDOW_RIGHT_BOTTOM = cp437_chars[0xBE]; + static public final char WINDOW_LEFT_TEE = cp437_chars[0xC6]; + static public final char WINDOW_RIGHT_TEE = cp437_chars[0xB5]; + static public final char WINDOW_SIDE_DOUBLE = cp437_chars[0xBA]; + static public final char WINDOW_LEFT_TOP_DOUBLE = cp437_chars[0xC9]; + static public final char WINDOW_RIGHT_TOP_DOUBLE = cp437_chars[0xBB]; + static public final char WINDOW_LEFT_BOTTOM_DOUBLE = cp437_chars[0xC8]; + static public final char WINDOW_RIGHT_BOTTOM_DOUBLE = cp437_chars[0xBC]; } diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index 7542f8a..ba745c5 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -33,6 +33,8 @@ package jexer.io; import java.io.BufferedReader; +import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; @@ -62,13 +64,28 @@ import static jexer.TKeypress.*; * X3.64 / ECMA-48 type terminals e.g. xterm, linux, vt100, ansi.sys, * etc. */ -public class ECMA48Terminal { +public class ECMA48Terminal implements Runnable { /** * The session information */ public SessionInfo session; + /** + * The event queue, filled up by a thread reading on input + */ + private List eventQueue; + + /** + * If true, we want the reader thread to exit gracefully. + */ + private boolean stopReaderThread; + + /** + * The reader thread + */ + private Thread readerThread; + /** * Parameters being collected. E.g. if the string is \033[1;3m, then * params[0] will be 1 and params[1] will be 3. @@ -139,11 +156,19 @@ public class ECMA48Terminal { /** * The terminal's input. If an InputStream is not specified in the - * constructor, then this InputReader will be bound to System.in with - * UTF-8 encoding. + * constructor, then this InputStreamReader will be bound to System.in + * with UTF-8 encoding. */ private Reader input; + /** + * The terminal's raw InputStream. If an InputStream is not specified in + * the constructor, then this InputReader will be bound to System.in. + * This is used by run() to see if bytes are available() before calling + * (Reader)input.read(). + */ + private InputStream inputStream; + /** * The terminal's output. If an OutputStream is not specified in the * constructor, then this PrintWriter will be bound to System.out with @@ -189,14 +214,12 @@ public class ECMA48Terminal { */ private void doStty(boolean mode) { String [] cmdRaw = { - "/bin/sh", "-c", "stty raw < /dev/tty" + "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty" }; String [] cmdCooked = { - "/bin/sh", "-c", "stty cooked < /dev/tty" + "/bin/sh", "-c", "stty sane cooked < /dev/tty" }; try { - System.out.println("spawn stty"); - Process process; if (mode == true) { process = Runtime.getRuntime().exec(cmdRaw); @@ -244,17 +267,21 @@ public class ECMA48Terminal { public ECMA48Terminal(InputStream input, OutputStream output) throws UnsupportedEncodingException { reset(); - mouse1 = false; - mouse2 = false; - mouse3 = false; + mouse1 = false; + mouse2 = false; + mouse3 = false; + stopReaderThread = false; if (input == null) { - this.input = new InputStreamReader(System.in, "UTF-8"); + // inputStream = System.in; + inputStream = new FileInputStream(FileDescriptor.in); sttyRaw(); setRawMode = true; } else { - this.input = new InputStreamReader(input); + inputStream = input; } + this.input = new InputStreamReader(inputStream, "UTF-8"); + // TODO: include TelnetSocket from NIB and have it implement // SessionInfo if (input instanceof SessionInfo) { @@ -283,18 +310,52 @@ public class ECMA48Terminal { // Hang onto the window size windowResize = new TResizeEvent(TResizeEvent.Type.Screen, session.getWindowWidth(), session.getWindowHeight()); + + // Spin up the input reader + eventQueue = new LinkedList(); + readerThread = new Thread(this); + readerThread.start(); } /** * Restore terminal to normal state */ public void shutdown() { + + // System.err.println("=== shutdown() ==="); System.err.flush(); + + // Tell the reader thread to stop looking at input + stopReaderThread = true; + try { + readerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Disable mouse reporting and show cursor + output.printf("%s%s%s", mouse(false), cursor(true), normal()); + output.flush(); + if (setRawMode) { sttyCooked(); setRawMode = false; + // We don't close System.in/out + } else { + // Shut down the streams, this should wake up the reader thread + // and make it exit. + try { + if (input != null) { + input.close(); + input = null; + } + if (output != null) { + output.close(); + output = null; + } + } catch (IOException e) { + e.printStackTrace(); + } } - // Disable mouse reporting and show cursor - output.printf("%s%s%s", mouse(false), cursor(true), normal()); } /** @@ -309,6 +370,7 @@ public class ECMA48Terminal { */ private void reset() { state = ParseState.GROUND; + params = new ArrayList(); paramI = 0; params.clear(); params.add(""); @@ -329,8 +391,12 @@ public class ECMA48Terminal { // System.err.printf("controlChar: %02x\n", ch); switch (ch) { - case '\r': - // ENTER + case 0x0D: + // Carriage return --> ENTER + event.key = kbEnter; + break; + case 0x0A: + // Linefeed --> ENTER event.key = kbEnter; break; case 0x1B: @@ -702,6 +768,18 @@ public class ECMA48Terminal { */ public List getEvents() { List events = new LinkedList(); + + synchronized(this) { + if (eventQueue.size() > 0) { + events.addAll(eventQueue); + eventQueue.clear(); + } + } + + // TEST: drop a cmAbort + // events.add(new jexer.event.TCommandEvent(jexer.TCommand.cmAbort)); + // events.add(new jexer.event.TKeypressEvent(kbAltX)); + return events; } @@ -731,6 +809,7 @@ public class ECMA48Terminal { TKeypressEvent keypress; Date now = new Date(); + /* // ESCDELAY type timeout if (state == ParseState.ESCAPE) { long escDelay = now.getTime() - escapeTime; @@ -740,6 +819,7 @@ public class ECMA48Terminal { reset(); } } + */ if (noChar == true) { int newWidth = session.getWindowWidth(); @@ -1501,4 +1581,59 @@ public class ECMA48Terminal { return "\033[?1003;1005l\033[?1049l"; } + /** + * Read function runs on a separate thread. + */ + public void run() { + boolean done = false; + // available() will often return > 1, so we need to read in chunks to + // stay caught up. + char [] readBuffer = new char[128]; + + while ((done == false) && (stopReaderThread == false)) { + try { + // We assume that if inputStream has bytes available, then + // input won't block on read(). + int n = inputStream.available(); + if (n > 0) { + if (readBuffer.length < n) { + // The buffer wasn't big enough, make it huger + readBuffer = new char[readBuffer.length * 2]; + } + + int rc = input.read(readBuffer, 0, n); + // System.err.printf("read() %d", rc); System.err.flush(); + if (rc == -1) { + // This is EOF + done = true; + } else { + for (int i = 0; i < rc; i++) { + int ch = readBuffer[i]; + + // System.err.printf("** READ 0x%x '%c'", ch, ch); + List events = getEvents((char)ch); + synchronized (this) { + /* + System.err.printf("adding %d events\n", + events.size()); + */ + eventQueue.addAll(events); + } + } + } + } else { + // Wait 5 millis for more data + Thread.sleep(5); + } + // System.err.println("end while loop"); System.err.flush(); + } catch (InterruptedException e) { + // SQUASH + } catch (IOException e) { + e.printStackTrace(); + done = true; + } + } // while ((done == false) && (stopReaderThread == false)) + // System.err.println("*** run() exiting..."); System.err.flush(); + } + } diff --git a/src/jexer/session/TTYSessionInfo.java b/src/jexer/session/TTYSessionInfo.java index b65eb0d..2236ac3 100644 --- a/src/jexer/session/TTYSessionInfo.java +++ b/src/jexer/session/TTYSessionInfo.java @@ -116,8 +116,6 @@ public class TTYSessionInfo implements SessionInfo { "/bin/sh", "-c", "stty size < /dev/tty" }; try { - System.out.println("spawn stty"); - Process process = Runtime.getRuntime().exec(cmd); BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); String line = in.readLine(); -- 2.27.0