1 /*
2 * MiniTerminal - A basic windowed terminal to allow
3 * command-line interaction from within app_server.
4 * Based on consoled and MuTerminal.
5 *
6 * Copyright 2005 Michael Lotz. All rights reserved.
7 * Distributed under the MIT License.
8 *
9 * Copyright 2004-2005, Haiku. All rights reserved.
10 * Distributed under the terms of the MIT License.
11 *
12 * Copyright 2002, Travis Geiselbrecht. All rights reserved.
13 * Distributed under the terms of the NewOS License.
14 */
15
16
17 #include <OS.h>
18 #include <image.h>
19 #include <Message.h>
20 #include <Window.h>
21
22 #include <termios.h>
23 #include <unistd.h>
24 #include <dirent.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <signal.h>
29
30 #include "Arguments.h"
31 #include "Console.h"
32 #include "MiniView.h"
33
34 #include "VTkeymap.h"
35
36 //#define TRACE_MINI_TERMINAL
37 #ifdef TRACE_MINI_TERMINAL
38 #ifdef __HAIKU__
39 #define TRACE(x) debug_printf x
40 #else
41 #define TRACE(x) printf x
42 #endif
43 #else
44 #define TRACE(x)
45 #endif
46
47 void
Setenv(const char * var,const char * value)48 Setenv(const char *var, const char *value)
49 {
50 int envindex = 0;
51 const int len = strlen(var);
52 const int val_len = strlen (value);
53
54 while (environ [envindex] != NULL) {
55 if (strncmp (environ [envindex], var, len) == 0) {
56 /* found it */
57 // TODO: shouldn't we free the old variable first?
58 environ[envindex] = (char *)malloc ((unsigned)len + val_len + 2);
59 sprintf (environ [envindex], "%s=%s", var, value);
60 return;
61 }
62 envindex ++;
63 }
64
65 environ [envindex] = (char *) malloc ((unsigned)len + val_len + 2);
66 sprintf (environ [envindex], "%s=%s", var, value);
67 environ [++envindex] = NULL;
68 }
69
MiniView(const Arguments & args)70 MiniView::MiniView(const Arguments &args)
71 : ViewBuffer(args.Bounds().OffsetToCopy(0, 0)),
72 fArguments(args)
73 {
74 // we need a message filter so that we get B_TAB keydowns
75 AddFilter(new BMessageFilter(B_KEY_DOWN, &MiniView::MessageFilter));
76 }
77
~MiniView()78 MiniView::~MiniView()
79 {
80 kill_thread(fConsoleWriter);
81 kill_thread(fShellExecutor);
82 kill_thread(fShellProcess);
83 }
84
85 void
Start()86 MiniView::Start()
87 {
88 fConsole = new Console(this);
89
90 if (OpenTTY() != B_OK) {
91 TRACE(("error in OpenTTY\n"));
92 return;
93 }
94
95 // we're a session leader
96 setsid();
97
98 if (SpawnThreads() != B_OK)
99 TRACE(("error in SpawnThreads\n"));
100 }
101
102 status_t
OpenTTY()103 MiniView::OpenTTY()
104 {
105 DIR *dir;
106
107 dir = opendir("/dev/pt");
108 if (dir != NULL) {
109 struct dirent *entry;
110 char name[64];
111
112 while ((entry = readdir(dir)) != NULL) {
113 if (entry->d_name[0] == '.') // filter out . and ..
114 continue;
115
116 sprintf(name, "/dev/pt/%s", entry->d_name);
117
118 fMasterFD = open(name, O_RDWR);
119 if (fMasterFD >= 0) {
120 sprintf(name, "/dev/tt/%s", entry->d_name);
121
122 fSlaveFD = open(name, O_RDWR);
123 if (fSlaveFD < 0) {
124 TRACE(("cannot open tty\n"));
125 close(fMasterFD);
126 } else {
127 struct termios tio;
128 tcgetattr(fSlaveFD, &tio);
129
130 // set signal default
131 signal(SIGCHLD, SIG_DFL);
132 signal(SIGHUP, SIG_DFL);
133 signal(SIGQUIT, SIG_DFL);
134 signal(SIGTERM, SIG_DFL);
135 signal(SIGINT, SIG_DFL);
136 signal(SIGTTOU, SIG_DFL);
137
138 // set terminal interface
139 tio.c_line = 0;
140 tio.c_lflag |= ECHOE;
141
142 // input: nl->nl, cr->nl
143 tio.c_iflag &= ~(INLCR|IGNCR);
144 tio.c_iflag |= ICRNL;
145 tio.c_iflag &= ~ISTRIP;
146
147 // output: cr->cr, nl in not retrun, no delays, ln->cr/ln
148 tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
149 tio.c_oflag |= ONLCR;
150 tio.c_oflag |= OPOST;
151
152 // baud rate is 19200 (equal to beterm)
153 tio.c_cflag &= ~(CBAUD);
154 tio.c_cflag |= B19200;
155
156 tio.c_cflag &= ~CSIZE;
157 tio.c_cflag |= CS8;
158 tio.c_cflag |= CREAD;
159
160 tio.c_cflag |= HUPCL;
161 tio.c_iflag &= ~(IGNBRK|BRKINT);
162
163 // enable signals, canonical processing (erase, kill, etc), echo
164 tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
165 tio.c_lflag &= ~ECHOK;
166
167 tio.c_lflag &= ~IEXTEN;
168
169 // set control charactors
170 tio.c_cc[VINTR] = 'C' & 0x1f; /* '^C' */
171 tio.c_cc[VQUIT] = '\\'& 0x1f; /* '^\' */
172 tio.c_cc[VERASE] = 0x08; /* '^H' */
173 tio.c_cc[VKILL] = 'U' & 0x1f; /* '^U' */
174 tio.c_cc[VEOF] = 'D' & 0x1f; /* '^D' */
175 tio.c_cc[VEOL] = 0; /* '^@' */
176 tio.c_cc[VMIN] = 4;
177 tio.c_cc[VTIME] = 0;
178 tio.c_cc[VEOL2] = 0; /* '^@' */
179 tio.c_cc[VSWTCH] = 0; /* '^@' */
180 tio.c_cc[VSTART] = 'S' & 0x1f; /* '^S' */
181 tio.c_cc[VSTOP] = 'Q' & 0x1f; /* '^Q' */
182 tio.c_cc[VSUSP] = '@' & 0x1f; /* '^@' */
183
184 // set terminal interface
185 tcsetattr(fSlaveFD, TCSANOW, &tio);
186
187 // set window size
188 winsize ws;
189 int32 rows, cols;
190 GetSize(&cols, &rows);
191 ws.ws_row = rows;
192 ws.ws_col = cols;
193 if (LockLooper()) {
194 ws.ws_xpixel = Bounds().IntegerWidth();
195 ws.ws_ypixel = Bounds().IntegerHeight();
196 UnlockLooper();
197 }
198 ioctl(fSlaveFD, TIOCSWINSZ, &ws);
199 }
200 break;
201 }
202 }
203
204
205 Setenv("TTY", name);
206 }
207
208 if (fMasterFD < 0 || fSlaveFD < 0) {
209 TRACE(("could not open master or slave fd\n"));
210 return B_ERROR;
211 }
212
213 Setenv("TERM", "beterm");
214 return B_OK;
215 }
216
217 void
FrameResized(float width,float height)218 MiniView::FrameResized(float width, float height)
219 {
220 ViewBuffer::FrameResized(width, height);
221
222 winsize ws;
223 int32 rows, cols;
224 GetSize(&cols, &rows);
225 ws.ws_row = rows;
226 ws.ws_col = cols;
227 ws.ws_xpixel = (uint16)width;
228 ws.ws_ypixel = (uint16)height;
229 ioctl(fSlaveFD, TIOCSWINSZ, &ws);
230 }
231
232
233 void
KeyDown(const char * bytes,int32 numBytes)234 MiniView::KeyDown(const char *bytes, int32 numBytes)
235 {
236 // TODO: add interrupt char handling
237 uint32 mod = modifiers();
238 if (numBytes == 1) {
239 if (mod & B_OPTION_KEY) {
240 char c = bytes[0] | 0x80;
241 write(fMasterFD, &c, 1);
242 } else {
243 switch (bytes[0]) {
244 case B_LEFT_ARROW:
245 write(fMasterFD, LEFT_ARROW_KEY_CODE, sizeof(LEFT_ARROW_KEY_CODE) - 1);
246 break;
247 case B_RIGHT_ARROW:
248 write(fMasterFD, RIGHT_ARROW_KEY_CODE, sizeof(RIGHT_ARROW_KEY_CODE) - 1);
249 break;
250 case B_UP_ARROW:
251 write(fMasterFD, UP_ARROW_KEY_CODE, sizeof(UP_ARROW_KEY_CODE) - 1);
252 break;
253 case B_DOWN_ARROW:
254 write(fMasterFD, DOWN_ARROW_KEY_CODE, sizeof(DOWN_ARROW_KEY_CODE) - 1);
255 break;
256 default:
257 write(fMasterFD, bytes, numBytes);
258 }
259 }
260 } else
261 write(fMasterFD, bytes, numBytes);
262 }
263
264 status_t
SpawnThreads()265 MiniView::SpawnThreads()
266 {
267 fConsoleWriter = spawn_thread(&MiniView::ConsoleWriter, "console writer", B_URGENT_DISPLAY_PRIORITY, this);
268 if (fConsoleWriter < 0)
269 return B_ERROR;
270 TRACE(("console writer thread is: %ld\n", fConsoleWriter));
271
272 fShellExecutor = spawn_thread(&MiniView::ExecuteShell, "shell process", B_URGENT_DISPLAY_PRIORITY, this);
273 if (fShellExecutor < 0)
274 return B_ERROR;
275 TRACE(("shell executor thread is: %ld\n", fShellExecutor));
276
277 resume_thread(fConsoleWriter);
278 resume_thread(fShellExecutor);
279 return B_OK;
280 }
281
282 int32
ConsoleWriter(void * arg)283 MiniView::ConsoleWriter(void *arg)
284 {
285 char buf[1024];
286 ssize_t len;
287 MiniView *view = (MiniView *)arg;
288
289 for (;;) {
290 len = read(view->fMasterFD, buf, sizeof(buf));
291 if (len < 0)
292 break;
293
294 view->fConsole->Write(buf, len);
295 }
296
297 return 0;
298 }
299
300 int32
ExecuteShell(void * arg)301 MiniView::ExecuteShell(void *arg)
302 {
303 MiniView *view = (MiniView *)arg;
304
305 for (;;) {
306 int argc;
307 const char *const *argv;
308 view->fArguments.GetShellArguments(argc, argv);
309
310 int saved_stdin = dup(0);
311 int saved_stdout = dup(1);
312 int saved_stderr = dup(2);
313
314 dup2(view->fSlaveFD, 0);
315 dup2(view->fSlaveFD, 1);
316 dup2(view->fSlaveFD, 2);
317
318 view->fShellProcess = load_image(argc, (const char **)argv,
319 (const char **)environ);
320 setpgid(view->fShellProcess, 0);
321 tcsetpgrp(view->fSlaveFD, view->fShellProcess);
322
323 dup2(saved_stdin, 0);
324 dup2(saved_stdout, 1);
325 dup2(saved_stderr, 2);
326 close(saved_stdin);
327 close(saved_stdout);
328 close(saved_stderr);
329
330 status_t return_code;
331 wait_for_thread(view->fShellProcess, &return_code);
332
333 if (!view->fArguments.StandardShell()) {
334 view->Window()->PostMessage(B_QUIT_REQUESTED);
335 break;
336 }
337 }
338
339 return B_OK;
340 }
341
342 filter_result
MessageFilter(BMessage * message,BHandler ** target,BMessageFilter * filter)343 MiniView::MessageFilter(BMessage *message, BHandler **target, BMessageFilter *filter)
344 {
345 MiniView *view = (MiniView *)(*target);
346
347 int32 raw_char;
348 message->FindInt32("raw_char", &raw_char);
349 if (raw_char == B_TAB) {
350 char bytes[2] = { B_TAB, 0 };
351 view->KeyDown(bytes, 1);
352 return B_SKIP_MESSAGE;
353 }
354
355 return B_DISPATCH_MESSAGE;
356 }
357