469 lines
10 KiB
C++
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;
|
|
}
|
|
|