⛏️ index : haiku.git

/*
 * Copyright 2004-2010, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2002, Travis Geiselbrecht. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */


#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <FindDirectory.h>
#include <image.h>
#include <InterfaceDefs.h>
#include <OS.h>

#include <keyboard_mouse_driver.h>
#include <Keymap.h>


struct console;

struct keyboard {
	struct keyboard*	next;
	int					device;
	int					target;
	thread_id			thread;
};

struct console {
	int					console_fd;
	thread_id			console_writer;

	struct keyboard*	keyboards;

	int					tty_master_fd;
	int					tty_slave_fd;
	int					tty_num;
};


struct console gConsole;


void
error(const char* message, ...)
{
	char buffer[2048];

	va_list args;
	va_start(args, message);

	vsnprintf(buffer, sizeof(buffer), message, args);

	va_end(args);

	// put it out on stderr as well as to serial/syslog
	fputs(buffer, stderr);
	debug_printf("%s", buffer);
}


void
update_leds(int fd, uint32 modifiers)
{
	char lockIO[3] = {0, 0, 0};

	if ((modifiers & B_NUM_LOCK) != 0)
		lockIO[0] = 1;
	if ((modifiers & B_CAPS_LOCK) != 0)
		lockIO[1] = 1;
	if ((modifiers & B_SCROLL_LOCK) != 0)
		lockIO[2] = 1;

	ioctl(fd, KB_SET_LEDS, &lockIO, sizeof(lockIO));
}


static int32
keyboard_reader(void* arg)
{
	struct keyboard* keyboard = (struct keyboard*)arg;
	uint8 activeDeadKey = 0;
	uint32 modifiers = 0;

	BKeymap keymap;
	// Load current keymap from disk (we can't talk to the input server)
	// TODO: find a better way (we shouldn't have to care about the on-disk
	// location)
	char path[PATH_MAX];
	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, -1, false,
		path, sizeof(path));
	if (status == B_OK) {
		strlcat(path, "/Key_map", sizeof(path));
		status = keymap.SetTo(path);
	}
	if (status != B_OK)
		keymap.SetToDefault();

	for (;;) {
		raw_key_info rawKeyInfo;
		if (ioctl(keyboard->device, KB_READ, &rawKeyInfo,
				sizeof(rawKeyInfo)) != 0)
			break;

		uint32 keycode = rawKeyInfo.keycode;
		bool isKeyDown = rawKeyInfo.is_keydown;

		if (keycode == 0)
			continue;

		uint32 changedModifiers = keymap.Modifier(keycode);
		bool isLock = (changedModifiers
			& (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0;
		if (changedModifiers != 0 && (!isLock || isKeyDown)) {
			uint32 oldModifiers = modifiers;

			if ((isKeyDown && !isLock)
				|| (isKeyDown && !(modifiers & changedModifiers)))
				modifiers |= changedModifiers;
			else {
				modifiers &= ~changedModifiers;

				// ensure that we don't clear a combined B_*_KEY when still
				// one of the individual B_{LEFT|RIGHT}_*_KEY is pressed
				if (modifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY))
					modifiers |= B_SHIFT_KEY;
				if (modifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY))
					modifiers |= B_COMMAND_KEY;
				if (modifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY))
					modifiers |= B_CONTROL_KEY;
				if (modifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY))
					modifiers |= B_OPTION_KEY;
			}

			if (modifiers != oldModifiers) {
				if (isLock)
					update_leds(keyboard->device, modifiers);
			}
		}

		uint8 newDeadKey = 0;
		if (activeDeadKey == 0 || !isKeyDown)
			newDeadKey = keymap.ActiveDeadKey(keycode, modifiers);

		char* string = NULL;
		int32 numBytes = 0;
		if (newDeadKey == 0 && isKeyDown) {
			keymap.GetChars(keycode, modifiers, activeDeadKey, &string,
				&numBytes);
			if (numBytes > 0)
				write(keyboard->target, string, numBytes);

			delete[] string;
		}

		if (newDeadKey == 0) {
			if (isKeyDown && !modifiers && activeDeadKey != 0) {
				// a dead key was completed
				activeDeadKey = 0;
			}
		} else if (isKeyDown) {
			// start of a dead key
			activeDeadKey = newDeadKey;
		}
	}

	return 0;
}


static int32
console_writer(void* arg)
{
	struct console* con = (struct console*)arg;

	for (;;) {
		char buffer[1024];
		ssize_t length = read(con->tty_master_fd, buffer, sizeof(buffer));
		if (length < 0)
			break;

		write(con->console_fd, buffer, length);
	}

	return 0;
}


static void
stop_keyboards(struct console* con)
{
	// close devices

	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;
			keyboard = keyboard->next) {
		close(keyboard->device);
	}

	// wait for the threads

	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;) {
		struct keyboard* next = keyboard->next;
		wait_for_thread(keyboard->thread, NULL);

		delete keyboard;
		keyboard = next;
	}

	con->keyboards = NULL;
}


