xref: /haiku/src/tests/apps/miniterminal/MiniView.cpp (revision 47a21c5c89fc9fd155a3929e5a8f6056b92a2053)
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