1 package org.argeo.swt.desktop;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.OutputStream;
7 import java.net.InetAddress;
8 import java.net.UnknownHostException;
9 import java.nio.charset.Charset;
10 import java.nio.charset.StandardCharsets;
11 import java.nio.file.Files;
12 import java.nio.file.Path;
13 import java.nio.file.Paths;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.List;
17 import java.util.StringTokenizer;
18
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.events.KeyEvent;
21 import org.eclipse.swt.events.KeyListener;
22 import org.eclipse.swt.events.PaintEvent;
23 import org.eclipse.swt.events.PaintListener;
24 import org.eclipse.swt.graphics.Font;
25 import org.eclipse.swt.graphics.GC;
26 import org.eclipse.swt.graphics.Point;
27 import org.eclipse.swt.layout.GridData;
28 import org.eclipse.swt.layout.GridLayout;
29 import org.eclipse.swt.widgets.Canvas;
30 import org.eclipse.swt.widgets.Caret;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.swt.widgets.Shell;
34
35 public class MiniTerminal implements KeyListener, PaintListener {
36
37 private Canvas area;
38 private Caret caret;
39
40 private StringBuffer buf = new StringBuffer("");
41 private StringBuffer userInput = new StringBuffer("");
42 private List<String> history = new ArrayList<>();
43
44 private Point charExtent = null;
45 private int charsPerLine = 0;
46 private String[] lines = new String[0];
47 private List<String> logicalLines = new ArrayList<>();
48
49 private Font mono;
50 private Charset charset;
51
52 private Path currentDir;
53 private Path homeDir;
54 private String host = "localhost";
55 private String username;
56
57
58 private Process process;
59 private boolean running = false;
60 private OutputStream stdIn = null;
61
62 private Thread readOut;
63
64 public MiniTerminal(Composite parent, int style) {
65 charset = StandardCharsets.UTF_8;
66
67 Display display = parent.getDisplay();
68
69 mono = new Font(display, "Monospace", 10, SWT.NONE);
70
71 parent.setLayout(new GridLayout());
72 area = new Canvas(parent, SWT.NONE);
73 area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
74 caret = new Caret(area, SWT.NONE);
75 area.setCaret(caret);
76
77 area.addKeyListener(this);
78 area.addPaintListener(this);
79
80 username = System.getProperty("user.name");
81 try {
82 host = InetAddress.getLocalHost().getHostName();
83 if (host.indexOf('.') > 0)
84 host = host.substring(0, host.indexOf('.'));
85 } catch (UnknownHostException e) {
86 host = "localhost";
87 }
88 homeDir = Paths.get(System.getProperty("user.home"));
89 currentDir = homeDir;
90
91 buf = new StringBuffer(prompt());
92 }
93
94 @Override
95 public void keyPressed(KeyEvent e) {
96 }
97
98 @Override
99 public void keyReleased(KeyEvent e) {
100 if (e.keyLocation != 0)
101 return;
102
103 if (e.keyCode == 0xd) {
104 markLogicalLine();
105 if (!running)
106 processUserInput();
107
108 } else if (e.keyCode == 0x8) {
109 if (userInput.length() == 0)
110 return;
111 userInput.setLength(userInput.length() - 1);
112 if (!running && buf.length() > 0)
113 buf.setLength(buf.length() - 1);
114 } else if (e.stateMask == 0x40000 && e.keyCode == 0x63) {
115 if (process != null)
116 process.destroy();
117 } else if (e.stateMask == 0x40000 && e.keyCode == 0xdf) {
118 if (process != null) {
119 process.destroyForcibly();
120 }
121 } else {
122
123 buf.append(e.character);
124 userInput.append(e.character);
125 }
126
127 if (area.isDisposed())
128 return;
129 area.redraw();
130
131
132 if (running) {
133 if (stdIn != null) {
134 try {
135 stdIn.write(Character.toString(e.character).getBytes(charset));
136 } catch (IOException e1) {
137
138 e1.printStackTrace();
139 }
140 }
141 }
142 }
143
144 protected String prompt() {
145 String fileName = currentDir.equals(homeDir) ? "~" : currentDir.getFileName().toString();
146 String end = username.equals("root") ? "]# " : "]$ ";
147 return "[" + username + "@" + host + " " + fileName + end;
148 }
149
150 private void displayPrompt() {
151 buf.append(prompt() + userInput);
152 }
153
154 protected void markLogicalLine() {
155 String str = buf.toString().trim();
156 logicalLines.add(str);
157 buf = new StringBuffer("");
158 }
159
160 private void processUserInput() {
161 String cmd = userInput.toString();
162 userInput = new StringBuffer("");
163 processUserInput(cmd);
164 history.add(cmd);
165 }
166
167 protected void processUserInput(String input) {
168 try {
169 StringTokenizer st = new StringTokenizer(input);
170 List<String> args = new ArrayList<>();
171 while (st.hasMoreTokens())
172 args.add(st.nextToken());
173 if (args.size() == 0) {
174 displayPrompt();
175 return;
176 }
177
178
179 if (args.get(0).equals("cd")) {
180 if (args.size() == 1) {
181 setPath(homeDir);
182 } else {
183 Path newPath = currentDir.resolve(args.get(1));
184 if (!Files.exists(newPath) || !Files.isDirectory(newPath)) {
185 println(newPath + ": No such file or directory");
186 return;
187 }
188 setPath(newPath);
189 }
190 displayPrompt();
191 return;
192 }
193
194 else if (args.get(0).equals("pwd")) {
195 println(currentDir);
196 displayPrompt();
197 return;
198 }
199
200 else if (args.get(0).equals("exit")) {
201 println("logout");
202 area.getShell().dispose();
203 return;
204 }
205
206 ProcessBuilder pb = new ProcessBuilder(args);
207 pb.redirectErrorStream(true);
208 pb.directory(currentDir.toFile());
209
210 process = pb.start();
211
212 stdIn = process.getOutputStream();
213 readOut = new Thread("MinitTerminal read out") {
214 @Override
215 public void run() {
216 running = true;
217 try (BufferedReader in = new BufferedReader(
218 new InputStreamReader(process.getInputStream(), charset))) {
219 String line = null;
220 while ((line = in.readLine()) != null) {
221 println(line);
222 }
223 } catch (IOException e) {
224 println(e.getMessage());
225 }
226 stdIn = null;
227 displayPrompt();
228 running = false;
229 readOut = null;
230 process = null;
231 }
232 };
233 readOut.start();
234 } catch (IOException e) {
235 println(e.getMessage());
236 displayPrompt();
237 }
238 }
239
240 protected int linesForLogicalLine(char[] line) {
241 return line.length / charsPerLine + 1;
242 }
243
244 protected void println(Object line) {
245 buf.append(line);
246 markLogicalLine();
247 }
248
249 protected void refreshLines(int charPerLine, int nbrOfLines) {
250 if (lines.length != nbrOfLines) {
251 lines = new String[nbrOfLines];
252 Arrays.fill(lines, null);
253 }
254 if (this.charsPerLine != charPerLine)
255 this.charsPerLine = charPerLine;
256
257 int currentLine = nbrOfLines - 1;
258
259 if (buf.length() > 0) {
260 lines[currentLine] = buf.toString();
261 } else {
262 lines[currentLine] = "";
263 }
264 currentLine--;
265
266 logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) {
267 char[] logicalLine = logicalLines.get(i).toCharArray();
268 int linesNeeded = linesForLogicalLine(logicalLine);
269 for (int j = linesNeeded - 1; j >= 0; j--) {
270 int from = j * charPerLine;
271 int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length);
272 lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to));
273
274 currentLine--;
275 if (currentLine < 0)
276 break logicalLines;
277 }
278 }
279 }
280
281 @Override
282 public void paintControl(PaintEvent e) {
283 GC gc = e.gc;
284 gc.setFont(mono);
285 if (charExtent == null)
286 charExtent = gc.textExtent("a");
287
288 Point areaSize = area.getSize();
289 int charPerLine = areaSize.x / charExtent.x;
290 int nbrOfLines = areaSize.y / charExtent.y;
291 refreshLines(charPerLine, nbrOfLines);
292
293 for (int i = 0; i < lines.length; i++) {
294 String line = lines[i];
295 if (line != null)
296 gc.drawString(line, 0, i * charExtent.y);
297 }
298
299
300
301 }
302
303 public void setPath(String path) {
304 this.currentDir = Paths.get(path);
305 }
306
307 public void setPath(Path path) {
308 this.currentDir = path;
309 }
310
311 public static void main(String[] args) {
312 Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
313 Shell shell = new Shell(display, SWT.SHELL_TRIM);
314
315 MiniTerminal miniBrowser = new MiniTerminal(shell, SWT.NONE);
316 String url = args.length > 0 ? args[0] : System.getProperty("user.home");
317 miniBrowser.setPath(url);
318
319 shell.open();
320 shell.setSize(new Point(800, 480));
321 while (!shell.isDisposed()) {
322 if (!display.readAndDispatch())
323 display.sleep();
324 }
325 }
326
327 }