2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 * Copyright (C) 2010-2015 Martin
19 package com
.googlecode
.lanterna
.terminal
.ansi
;
21 import com
.googlecode
.lanterna
.SGR
;
22 import com
.googlecode
.lanterna
.input
.*;
23 import com
.googlecode
.lanterna
.TerminalSize
;
24 import com
.googlecode
.lanterna
.TextColor
;
25 import com
.googlecode
.lanterna
.terminal
.ExtendedTerminal
;
26 import com
.googlecode
.lanterna
.terminal
.MouseCaptureMode
;
28 import java
.io
.IOException
;
29 import java
.io
.InputStream
;
30 import java
.io
.OutputStream
;
31 import java
.nio
.charset
.Charset
;
34 * Class containing graphics code for ANSI compliant text terminals and terminal emulators. All the methods inside of
35 * this class uses ANSI escape codes written to the underlying output stream.
37 * @see <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia</a>
40 public abstract class ANSITerminal
extends StreamBasedTerminal
implements ExtendedTerminal
{
42 private MouseCaptureMode mouseCaptureMode
;
43 private boolean inPrivateMode
;
45 @SuppressWarnings("WeakerAccess")
46 protected ANSITerminal(InputStream terminalInput
, OutputStream terminalOutput
, Charset terminalCharset
) {
47 super(terminalInput
, terminalOutput
, terminalCharset
);
48 this.inPrivateMode
= false;
49 this.mouseCaptureMode
= null;
50 getInputDecoder().addProfile(getDefaultKeyDecodingProfile());
54 * This method can be overridden in a custom terminal implementation to change the default key decoders.
55 * @return The KeyDecodingProfile used by the terminal when translating character sequences to keystrokes
57 protected KeyDecodingProfile
getDefaultKeyDecodingProfile() {
58 return new DefaultKeyDecodingProfile();
61 private void writeCSISequenceToTerminal(byte... tail
) throws IOException
{
62 byte[] completeSequence
= new byte[tail
.length
+ 2];
63 completeSequence
[0] = (byte)0x1b;
64 completeSequence
[1] = (byte)'[';
65 System
.arraycopy(tail
, 0, completeSequence
, 2, tail
.length
);
66 writeToTerminal(completeSequence
);
69 private void writeSGRSequenceToTerminal(byte... sgrParameters
) throws IOException
{
70 byte[] completeSequence
= new byte[sgrParameters
.length
+ 3];
71 completeSequence
[0] = (byte)0x1b;
72 completeSequence
[1] = (byte)'[';
73 completeSequence
[completeSequence
.length
- 1] = (byte)'m';
74 System
.arraycopy(sgrParameters
, 0, completeSequence
, 2, sgrParameters
.length
);
75 writeToTerminal(completeSequence
);
78 private void writeOSCSequenceToTerminal(byte... tail
) throws IOException
{
79 byte[] completeSequence
= new byte[tail
.length
+ 2];
80 completeSequence
[0] = (byte)0x1b;
81 completeSequence
[1] = (byte)']';
82 System
.arraycopy(tail
, 0, completeSequence
, 2, tail
.length
);
83 writeToTerminal(completeSequence
);
87 public TerminalSize
getTerminalSize() throws IOException
{
89 setCursorPosition(5000, 5000);
91 restoreCursorPosition();
92 return waitForTerminalSizeReport();
96 public void setTerminalSize(int columns
, int rows
) throws IOException
{
97 writeCSISequenceToTerminal(("8;" + rows
+ ";" + columns
+ "t").getBytes());
99 //We can't trust that the previous call was honoured by the terminal so force a re-query here, which will
100 //trigger a resize event if one actually took place
105 public void setTitle(String title
) throws IOException
{
106 //The bell character is our 'null terminator', make sure there's none in the title
107 title
= title
.replace("\007", "");
108 writeOSCSequenceToTerminal(("2;" + title
+ "\007").getBytes());
112 public void setForegroundColor(TextColor color
) throws IOException
{
113 writeSGRSequenceToTerminal(color
.getForegroundSGRSequence());
117 public void setBackgroundColor(TextColor color
) throws IOException
{
118 writeSGRSequenceToTerminal(color
.getBackgroundSGRSequence());
122 public void enableSGR(SGR sgr
) throws IOException
{
125 writeCSISequenceToTerminal((byte) '5', (byte) 'm');
128 writeCSISequenceToTerminal((byte) '1', (byte) 'm');
131 writeCSISequenceToTerminal((byte) '5', (byte) '1', (byte) 'm');
134 writeCSISequenceToTerminal((byte) '5', (byte) '2', (byte) 'm');
137 writeCSISequenceToTerminal((byte) '9', (byte) 'm');
140 writeCSISequenceToTerminal((byte) '2', (byte) '0', (byte) 'm');
143 writeCSISequenceToTerminal((byte) '7', (byte) 'm');
146 writeCSISequenceToTerminal((byte) '4', (byte) 'm');
152 public void disableSGR(SGR sgr
) throws IOException
{
155 writeCSISequenceToTerminal((byte) '2', (byte) '5', (byte) 'm');
158 writeCSISequenceToTerminal((byte) '2', (byte) '2', (byte) 'm');
161 writeCSISequenceToTerminal((byte) '5', (byte) '4', (byte) 'm');
164 writeCSISequenceToTerminal((byte) '5', (byte) '4', (byte) 'm');
167 writeCSISequenceToTerminal((byte) '2', (byte) '9', (byte) 'm');
170 writeCSISequenceToTerminal((byte) '2', (byte) '3', (byte) 'm');
173 writeCSISequenceToTerminal((byte) '2', (byte) '7', (byte) 'm');
176 writeCSISequenceToTerminal((byte) '2', (byte) '4', (byte) 'm');
182 public void resetColorAndSGR() throws IOException
{
183 writeCSISequenceToTerminal((byte) '0', (byte) 'm');
187 public void clearScreen() throws IOException
{
188 writeCSISequenceToTerminal((byte) '2', (byte) 'J');
192 public void enterPrivateMode() throws IOException
{
194 throw new IllegalStateException("Cannot call enterPrivateMode() when already in private mode");
196 writeCSISequenceToTerminal((byte) '?', (byte) '1', (byte) '0', (byte) '4', (byte) '9', (byte) 'h');
197 inPrivateMode
= true;
201 public void exitPrivateMode() throws IOException
{
203 throw new IllegalStateException("Cannot call exitPrivateMode() when not in private mode");
206 setCursorVisible(true);
207 writeCSISequenceToTerminal((byte) '?', (byte) '1', (byte) '0', (byte) '4', (byte) '9', (byte) 'l');
208 inPrivateMode
= false;
212 public void setCursorPosition(int x
, int y
) throws IOException
{
213 writeCSISequenceToTerminal(((y
+ 1) + ";" + (x
+ 1) + "H").getBytes());
217 public void setCursorVisible(boolean visible
) throws IOException
{
218 writeCSISequenceToTerminal(("?25" + (visible ?
"h" : "l")).getBytes());
222 public KeyStroke
readInput() throws IOException
{
225 keyStroke
= filterMouseEvents(super.readInput());
226 } while(keyStroke
== null);
231 public KeyStroke
pollInput() throws IOException
{
232 return filterMouseEvents(super.pollInput());
235 private KeyStroke
filterMouseEvents(KeyStroke keyStroke
) {
236 //Remove bad input events from terminals that are not following the xterm protocol properly
237 if(keyStroke
== null || keyStroke
.getKeyType() != KeyType
.MouseEvent
) {
241 MouseAction mouseAction
= (MouseAction
)keyStroke
;
242 switch(mouseAction
.getActionType()) {
244 if(mouseCaptureMode
== MouseCaptureMode
.CLICK
) {
249 if(mouseCaptureMode
== MouseCaptureMode
.CLICK
||
250 mouseCaptureMode
== MouseCaptureMode
.CLICK_RELEASE
) {
255 if(mouseCaptureMode
== MouseCaptureMode
.CLICK
||
256 mouseCaptureMode
== MouseCaptureMode
.CLICK_RELEASE
||
257 mouseCaptureMode
== MouseCaptureMode
.CLICK_RELEASE_DRAG
) {
267 public void pushTitle() throws IOException
{
268 throw new UnsupportedOperationException("Not implemented yet");
272 public void popTitle() throws IOException
{
273 throw new UnsupportedOperationException("Not implemented yet");
277 public void iconify() throws IOException
{
278 writeCSISequenceToTerminal((byte)'2', (byte)'t');
282 public void deiconify() throws IOException
{
283 writeCSISequenceToTerminal((byte)'1', (byte)'t');
287 public void maximize() throws IOException
{
288 writeCSISequenceToTerminal((byte)'9', (byte)';', (byte)'1', (byte)'t');
292 public void unmaximize() throws IOException
{
293 writeCSISequenceToTerminal((byte)'9', (byte)';', (byte)'0', (byte)'t');
297 public void setMouseCaptureMode(MouseCaptureMode mouseCaptureMode
) throws IOException
{
298 if(this.mouseCaptureMode
!= null) {
299 switch(this.mouseCaptureMode
) {
301 writeCSISequenceToTerminal((byte)'?', (byte)'9', (byte)'l');
304 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'0', (byte)'l');
306 case CLICK_RELEASE_DRAG
:
307 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'2', (byte)'l');
309 case CLICK_RELEASE_DRAG_MOVE
:
310 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'3', (byte)'l');
313 if(getCharset().equals(Charset
.forName("UTF-8"))) {
314 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'5', (byte)'l');
317 this.mouseCaptureMode
= mouseCaptureMode
;
318 if(this.mouseCaptureMode
!= null) {
319 switch(this.mouseCaptureMode
) {
321 writeCSISequenceToTerminal((byte)'?', (byte)'9', (byte)'h');
324 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'0', (byte)'h');
326 case CLICK_RELEASE_DRAG
:
327 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'2', (byte)'h');
329 case CLICK_RELEASE_DRAG_MOVE
:
330 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'3', (byte)'h');
333 if(getCharset().equals(Charset
.forName("UTF-8"))) {
334 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'5', (byte)'h');
340 * Method to test if the terminal (as far as the library knows) is in private mode.
342 * @return True if there has been a call to enterPrivateMode() but not yet exitPrivateMode()
344 boolean isInPrivateMode() {
345 return inPrivateMode
;
348 void reportPosition() throws IOException
{
349 writeCSISequenceToTerminal("6n".getBytes());
352 void restoreCursorPosition() throws IOException
{
353 writeCSISequenceToTerminal("u".getBytes());
356 void saveCursorPosition() throws IOException
{
357 writeCSISequenceToTerminal("s".getBytes());
361 public void scrollLines(int firstLine
, int lastLine
, int distance
) throws IOException
{
362 final String CSI
= "\033[";
364 // some sanity checks:
365 if (distance
== 0) { return; }
366 if (firstLine
< 0) { firstLine
= 0; }
367 if (lastLine
< firstLine
) { return; }
368 StringBuilder sb
= new StringBuilder();
371 sb
.append(CSI
).append(firstLine
+1)
372 .append(';').append(lastLine
+1).append('r');
374 // place cursor on line to scroll away from:
375 int target
= distance
> 0 ? lastLine
: firstLine
;
376 sb
.append(CSI
).append(target
+1).append(";1H");
380 int num
= Math
.min( distance
, lastLine
- firstLine
+ 1);
381 for (int i
= 0; i
< num
; i
++) { sb
.append('\n'); }
382 } else { // distance < 0
383 int num
= Math
.min( -distance
, lastLine
- firstLine
+ 1);
384 for (int i
= 0; i
< num
; i
++) { sb
.append("\033M"); }
388 sb
.append(CSI
).append('r');
391 writeToTerminal(sb
.toString().getBytes());