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
47void
48Setenv(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
70MiniView::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
78MiniView::~MiniView()
79{
80	kill_thread(fConsoleWriter);
81	kill_thread(fShellExecutor);
82	kill_thread(fShellProcess);
83}
84
85void
86MiniView::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
102status_t
103MiniView::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
217void
218MiniView::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
233void
234MiniView::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
264status_t
265MiniView::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
282int32
283MiniView::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
300int32
301MiniView::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
342filter_result
343MiniView::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