1 /*
   2  * Copyright (c) 2002-2012, the original author or authors.
   3  *
   4  * This software is distributable under the BSD license. See the terms of the
   5  * BSD license in the documentation provided with this software.
   6  *
   7  * http://www.opensource.org/licenses/bsd-license.php
   8  */
   9 package jdk.internal.jline;
  10 
  11 import java.io.FileDescriptor;
  12 import java.io.FileInputStream;
  13 import java.io.IOException;
  14 import java.io.InputStream;
  15 
  16 import jdk.internal.jline.internal.Configuration;
  17 import jdk.internal.jline.internal.Log;
  18 //import org.fusesource.jansi.internal.WindowsSupport;
  19 //import org.fusesource.jansi.internal.Kernel32;
  20 //import static org.fusesource.jansi.internal.Kernel32.*;
  21 
  22 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_ECHO_INPUT;
  23 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_LINE_INPUT;
  24 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_PROCESSED_INPUT;
  25 import static jdk.internal.jline.WindowsTerminal.ConsoleMode.ENABLE_WINDOW_INPUT;
  26 
  27 /**
  28  * Terminal implementation for Microsoft Windows. Terminal initialization in
  29  * {@link #init} is accomplished by extracting the
  30  * <em>jline_<i>version</i>.dll</em>, saving it to the system temporary
  31  * directoy (determined by the setting of the <em>java.io.tmpdir</em> System
  32  * property), loading the library, and then calling the Win32 APIs <a
  33  * href="http://msdn.microsoft.com/library/default.asp?
  34  * url=/library/en-us/dllproc/base/setconsolemode.asp">SetConsoleMode</a> and
  35  * <a href="http://msdn.microsoft.com/library/default.asp?
  36  * url=/library/en-us/dllproc/base/getconsolemode.asp">GetConsoleMode</a> to
  37  * disable character echoing.
  38  * <p/>
  39  * <p>
  40  * By default, the {@link #wrapInIfNeeded(java.io.InputStream)} method will attempt
  41  * to test to see if the specified {@link InputStream} is {@link System#in} or a wrapper
  42  * around {@link FileDescriptor#in}, and if so, will bypass the character reading to
  43  * directly invoke the readc() method in the JNI library. This is so the class
  44  * can read special keys (like arrow keys) which are otherwise inaccessible via
  45  * the {@link System#in} stream. Using JNI reading can be bypassed by setting
  46  * the <code>jline.WindowsTerminal.directConsole</code> system property
  47  * to <code>false</code>.
  48  * </p>
  49  *
  50  * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
  51  * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
  52  * @since 2.0
  53  */
  54 public class WindowsTerminal
  55     extends TerminalSupport
  56 {
  57     public static final String DIRECT_CONSOLE = WindowsTerminal.class.getName() + ".directConsole";
  58 
  59     public static final String ANSI = WindowsTerminal.class.getName() + ".ansi";
  60 
  61     private boolean directConsole;
  62 
  63     private int originalMode;
  64 
  65     public WindowsTerminal() throws Exception {
  66         super(true);
  67         System.loadLibrary("le");
  68     }
  69 
  70     @Override
  71     public void init() throws Exception {
  72         super.init();
  73 
  74 //        setAnsiSupported(Configuration.getBoolean(ANSI, true));
  75         setAnsiSupported(false);
  76 
  77         //
  78         // FIXME: Need a way to disable direct console and sysin detection muck
  79         //
  80 
  81         setDirectConsole(Configuration.getBoolean(DIRECT_CONSOLE, true));
  82 
  83         this.originalMode = getConsoleMode();
  84         setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT.code);
  85         setEchoEnabled(false);
  86     }
  87 
  88     /**
  89      * Restore the original terminal configuration, which can be used when
  90      * shutting down the console reader. The ConsoleReader cannot be
  91      * used after calling this method.
  92      */
  93     @Override
  94     public void restore() throws Exception {
  95         // restore the old console mode
  96         setConsoleMode(originalMode);
  97         super.restore();
  98     }
  99 
 100     @Override
 101     public int getWidth() {
 102         int w = getWindowsTerminalWidth();
 103         return w < 1 ? DEFAULT_WIDTH : w;
 104     }
 105 
 106     @Override
 107     public int getHeight() {
 108         int h = getWindowsTerminalHeight();
 109         return h < 1 ? DEFAULT_HEIGHT : h;
 110     }
 111 
 112     @Override
 113     public void setEchoEnabled(final boolean enabled) {
 114         // Must set these four modes at the same time to make it work fine.
 115         if (enabled) {
 116             setConsoleMode(getConsoleMode() |
 117                 ENABLE_ECHO_INPUT.code |
 118                 ENABLE_LINE_INPUT.code |
 119                 ENABLE_PROCESSED_INPUT.code |
 120                 ENABLE_WINDOW_INPUT.code);
 121         }
 122         else {
 123             setConsoleMode(getConsoleMode() &
 124                 ~(ENABLE_LINE_INPUT.code |
 125                     ENABLE_ECHO_INPUT.code |
 126                     ENABLE_PROCESSED_INPUT.code |
 127                     ENABLE_WINDOW_INPUT.code));
 128         }
 129         super.setEchoEnabled(enabled);
 130     }
 131 
 132     /**
 133      * Whether or not to allow the use of the JNI console interaction.
 134      */
 135     public void setDirectConsole(final boolean flag) {
 136         this.directConsole = flag;
 137         Log.debug("Direct console: ", flag);
 138     }
 139 
 140     /**
 141      * Whether or not to allow the use of the JNI console interaction.
 142      */
 143     public Boolean getDirectConsole() {
 144         return directConsole;
 145     }
 146 
 147 
 148     @Override
 149     public InputStream wrapInIfNeeded(InputStream in) throws IOException {
 150         if (directConsole && isSystemIn(in)) {
 151             return new InputStream() {
 152                 private byte[] buf = null;
 153                 int bufIdx = 0;
 154 
 155                 @Override
 156                 public int read() throws IOException {
 157                     while (buf == null || bufIdx == buf.length) {
 158                         buf = readConsoleInput();
 159                         bufIdx = 0;
 160                     }
 161                     int c = buf[bufIdx] & 0xFF;
 162                     bufIdx++;
 163                     return c;
 164                 }
 165             };
 166         } else {
 167             return super.wrapInIfNeeded(in);
 168         }
 169     }
 170 
 171     protected boolean isSystemIn(final InputStream in) throws IOException {
 172         if (in == null) {
 173             return false;
 174         }
 175         else if (in == System.in) {
 176             return true;
 177         }
 178         else if (in instanceof FileInputStream && ((FileInputStream) in).getFD() == FileDescriptor.in) {
 179             return true;
 180         }
 181 
 182         return false;
 183     }
 184 
 185     @Override
 186     public String getOutputEncoding() {
 187         int codepage = getConsoleOutputCodepage();
 188         //http://docs.oracle.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
 189         String charsetMS = "ms" + codepage;
 190         if (java.nio.charset.Charset.isSupported(charsetMS)) {
 191             return charsetMS;
 192         }
 193         String charsetCP = "cp" + codepage;
 194         if (java.nio.charset.Charset.isSupported(charsetCP)) {
 195             return charsetCP;
 196         }
 197         Log.debug("can't figure out the Java Charset of this code page (" + codepage + ")...");
 198         return super.getOutputEncoding();
 199     }
 200 
 201     //
 202     // Original code:
 203     //
 204 //    private int getConsoleMode() {
 205 //        return WindowsSupport.getConsoleMode();
 206 //    }
 207 //
 208 //    private void setConsoleMode(int mode) {
 209 //        WindowsSupport.setConsoleMode(mode);
 210 //    }
 211 //
 212 //    private byte[] readConsoleInput() {
 213 //        // XXX does how many events to read in one call matter?
 214 //        INPUT_RECORD[] events = null;
 215 //        try {
 216 //            events = WindowsSupport.readConsoleInput(1);
 217 //        } catch (IOException e) {
 218 //            Log.debug("read Windows console input error: ", e);
 219 //        }
 220 //        if (events == null) {
 221 //            return new byte[0];
 222 //        }
 223 //        StringBuilder sb = new StringBuilder();
 224 //        for (int i = 0; i < events.length; i++ ) {
 225 //            KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
 226 //            //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar);
 227 //            if (keyEvent.keyDown) {
 228 //                if (keyEvent.uchar > 0) {
 229 //                    // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
 230 //                    // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
 231 //                    final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
 232 //                    // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
 233 //                    // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
 234 //                    final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
 235 //                    if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
 236 //                        && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
 237 //                        sb.append('\u001B'); // ESC
 238 //                    }
 239 //
 240 //                    sb.append(keyEvent.uchar);
 241 //                    continue;
 242 //                }
 243 //                // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
 244 //                // just add support for basic editing keys (no control state, no numpad keys)
 245 //                String escapeSequence = null;
 246 //                switch (keyEvent.keyCode) {
 247 //                case 0x21: // VK_PRIOR PageUp
 248 //                    escapeSequence = "\u001B[5~";
 249 //                    break;
 250 //                case 0x22: // VK_NEXT PageDown
 251 //                    escapeSequence = "\u001B[6~";
 252 //                    break;
 253 //                case 0x23: // VK_END
 254 //                    escapeSequence = "\u001B[4~";
 255 //                    break;
 256 //                case 0x24: // VK_HOME
 257 //                    escapeSequence = "\u001B[1~";
 258 //                    break;
 259 //                case 0x25: // VK_LEFT
 260 //                    escapeSequence = "\u001B[D";
 261 //                    break;
 262 //                case 0x26: // VK_UP
 263 //                    escapeSequence = "\u001B[A";
 264 //                    break;
 265 //                case 0x27: // VK_RIGHT
 266 //                    escapeSequence = "\u001B[C";
 267 //                    break;
 268 //                case 0x28: // VK_DOWN
 269 //                    escapeSequence = "\u001B[B";
 270 //                    break;
 271 //                case 0x2D: // VK_INSERT
 272 //                    escapeSequence = "\u001B[2~";
 273 //                    break;
 274 //                case 0x2E: // VK_DELETE
 275 //                    escapeSequence = "\u001B[3~";
 276 //                    break;
 277 //                default:
 278 //                    break;
 279 //                }
 280 //                if (escapeSequence != null) {
 281 //                    for (int k = 0; k < keyEvent.repeatCount; k++) {
 282 //                        sb.append(escapeSequence);
 283 //                    }
 284 //                }
 285 //            } else {
 286 //                // key up event
 287 //                // support ALT+NumPad input method
 288 //                if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
 289 //                    sb.append(keyEvent.uchar);
 290 //                }
 291 //            }
 292 //        }
 293 //        return sb.toString().getBytes();
 294 //    }
 295 //
 296 //    private int getConsoleOutputCodepage() {
 297 //        return Kernel32.GetConsoleOutputCP();
 298 //    }
 299 //
 300 //    private int getWindowsTerminalWidth() {
 301 //        return WindowsSupport.getWindowsTerminalWidth();
 302 //    }
 303 //
 304 //    private int getWindowsTerminalHeight() {
 305 //        return WindowsSupport.getWindowsTerminalHeight();
 306 //    }
 307 
 308     //
 309     // Native Bits
 310     //
 311     private native int getConsoleMode();
 312 
 313     private native void setConsoleMode(int mode);
 314 
 315     private byte[] readConsoleInput() {
 316         KEY_EVENT_RECORD keyEvent = readKeyEvent();
 317 
 318         return convertKeys(keyEvent).getBytes();
 319     }
 320 
 321     public static String convertKeys(KEY_EVENT_RECORD keyEvent) {
 322         if (keyEvent == null) {
 323             return "";
 324         }
 325 
 326         StringBuilder sb = new StringBuilder();
 327 
 328         if (keyEvent.keyDown) {
 329             if (keyEvent.uchar > 0) {
 330                 // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
 331                 // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
 332                 final int altState = KEY_EVENT_RECORD.ALT_PRESSED;
 333                 // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
 334                 // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
 335                 final int ctrlState = KEY_EVENT_RECORD.CTRL_PRESSED;
 336 
 337                 boolean handled = false;
 338 
 339                 if ((keyEvent.controlKeyState & ctrlState) != 0) {
 340                     switch (keyEvent.keyCode) {
 341                         case 0x43: //Ctrl-C
 342                             sb.append("\003");
 343                             handled = true;
 344                             break;
 345                     }
 346                 }
 347 
 348                 if ((keyEvent.controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
 349                     switch (keyEvent.keyCode) {
 350                         case 0x09: //Shift-Tab
 351                             sb.append("\033\133\132");
 352                             handled = true;
 353                             break;
 354                     }
 355                 }
 356 
 357                 if (!handled) {
 358                     if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
 359                         && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
 360                         sb.append('\u001B'); // ESC
 361                     }
 362 
 363                     sb.append(keyEvent.uchar);
 364                 }
 365             } else {
 366                 // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
 367                 // just add support for basic editing keys (no control state, no numpad keys)
 368                 String escapeSequence = null;
 369                 switch (keyEvent.keyCode) {
 370                 case 0x21: // VK_PRIOR PageUp
 371                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[5~", "\u001B[5;%d~");
 372                     break;
 373                 case 0x22: // VK_NEXT PageDown
 374                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[6~", "\u001B[6;%d~");
 375                     break;
 376                 case 0x23: // VK_END
 377                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[4~", "\u001B[4;%d~");
 378                     break;
 379                 case 0x24: // VK_HOME
 380                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[1~", "\u001B[1;%d~");
 381                     break;
 382                 case 0x25: // VK_LEFT
 383                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[D", "\u001B[1;%dD");
 384                     break;
 385                 case 0x26: // VK_UP
 386                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[A", "\u001B[1;%dA");
 387                     break;
 388                 case 0x27: // VK_RIGHT
 389                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[C", "\u001B[1;%dC");
 390                     break;
 391                 case 0x28: // VK_DOWN
 392                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[B", "\u001B[1;%dB");
 393                     break;
 394                 case 0x2D: // VK_INSERT
 395                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[2~", "\u001B[2;%d~");
 396                     break;
 397                 case 0x2E: // VK_DELETE
 398                     escapeSequence = escapeSequence(keyEvent.controlKeyState, "\u001B[3~", "\u001B[3;%d~");
 399                     break;
 400                 default:
 401                     break;
 402                 }
 403                 if (escapeSequence != null) {
 404                     for (int k = 0; k < keyEvent.repeatCount; k++) {
 405                         sb.append(escapeSequence);
 406                     }
 407                 }
 408             }
 409         } else {
 410             // key up event
 411             // support ALT+NumPad input method
 412             if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
 413                 sb.append(keyEvent.uchar);
 414             }
 415         }
 416         return sb.toString();
 417     }
 418 
 419     private static String escapeSequence(int controlKeyState, String noControlSequence, String withControlSequence) {
 420         int controlNum = 1;
 421 
 422         if ((controlKeyState & KEY_EVENT_RECORD.SHIFT_PRESSED) != 0) {
 423             controlNum += 1;
 424         }
 425 
 426         if ((controlKeyState & KEY_EVENT_RECORD.ALT_PRESSED) != 0) {
 427             controlNum += 2;
 428         }
 429 
 430         if ((controlKeyState & KEY_EVENT_RECORD.CTRL_PRESSED) != 0) {
 431             controlNum += 4;
 432         }
 433 
 434         if (controlNum > 1) {
 435             return String.format(withControlSequence, controlNum);
 436         } else {
 437             return noControlSequence;
 438         }
 439     }
 440 
 441     private native KEY_EVENT_RECORD readKeyEvent();
 442 
 443     public static class KEY_EVENT_RECORD {
 444         public final static int ALT_PRESSED = 0x3;
 445         public final static int CTRL_PRESSED = 0xC;
 446         public final static int SHIFT_PRESSED = 0x10;
 447         public final boolean keyDown;
 448         public final char uchar;
 449         public final int controlKeyState;
 450         public final int keyCode;
 451         public final int repeatCount;
 452 
 453         public KEY_EVENT_RECORD(boolean keyDown, char uchar, int controlKeyState, int keyCode, int repeatCount) {
 454             this.keyDown = keyDown;
 455             this.uchar = uchar;
 456             this.controlKeyState = controlKeyState;
 457             this.keyCode = keyCode;
 458             this.repeatCount = repeatCount;
 459         }
 460 
 461     }
 462 
 463     private native int getConsoleOutputCodepage();
 464 
 465     private native int getWindowsTerminalWidth();
 466 
 467     private native int getWindowsTerminalHeight();
 468 
 469     /**
 470      * Console mode
 471      * <p/>
 472      * Constants copied <tt>wincon.h</tt>.
 473      */
 474     public static enum ConsoleMode
 475     {
 476         /**
 477          * The ReadFile or ReadConsole function returns only when a carriage return
 478          * character is read. If this mode is disable, the functions return when one
 479          * or more characters are available.
 480          */
 481         ENABLE_LINE_INPUT(2),
 482 
 483         /**
 484          * Characters read by the ReadFile or ReadConsole function are written to
 485          * the active screen buffer as they are read. This mode can be used only if
 486          * the ENABLE_LINE_INPUT mode is also enabled.
 487          */
 488         ENABLE_ECHO_INPUT(4),
 489 
 490         /**
 491          * CTRL+C is processed by the system and is not placed in the input buffer.
 492          * If the input buffer is being read by ReadFile or ReadConsole, other
 493          * control keys are processed by the system and are not returned in the
 494          * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also
 495          * enabled, backspace, carriage return, and linefeed characters are handled
 496          * by the system.
 497          */
 498         ENABLE_PROCESSED_INPUT(1),
 499 
 500         /**
 501          * User interactions that change the size of the console screen buffer are
 502          * reported in the console's input buffee. Information about these events
 503          * can be read from the input buffer by applications using
 504          * theReadConsoleInput function, but not by those using ReadFile
 505          * orReadConsole.
 506          */
 507         ENABLE_WINDOW_INPUT(8),
 508 
 509         /**
 510          * If the mouse pointer is within the borders of the console window and the
 511          * window has the keyboard focus, mouse events generated by mouse movement
 512          * and button presses are placed in the input buffer. These events are
 513          * discarded by ReadFile or ReadConsole, even when this mode is enabled.
 514          */
 515         ENABLE_MOUSE_INPUT(16),
 516 
 517         /**
 518          * When enabled, text entered in a console window will be inserted at the
 519          * current cursor location and all text following that location will not be
 520          * overwritten. When disabled, all following text will be overwritten. An OR
 521          * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS
 522          * flag to enable this functionality.
 523          */
 524         ENABLE_PROCESSED_OUTPUT(1),
 525 
 526         /**
 527          * This flag enables the user to use the mouse to select and edit text. To
 528          * enable this option, use the OR to combine this flag with
 529          * ENABLE_EXTENDED_FLAGS.
 530          */
 531         ENABLE_WRAP_AT_EOL_OUTPUT(2),;
 532 
 533         public final int code;
 534 
 535         ConsoleMode(final int code) {
 536             this.code = code;
 537         }
 538     }
 539 
 540 }