/*
 * 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; } } }