/*
* Copyright (c) 2002-2012, the original author or authors.
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package jdk.internal.jline;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import jdk.internal.jline.internal.Configuration;
import jdk.internal.jline.internal.Log;
//import org.fusesource.jansi.internal.WindowsSupport;
//import org.fusesource.jansi.internal.Kernel32;
//import static org.fusesource.jansi.internal.Kernel32.*;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT;
import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT;
Terminal implementation for Microsoft Windows. Terminal initialization in init
is accomplished by extracting the jline_version.dll, saving it to the system temporary
directoy (determined by the setting of the java.io.tmpdir System
property), loading the library, and then calling the Win32 APIs SetConsoleMode and
GetConsoleMode to
disable character echoing.
By default, the wrapInIfNeeded(InputStream)
method will attempt to test to see if the specified InputStream
is System.in
or a wrapper around FileDescriptor.in
, and if so, will bypass the character reading to directly invoke the readc() method in the JNI library. This is so the class can read special keys (like arrow keys) which are otherwise inaccessible via the System.in
stream. Using JNI reading can be bypassed by setting the jline.WindowsTerminal.directConsole
system property
to false
.
Author: Marc Prud'hommeaux, Jason Dillon Since: 2.0
/**
* Terminal implementation for Microsoft Windows. Terminal initialization in
* {@link #init} is accomplished by extracting the
* <em>jline_<i>version</i>.dll</em>, saving it to the system temporary
* directoy (determined by the setting of the <em>java.io.tmpdir</em> System
* property), loading the library, and then calling the Win32 APIs <a
* href="http://msdn.microsoft.com/library/default.asp?
* url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and
* <a href="http://msdn.microsoft.com/library/default.asp?
* url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to
* disable character echoing.
* <p/>
* <p>
* By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt
* to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper
* around {@link FileDescriptor#in}, and if so, will bypass the character reading to
* directly invoke the readc() method in the JNI library. This is so the class
* can read special keys (like arrow keys) which are otherwise inaccessible via
* the {@link System#in} stream. Using JNI reading can be bypassed by setting
* the <code>jline.WindowsTerminal.directConsole</code> system property
* to <code>false</code>.
* </p>
*
* @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 2.0
*/
public class WindowsTerminal
extends TerminalSupport
{
public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
public static final String ANSI = WindowsTerminal.class.getName() + ".ansi";
private boolean directConsole;
private int originalMode;
public WindowsTerminal() throws Exception {
super(true);
}
@Override
public void init() throws Exception {
super.init();
// setAnsiSupported(Configuration.getBoolean(ANSI, true));
setAnsiSupported(false);
//
// FIXME: Need a way to disable direct console and sysin detection muck
//
setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true));
this.originalMode = getConsoleMode();
setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code);
setEchoEnabled(false);
}
Restore the original terminal configuration, which can be used when
shutting down the console reader. The ConsoleReader cannot be
used after calling this method.
/**
* Restore the original terminal configuration, which can be used when
* shutting down the console reader. The ConsoleReader cannot be
* used after calling this method.
*/
@Override
public void restore() throws Exception {
// restore the old console mode
setConsoleMode(originalMode);
super.restore();
}
@Override
public int getWidth() {
int w = getWindowsTerminalWidth();
return w < 1 ? DEFAULT_WIDTH : w;
}
@Override
public int getHeight() {
int h = getWindowsTerminalHeight();
return h < 1 ? DEFAULT_HEIGHT : h;
}
@Override
public void setEchoEnabled(final boolean enabled) {
// Must set these four modes at the same time to make it work fine.
if (enabled) {
setConsoleMode(getConsoleMode() |
ENABLE_ECHO_INPUT.code |
ENABLE_LINE_INPUT.code |
ENABLE_PROCESSED_INPUT.code |
ENABLE_WINDOW_INPUT.code);
}
else {
setConsoleMode(getConsoleMode() &
~(ENABLE_LINE_INPUT.code |
ENABLE_ECHO_INPUT.code |
ENABLE_PROCESSED_INPUT.code |
ENABLE_WINDOW_INPUT.code));
}
super.setEchoEnabled(enabled);
}
Whether or not to allow the use of the JNI console interaction.
/**
* Whether or not to allow the use of the JNI console interaction.
*/
public void setDirectConsole(final boolean flag) {
this.directConsole = flag;
Log.debug("Direct console: ", flag);
}
Whether or not to allow the use of the JNI console interaction.
/**
* Whether or not to allow the use of the JNI console interaction.
*/
public Boolean getDirectConsole() {
return directConsole;
}
@Override
public InputStream wrapInIfNeeded(InputStream in) throws IOException {
if (directConsole && isSystemIn(in)) {
return new InputStream() {
private byte[] buf = null;
int bufIdx = 0;
@Override
public int read() throws IOException {
while (buf == null || bufIdx == buf.length) {
buf = readConsoleInput();
bufIdx = 0;
}
int c = buf[bufIdx] & 0xFF;
bufIdx++;
return c;
}
};
} else {
return super.wrapInIfNeeded(in);
}
}
protected boolean isSystemIn(final InputStream in) throws IOException {
if (in == null) {
return false;
}
else if (in == System.in) {
return true;
}
else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
return true;
}
return false;
}
@Override
public String getOutputEncoding() {
int codepage = getConsoleOutputCodepage();
//http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
String charsetMS = "ms" + codepage;
if (java.nio.charset.Charset.isSupported(charsetMS)) {
return charsetMS;
}
String charsetCP = "cp" + codepage;
if (java.nio.charset.Charset.isSupported(charsetCP)) {
return charsetCP;
}
Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")...");
return super.getOutputEncoding();
}
//
// Original code:
//
// private int getConsoleMode() {
// return WindowsSupport.getConsoleMode();
// }
//
// private void setConsoleMode(int mode) {
// WindowsSupport.setConsoleMode(mode);
// }
//
// private byte[] readConsoleInput() {
// // XXX does how many events to read in one call matter?
// INPUT_RECORD[] events = null;
// try {
// events = WindowsSupport.readConsoleInput(1);
// } catch (IOException e) {
// Log.debug("read Windows console input error: ", e);
// }
// if (events == null) {
// return new byte[0];
// }
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < events.length; i++ ) {
// KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
// //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar);
// if (keyEvent.keyDown) {
// if (keyEvent.uchar > 0) {
// // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
// // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
// final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
// // 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
// final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
// if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
// && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
// sb.append('\u001B'); // ESC
// }
//
// sb.append(keyEvent.uchar);
// continue;
// }
// // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// // just add support for basic editing keys (no control state, no numpad keys)
// String escapeSequence = null;
// switch (keyEvent.keyCode) {
// case 0x21: // VK_PRIOR PageUp
// escapeSequence = "\u001B[5~";
// break;
// case 0x22: // VK_NEXT PageDown
// escapeSequence = "\u001B[6~";
// break;
// case 0x23: // VK_END
// escapeSequence = "\u001B[4~";
// break;
// case 0x24: // VK_HOME
// escapeSequence = "\u001B[1~";
// break;
// case 0x25: // VK_LEFT
// escapeSequence = "\u001B[D";
// break;
// case 0x26: // VK_UP
// escapeSequence = "\u001B[A";
// break;
// case 0x27: // VK_RIGHT
// escapeSequence = "\u001B[C";
// break;
// case 0x28: // VK_DOWN
// escapeSequence = "\u001B[B";
// break;
// case 0x2D: // VK_INSERT
// escapeSequence = "\u001B[2~";
// break;
// case 0x2E: // VK_DELETE
// escapeSequence = "\u001B[3~";
// break;
// default:
// break;
// }
// if (escapeSequence != null) {
// for (int k = 0; k < keyEvent.repeatCount; k++) {
// sb.append(escapeSequence);
// }
// }
// } else {
// // key up event
// // support ALT+NumPad input method
// if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
// sb.append(keyEvent.uchar);
// }
// }
// }
// return sb.toString().getBytes();
// }
//
// private int getConsoleOutputCodepage() {
// return Kernel32.GetConsoleOutputCP();
// }
//
// private int getWindowsTerminalWidth() {
// return WindowsSupport.getWindowsTerminalWidth();
// }
//
// private int getWindowsTerminalHeight() {
// return WindowsSupport.getWindowsTerminalHeight();
// }
//
// Native Bits
//
static {
System.loadLibrary("le");
initIDs();
}
private static native void initIDs();
protected native int getConsoleMode();
protected native void setConsoleMode(int mode);
private byte[] readConsoleInput() {
KEY_EVENT_RECORD keyEvent = readKeyEvent();
return convertKeys(keyEvent).getBytes();
}
public static String convertKeys(KEY_EVENT_RECORD keyEvent) {
if (keyEvent == null) {
return "";
}
StringBuilder sb = new StringBuilder();
if (keyEvent.keyDown) {
if (keyEvent.uchar > 0) {
// support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
// http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
final int altState = KEY_EVENT_RECORD.ALT_PRESSED;
// 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
final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED;
boolean handled = false;
if ((keyEvent.controlKeyState & ctrlState) != 0) {
switch (keyEvent.keyCode) {
case 0x43: //Ctrl-C
sb.append("\003");
handled = true;
break;
}
}
if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
switch (keyEvent.keyCode) {
case 0x09: //Shift-Tab
sb.append("\033\133\132");
handled = true;
break;
}
}
if (!handled) {
if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
&& ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
sb.append('\u001B'); // ESC
}
sb.append(keyEvent.uchar);
}
} else {
// virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// xterm escape codes: E. Moy, S. Gildea and T. Dickey, "XTerm Control Sequences":
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
// http://xorg.freedesktop.org/releases/X11R6.8.1/PDF/ctlseqs.pdf
// just add support for basic editing keys and function keys
String escapeSequence = null;
switch (keyEvent.keyCode) {
case 0x21: // VK_PRIOR PageUp
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~");
break;
case 0x22: // VK_NEXT PageDown
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~");
break;
case 0x23: // VK_END
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~");
break;
case 0x24: // VK_HOME
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~");
break;
case 0x25: // VK_LEFT
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD");
break;
case 0x26: // VK_UP
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA");
break;
case 0x27: // VK_RIGHT
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC");
break;
case 0x28: // VK_DOWN
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB");
break;
case 0x2D: // VK_INSERT
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~");
break;
case 0x2E: // VK_DELETE
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~");
break;
case 0x70: // VK_F1
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOP", "\u001BO%dP");
break;
case 0x71: // VK_F2
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOQ", "\u001BO%dQ");
break;
case 0x72: // VK_F3
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOR", "\u001BO%dR");
break;
case 0x73: // VK_F4
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001BOS", "\u001BO%dS");
break;
case 0x74: // VK_F5
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[15~", "\u001B[15;%d~");
break;
case 0x75: // VK_F6
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[17~", "\u001B[17;%d~");
break;
case 0x76: // VK_F7
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[18~", "\u001B[18;%d~");
break;
case 0x77: // VK_F8
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[19~", "\u001B[19;%d~");
break;
case 0x78: // VK_F9
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[20~", "\u001B[20;%d~");
break;
case 0x79: // VK_F10
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[21~", "\u001B[21;%d~");
break;
case 0x7A: // VK_F11
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[23~", "\u001B[23;%d~");
break;
case 0x7B: // VK_F12
escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[24~", "\u001B[24;%d~");
break;
default:
break;
}
if (escapeSequence != null) {
for (int k = 0; k < keyEvent.repeatCount; k++) {
sb.append(escapeSequence);
}
}
}
} else {
// key up event
// support ALT+NumPad input method
if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
sb.append(keyEvent.uchar);
}
}
return sb.toString();
}
private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) {
int controlNum = 1;
if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
controlNum += 1;
}
if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) {
controlNum += 2;
}
if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) {
controlNum += 4;
}
if (controlNum > 1) {
return String.format(withControlSequence, controlNum);
} else {
return noControlSequence;
}
}
private native KEY_EVENT_RECORD readKeyEvent();
public static class KEY_EVENT_RECORD {
public final static int ALT_PRESSED = 0x3;
public final static int CTRL_PRESSED = 0xC;
public final static int SHIFT_PRESSED = 0x10;
public final boolean keyDown;
public final char uchar;
public final int controlKeyState;
public final int keyCode;
public final int repeatCount;
public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) {
this.keyDown = keyDown;
this.uchar = uchar;
this.controlKeyState = controlKeyState;
this.keyCode = keyCode;
this.repeatCount = repeatCount;
}
}
private native int getConsoleOutputCodepage();
private native int getWindowsTerminalWidth();
private native int getWindowsTerminalHeight();
Console mode
Constants copied wincon.h.
/**
* Console mode
* <p/>
* Constants copied <tt>wincon.h</tt>.
*/
public static enum ConsoleMode
{
The ReadFile or ReadConsole function returns only when a carriage return
character is read. If this mode is disable, the functions return when one
or more characters are available.
/**
* The ReadFile or ReadConsole function returns only when a carriage return
* character is read. If this mode is disable, the functions return when one
* or more characters are available.
*/
ENABLE_LINE_INPUT(2),
Characters read by the ReadFile or ReadConsole function are written to
the active screen buffer as they are read. This mode can be used only if
the ENABLE_LINE_INPUT mode is also enabled.
/**
* Characters read by the ReadFile or ReadConsole function are written to
* the active screen buffer as they are read. This mode can be used only if
* the ENABLE_LINE_INPUT mode is also enabled.
*/
ENABLE_ECHO_INPUT(4),
CTRL+C is processed by the system and is not placed in the input buffer.
If the input buffer is being read by ReadFile or ReadConsole, other
control keys are processed by the system and are not returned in the
ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also
enabled, backspace, carriage return, and linefeed characters are handled
by the system.
/**
* CTRL+C is processed by the system and is not placed in the input buffer.
* If the input buffer is being read by ReadFile or ReadConsole, other
* control keys are processed by the system and are not returned in the
* ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also
* enabled, backspace, carriage return, and linefeed characters are handled
* by the system.
*/
ENABLE_PROCESSED_INPUT(1),
User interactions that change the size of the console screen buffer are
reported in the console's input buffee. Information about these events
can be read from the input buffer by applications using
theReadConsoleInput function, but not by those using ReadFile
orReadConsole.
/**
* User interactions that change the size of the console screen buffer are
* reported in the console's input buffee. Information about these events
* can be read from the input buffer by applications using
* theReadConsoleInput function, but not by those using ReadFile
* orReadConsole.
*/
ENABLE_WINDOW_INPUT(8),
If the mouse pointer is within the borders of the console window and the
window has the keyboard focus, mouse events generated by mouse movement
and button presses are placed in the input buffer. These events are
discarded by ReadFile or ReadConsole, even when this mode is enabled.
/**
* If the mouse pointer is within the borders of the console window and the
* window has the keyboard focus, mouse events generated by mouse movement
* and button presses are placed in the input buffer. These events are
* discarded by ReadFile or ReadConsole, even when this mode is enabled.
*/
ENABLE_MOUSE_INPUT(16),
When enabled, text entered in a console window will be inserted at the
current cursor location and all text following that location will not be
overwritten. When disabled, all following text will be overwritten. An OR
operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
flag to enable this functionality.
/**
* When enabled, text entered in a console window will be inserted at the
* current cursor location and all text following that location will not be
* overwritten. When disabled, all following text will be overwritten. An OR
* operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
* flag to enable this functionality.
*/
ENABLE_PROCESSED_OUTPUT(1),
This flag enables the user to use the mouse to select and edit text. To
enable this option, use the OR to combine this flag with
ENABLE_EXTENDED_FLAGS.
/**
* This flag enables the user to use the mouse to select and edit text. To
* enable this option, use the OR to combine this flag with
* ENABLE_EXTENDED_FLAGS.
*/
ENABLE_WRAP_AT_EOL_OUTPUT(2),;
public final int code;
ConsoleMode(final int code) {
this.code = code;
}
}
}