src/jdk.internal.le/share/classes/jdk/internal/jline/WindowsTerminal.java
changeset 47216 71c04702a3d5
parent 43540 3b6ae4a56de2
child 50338 1d5694c1aa03
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.internal.le/share/classes/jdk/internal/jline/WindowsTerminal.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,585 @@
+/*
+ * 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
+ * <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.
+     */
+    @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 <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
+     * <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.
+         */
+        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;
+        }
+    }
+
+}