View Javadoc

1   /**
2    *	jline - Java console input library
3    *	Copyright (c) 2002, 2003, 2004, 2005, Marc Prud'hommeaux <mwp1@cornell.edu>
4    *	All rights reserved.
5    *
6    *	Redistribution and use in source and binary forms, with or
7    *	without modification, are permitted provided that the following
8    *	conditions are met:
9    *
10   *	Redistributions of source code must retain the above copyright
11   *	notice, this list of conditions and the following disclaimer.
12   *
13   *	Redistributions in binary form must reproduce the above copyright
14   *	notice, this list of conditions and the following disclaimer
15   *	in the documentation and/or other materials provided with
16   *	the distribution.
17   *
18   *	Neither the name of JLine nor the names of its contributors
19   *	may be used to endorse or promote products derived from this
20   *	software without specific prior written permission.
21   *
22   *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23   *	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24   *	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
25   *	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
26   *	EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
27   *	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
28   *	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29   *	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30   *	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31   *	AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32   *	LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
33   *	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34   *	OF THE POSSIBILITY OF SUCH DAMAGE.
35   */
36  package jline;
37  
38  import java.io.*;
39  import java.util.*;
40  
41  
42  /**
43   *  <p>
44   *  Terminal that is used for unix platforms. Terminal initialization
45   *  is handled by issuing the <em>stty</em> command against the
46   *  <em>/dev/tty</em> file to disable character echoing and enable
47   *  character input. All known unix systems (including
48   *  Linux and Macintosh OS X) support the <em>stty</em>), so this
49   *  implementation should work for an reasonable POSIX system.
50   *	</p>
51   *
52   *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
53   */
54  public class UnixTerminal
55  	extends Terminal
56  {
57  	public static final short ARROW_START           = 27;
58  	public static final short ARROW_PREFIX          = 91;
59  	public static final short ARROW_LEFT            = 68;
60  	public static final short ARROW_RIGHT           = 67;
61  	public static final short ARROW_UP              = 65;
62  	public static final short ARROW_DOWN            = 66;
63  
64  	private Map terminfo;
65  	private int width = -1;
66  	private int height = -1;
67  
68  
69  	/**
70  	 *  Remove line-buffered input by invoking "stty -icanon min 1"
71  	 *  against the current terminal.
72  	 */
73  	public void initializeTerminal ()
74  		throws IOException, InterruptedException
75  	{
76  		// save the initial tty configuration
77  		final String ttyConfig = stty ("-g");
78  
79  		// sanity check
80  		if (ttyConfig.length () == 0
81  			|| (ttyConfig.indexOf ("=") == -1
82  			&& ttyConfig.indexOf (":") == -1))
83  		{
84  			throw new IOException ("Unrecognized stty code: " + ttyConfig);
85  		}
86  
87  
88  		// set the console to be character-buffered instead of line-buffered
89  		stty ("-icanon min 1");
90  
91  		// disable character echoing
92  		stty ("-echo");
93  
94  		// at exit, restore the original tty configuration (for JDK 1.3+)
95  		try
96  		{
97  			Runtime.getRuntime ().addShutdownHook (new Thread ()
98  			{
99  				public void start ()
100 				{
101 					try
102 					{
103 						stty (ttyConfig);
104 					}
105 					catch (Exception e)
106 					{
107 						consumeException (e);
108 					}
109 				}
110 			});
111 		}
112 		catch (AbstractMethodError ame)
113 		{
114 			// JDK 1.3+ only method. Bummer.
115 			consumeException (ame);
116 		}
117 	}
118 
119 
120 	public int readVirtualKey (InputStream in)
121 		throws IOException
122 	{
123 		int c = readCharacter (in);
124 
125 		// in Unix terminals, arrow keys are represented by
126 		// a sequence of 3 characters. E.g., the up arrow
127 		// key yields 27, 91, 68
128 		if (c == ARROW_START)
129 		{
130 			c = readCharacter (in);
131 			if (c == ARROW_PREFIX)
132 			{
133 				c = readCharacter (in);
134 				if (c == ARROW_UP)
135 					return CTRL_P;
136 				else if (c == ARROW_DOWN)
137 					return CTRL_N;
138 				else if (c == ARROW_LEFT)
139 					return CTRL_B;
140 				else if (c == ARROW_RIGHT)
141 					return CTRL_F;
142 			}
143 		}
144 
145 
146 		return c;
147 	}
148 
149 
150 	/** 
151 	 *  No-op for exceptions we want to silently consume.
152 	 */
153 	private void consumeException (Throwable e)
154 	{
155 	}
156 
157 
158 	public boolean isSupported ()
159 	{
160 		return true;
161 	}
162 
163 
164 	public boolean getEcho ()
165 	{
166 		return false;
167 	}
168 
169 
170 	/**
171  	 *	Returns the value of "stty size" width param.
172 	 *
173 	 *	<strong>Note</strong>: this method caches the value from the
174 	 *	first time it is called in order to increase speed, which means
175 	 *	that changing to size of the terminal will not be reflected
176 	 *	in the console.
177  	 */
178 	public int getTerminalWidth ()
179 	{
180 		if (width != -1)
181 			return width;
182 
183 		int val = 80;
184 		try
185 		{
186 			String size = stty ("size");
187 			if (size.length () != 0 && size.indexOf (" ") != -1)
188 			{
189 				val = Integer.parseInt (
190 					size.substring (size.indexOf (" ") + 1));
191 			}
192 		}
193 		catch (Exception e)
194 		{
195 			consumeException (e);
196 		}
197 
198 		return width = val;
199 	}
200 
201 
202 	/**
203  	 *	Returns the value of "stty size" height param.
204 	 *
205 	 *	<strong>Note</strong>: this method caches the value from the
206 	 *	first time it is called in order to increase speed, which means
207 	 *	that changing to size of the terminal will not be reflected
208 	 *	in the console.
209  	 */
210 	public int getTerminalHeight ()
211 	{
212 		if (height != -1)
213 			return height;
214 
215 		int val = 24;
216 
217 		try
218 		{
219 			String size = stty ("size");
220 			if (size.length () != 0 && size.indexOf (" ") != -1)
221 			{
222 				val = Integer.parseInt (
223 					size.substring (0, size.indexOf (" ")));
224 			}
225 		}
226 		catch (Exception e)
227 		{
228 		}
229 
230 		return height = val;
231 	}
232 
233 
234 	/**
235 	 *  Execute the stty command with the specified arguments
236 	 *  against the current active terminal.
237 	 */
238 	private static String stty (final String args)
239 		throws IOException, InterruptedException
240 	{
241 		return exec ("stty " + args + " < /dev/tty").trim ();
242 	}
243 
244 
245 	/**
246 	 *  Execute the specified command and return the output
247 	 *  (both stdout and stderr).
248 	 */
249 	private static String exec (final String cmd)
250 		throws IOException, InterruptedException
251 	{
252 		return exec (new String [] { "sh", "-c", cmd });
253 	}
254 
255 
256 	/**
257 	 *  Execute the specified command and return the output
258 	 *  (both stdout and stderr).
259 	 */
260 	private static String exec (final String [] cmd)
261 		throws IOException, InterruptedException
262 	{
263 		ByteArrayOutputStream bout = new ByteArrayOutputStream ();
264 
265 		Process p = Runtime.getRuntime ().exec (cmd);
266 		int c;
267 		InputStream in;
268 			
269 		in = p.getInputStream ();
270 		while ((c = in.read ()) != -1)
271 			bout.write (c);
272 
273 		in = p.getErrorStream ();
274 		while ((c = in.read ()) != -1)
275 			bout.write (c);
276 
277 		p.waitFor ();
278 
279 		String result = new String (bout.toByteArray ());
280 		return result;
281 	}
282 }
283