All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jline.WindowsTerminal Maven / Gradle / Ivy

There is a newer version: 2024.03.6
Show newest version
/*
 * 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;

/**
 * 

* Terminal implementation for Microsoft Windows. Terminal initialization in * {@link #initializeTerminal} 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 #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 jline.WindowsTerminal.disableDirectConsole system property * to true. *

* * @author Marc Prud'hommeaux */ 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 = getClass().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(getClass() .getResourceAsStream(name + bits + ".dll")); try { OutputStream 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); } fout.close(); } 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; } } // 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 [email protected] 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 getClass().getResourceAsStream("windowsbindings.properties"); } /** * This is awkward and inefficient, but probably the minimal way to add * UTF-8 support to JLine * * @author Marc Herbert */ 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy