* MiniTerminal - A basic windowed terminal to allow
* command-line interaction from within app_server.
* Based on consoled and MuTerminal.
*
* Copyright 2005 Michael Lotz. All rights reserved.
* Distributed under the MIT License.
*
* Copyright 2004-2005, 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 <OS.h>
#include <image.h>
#include <Message.h>
#include <Window.h>
#include <termios.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "Arguments.h"
#include "Console.h"
#include "MiniView.h"
#include "VTkeymap.h"
#ifdef TRACE_MINI_TERMINAL
#ifdef __HAIKU__
#define TRACE(x) debug_printf x
#else
#define TRACE(x) printf x
#endif
#else
#define TRACE(x)
#endif
void
Setenv(const char *var, const char *value)
{
int envindex = 0;
const int len = strlen(var);
const int val_len = strlen (value);
while (environ [envindex] != NULL) {
if (strncmp (environ [envindex], var, len) == 0) {
environ[envindex] = (char *)malloc ((unsigned)len + val_len + 2);
sprintf (environ [envindex], "%s=%s", var, value);
return;
}
envindex ++;
}
environ [envindex] = (char *) malloc ((unsigned)len + val_len + 2);
sprintf (environ [envindex], "%s=%s", var, value);
environ [++envindex] = NULL;
}
MiniView::MiniView(const Arguments &args)
: ViewBuffer(args.Bounds().OffsetToCopy(0, 0)),
fArguments(args)
{
AddFilter(new BMessageFilter(B_KEY_DOWN, &MiniView::MessageFilter));
}
MiniView::~MiniView()
{
kill_thread(fConsoleWriter);
kill_thread(fShellExecutor);
kill_thread(fShellProcess);
}
void
MiniView::Start()
{
fConsole = new Console(this);
if (OpenTTY() != B_OK) {
TRACE(("error in OpenTTY\n"));
return;
}
setsid();
if (SpawnThreads() != B_OK)
TRACE(("error in SpawnThreads\n"));
}
status_t
MiniView::OpenTTY()
{
DIR *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;
sprintf(name, "/dev/pt/%s", entry->d_name);
fMasterFD = open(name, O_RDWR);
if (fMasterFD >= 0) {
sprintf(name, "/dev/tt/%s", entry->d_name);
fSlaveFD = open(name, O_RDWR);
if (fSlaveFD < 0) {
TRACE(("cannot open tty\n"));
close(fMasterFD);
} else {
struct termios tio;
tcgetattr(fSlaveFD, &tio);
signal(SIGCHLD, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
tio.c_line = 0;
tio.c_lflag |= ECHOE;
tio.c_iflag &= ~(INLCR|IGNCR);
tio.c_iflag |= ICRNL;
tio.c_iflag &= ~ISTRIP;
tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
tio.c_oflag |= ONLCR;
tio.c_oflag |= OPOST;
tio.c_cflag &= ~(CBAUD);
tio.c_cflag |= B19200;
tio.c_cflag &= ~CSIZE;
tio.c_cflag |= CS8;
tio.c_cflag |= CREAD;
tio.c_cflag |= HUPCL;
tio.c_iflag &= ~(IGNBRK|BRKINT);
tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
tio.c_lflag &= ~ECHOK;
tio.c_lflag &= ~IEXTEN;
tio.c_cc[VINTR] = 'C' & 0x1f;
tio.c_cc[VQUIT] = '\\'& 0x1f;
tio.c_cc[VERASE] = 0x08;
tio.c_cc[VKILL] = 'U' & 0x1f;
tio.c_cc[VEOF] = 'D' & 0x1f;
tio.c_cc[VEOL] = 0;
tio.c_cc[VMIN] = 4;
tio.c_cc[VTIME] = 0;
tio.c_cc[VEOL2] = 0;
tio.c_cc[VSWTCH] = 0;
tio.c_cc[VSTART] = 'S' & 0x1f;
tio.c_cc[VSTOP] = 'Q' & 0x1f;
tio.c_cc[VSUSP] = '@' & 0x1f;
tcsetattr(fSlaveFD, TCSANOW, &tio);
winsize ws;
int32 rows, cols;
GetSize(&cols, &rows);
ws.ws_row = rows;
ws.ws_col = cols;
if (LockLooper()) {
ws.ws_xpixel = Bounds().IntegerWidth();
ws.ws_ypixel = Bounds().IntegerHeight();
UnlockLooper();
}
ioctl(fSlaveFD, TIOCSWINSZ, &ws);
}
break;
}
}
Setenv("TTY", name);
}
if (fMasterFD < 0 || fSlaveFD < 0) {
TRACE(("could not open master or slave fd\n"));
return B_ERROR;
}
Setenv("TERM", "beterm");
return B_OK;
}
void
MiniView::FrameResized(float width, float height)
{
ViewBuffer::FrameResized(width, height);
winsize ws;
int32 rows, cols;
GetSize(&cols, &rows);
ws.ws_row = rows;
ws.ws_col = cols;
ws.ws_xpixel = (uint16)width;
ws.ws_ypixel = (uint16)height;
ioctl(fSlaveFD, TIOCSWINSZ, &ws);
}
void
MiniView::KeyDown(const char *bytes, int32 numBytes)
{
uint32 mod = modifiers();
if (numBytes == 1) {
if (mod & B_OPTION_KEY) {
char c = bytes[0] | 0x80;
write(fMasterFD, &c, 1);
} else {
switch (bytes[0]) {
case B_LEFT_ARROW:
write(fMasterFD, LEFT_ARROW_KEY_CODE, sizeof(LEFT_ARROW_KEY_CODE) - 1);
break;
case B_RIGHT_ARROW:
write(fMasterFD, RIGHT_ARROW_KEY_CODE, sizeof(RIGHT_ARROW_KEY_CODE) - 1);
break;
case B_UP_ARROW:
write(fMasterFD, UP_ARROW_KEY_CODE, sizeof(UP_ARROW_KEY_CODE) - 1);
break;
case B_DOWN_ARROW:
write(fMasterFD, DOWN_ARROW_KEY_CODE, sizeof(DOWN_ARROW_KEY_CODE) - 1);
break;
default:
write(fMasterFD, bytes, numBytes);
}
}
} else
write(fMasterFD, bytes, numBytes);
}
status_t
MiniView::SpawnThreads()
{
fConsoleWriter = spawn_thread(&MiniView::ConsoleWriter, "console writer", B_URGENT_DISPLAY_PRIORITY, this);
if (fConsoleWriter < 0)
return B_ERROR;
TRACE(("console writer thread is: %ld\n", fConsoleWriter));
fShellExecutor = spawn_thread(&MiniView::ExecuteShell, "shell process", B_URGENT_DISPLAY_PRIORITY, this);
if (fShellExecutor < 0)
return B_ERROR;
TRACE(("shell executor thread is: %ld\n", fShellExecutor));
resume_thread(fConsoleWriter);
resume_thread(fShellExecutor);
return B_OK;
}
int32
MiniView::ConsoleWriter(void *arg)
{
char buf[1024];
ssize_t len;
MiniView *view = (MiniView *)arg;
for (;;) {
len = read(view->fMasterFD, buf, sizeof(buf));
if (len < 0)
break;
view->fConsole->Write(buf, len);
}
return 0;
}
int32
MiniView::ExecuteShell(void *arg)
{
MiniView *view = (MiniView *)arg;
for (;;) {
int argc;
const char *const *argv;
view->fArguments.GetShellArguments(argc, argv);
int saved_stdin = dup(0);
int saved_stdout = dup(1);
int saved_stderr = dup(2);
dup2(view->fSlaveFD, 0);
dup2(view->fSlaveFD, 1);
dup2(view->fSlaveFD, 2);
view->fShellProcess = load_image(argc, (const char **)argv,
(const char **)environ);
setpgid(view->fShellProcess, 0);
tcsetpgrp(view->fSlaveFD, view->fShellProcess);
dup2(saved_stdin, 0);
dup2(saved_stdout, 1);
dup2(saved_stderr, 2);
close(saved_stdin);
close(saved_stdout);
close(saved_stderr);
status_t return_code;
wait_for_thread(view->fShellProcess, &return_code);
if (!view->fArguments.StandardShell()) {
view->Window()->PostMessage(B_QUIT_REQUESTED);
break;
}
}
return B_OK;
}
filter_result
MiniView::MessageFilter(BMessage *message, BHandler **target, BMessageFilter *filter)
{
MiniView *view = (MiniView *)(*target);
int32 raw_char;
message->FindInt32("raw_char", &raw_char);
if (raw_char == B_TAB) {
char bytes[2] = { B_TAB, 0 };
view->KeyDown(bytes, 1);
return B_SKIP_MESSAGE;
}
return B_DISPATCH_MESSAGE;
}