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