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, sizeof(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,
110				sizeof(rawKeyInfo)) != 0)
111			break;
112
113		uint32 keycode = rawKeyInfo.keycode;
114		bool isKeyDown = rawKeyInfo.is_keydown;
115
116		if (keycode == 0)
117			continue;
118
119		uint32 changedModifiers = keymap.Modifier(keycode);
120		bool isLock = (changedModifiers
121			& (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0;
122		if (changedModifiers != 0 && (!isLock || isKeyDown)) {
123			uint32 oldModifiers = modifiers;
124
125			if ((isKeyDown && !isLock)
126				|| (isKeyDown && !(modifiers & changedModifiers)))
127				modifiers |= changedModifiers;
128			else {
129				modifiers &= ~changedModifiers;
130
131				// ensure that we don't clear a combined B_*_KEY when still
132				// one of the individual B_{LEFT|RIGHT}_*_KEY is pressed
133				if (modifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY))
134					modifiers |= B_SHIFT_KEY;
135				if (modifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY))
136					modifiers |= B_COMMAND_KEY;
137				if (modifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY))
138					modifiers |= B_CONTROL_KEY;
139				if (modifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY))
140					modifiers |= B_OPTION_KEY;
141			}
142
143			if (modifiers != oldModifiers) {
144				if (isLock)
145					update_leds(keyboard->device, modifiers);
146			}
147		}
148
149		uint8 newDeadKey = 0;
150		if (activeDeadKey == 0 || !isKeyDown)
151			newDeadKey = keymap.ActiveDeadKey(keycode, modifiers);
152
153		char* string = NULL;
154		int32 numBytes = 0;
155		if (newDeadKey == 0 && isKeyDown) {
156			keymap.GetChars(keycode, modifiers, activeDeadKey, &string,
157				&numBytes);
158			if (numBytes > 0)
159				write(keyboard->target, string, numBytes);
160
161			delete[] string;
162		}
163
164		if (newDeadKey == 0) {
165			if (isKeyDown && !modifiers && activeDeadKey != 0) {
166				// a dead key was completed
167				activeDeadKey = 0;
168			}
169		} else if (isKeyDown) {
170			// start of a dead key
171			activeDeadKey = newDeadKey;
172		}
173	}
174
175	return 0;
176}
177
178
179static int32
180console_writer(void* arg)
181{
182	struct console* con = (struct console*)arg;
183
184	for (;;) {
185		char buffer[1024];
186		ssize_t length = read(con->tty_master_fd, buffer, sizeof(buffer));
187		if (length < 0)
188			break;
189
190		write(con->console_fd, buffer, length);
191	}
192
193	return 0;
194}
195
196
197static void
198stop_keyboards(struct console* con)
199{
200	// close devices
201
202	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;
203			keyboard = keyboard->next) {
204		close(keyboard->device);
205	}
206
207	// wait for the threads
208
209	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;) {
210		struct keyboard* next = keyboard->next;
211		wait_for_thread(keyboard->thread, NULL);
212
213		delete keyboard;
214		keyboard = next;
215	}
216
217	con->keyboards = NULL;
218}
219
220
221/*!	Opens the all keyboard drivers it finds starting from the given
222	location \a start that support the debugger extension.
223*/
224static struct keyboard*
225open_keyboards(int target, const char* start, struct keyboard* previous)
226{
227	// Wait for the directory to appear, if we're loaded early in boot
228	// it may take a while for it to appear while the drivers load.
229	DIR* dir;
230	int32 tries = 0;
231	while (true) {
232		dir = opendir(start);
233		if (dir != NULL)
234			break;
235		if(++tries == 10)
236			return NULL;
237		sleep(1);
238	}
239
240	struct keyboard* keyboard = previous;
241
242	while (true) {
243		dirent* entry = readdir(dir);
244		if (entry == NULL)
245			break;
246		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
247			continue;
248
249		char path[PATH_MAX];
250		strlcpy(path, start, sizeof(path));
251		strlcat(path, "/", sizeof(path));
252		strlcat(path, entry->d_name, sizeof(path));
253
254		struct stat stat;
255		if (::stat(path, &stat) != 0)
256			continue;
257
258		if (S_ISDIR(stat.st_mode)) {
259			keyboard = open_keyboards(target, path, keyboard);
260			continue;
261		}
262
263		// Try to open it as a device
264		int fd = open(path, O_RDONLY);
265		if (fd >= 0) {
266			// Turn on debugger mode
267			if (ioctl(fd, KB_SET_DEBUG_READER, NULL, 0) == 0) {
268				keyboard = new ::keyboard();
269				keyboard->device = fd;
270				keyboard->target = target;
271				keyboard->thread = spawn_thread(&keyboard_reader, path,
272					B_URGENT_DISPLAY_PRIORITY, keyboard);
273				if (keyboard->thread < 0) {
274					close(fd);
275					closedir(dir);
276					delete keyboard;
277					return NULL;
278				}
279
280				if (previous != NULL)
281					previous->next = keyboard;
282
283				resume_thread(keyboard->thread);
284			} else
285				close(fd);
286		}
287	}
288
289	closedir(dir);
290	return keyboard;
291}
292
293
294static int
295start_console(struct console* con)
296{
297	memset(con, 0, sizeof(struct console));
298	con->console_fd = -1;
299	con->tty_master_fd = -1;
300	con->tty_slave_fd = -1;
301	con->console_writer = -1;
302
303	con->console_fd = open("/dev/console", O_WRONLY);
304	if (con->console_fd < 0)
305		return -2;
306
307	DIR* dir = opendir("/dev/pt");
308	if (dir != NULL) {
309		struct dirent* entry;
310		char name[64];
311
312		while ((entry = readdir(dir)) != NULL) {
313			if (entry->d_name[0] == '.')
314				continue;
315
316			snprintf(name, sizeof(name), "/dev/pt/%s", entry->d_name);
317
318			con->tty_master_fd = open(name, O_RDWR);
319			if (con->tty_master_fd >= 0) {
320				snprintf(name, sizeof(name), "/dev/tt/%s", entry->d_name);
321
322				con->tty_slave_fd = open(name, O_RDWR);
323				if (con->tty_slave_fd < 0) {
324					error("Could not open tty %s: %s!\n", name,
325						strerror(errno));
326					close(con->tty_master_fd);
327				} else {
328					// set default mode
329					struct termios termios;
330					struct winsize size;
331
332					if (tcgetattr(con->tty_slave_fd, &termios) == 0) {
333						termios.c_iflag = ICRNL;
334						termios.c_oflag = OPOST | ONLCR;
335						termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHONL;
336
337						tcsetattr(con->tty_slave_fd, TCSANOW, &termios);
338					}
339
340					if (ioctl(con->console_fd, TIOCGWINSZ, &size,
341							sizeof(struct winsize)) == 0) {
342						// we got the window size from the console
343						ioctl(con->tty_slave_fd, TIOCSWINSZ, &size,
344							sizeof(struct winsize));
345					}
346				}
347				break;
348			}
349		}
350
351		setenv("TTY", name, true);
352	}
353
354	if (con->tty_master_fd < 0 || con->tty_slave_fd < 0)
355		return -3;
356
357	con->keyboards
358		= open_keyboards(con->tty_master_fd, "/dev/input/keyboard", NULL);
359	if (con->keyboards == NULL)
360		return -4;
361
362	con->console_writer = spawn_thread(&console_writer, "console writer",
363		B_URGENT_DISPLAY_PRIORITY, con);
364	if (con->console_writer < 0)
365		return -5;
366
367	resume_thread(con->console_writer);
368	setenv("TERM", "xterm", true);
369
370	return 0;
371}
372
373
374static void
375stop_console(struct console* con)
376{
377	// close TTY FDs; this will also unblock the threads
378	close(con->tty_master_fd);
379	close(con->tty_slave_fd);
380
381	// close console and keyboards
382	close(con->console_fd);
383	wait_for_thread(con->console_writer, NULL);
384
385	stop_keyboards(con);
386}
387
388
389static pid_t
390start_process(int argc, const char** argv, struct console* con)
391{
392	int savedInput = dup(0);
393	int savedOutput = dup(1);
394	int savedError = dup(2);
395
396	dup2(con->tty_slave_fd, 0);
397	dup2(con->tty_slave_fd, 1);
398	dup2(con->tty_slave_fd, 2);
399
400	pid_t pid = load_image(argc, argv, (const char**)environ);
401	resume_thread(pid);
402	setpgid(pid, 0);
403	tcsetpgrp(con->tty_slave_fd, pid);
404
405	dup2(savedInput, 0);
406	dup2(savedOutput, 1);
407	dup2(savedError, 2);
408	close(savedInput);
409	close(savedOutput);
410	close(savedError);
411
412	return pid;
413}
414
415
416int
417main(int argc, char** argv)
418{
419	// we're a session leader
420	setsid();
421
422	int err = start_console(&gConsole);
423	if (err < 0) {
424		error("consoled: error %d starting console.\n", err);
425		return err;
426	}
427
428	// move our stdin and stdout to the console
429	dup2(gConsole.tty_slave_fd, 0);
430	dup2(gConsole.tty_slave_fd, 1);
431	dup2(gConsole.tty_slave_fd, 2);
432
433	if (argc > 1) {
434		// a command was given: we run it only once
435
436		// get the command argument vector
437		int commandArgc = argc - 1;
438		const char** commandArgv = new const char*[commandArgc + 1];
439		for (int i = 0; i < commandArgc; i++)
440			commandArgv[i] = argv[i + 1];
441
442		commandArgv[commandArgc] = NULL;
443
444		// start the process
445		pid_t process = start_process(commandArgc, commandArgv, &gConsole);
446
447		status_t returnCode;
448		wait_for_thread(process, &returnCode);
449	} else {
450		// no command given: start a shell in an endless loop
451		for (;;) {
452			pid_t shellProcess;
453			status_t returnCode;
454			const char* shellArgv[] = { "/bin/sh", "--login", NULL };
455
456			shellProcess = start_process(2, shellArgv, &gConsole);
457
458			wait_for_thread(shellProcess, &returnCode);
459
460			puts("Restart shell");
461		}
462	}
463
464	stop_console(&gConsole);
465
466	return 0;
467}
468
469