View Javadoc
1   /**
2    * Copyright 2017 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.grouper.app.gsh.jline;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  
23  import org.fusesource.jansi.internal.WindowsSupport;
24  import org.fusesource.jansi.internal.Kernel32.INPUT_RECORD;
25  import org.fusesource.jansi.internal.Kernel32.KEY_EVENT_RECORD;
26  
27  import edu.internet2.middleware.grouper.app.gsh.GrouperShell;
28  
29  import jline.AnsiWindowsTerminal;
30  
31  public class WindowsTerminal extends AnsiWindowsTerminal {
32    
33    /**
34     * @throws Exception
35     */
36    public WindowsTerminal() throws Exception {
37      super();
38    }
39    
40    @Override
41    public InputStream wrapInIfNeeded(InputStream in) throws IOException {
42  
43      String groovyPreloadString = GrouperShell.getGroovyPreloadString() + "\n";
44      final ByteArrayInputStream groovyPreload = new ByteArrayInputStream(groovyPreloadString.getBytes("UTF-8"));
45      
46      return new InputStream() {
47        private byte[] buf = null;
48        int bufIdx = 0;
49  
50        @Override
51        public int read() throws IOException {
52          if (groovyPreload.available() > 0) {
53            return groovyPreload.read();
54          }
55          
56          while (buf == null || bufIdx == buf.length) {
57            buf = readConsoleInput();
58            bufIdx = 0;
59          }
60          int c = buf[bufIdx] & 0xFF;
61          bufIdx++;
62          return c;
63        }
64      };
65    }
66  
67    // completely copied from jline
68    private byte[] readConsoleInput() {
69      // XXX does how many events to read in one call matter?
70      INPUT_RECORD[] events = null;
71      try {
72        events = WindowsSupport.readConsoleInput(1);
73      } catch (IOException e) {
74  
75      }
76      if (events == null) {
77        return new byte[0];
78      }
79      StringBuilder sb = new StringBuilder();
80      for (int i = 0; i < events.length; i++ ) {
81        KEY_EVENT_RECORD keyEvent = events[i].keyEvent;
82        //Log.trace(keyEvent.keyDown? "KEY_DOWN" : "KEY_UP", "key code:", keyEvent.keyCode, "char:", (long)keyEvent.uchar); 
83        if (keyEvent.keyDown) {
84          if (keyEvent.uchar > 0) {
85            // support some C1 control sequences: ALT + [@-_] (and [a-z]?) => ESC <ascii>
86            // http://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_set
87            final int altState = KEY_EVENT_RECORD.LEFT_ALT_PRESSED | KEY_EVENT_RECORD.RIGHT_ALT_PRESSED;
88            // Pressing "Alt Gr" is translated to Alt-Ctrl, hence it has to be checked that Ctrl is _not_ pressed,
89            // otherwise inserting of "Alt Gr" codes on non-US keyboards would yield errors
90            final int ctrlState = KEY_EVENT_RECORD.LEFT_CTRL_PRESSED | KEY_EVENT_RECORD.RIGHT_CTRL_PRESSED;
91            if (((keyEvent.uchar >= '@' && keyEvent.uchar <= '_') || (keyEvent.uchar >= 'a' && keyEvent.uchar <= 'z'))
92                && ((keyEvent.controlKeyState & altState) != 0) && ((keyEvent.controlKeyState & ctrlState) == 0)) {
93              sb.append('\u001B'); // ESC
94            }
95  
96            sb.append(keyEvent.uchar);
97            continue;
98          }
99          // virtual keycodes: http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
100         // just add support for basic editing keys (no control state, no numpad keys)
101         String escapeSequence = null;
102         switch (keyEvent.keyCode) {
103           case 0x21: // VK_PRIOR PageUp
104             escapeSequence = "\u001B[5~";
105             break;
106           case 0x22: // VK_NEXT PageDown
107             escapeSequence = "\u001B[6~";
108             break;
109           case 0x23: // VK_END
110             escapeSequence = "\u001B[4~";
111             break;
112           case 0x24: // VK_HOME
113             escapeSequence = "\u001B[1~";
114             break;
115           case 0x25: // VK_LEFT
116             escapeSequence = "\u001B[D";
117             break;
118           case 0x26: // VK_UP
119             escapeSequence = "\u001B[A";
120             break;
121           case 0x27: // VK_RIGHT
122             escapeSequence = "\u001B[C";
123             break;
124           case 0x28: // VK_DOWN
125             escapeSequence = "\u001B[B";
126             break;
127           case 0x2D: // VK_INSERT
128             escapeSequence = "\u001B[2~";
129             break;
130           case 0x2E: // VK_DELETE
131             escapeSequence = "\u001B[3~";
132             break;
133           default:
134             break;
135         }
136         if (escapeSequence != null) {
137           for (int k = 0; k < keyEvent.repeatCount; k++) {
138             sb.append(escapeSequence);
139           }
140         }
141       } else {
142         // key up event
143         // support ALT+NumPad input method
144         if (keyEvent.keyCode == 0x12/*VK_MENU ALT key*/ && keyEvent.uchar > 0) {
145           sb.append(keyEvent.uchar);
146         }
147       }
148     }
149     return sb.toString().getBytes();
150   }
151 }