| /* |
| * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. |
| * |
| * This software is distributable under the BSD license. See the terms of the |
| * BSD license in the documentation provided with this software. |
| */ |
| package jline; |
| |
| import java.io.*; |
| |
| import jline.UnixTerminal.ReplayPrefixOneCharInputStream; |
| |
| /** |
| * <p> |
| * Terminal implementation for Microsoft Windows. Terminal initialization in |
| * {@link #initializeTerminal} 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 #readCharacter} 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> |
| */ |
| public class WindowsTerminal extends Terminal { |
| // constants copied from wincon.h |
| |
| /** |
| * 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. |
| */ |
| private static final int 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. |
| */ |
| private static final int 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. |
| */ |
| private static final int 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. |
| */ |
| private static final int 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. |
| */ |
| private static final int 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. |
| */ |
| private static final int 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. |
| */ |
| private static final int ENABLE_WRAP_AT_EOL_OUTPUT = 2; |
| |
| /** |
| * On windows terminals, this character indicates that a 'special' key has |
| * been pressed. This means that a key such as an arrow key, or delete, or |
| * home, etc. will be indicated by the next character. |
| */ |
| public static final int SPECIAL_KEY_INDICATOR = 224; |
| |
| /** |
| * On windows terminals, this character indicates that a special key on the |
| * number pad has been pressed. |
| */ |
| public static final int NUMPAD_KEY_INDICATOR = 0; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, |
| * this character indicates an left arrow key press. |
| */ |
| public static final int LEFT_ARROW_KEY = 75; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates an |
| * right arrow key press. |
| */ |
| public static final int RIGHT_ARROW_KEY = 77; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates an up |
| * arrow key press. |
| */ |
| public static final int UP_ARROW_KEY = 72; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates an |
| * down arrow key press. |
| */ |
| public static final int DOWN_ARROW_KEY = 80; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates that |
| * the delete key was pressed. |
| */ |
| public static final int DELETE_KEY = 83; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates that |
| * the home key was pressed. |
| */ |
| public static final int HOME_KEY = 71; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates that |
| * the end key was pressed. |
| */ |
| public static final char END_KEY = 79; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates that |
| * the page up key was pressed. |
| */ |
| public static final char PAGE_UP_KEY = 73; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates that |
| * the page down key was pressed. |
| */ |
| public static final char PAGE_DOWN_KEY = 81; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR |
| * this character indicates that |
| * the insert key was pressed. |
| */ |
| public static final char INSERT_KEY = 82; |
| |
| /** |
| * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, |
| * this character indicates that the escape key was pressed. |
| */ |
| public static final char ESCAPE_KEY = 0; |
| |
| private Boolean directConsole; |
| |
| private boolean echoEnabled; |
| |
| String encoding = System.getProperty("jline.WindowsTerminal.input.encoding", System.getProperty("file.encoding")); |
| ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding); |
| InputStreamReader replayReader; |
| |
| public WindowsTerminal() { |
| String dir = System.getProperty("jline.WindowsTerminal.directConsole"); |
| |
| if ("true".equals(dir)) { |
| directConsole = Boolean.TRUE; |
| } else if ("false".equals(dir)) { |
| directConsole = Boolean.FALSE; |
| } |
| |
| try { |
| replayReader = new InputStreamReader(replayStream, encoding); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| |
| } |
| |
| private native int getConsoleMode(); |
| |
| private native void setConsoleMode(final int mode); |
| |
| private native int readByte(); |
| |
| private native int getWindowsTerminalWidth(); |
| |
| private native int getWindowsTerminalHeight(); |
| |
| public int readCharacter(final InputStream in) throws IOException { |
| // if we can detect that we are directly wrapping the system |
| // input, then bypass the input stream and read directly (which |
| // allows us to access otherwise unreadable strokes, such as |
| // the arrow keys) |
| if (directConsole == Boolean.FALSE) { |
| return super.readCharacter(in); |
| } else if ((directConsole == Boolean.TRUE) |
| || ((in == System.in) || (in instanceof FileInputStream |
| && (((FileInputStream) in).getFD() == FileDescriptor.in)))) { |
| return readByte(); |
| } else { |
| return super.readCharacter(in); |
| } |
| } |
| |
| public void initializeTerminal() throws Exception { |
| loadLibrary("jline"); |
| |
| final int originalMode = getConsoleMode(); |
| |
| setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT); |
| |
| // set the console to raw mode |
| int newMode = originalMode |
| & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT |
| | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); |
| echoEnabled = false; |
| setConsoleMode(newMode); |
| |
| // at exit, restore the original tty configuration (for JDK 1.3+) |
| try { |
| Runtime.getRuntime().addShutdownHook(new Thread() { |
| public void start() { |
| // restore the old console mode |
| setConsoleMode(originalMode); |
| } |
| }); |
| } catch (AbstractMethodError ame) { |
| // JDK 1.3+ only method. Bummer. |
| consumeException(ame); |
| } |
| } |
| |
| private void loadLibrary(final String name) throws IOException { |
| // store the DLL in the temporary directory for the System |
| String version = WindowsTerminal.class.getPackage().getImplementationVersion(); |
| |
| if (version == null) { |
| version = ""; |
| } |
| |
| version = version.replace('.', '_'); |
| |
| File f = new File(System.getProperty("java.io.tmpdir"), name + "_" |
| + version + ".dll"); |
| boolean exists = f.isFile(); // check if it already exists |
| |
| // extract the embedded jline.dll file from the jar and save |
| // it to the current directory |
| int bits = 32; |
| |
| // check for 64-bit systems and use to appropriate DLL |
| if (System.getProperty("os.arch").indexOf("64") != -1) |
| bits = 64; |
| |
| InputStream in = new BufferedInputStream(WindowsTerminal.class.getResourceAsStream(name + bits + ".dll")); |
| |
| OutputStream fout = null; |
| try { |
| fout = new BufferedOutputStream( |
| new FileOutputStream(f)); |
| byte[] bytes = new byte[1024 * 10]; |
| |
| for (int n = 0; n != -1; n = in.read(bytes)) { |
| fout.write(bytes, 0, n); |
| } |
| |
| } catch (IOException ioe) { |
| // We might get an IOException trying to overwrite an existing |
| // jline.dll file if there is another process using the DLL. |
| // If this happens, ignore errors. |
| if (!exists) { |
| throw ioe; |
| } |
| } finally { |
| if (fout != null) { |
| try { |
| fout.close(); |
| } catch (IOException ioe) { |
| // ignore |
| } |
| } |
| } |
| |
| // try to clean up the DLL after the JVM exits |
| f.deleteOnExit(); |
| |
| // now actually load the DLL |
| System.load(f.getAbsolutePath()); |
| } |
| |
| public int readVirtualKey(InputStream in) throws IOException { |
| int indicator = readCharacter(in); |
| |
| // in Windows terminals, arrow keys are represented by |
| // a sequence of 2 characters. E.g., the up arrow |
| // key yields 224, 72 |
| if (indicator == SPECIAL_KEY_INDICATOR |
| || indicator == NUMPAD_KEY_INDICATOR) { |
| int key = readCharacter(in); |
| |
| switch (key) { |
| case UP_ARROW_KEY: |
| return CTRL_P; // translate UP -> CTRL-P |
| case LEFT_ARROW_KEY: |
| return CTRL_B; // translate LEFT -> CTRL-B |
| case RIGHT_ARROW_KEY: |
| return CTRL_F; // translate RIGHT -> CTRL-F |
| case DOWN_ARROW_KEY: |
| return CTRL_N; // translate DOWN -> CTRL-N |
| case DELETE_KEY: |
| return CTRL_QM; // translate DELETE -> CTRL-? |
| case HOME_KEY: |
| return CTRL_A; |
| case END_KEY: |
| return CTRL_E; |
| case PAGE_UP_KEY: |
| return CTRL_K; |
| case PAGE_DOWN_KEY: |
| return CTRL_L; |
| case ESCAPE_KEY: |
| return CTRL_OB; // translate ESCAPE -> CTRL-[ |
| case INSERT_KEY: |
| return CTRL_C; |
| default: |
| return 0; |
| } |
| } else if (indicator > 128) { |
| // handle unicode characters longer than 2 bytes, |
| // thanks to Marc.Herbert@continuent.com |
| replayStream.setInput(indicator, in); |
| // replayReader = new InputStreamReader(replayStream, encoding); |
| indicator = replayReader.read(); |
| |
| } |
| |
| return indicator; |
| |
| } |
| |
| public boolean isSupported() { |
| return true; |
| } |
| |
| /** |
| * Windows doesn't support ANSI codes by default; disable them. |
| */ |
| public boolean isANSISupported() { |
| return false; |
| } |
| |
| public boolean getEcho() { |
| return false; |
| } |
| |
| /** |
| * Unsupported; return the default. |
| * |
| * @see Terminal#getTerminalWidth |
| */ |
| public int getTerminalWidth() { |
| return getWindowsTerminalWidth(); |
| } |
| |
| /** |
| * Unsupported; return the default. |
| * |
| * @see Terminal#getTerminalHeight |
| */ |
| public int getTerminalHeight() { |
| return getWindowsTerminalHeight(); |
| } |
| |
| /** |
| * No-op for exceptions we want to silently consume. |
| */ |
| private void consumeException(final Throwable e) { |
| } |
| |
| /** |
| * Whether or not to allow the use of the JNI console interaction. |
| */ |
| public void setDirectConsole(Boolean directConsole) { |
| this.directConsole = directConsole; |
| } |
| |
| /** |
| * Whether or not to allow the use of the JNI console interaction. |
| */ |
| public Boolean getDirectConsole() { |
| return this.directConsole; |
| } |
| |
| public synchronized boolean isEchoEnabled() { |
| return echoEnabled; |
| } |
| |
| public synchronized void enableEcho() { |
| // Must set these four modes at the same time to make it work fine. |
| setConsoleMode(getConsoleMode() | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT |
| | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); |
| echoEnabled = true; |
| } |
| |
| public synchronized void disableEcho() { |
| // Must set these four modes at the same time to make it work fine. |
| setConsoleMode(getConsoleMode() |
| & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT |
| | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT)); |
| echoEnabled = true; |
| } |
| |
| public InputStream getDefaultBindings() { |
| return WindowsTerminal.class.getResourceAsStream("windowsbindings.properties"); |
| } |
| |
| /** |
| * This is awkward and inefficient, but probably the minimal way to add |
| * UTF-8 support to JLine |
| * |
| * @author <a href="mailto:Marc.Herbert@continuent.com">Marc Herbert</a> |
| */ |
| static class ReplayPrefixOneCharInputStream extends InputStream { |
| byte firstByte; |
| int byteLength; |
| InputStream wrappedStream; |
| int byteRead; |
| |
| final String encoding; |
| |
| public ReplayPrefixOneCharInputStream(String encoding) { |
| this.encoding = encoding; |
| } |
| |
| public void setInput(int recorded, InputStream wrapped) throws IOException { |
| this.byteRead = 0; |
| this.firstByte = (byte) recorded; |
| this.wrappedStream = wrapped; |
| |
| byteLength = 1; |
| if (encoding.equalsIgnoreCase("UTF-8")) |
| setInputUTF8(recorded, wrapped); |
| else if (encoding.equalsIgnoreCase("UTF-16")) |
| byteLength = 2; |
| else if (encoding.equalsIgnoreCase("UTF-32")) |
| byteLength = 4; |
| } |
| |
| |
| public void setInputUTF8(int recorded, InputStream wrapped) throws IOException { |
| // 110yyyyy 10zzzzzz |
| if ((firstByte & (byte) 0xE0) == (byte) 0xC0) |
| this.byteLength = 2; |
| // 1110xxxx 10yyyyyy 10zzzzzz |
| else if ((firstByte & (byte) 0xF0) == (byte) 0xE0) |
| this.byteLength = 3; |
| // 11110www 10xxxxxx 10yyyyyy 10zzzzzz |
| else if ((firstByte & (byte) 0xF8) == (byte) 0xF0) |
| this.byteLength = 4; |
| else |
| throw new IOException("invalid UTF-8 first byte: " + firstByte); |
| } |
| |
| public int read() throws IOException { |
| if (available() == 0) |
| return -1; |
| |
| byteRead++; |
| |
| if (byteRead == 1) |
| return firstByte; |
| |
| return wrappedStream.read(); |
| } |
| |
| /** |
| * InputStreamReader is greedy and will try to read bytes in advance. We |
| * do NOT want this to happen since we use a temporary/"losing bytes" |
| * InputStreamReader above, that's why we hide the real |
| * wrappedStream.available() here. |
| */ |
| public int available() { |
| return byteLength - byteRead; |
| } |
| } |
| |
| } |