1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.hbtop.terminal.impl;
19
20 import static org.apache.hadoop.hbase.hbtop.terminal.impl.EscapeSequences.clearAll;
21 import static org.apache.hadoop.hbase.hbtop.terminal.impl.EscapeSequences.cursor;
22 import static org.apache.hadoop.hbase.hbtop.terminal.impl.EscapeSequences.moveCursor;
23 import static org.apache.hadoop.hbase.hbtop.terminal.impl.EscapeSequences.normal;
24
25 import edu.umd.cs.findbugs.annotations.Nullable;
26 import java.io.BufferedReader;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.OutputStreamWriter;
30 import java.io.PrintWriter;
31 import java.nio.charset.StandardCharsets;
32 import java.util.Queue;
33 import java.util.StringTokenizer;
34 import java.util.concurrent.ConcurrentLinkedQueue;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.hadoop.hbase.classification.InterfaceAudience;
39 import org.apache.hadoop.hbase.hbtop.terminal.CursorPosition;
40 import org.apache.hadoop.hbase.hbtop.terminal.KeyPress;
41 import org.apache.hadoop.hbase.hbtop.terminal.Terminal;
42 import org.apache.hadoop.hbase.hbtop.terminal.TerminalPrinter;
43 import org.apache.hadoop.hbase.hbtop.terminal.TerminalSize;
44
45
46
47
48
49
50
51 @InterfaceAudience.Private
52 public class TerminalImpl implements Terminal {
53
54 private static final Log LOG = LogFactory.getLog(TerminalImpl.class);
55
56 private TerminalSize cachedTerminalSize;
57
58 private final PrintWriter output;
59
60 private final ScreenBuffer screenBuffer;
61
62 private final Queue<KeyPress> keyPressQueue;
63 private final KeyPressGenerator keyPressGenerator;
64
65 public TerminalImpl() {
66 this(null);
67 }
68
69 public TerminalImpl(@Nullable String title) {
70 output = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
71 sttyRaw();
72
73 if (title != null) {
74 setTitle(title);
75 }
76
77 screenBuffer = new ScreenBuffer();
78
79 cachedTerminalSize = queryTerminalSize();
80 updateTerminalSize(cachedTerminalSize.getColumns(), cachedTerminalSize.getRows());
81
82 keyPressQueue = new ConcurrentLinkedQueue<>();
83 keyPressGenerator = new KeyPressGenerator(System.in, keyPressQueue);
84 keyPressGenerator.start();
85
86 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
87 @Override
88 public void run() {
89 output.printf("%s%s%s%s", moveCursor(0, 0), cursor(true), normal(), clearAll());
90 output.flush();
91 sttyCooked();
92 }
93 }));
94
95
96 output.write(clearAll());
97 output.flush();
98 }
99
100 private void setTitle(String title) {
101 output.write(EscapeSequences.setTitle(title));
102 output.flush();
103 }
104
105 private void updateTerminalSize(int columns, int rows) {
106 screenBuffer.reallocate(columns, rows);
107 }
108
109 @Override
110 public void clear() {
111 screenBuffer.clear();
112 }
113
114 @Override
115 public void refresh() {
116 screenBuffer.flush(output);
117 }
118
119 @Override
120 public TerminalSize getSize() {
121 return cachedTerminalSize;
122 }
123
124 @Nullable
125 @Override
126 public TerminalSize doResizeIfNecessary() {
127 TerminalSize currentTerminalSize = queryTerminalSize();
128 if (!currentTerminalSize.equals(cachedTerminalSize)) {
129 cachedTerminalSize = currentTerminalSize;
130 updateTerminalSize(cachedTerminalSize.getColumns(), cachedTerminalSize.getRows());
131 return cachedTerminalSize;
132 }
133 return null;
134 }
135
136 @Nullable
137 @Override
138 public KeyPress pollKeyPress() {
139 return keyPressQueue.poll();
140 }
141
142 @Override
143 public CursorPosition getCursorPosition() {
144 return screenBuffer.getCursorPosition();
145 }
146
147 @Override
148 public void setCursorPosition(int column, int row) {
149 screenBuffer.setCursorPosition(column, row);
150 }
151
152 @Override
153 public void hideCursor() {
154 screenBuffer.hideCursor();
155 }
156
157 @Override
158 public TerminalPrinter getTerminalPrinter(int startRow) {
159 return new TerminalPrinterImpl(screenBuffer, startRow);
160 }
161
162 @Override
163 public void close() {
164 keyPressGenerator.stop();
165 }
166
167 private TerminalSize queryTerminalSize() {
168 String sizeString = doStty("size");
169
170 int rows = 0;
171 int columns = 0;
172
173 StringTokenizer tokenizer = new StringTokenizer(sizeString);
174 int rc = Integer.parseInt(tokenizer.nextToken());
175 if (rc > 0) {
176 rows = rc;
177 }
178
179 rc = Integer.parseInt(tokenizer.nextToken());
180 if (rc > 0) {
181 columns = rc;
182 }
183 return new TerminalSize(columns, rows);
184 }
185
186 private void sttyRaw() {
187 doStty("-ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost " +
188 "-echo -echonl -icanon -isig -iexten -parenb cs8 min 1");
189 }
190
191 private void sttyCooked() {
192 doStty("sane cooked");
193 }
194
195 private String doStty(String sttyOptionsString) {
196 String [] cmd = {"/bin/sh", "-c", "stty " + sttyOptionsString + " < /dev/tty"};
197
198 try {
199 Process process = Runtime.getRuntime().exec(cmd);
200
201 String ret;
202
203
204 try (BufferedReader stdout = new BufferedReader(new InputStreamReader(
205 process.getInputStream(), StandardCharsets.UTF_8))) {
206 ret = stdout.readLine();
207 }
208
209
210 try (BufferedReader stderr = new BufferedReader(new InputStreamReader(
211 process.getErrorStream(), StandardCharsets.UTF_8))) {
212 String line = stderr.readLine();
213 if ((line != null) && (line.length() > 0)) {
214 LOG.error("Error output from stty: " + line);
215 }
216 }
217
218 try {
219 process.waitFor();
220 } catch (InterruptedException ignored) {
221 }
222
223 int exitValue = process.exitValue();
224 if (exitValue != 0) {
225 LOG.error("stty returned error code: " + exitValue);
226 }
227 return ret;
228 } catch (IOException e) {
229 throw new RuntimeException(e);
230 }
231 }
232 }