haiku/src/bin/consoled/consoled.cpp

469 lines
10 KiB
C++

/*
* 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;
}