/* * 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); System.loadLibrary("jline"); } @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 // 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; } } }