1/*
2 * Copyright 2004-2010, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Copyright 2002, Travis Geiselbrecht. All rights reserved.
6 * Distributed under the terms of the NewOS License.
7 */
8
9
10#include <ctype.h>
11#include <dirent.h>
12#include <errno.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <termios.h>
17#include <unistd.h>
18
19#include <FindDirectory.h>
20#include <image.h>
21#include <InterfaceDefs.h>
22#include <OS.h>
23
24#include <keyboard_mouse_driver.h>
25#include <Keymap.h>
26
27
28struct console;
29
30struct keyboard {
31	struct keyboard*	next;
32	int					device;
33	int					target;
34	thread_id			thread;
35};
36
37struct console {
38	int					console_fd;
39	thread_id			console_writer;
40
41	struct keyboard*	keyboards;
42
43	int					tty_master_fd;
44	int					tty_slave_fd;
45	int					tty_num;
46};
47
48
49struct console gConsole;
50
51
52void
53error(const char* message, ...)
54{
55	char buffer[2048];
56
57	va_list args;
58	va_start(args, message);
59
60	vsnprintf(buffer, sizeof(buffer), message, args);
61
62	va_end(args);
63
64	// put it out on stderr as well as to serial/syslog
65	fputs(buffer, stderr);
66	debug_printf("%s", buffer);
67}
68
69
70void
71update_leds(int fd, uint32 modifiers)
72{
73	char lockIO[3] = {0, 0, 0};
74
75	if ((modifiers & B_NUM_LOCK) != 0)
76		lockIO[0] = 1;
77	if ((modifiers & B_CAPS_LOCK) != 0)
78		lockIO[1] = 1;
79	if ((modifiers & B_SCROLL_LOCK) != 0)
80		lockIO[2] = 1;
81
82	ioctl(fd, KB_SET_LEDS, &lockIO);
83}
84
85
86static int32
87keyboard_reader(void* arg)
88{
89	struct keyboard* keyboard = (struct keyboard*)arg;
90	uint8 activeDeadKey = 0;
91	uint32 modifiers = 0;
92
93	BKeymap keymap;
94	// Load current keymap from disk (we can't talk to the input server)
95	// TODO: find a better way (we shouldn't have to care about the on-disk
96	// location)
97	char path[PATH_MAX];
98	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, -1, false,
99		path, sizeof(path));
100	if (status == B_OK) {
101		strlcat(path, "/Key_map", sizeof(path));
102		status = keymap.SetTo(path);
103	}
104	if (status != B_OK)
105		keymap.SetToDefault();
106
107	for (;;) {
108		raw_key_info rawKeyInfo;
109		if (ioctl(keyboard->device, KB_READ, &rawKeyInfo) != 0)
110			break;
111
112		uint32 keycode = rawKeyInfo.keycode;
113		bool isKeyDown = rawKeyInfo.is_keydown;
114
115		if (keycode == 0)
116			continue;
117
118		uint32 changedModifiers = keymap.Modifier(keycode);
119		bool isLock = (changedModifiers
120			& (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0;
121		if (changedModifiers != 0 && (!isLock || isKeyDown)) {
122			uint32 oldModifiers = modifiers;
123
124			if ((isKeyDown && !isLock)
125				|| (isKeyDown && !(modifiers & changedModifiers)))
126				modifiers |= changedModifiers;
127			else {
128				modifiers &= ~changedModifiers;
129
130				// ensure that we don't clear a combined B_*_KEY when still
131				// one of the individual B_{LEFT|RIGHT}_*_KEY is pressed
132				if (modifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY))
133					modifiers |= B_SHIFT_KEY;
134				if (modifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY))
135					modifiers |= B_COMMAND_KEY;
136				if (modifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY))
137					modifiers |= B_CONTROL_KEY;
138				if (modifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY))
139					modifiers |= B_OPTION_KEY;
140			}
141
142			if (modifiers != oldModifiers) {
143				if (isLock)
144					update_leds(keyboard->device, modifiers);
145			}
146		}
147
148		uint8 newDeadKey = 0;
149		if (activeDeadKey == 0 || !isKeyDown)
150			newDeadKey = keymap.ActiveDeadKey(keycode, modifiers);
151
152		char* string = NULL;
153		int32 numBytes = 0;
154		if (newDeadKey == 0 && isKeyDown) {
155			keymap.GetChars(keycode, modifiers, activeDeadKey, &string,
156				&numBytes);
157			if (numBytes > 0)
158				write(keyboard->target, string, numBytes);
159
160			delete[] string;
161		}
162
163		if (newDeadKey == 0) {
164			if (isKeyDown && !modifiers && activeDeadKey != 0) {
165				// a dead key was completed
166				activeDeadKey = 0;
167			}
168		} else if (isKeyDown) {
169			// start of a dead key
170			activeDeadKey = newDeadKey;
171		}
172	}
173
174	return 0;
175}
176
177
178static int32
179console_writer(void* arg)
180{
181	struct console* con = (struct console*)arg;
182
183	for (;;) {
184		char buffer[1024];
185		ssize_t length = read(con->tty_master_fd, buffer, sizeof(buffer));
186		if (length < 0)
187			break;
188
189		write(con->console_fd, buffer, length);
190	}
191
192	return 0;
193}
194
195
196static void
197stop_keyboards(struct console* con)
198{
199	// close devices
200
201	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;
202			keyboard = keyboard->next) {
203		close(keyboard->device);
204	}
205
206	// wait for the threads
207
208	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;) {
209		struct keyboard* next = keyboard->next;
210		wait_for_thread(keyboard->thread, NULL);
211
212		delete keyboard;
213		keyboard = next;
214	}
215
216	con->keyboards = NULL;
217}
218
219
220/*!	Opens the all keyboard drivers it finds starting from the given
221	location \a start that support the debugger extension.
222*/
223static struct keyboard*
224open_keyboards(int target, const char* start, struct keyboard* previous)
225{
226	// Wait for the directory to appear, if we're loaded early in boot
227	// it may take a while for it to appear while the drivers load.
228	DIR* dir;
229	int32 tries = 0;
230	while (true) {
231		dir = opendir(start);
232		if (dir != NULL)
233			break;
234		if(++tries == 10)
235			return NULL;
236		sleep(1);
237	}
238
239	struct keyboard* keyboard = previous;
240
241	while (true) {
242		dirent* entry = readdir(dir);
243		if (entry == NULL)
244			break;
245		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
246			continue;
247
248		char path[PATH_MAX];
249		strlcpy(path, start, sizeof(path));
250		strlcat(path, "/", sizeof(path));
251		strlcat(path, entry->d_name, sizeof(path));
252
253		struct stat stat;
254		if (::stat(path, &stat) != 0)
255			continue;
256
257		if (S_ISDIR(stat.st_mode)) {
258			keyboard = open_keyboards(target, path, keyboard);
259			continue;
260		}
261
262		// Try to open it as a device
263		int fd = open(path, O_RDONLY);
264		if (fd >= 0) {
265			// Turn on debugger mode
266			if (ioctl(fd, KB_SET_DEBUG_READER, NULL, 0) == 0) {
267				keyboard = new ::keyboard();
268				keyboard->device = fd;
269				keyboard->target = target;
270				keyboard->thread = spawn_thread(&keyboard_reader, path,
271					B_URGENT_DISPLAY_PRIORITY, keyboard);
272				if (keyboard->thread < 0) {
273					close(fd);
274					closedir(dir);
275					delete keyboard;
276					return NULL;
277				}
278
279				if (previous != NULL)
280					previous->next = keyboard;
281
282				resume_thread(keyboard->thread);
283			} else
284				close(fd);
285		}
286	}
287
288	closedir(dir);
289	return keyboard;
290}
291
292
293static int
294start_console(struct console* con)
295{
296	memset(con, 0, sizeof(struct console));
297	con->console_fd = -1;
298	con->tty_master_fd = -1;
299	con->tty_slave_fd = -1;
300	con->console_writer = -1;
301
302	con->console_fd = open("/dev/console", O_WRONLY);
303	if (con->console_fd < 0)
304		return -2;
305
306	DIR* dir = opendir("/dev/pt");
307	if (dir != NULL) {
308		struct dirent* entry;
309		char name[64];
310
311		while ((entry = readdir(dir)) != NULL) {
312			if (entry->d_name[0] == '.')
313				continue;
314
315			snprintf(name, sizeof(name), "/dev/pt/%s", entry->d_name);
316
317			con->tty_master_fd = open(name, O_RDWR);
318			if (con->tty_master_fd >= 0) {
319				snprintf(name, sizeof(name), "/dev/tt/%s", entry->d_name);
320
321				con->tty_slave_fd = open(name, O_RDWR);
322				if (con->tty_slave_fd < 0) {
323					error("Could not open tty %s: %s!\n", name,
324						strerror(errno));
325					close(con->tty_master_fd);
326				} else {
327					// set default mode
328					struct termios termios;
329					struct winsize size;
330
331					if (tcgetattr(con->tty_slave_fd, &termios) == 0) {
332						termios.c_iflag = ICRNL;
333						termios.c_oflag = OPOST | ONLCR;
334						termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHONL;
335
336						tcsetattr(con->tty_slave_fd, TCSANOW, &termios);
337					}
338
339					if (ioctl(con->console_fd, TIOCGWINSZ, &size,
340							sizeof(struct winsize)) == 0) {
341						// we got the window size from the console
342						ioctl(con->tty_slave_fd, TIOCSWINSZ, &size,
343							sizeof(struct winsize));
344					}
345				}
346				break;
347			}
348		}
349
350		setenv("TTY", name, true);
351	}
352
353	if (con->tty_master_fd < 0 || con->tty_slave_fd < 0)
354		return -3;
355
356	con->keyboards
357		= open_keyboards(con->tty_master_fd, "/dev/input/keyboard", NULL);
358	if (con->keyboards == NULL)
359		return -4;
360
361	con->console_writer = spawn_thread(&console_writer, "console writer",
362		B_URGENT_DISPLAY_PRIORITY, con);
363	if (con->console_writer < 0)
364		return -5;
365
366	resume_thread(con->console_writer);
367	setenv("TERM", "xterm", true);
368
369	return 0;
370}
371
372
373static void
374stop_console(struct console* con)
375{
376	// close TTY FDs; this will also unblock the threads
377	close(con->tty_master_fd);
378	close(con->tty_slave_fd);
379
380	// close console and keyboards
381	close(con->console_fd);
382	wait_for_thread(con->console_writer, NULL);
383
384	stop_keyboards(con);
385}
386
387
388static pid_t
389start_process(int argc, const char** argv, struct console* con)
390{
391	int savedInput = dup(0);
392	int savedOutput = dup(1);
393	int savedError = dup(2);
394
395	dup2(con->tty_slave_fd, 0);
396	dup2(con->tty_slave_fd, 1);
397	dup2(con->tty_slave_fd, 2);
398
399	pid_t pid = load_image(argc, argv, (const char**)environ);
400	resume_thread(pid);
401	setpgid(pid, 0);
402	tcsetpgrp(con->tty_slave_fd, pid);
403
404	dup2(savedInput, 0);
405	dup2(savedOutput, 1);
406	dup2(savedError, 2);
407	close(savedInput);
408	close(savedOutput);
409	close(savedError);
410
411	return pid;
412}
413
414
415int
416main(int argc, char** argv)
417{
418	// we're a session leader
419	setsid();
420
421	int err = start_console(&gConsole);
422	if (err < 0) {
423		error("consoled: error %d starting console.\n", err);
424		return err;
425	}
426
427	// move our stdin and stdout to the console
428	dup2(gConsole.tty_slave_fd, 0);
429	dup2(gConsole.tty_slave_fd, 1);
430	dup2(gConsole.tty_slave_fd, 2);
431
432	if (argc > 1) {
433		// a command was given: we run it only once
434
435		// get the command argument vector
436		int commandArgc = argc - 1;
437		const char** commandArgv = new const char*[commandArgc + 1];
438		for (int i = 0; i < commandArgc; i++)
439			commandArgv[i] = argv[i + 1];
440
441		commandArgv[commandArgc] = NULL;
442
443		// start the process
444		pid_t process = start_process(commandArgc, commandArgv, &gConsole);
445
446		status_t returnCode;
447		wait_for_thread(process, &returnCode);
448	} else {
449		// no command given: start a shell in an endless loop
450		for (;;) {
451			pid_t shellProcess;
452			status_t returnCode;
453			const char* shellArgv[] = { "/bin/sh", "--login", NULL };
454
455			shellProcess = start_process(2, shellArgv, &gConsole);
456
457			wait_for_thread(shellProcess, &returnCode);
458
459			puts("Restart shell");
460		}
461	}
462
463	stop_console(&gConsole);
464
465	return 0;
466}
467
468