Skip to content

Commit

Permalink
Windows: Avoid extra buffering when reading console input
Browse files Browse the repository at this point in the history
  • Loading branch information
stephan-gh committed Sep 20, 2017
1 parent 9fab73f commit ec009b5
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,18 @@ public Size getSize() {
return size;
}

protected String readConsoleInput() throws IOException {
protected boolean processConsoleInput() throws IOException {
INPUT_RECORD[] events = WindowsSupport.readConsoleInput(1);
if (events == null) {
return "";
return false;
}
StringBuilder sb = new StringBuilder();

for (INPUT_RECORD event : events) {
KEY_EVENT_RECORD keyEvent = event.keyEvent;
sb.append(getEscapeSequenceFromConsoleInput(keyEvent.keyDown , keyEvent.keyCode, keyEvent.uchar, keyEvent.controlKeyState, keyEvent.repeatCount,keyEvent.scanCode));
processKeyEvent(keyEvent.keyDown , keyEvent.keyCode, keyEvent.uchar, keyEvent.controlKeyState);
}
return sb.toString();

return true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ public class JnaWinSysTerminal extends AbstractWindowsTerminal {
private static final Pointer consoleIn = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_INPUT_HANDLE);
private static final Pointer consoleOut = Kernel32.INSTANCE.GetStdHandle(Kernel32.STD_OUTPUT_HANDLE);

private int prevButtonState;

public JnaWinSysTerminal(String name, boolean nativeSignals) throws IOException {
this(name, 0, nativeSignals, SignalHandler.SIG_DFL);
}
Expand Down Expand Up @@ -59,66 +57,75 @@ public Size getSize() {
return new Size(info.windowWidth(), info.windowHeight());
}

protected boolean processConsoleInput() throws IOException {
Kernel32.INPUT_RECORD event = readConsoleInput();
if (event == null) {
return false;
}

switch (event.EventType) {
case Kernel32.INPUT_RECORD.KEY_EVENT:
processKeyEvent(event.Event.KeyEvent);
return true;
case Kernel32.INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT:
raise(Signal.WINCH);
return false;
case Kernel32.INPUT_RECORD.MOUSE_EVENT:
processMouseEvent(event.Event.MouseEvent);
return true;
default:
// Skip event
return false;
}
}

private void processKeyEvent(Kernel32.KEY_EVENT_RECORD keyEvent) throws IOException {
processKeyEvent(keyEvent.bKeyDown, keyEvent.wVirtualKeyCode, keyEvent.uChar.UnicodeChar, keyEvent.dwControlKeyState);
}

private char[] mouse = new char[] { '\033', '[', 'M', ' ', ' ', ' ' };

protected String readConsoleInput() throws IOException {
Kernel32.INPUT_RECORD[] events = doReadConsoleInput();
if (events == null) {
return "";
private void processMouseEvent(Kernel32.MOUSE_EVENT_RECORD mouseEvent) throws IOException {
int dwEventFlags = mouseEvent.dwEventFlags;
int dwButtonState = mouseEvent.dwButtonState;
if (tracking == MouseTracking.Off
|| tracking == MouseTracking.Normal && dwEventFlags == Kernel32.MOUSE_MOVED
|| tracking == MouseTracking.Button && dwEventFlags == Kernel32.MOUSE_MOVED && dwButtonState == 0) {
return;
}
StringBuilder sb = new StringBuilder();
for (Kernel32.INPUT_RECORD event : events) {
if (event.EventType == Kernel32.INPUT_RECORD.KEY_EVENT) {
Kernel32.KEY_EVENT_RECORD keyEvent = event.Event.KeyEvent;
sb.append(getEscapeSequenceFromConsoleInput(keyEvent.bKeyDown, keyEvent.wVirtualKeyCode, keyEvent.uChar.UnicodeChar, keyEvent.dwControlKeyState, keyEvent.wRepeatCount, keyEvent.wVirtualScanCode));
} else if (event.EventType == Kernel32.INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT) {
raise(Signal.WINCH);
} else if (event.EventType == Kernel32.INPUT_RECORD.MOUSE_EVENT) {
Kernel32.MOUSE_EVENT_RECORD mouseEvent = event.Event.MouseEvent;
int dwEventFlags = mouseEvent.dwEventFlags;
int dwButtonState = mouseEvent.dwButtonState;
if (tracking == MouseTracking.Off
|| tracking == MouseTracking.Normal && dwEventFlags == Kernel32.MOUSE_MOVED
|| tracking == MouseTracking.Button && dwEventFlags == Kernel32.MOUSE_MOVED && dwButtonState == 0) {
continue;
}
int cb = 0;
dwEventFlags &= ~ Kernel32.DOUBLE_CLICK; // Treat double-clicks as normal
if (dwEventFlags == Kernel32.MOUSE_WHEELED) {
cb |= 64;
if ((dwButtonState >> 16) < 0) {
cb |= 1;
}
} else if (dwEventFlags == Kernel32.MOUSE_HWHEELED) {
continue;
} else if ((dwButtonState & Kernel32.FROM_LEFT_1ST_BUTTON_PRESSED) != 0) {
cb |= 0x00;
} else if ((dwButtonState & Kernel32.RIGHTMOST_BUTTON_PRESSED) != 0) {
cb |= 0x01;
} else if ((dwButtonState & Kernel32.FROM_LEFT_2ND_BUTTON_PRESSED) != 0) {
cb |= 0x02;
} else {
cb |= 0x03;
}
int cx = mouseEvent.dwMousePosition.X;
int cy = mouseEvent.dwMousePosition.Y;
mouse[3] = (char) (' ' + cb);
mouse[4] = (char) (' ' + cx + 1);
mouse[5] = (char) (' ' + cy + 1);
sb.append(mouse);
prevButtonState = dwButtonState;
int cb = 0;
dwEventFlags &= ~ Kernel32.DOUBLE_CLICK; // Treat double-clicks as normal
if (dwEventFlags == Kernel32.MOUSE_WHEELED) {
cb |= 64;
if ((dwButtonState >> 16) < 0) {
cb |= 1;
}
} else if (dwEventFlags == Kernel32.MOUSE_HWHEELED) {
return;
} else if ((dwButtonState & Kernel32.FROM_LEFT_1ST_BUTTON_PRESSED) != 0) {
cb |= 0x00;
} else if ((dwButtonState & Kernel32.RIGHTMOST_BUTTON_PRESSED) != 0) {
cb |= 0x01;
} else if ((dwButtonState & Kernel32.FROM_LEFT_2ND_BUTTON_PRESSED) != 0) {
cb |= 0x02;
} else {
cb |= 0x03;
}
return sb.toString();
int cx = mouseEvent.dwMousePosition.X;
int cy = mouseEvent.dwMousePosition.Y;
mouse[3] = (char) (' ' + cb);
mouse[4] = (char) (' ' + cx + 1);
mouse[5] = (char) (' ' + cy + 1);
slaveInputPipe.write(mouse);
}

private final Kernel32.INPUT_RECORD[] inputEvents = new Kernel32.INPUT_RECORD[1];
private final IntByReference eventsRead = new IntByReference();

private Kernel32.INPUT_RECORD[] doReadConsoleInput() throws IOException {
private Kernel32.INPUT_RECORD readConsoleInput() throws IOException {
Kernel32.INSTANCE.ReadConsoleInput(consoleIn, inputEvents, 1, eventsRead);
if (eventsRead.getValue() == 1) {
return inputEvents;
return inputEvents[0];
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import org.jline.utils.Signals;
import org.jline.utils.WriterOutputStream;

import java.io.InputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
Expand All @@ -35,8 +35,6 @@ public abstract class AbstractWindowsTerminal extends AbstractTerminal {

public static final String TYPE_WINDOWS = "windows";

private static final int PIPE_SIZE = 1024;

private static final int UTF8_CODE_PAGE = 65001;

protected static final int ENABLE_PROCESSED_INPUT = 0x0001;
Expand Down Expand Up @@ -206,24 +204,25 @@ public void close() throws IOException {
static final int SCROLLLOCK_ON = 0x0040;
static final int CAPSLOCK_ON = 0x0080;

protected String getEscapeSequenceFromConsoleInput(final boolean isKeyDown, final short virtualKeyCode, final char uchar, final int controlKeyState, final short repeatCount, final short scanCode) {
protected void processKeyEvent(final boolean isKeyDown, final short virtualKeyCode, char ch, final int controlKeyState) throws IOException {
final boolean isCtrl = (controlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) > 0;
final boolean isAlt = (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED)) > 0;
final boolean isShift = (controlKeyState & SHIFT_PRESSED) > 0;
char ch = uchar;
StringBuilder sb = new StringBuilder(32);
// key down event
if (isKeyDown && ch != '\3') {
// Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
// otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
if (ch != 0
&& (controlKeyState & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED | SHIFT_PRESSED))
== (RIGHT_ALT_PRESSED | LEFT_CTRL_PRESSED)) {
sb.append(ch);
processInputChar(ch);
} else {
final String keySeq = getEscapeSequence(virtualKeyCode, (isCtrl ? CTRL_FLAG : 0) + (isAlt ? ALT_FLAG : 0) + (isShift ? SHIFT_FLAG : 0));
if (keySeq != null) {
return keySeq;
for (char c : keySeq.toCharArray()) {
processInputChar(c);
}
return;
}
/* uchar value in Windows when CTRL is pressed:
* 1). Ctrl + <0x41 to 0x5e> : uchar=<keyCode> - 'A' + 1
Expand All @@ -235,12 +234,12 @@ protected String getEscapeSequenceFromConsoleInput(final boolean isKeyDown, fina
*/
if (ch > 0) {
if (isAlt) {
sb.append("\033");
processInputChar('\033');
}
if (isCtrl && ch != ' ' && ch != '\n' && ch != 0x7f) {
sb.append((char) (ch == '?' ? 0x7f : Character.toUpperCase(ch) & 0x1f));
processInputChar((char) (ch == '?' ? 0x7f : Character.toUpperCase(ch) & 0x1f));
} else {
sb.append(ch);
processInputChar(ch);
}
} else if (isCtrl) { //Handles the ctrl key events(uchar=0)
if (virtualKeyCode >= 'A' && virtualKeyCode <= 'Z') {
Expand All @@ -250,24 +249,22 @@ protected String getEscapeSequenceFromConsoleInput(final boolean isKeyDown, fina
}
if (ch > 0) {
if (isAlt) {
sb.append("\033");
processInputChar('\033');
}
sb.append(ch);
processInputChar(ch);
}
}
}
} else if (ch == '\3') {
processInputChar('\3');
}
// key up event
else {
if (ch == '\3') {
return "\3";
}
// support ALT+NumPad input method
if (virtualKeyCode == 0x12 /*VK_MENU ALT key*/ && ch > 0) {
sb.append(ch); // no such combination in Windows
processInputChar(ch); // no such combination in Windows
}
}
return sb.toString();
}

protected String getEscapeSequence(short keyCode, int keyState) {
Expand Down Expand Up @@ -349,7 +346,8 @@ protected String getEscapeSequence(short keyCode, int keyState) {
break;
case 0x5D: // VK_CLOSE_BRACKET(Menu key)
case 0x5B: // VK_OPEN_BRACKET(Window key)
break;
default:
return null;
}
return translate(escapeSequence, keyState + 1);
}
Expand Down Expand Up @@ -378,9 +376,8 @@ private String translate(String str, Object... params) {
protected void pump() {
try {
while (!closing) {
String buf = readConsoleInput();
for (char b : buf.toCharArray()) {
processInputChar(b);
if (processConsoleInput()) {
slaveInputPipe.flush();
}
}
} catch (IOException e) {
Expand Down Expand Up @@ -420,7 +417,6 @@ public void processInputChar(char c) throws IOException {
// masterOutput.flush();
// }
slaveInputPipe.write(c);
slaveInputPipe.flush();
}

@Override
Expand All @@ -436,7 +432,12 @@ public boolean trackMouse(MouseTracking tracking) {

protected abstract void setConsoleMode(int mode);

protected abstract String readConsoleInput() throws IOException;
/**
* Read a single input event from the input buffer and process it.
*
* @return true if new input was generated from the event
*/
protected abstract boolean processConsoleInput() throws IOException;

}

15 changes: 11 additions & 4 deletions terminal/src/main/java/org/jline/utils/PumpReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
public class PumpReader extends Reader {

private static final int EOF = -1;
private static final int BUFFER_SIZE = 4096;
private static final int DEFAULT_BUFFER_SIZE = 4096;

// Read and write buffer are backed by the same array
private final CharBuffer readBuffer;
Expand All @@ -32,7 +32,11 @@ public class PumpReader extends Reader {
private boolean closed;

public PumpReader() {
char[] buf = new char[BUFFER_SIZE];
this(DEFAULT_BUFFER_SIZE);
}

public PumpReader(int bufferSize) {
char[] buf = new char[bufferSize];
this.readBuffer = CharBuffer.wrap(buf);
this.writeBuffer = CharBuffer.wrap(buf);
this.writer = new Writer(this);
Expand Down Expand Up @@ -247,8 +251,11 @@ synchronized void write(String str, int off, int len) throws IOException {
}

synchronized void flush() {
// Notify readers
notifyAll();
// Avoid waking up readers when there is nothing to read
if (readBuffer.hasRemaining()) {
// Notify readers
notifyAll();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ protected int getConsoleMode() {
protected void setConsoleMode(int mode) {
}
@Override
protected String readConsoleInput() throws IOException {
return "";
protected boolean processConsoleInput() throws IOException {
return false;
}
@Override
public Size getSize() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ protected void setConsoleMode(int mode) {
}

@Override
protected String readConsoleInput() throws IOException {
protected boolean processConsoleInput() throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
return "";

return false;
}
}

Expand Down

0 comments on commit ec009b5

Please sign in to comment.