--- /dev/null 2015-04-26 06:51:08.003313989 -0700 +++ new/jdk/src/jdk.internal.le/share/classes/jdk/internal/jline/WindowsTerminal.java 2015-07-02 08:21:31.167661104 -0700 @@ -0,0 +1,546 @@ +/* + * 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 + * {@link #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 {@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 jline.WindowsTerminal.directConsole system property + * to false. + *

+ * + * @author Marc Prud'hommeaux + * @author Jason Dillon + * @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. + */ + @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. + */ + 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. + */ + 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 +// // 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(); + + private native int getConsoleMode(); + + private 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 + // 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 + // 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 = 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; + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + ENABLE_WRAP_AT_EOL_OUTPUT(2),; + + public final int code; + + ConsoleMode(final int code) { + this.code = code; + } + } + +}