/*!	Opens the all keyboard drivers it finds starting from the given
	location \a start that support the debugger extension.
*/
static struct keyboard*
open_keyboards(int target, const char* start, struct keyboard* previous)
{
	// Wait for the directory to appear, if we're loaded early in boot
	// it may take a while for it to appear while the drivers load.
	DIR* dir;
	int32 tries = 0;
	while (true) {
		dir = opendir(start);
		if (dir != NULL)
			break;
		if(++tries == 10)
			return NULL;
		sleep(1);
	}

	struct keyboard* keyboard = previous;

	while (true) {
		dirent* entry = readdir(dir);
		if (entry == NULL)
			break;
		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
			continue;

		char path[PATH_MAX];
		strlcpy(path, start, sizeof(path));
		strlcat(path, "/", sizeof(path));
		strlcat(path, entry->d_name, sizeof(path));

		struct stat stat;
		if (::stat(path, &stat) != 0)
			continue;

		if (S_ISDIR(stat.st_mode)) {
			keyboard = open_keyboards(target, path, keyboard);
			continue;
		}

		// Try to open it as a device
		int fd = open(path, O_RDONLY);
		if (fd >= 0) {
			// Turn on debugger mode
			if (ioctl(fd, KB_SET_DEBUG_READER, NULL, 0) == 0) {
				keyboard = new ::keyboard();
				keyboard->device = fd;
				keyboard->target = target;
				keyboard->thread = spawn_thread(&keyboard_reader, path,
					B_URGENT_DISPLAY_PRIORITY, keyboard);
				if (keyboard->thread < 0) {
					close(fd);
					closedir(dir);
					delete keyboard;
					return NULL;
				}

				if (previous != NULL)
					previous->next = keyboard;

				resume_thread(keyboard->thread);
			} else
				close(fd);
		}
	}

	closedir(dir);
	return keyboard;
}


static int
start_console(struct console* con)
{
	memset(con, 0, sizeof(struct console));
	con->console_fd = -1;
	con->tty_master_fd = -1;
	con->tty_slave_fd = -1;
	con->console_writer = -1;

	con->console_fd = open("/dev/console", O_WRONLY);
	if (con->console_fd < 0)
		return -2;

	DIR* dir = opendir("/dev/pt");
	if (dir != NULL) {
		struct dirent* entry;
		char name[64];

		while ((entry = readdir(dir)) != NULL) {
			if (entry->d_name[0] == '.')
				continue;

			snprintf(name, sizeof(name), "/dev/pt/%s", entry->d_name);

			con->tty_master_fd = open(name, O_RDWR);
			if (con->tty_master_fd >= 0) {
				snprintf(name, sizeof(name), "/dev/tt/%s", entry->d_name);

				con->tty_slave_fd = open(name, O_RDWR);
				if (con->tty_slave_fd < 0) {
					error("Could not open tty %s: %s!\n", name,
						strerror(errno));
					close(con->tty_master_fd);
				} else {
					// set default mode
					struct termios termios;
					struct winsize size;

					if (tcgetattr(con->tty_slave_fd, &termios) == 0) {
						termios.c_iflag = ICRNL;
						termios.c_oflag = OPOST | ONLCR;
						termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHONL;

						tcsetattr(con->tty_slave_fd, TCSANOW, &termios);
					}

					if (ioctl(con->console_fd, TIOCGWINSZ, &size,
							sizeof(struct winsize)) == 0) {
						// we got the window size from the console
						ioctl(con->tty_slave_fd, TIOCSWINSZ, &size,
							sizeof(struct winsize));
					}
				}
				break;
			}
		}

		setenv("TTY", name, true);
	}

	if (con->tty_master_fd < 0 || con->tty_slave_fd < 0)
		return -3;

	con->keyboards
		= open_keyboards(con->tty_master_fd, "/dev/input/keyboard", NULL);
	if (con->keyboards == NULL)
		return -4;

	con->console_writer = spawn_thread(&console_writer, "console writer",
		B_URGENT_DISPLAY_PRIORITY, con);
	if (con->console_writer < 0)
		return -5;

	resume_thread(con->console_writer);
	setenv("TERM", "xterm", true);

	return 0;
}


static void
stop_console(struct console* con)
{
	// close TTY FDs; this will also unblock the threads
	close(con->tty_master_fd);
	close(con->tty_slave_fd);

	// close console and keyboards
	close(con->console_fd);
	wait_for_thread(con->console_writer, NULL);

	stop_keyboards(con);
}


static pid_t
start_process(int argc, const char** argv, struct console* con)
{
	int savedInput = dup(0);
	int savedOutput = dup(1);
	int savedError = dup(2);

	dup2(con->tty_slave_fd, 0);
	dup2(con->tty_slave_fd, 1);
	dup2(con->tty_slave_fd, 2);

	pid_t pid = load_image(argc, argv, (const char**)environ);
	resume_thread(pid);
	setpgid(pid, 0);
	tcsetpgrp(con->tty_slave_fd, pid);

	dup2(savedInput, 0);
	dup2(savedOutput, 1);
	dup2(savedError, 2);
	close(savedInput);
	close(savedOutput);
	close(savedError);

	return pid;
}


int
main(int argc, char** argv)
{
	// we're a session leader
	setsid();

	int err = start_console(&gConsole);
	if (err < 0) {
		error("consoled: error %d starting console.\n", err);
		return err;
	}

	// move our stdin and stdout to the console
	dup2(gConsole.tty_slave_fd, 0);
	dup2(gConsole.tty_slave_fd, 1);
	dup2(gConsole.tty_slave_fd, 2);

	if (argc > 1) {
		// a command was given: we run it only once

		// get the command argument vector
		int commandArgc = argc - 1;
		const char** commandArgv = new const char*[commandArgc + 1];
		for (int i = 0; i < commandArgc; i++)
			commandArgv[i] = argv[i + 1];

		commandArgv[commandArgc] = NULL;

		// start the process
		pid_t process = start_process(commandArgc, commandArgv, &gConsole);

		status_t returnCode;
		wait_for_thread(process, &returnCode);
	} else {
		// no command given: start a shell in an endless loop
		for (;;) {
			pid_t shellProcess;
			status_t returnCode;
			const char* shellArgv[] = { "/bin/sh", "--login", NULL };

			shellProcess = start_process(2, shellArgv, &gConsole);

			wait_for_thread(shellProcess, &returnCode);

			puts("Restart shell");
		}
	}

	stop_console(&gConsole);

	return 0;
}