xref: /haiku/src/tests/apps/miniterminal/MiniView.cpp (revision 160bd2ffca5cc2603fe4f32697dc09aa44a1e5fc)
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 Haiku 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 
20 #include <termios.h>
21 #include <unistd.h>
22 #include <dirent.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <signal.h>
27 
28 #include "MiniView.h"
29 #include "Console.h"
30 
31 #include "VTkeymap.h"
32 
33 //#define TRACE_MINI_TERMINAL
34 #ifdef TRACE_MINI_TERMINAL
35 	#ifdef __HAIKU__
36 	#define TRACE(x)	debug_printf x
37 	#else
38 	#define	TRACE(x)	printf x
39 	#endif
40 #else
41 	#define TRACE(x)
42 #endif
43 
44 void
45 Setenv(const char *var, const char *value)
46 {
47 	int envindex = 0;
48 	const int len = strlen(var);
49 	const int val_len = strlen (value);
50 
51 	while (environ [envindex] != NULL) {
52 		if (strncmp (environ [envindex], var, len) == 0) {
53 			/* found it */
54 			environ[envindex] = (char *)malloc ((unsigned)len + val_len + 1);
55 			sprintf (environ [envindex], "%s%s", var, value);
56 			return;
57 		}
58 		envindex ++;
59 	}
60 
61 	environ [envindex] = (char *) malloc ((unsigned)len + val_len + 1);
62 	sprintf (environ [envindex], "%s%s", var, value);
63 	environ [++envindex] = NULL;
64 }
65 
66 static int32
67 ConsoleWriter(void *arg)
68 {
69 	char buf[1024];
70 	ssize_t len;
71 	MiniView *view = (MiniView *)arg;
72 
73 	for (;;) {
74 		len = read(view->fMasterFD, buf, sizeof(buf));
75 		if (len < 0)
76 			break;
77 
78 		view->fConsole->Write(buf, len);
79 	}
80 
81 	return 0;
82 }
83 
84 static int32
85 ExecuteShell(void *arg)
86 {
87 	MiniView *view = (MiniView *)arg;
88 
89 	for (;;) {
90 		const char *argv[3];
91 		argv[0] = "/bin/sh";
92 		argv[1] = "--login";
93 		argv[2] = NULL;
94 
95 		int saved_stdin = dup(0);
96 		int saved_stdout = dup(1);
97 		int saved_stderr = dup(2);
98 
99 		dup2(view->fSlaveFD, 0);
100 		dup2(view->fSlaveFD, 1);
101 		dup2(view->fSlaveFD, 2);
102 
103 		thread_id shell = load_image(2, argv, (const char **)environ);
104 		setpgid(shell, 0);
105 
106 		status_t return_code;
107 		wait_for_thread(shell, &return_code);
108 
109 		dup2(saved_stdin, 0);
110 		dup2(saved_stdout, 1);
111 		dup2(saved_stderr, 2);
112 		close(saved_stdin);
113 		close(saved_stdout);
114 		close(saved_stderr);
115 	}
116 
117 	return B_OK;
118 }
119 
120 MiniView::MiniView(BRect frame)
121 	:	ViewBuffer(frame)
122 {
123 }
124 
125 MiniView::~MiniView()
126 {
127 }
128 
129 void
130 MiniView::Start()
131 {
132 	fConsole = new Console(this);
133 
134 	if (OpenTTY() != B_OK) {
135 		TRACE(("error in OpenTTY\n"));
136 		return;
137 	}
138 
139 	// we're a session leader
140 	setsid();
141 
142 	// move our stdin and stdout to the console
143 	dup2(fSlaveFD, 0);
144 	dup2(fSlaveFD, 1);
145 	dup2(fSlaveFD, 2);
146 
147 	if (SpawnThreads() != B_OK)
148 		TRACE(("error in SpawnThreads\n"));
149 }
150 
151 status_t
152 MiniView::OpenTTY()
153 {
154 	DIR *dir;
155 
156 	dir = opendir("/dev/pt");
157 	if (dir != NULL) {
158 		struct dirent *entry;
159 		char name[64];
160 
161 		while ((entry = readdir(dir)) != NULL) {
162 			if (entry->d_name[0] == '.') // filter out . and ..
163 				continue;
164 
165 			sprintf(name, "/dev/pt/%s", entry->d_name);
166 
167 			fMasterFD = open(name, O_RDWR);
168 			if (fMasterFD >= 0) {
169 				sprintf(name, "/dev/tt/%s", entry->d_name);
170 
171 				fSlaveFD = open(name, O_RDWR);
172 				if (fSlaveFD < 0) {
173 					TRACE(("cannot open tty\n"));
174 					close(fMasterFD);
175 				} else {
176 					struct termios tio;
177 					tcgetattr(fSlaveFD, &tio);
178 
179 					// set signal default
180 					signal(SIGCHLD, SIG_DFL);
181 					signal(SIGHUP, SIG_DFL);
182 					signal(SIGQUIT, SIG_DFL);
183 					signal(SIGTERM, SIG_DFL);
184 					signal(SIGINT, SIG_DFL);
185 					signal(SIGTTOU, SIG_DFL);
186 
187 					// set terminal interface
188 					tio.c_line = 0;
189 					tio.c_lflag |= ECHOE;
190 
191 					// input: nl->nl, cr->nl
192 					tio.c_iflag &= ~(INLCR|IGNCR);
193 					tio.c_iflag |= ICRNL;
194 					tio.c_iflag &= ~ISTRIP;
195 
196 					// output: cr->cr, nl in not retrun, no delays, ln->cr/ln
197 					tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
198 					tio.c_oflag |= ONLCR;
199 					tio.c_oflag |= OPOST;
200 
201 					// baud rate is 19200 (equal to beterm)
202 					tio.c_cflag &= ~(CBAUD);
203 					tio.c_cflag |= B19200;
204 
205 					tio.c_cflag &= ~CSIZE;
206 					tio.c_cflag |= CS8;
207 					tio.c_cflag |= CREAD;
208 
209 					tio.c_cflag |= HUPCL;
210 					tio.c_iflag &= ~(IGNBRK|BRKINT);
211 
212 					// enable signals, canonical processing (erase, kill, etc), echo
213 					tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
214 					tio.c_lflag &= ~ECHOK;
215 
216 					tio.c_lflag &= ~IEXTEN;
217 
218 					// set control charactors
219 					tio.c_cc[VINTR]  = 'C' & 0x1f;	/* '^C'	*/
220 					tio.c_cc[VQUIT]  = '\\'& 0x1f;	/* '^\'	*/
221 					tio.c_cc[VERASE] = 0x08;		/* '^H'	*/
222 					tio.c_cc[VKILL]  = 'U' & 0x1f;	/* '^U'	*/
223 					tio.c_cc[VEOF]   = 'D' & 0x1f;	/* '^D' */
224 					tio.c_cc[VEOL]   = 0;			/* '^@' */
225 					tio.c_cc[VMIN]   = 4;
226 					tio.c_cc[VTIME]  = 0;
227 					tio.c_cc[VEOL2]  = 0;			/* '^@' */
228 					tio.c_cc[VSWTCH] = 0;			/* '^@' */
229 					tio.c_cc[VSTART] = 'S' & 0x1f;	/* '^S' */
230 					tio.c_cc[VSTOP]  = 'Q' & 0x1f;	/* '^Q' */
231 					tio.c_cc[VSUSP]  = '@' & 0x1f;	/* '^@' */
232 
233 					// set terminal interface
234 					tcsetattr(fSlaveFD, TCSANOW, &tio);
235 
236 					// set window size (currently disabled)
237 					/*ws.ws_row = rows;
238 					ws.ws_col = cols;
239 
240 					ioctl(con->tty_slave_fd, TIOCSWINSZ, &ws);*/
241 				}
242 				break;
243 			}
244 		}
245 
246 
247 		Setenv("TTY", name);
248 	}
249 
250 	if (fMasterFD < 0 || fSlaveFD < 0) {
251 		TRACE(("could not open master or slave fd\n"));
252 		return B_ERROR;
253 	}
254 
255 	Setenv("TERM", "beterm");
256 	return B_OK;
257 }
258 
259 void
260 MiniView::KeyDown(const char *bytes, int32 numBytes)
261 {
262 	// TODO: add interrupt char handling
263 	uint32 mod = modifiers();
264 	if (numBytes == 1) {
265 		if (mod & B_OPTION_KEY) {
266 			char c = bytes[0] | 0x80;
267 			write(fMasterFD, &c, 1);
268 		} else {
269 			switch (bytes[0]) {
270 				case B_LEFT_ARROW:
271 					write(fMasterFD, LEFT_ARROW_KEY_CODE, sizeof(LEFT_ARROW_KEY_CODE));
272 					break;
273 				case B_RIGHT_ARROW:
274 					write(fMasterFD, RIGHT_ARROW_KEY_CODE, sizeof(RIGHT_ARROW_KEY_CODE));
275 					break;
276 				case B_UP_ARROW:
277 					write(fMasterFD, UP_ARROW_KEY_CODE, sizeof(UP_ARROW_KEY_CODE));
278 					break;
279 				case B_DOWN_ARROW:
280 					write(fMasterFD, DOWN_ARROW_KEY_CODE, sizeof(DOWN_ARROW_KEY_CODE));
281 					break;
282 				default:
283 					write(fMasterFD, bytes, numBytes);
284 			}
285 		}
286 	} else
287 		write(fMasterFD, bytes, numBytes);
288 }
289 
290 status_t
291 MiniView::SpawnThreads()
292 {
293 	fConsoleWriter = spawn_thread(&ConsoleWriter, "console writer", B_URGENT_DISPLAY_PRIORITY, this);
294 	if (fConsoleWriter < 0)
295 		return B_ERROR;
296 	TRACE(("console writer thread is: %d\n", fConsoleWriter));
297 
298 	fShellProcess = spawn_thread(&ExecuteShell, "shell process", B_URGENT_DISPLAY_PRIORITY, this);
299 	if (fShellProcess < 0)
300 		return B_ERROR;
301 	TRACE(("shell process thread is: %d\n", fShellProcess));
302 
303 	resume_thread(fConsoleWriter);
304 	resume_thread(fShellProcess);
305 	return B_OK;
306 }
307