1 /*
2  * Copyright (c) 2002-2018, 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  * https://opensource.org/licenses/BSD-3-Clause
8  */
9 package jdk.internal.org.jline.terminal.impl;
10 
11 import java.io.IOException;
12 import java.io.InterruptedIOException;
13 import java.nio.charset.Charset;
14 import java.util.EnumSet;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Map;
18 import java.util.Objects;
19 import java.util.Set;
20 import java.util.function.IntConsumer;
21 import java.util.function.IntSupplier;
22 
23 import jdk.internal.org.jline.terminal.Attributes;
24 import jdk.internal.org.jline.terminal.Attributes.ControlChar;
25 import jdk.internal.org.jline.terminal.Attributes.InputFlag;
26 import jdk.internal.org.jline.terminal.Attributes.LocalFlag;
27 import jdk.internal.org.jline.terminal.Cursor;
28 import jdk.internal.org.jline.terminal.MouseEvent;
29 import jdk.internal.org.jline.terminal.Terminal;
30 import jdk.internal.org.jline.utils.Curses;
31 import jdk.internal.org.jline.utils.InfoCmp;
32 import jdk.internal.org.jline.utils.InfoCmp.Capability;
33 import jdk.internal.org.jline.utils.Log;
34 import jdk.internal.org.jline.utils.Status;
35 
36 public abstract class AbstractTerminal implements Terminal {
37 
38     protected final String name;
39     protected final String type;
40     protected final Charset encoding;
41     protected final Map<Signal, SignalHandler> handlers = new HashMap<>();
42     protected final Set<Capability> bools = new HashSet<>();
43     protected final Map<Capability, Integer> ints = new HashMap<>();
44     protected final Map<Capability, String> strings = new HashMap<>();
45     protected Status status;
46 
AbstractTerminal(String name, String type)47     public AbstractTerminal(String name, String type) throws IOException {
48         this(name, type, null, SignalHandler.SIG_DFL);
49     }
50 
AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler)51     public AbstractTerminal(String name, String type, Charset encoding, SignalHandler signalHandler) throws IOException {
52         this.name = name;
53         this.type = type;
54         this.encoding = encoding != null ? encoding : Charset.defaultCharset();
55         for (Signal signal : Signal.values()) {
56             handlers.put(signal, signalHandler);
57         }
58     }
59 
getStatus()60     public Status getStatus() {
61         return getStatus(true);
62     }
63 
getStatus(boolean create)64     public Status getStatus(boolean create) {
65         if (status == null && create) {
66             status = new Status(this);
67         }
68         return status;
69     }
70 
handle(Signal signal, SignalHandler handler)71     public SignalHandler handle(Signal signal, SignalHandler handler) {
72         Objects.requireNonNull(signal);
73         Objects.requireNonNull(handler);
74         return handlers.put(signal, handler);
75     }
76 
raise(Signal signal)77     public void raise(Signal signal) {
78         Objects.requireNonNull(signal);
79         SignalHandler handler = handlers.get(signal);
80         if (handler != SignalHandler.SIG_DFL && handler != SignalHandler.SIG_IGN) {
81             handler.handle(signal);
82         }
83         if (status != null && signal == Signal.WINCH) {
84             status.resize();
85         }
86     }
87 
close()88     public void close() throws IOException {
89         if (status != null) {
90             status.update(null);
91             flush();
92         }
93     }
94 
echoSignal(Signal signal)95     protected void echoSignal(Signal signal) {
96         ControlChar cc = null;
97         switch (signal) {
98             case INT:
99                 cc = ControlChar.VINTR;
100                 break;
101             case QUIT:
102                 cc = ControlChar.VQUIT;
103                 break;
104             case TSTP:
105                 cc = ControlChar.VSUSP;
106                 break;
107         }
108         if (cc != null) {
109             int vcc = getAttributes().getControlChar(cc);
110             if (vcc > 0 && vcc < 32) {
111                 writer().write(new char[]{'^', (char) (vcc + '@')}, 0, 2);
112             }
113         }
114     }
115 
enterRawMode()116     public Attributes enterRawMode() {
117         Attributes prvAttr = getAttributes();
118         Attributes newAttr = new Attributes(prvAttr);
119         newAttr.setLocalFlags(EnumSet.of(LocalFlag.ICANON, LocalFlag.ECHO, LocalFlag.IEXTEN), false);
120         newAttr.setInputFlags(EnumSet.of(InputFlag.IXON, InputFlag.ICRNL, InputFlag.INLCR), false);
121         newAttr.setControlChar(ControlChar.VMIN, 0);
122         newAttr.setControlChar(ControlChar.VTIME, 1);
123         setAttributes(newAttr);
124         return prvAttr;
125     }
126 
echo()127     public boolean echo() {
128         return getAttributes().getLocalFlag(LocalFlag.ECHO);
129     }
130 
echo(boolean echo)131     public boolean echo(boolean echo) {
132         Attributes attr = getAttributes();
133         boolean prev = attr.getLocalFlag(LocalFlag.ECHO);
134         if (prev != echo) {
135             attr.setLocalFlag(LocalFlag.ECHO, echo);
136             setAttributes(attr);
137         }
138         return prev;
139     }
140 
getName()141     public String getName() {
142         return name;
143     }
144 
getType()145     public String getType() {
146         return type;
147     }
148 
getKind()149     public String getKind() {
150         return getClass().getSimpleName();
151     }
152 
153     @Override
encoding()154     public Charset encoding() {
155         return this.encoding;
156     }
157 
flush()158     public void flush() {
159         writer().flush();
160     }
161 
puts(Capability capability, Object... params)162     public boolean puts(Capability capability, Object... params) {
163         String str = getStringCapability(capability);
164         if (str == null) {
165             return false;
166         }
167         Curses.tputs(writer(), str, params);
168         return true;
169     }
170 
getBooleanCapability(Capability capability)171     public boolean getBooleanCapability(Capability capability) {
172         return bools.contains(capability);
173     }
174 
getNumericCapability(Capability capability)175     public Integer getNumericCapability(Capability capability) {
176         return ints.get(capability);
177     }
178 
getStringCapability(Capability capability)179     public String getStringCapability(Capability capability) {
180         return strings.get(capability);
181     }
182 
parseInfoCmp()183     protected void parseInfoCmp() {
184         String capabilities = null;
185         if (type != null) {
186             try {
187                 capabilities = InfoCmp.getInfoCmp(type);
188             } catch (Exception e) {
189                 Log.warn("Unable to retrieve infocmp for type " + type, e);
190             }
191         }
192         if (capabilities == null) {
193             capabilities = InfoCmp.getLoadedInfoCmp("ansi");
194         }
195         InfoCmp.parseInfoCmp(capabilities, bools, ints, strings);
196     }
197 
198     @Override
getCursorPosition(IntConsumer discarded)199     public Cursor getCursorPosition(IntConsumer discarded) {
200         return null;
201     }
202 
203     private MouseEvent lastMouseEvent = new MouseEvent(
204                 MouseEvent.Type.Moved, MouseEvent.Button.NoButton,
205                 EnumSet.noneOf(MouseEvent.Modifier.class), 0, 0);
206 
207     @Override
hasMouseSupport()208     public boolean hasMouseSupport() {
209         return MouseSupport.hasMouseSupport(this);
210     }
211 
212     @Override
trackMouse(MouseTracking tracking)213     public boolean trackMouse(MouseTracking tracking) {
214         return MouseSupport.trackMouse(this, tracking);
215     }
216 
217     @Override
readMouseEvent()218     public MouseEvent readMouseEvent() {
219         return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent);
220     }
221 
222     @Override
readMouseEvent(IntSupplier reader)223     public MouseEvent readMouseEvent(IntSupplier reader) {
224         return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent);
225     }
226 
227     @Override
hasFocusSupport()228     public boolean hasFocusSupport() {
229         return type != null && type.startsWith("xterm");
230     }
231 
232     @Override
trackFocus(boolean tracking)233     public boolean trackFocus(boolean tracking) {
234         if (hasFocusSupport()) {
235             writer().write(tracking ? "\033[?1004h" : "\033[?1004l");
236             writer().flush();
237             return true;
238         } else {
239             return false;
240         }
241     }
242 
checkInterrupted()243     protected void checkInterrupted() throws InterruptedIOException {
244         if (Thread.interrupted()) {
245             throw new InterruptedIOException();
246         }
247     }
248 
249     @Override
canPauseResume()250     public boolean canPauseResume() {
251         return false;
252     }
253 
254     @Override
pause()255     public void pause() {
256     }
257 
258     @Override
pause(boolean wait)259     public void pause(boolean wait) throws InterruptedException {
260     }
261 
262     @Override
resume()263     public void resume() {
264     }
265 
266     @Override
paused()267     public boolean paused() {
268         return false;
269     }
270 
271 }
272