root/src/tests/apps/miniterminal/MiniView.cpp
/*
 * 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"

//#define TRACE_MINI_TERMINAL
#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) {
                        /* found it */
                        // TODO: shouldn't we free the old variable first?
                        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)
{
        // we need a message filter so that we get B_TAB keydowns
        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;
        }
        
        // we're a session leader
        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] == '.') // filter out . and ..
                                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);
                                        
                                        // set signal default
                                        signal(SIGCHLD, SIG_DFL);
                                        signal(SIGHUP, SIG_DFL);
                                        signal(SIGQUIT, SIG_DFL);
                                        signal(SIGTERM, SIG_DFL);
                                        signal(SIGINT, SIG_DFL);
                                        signal(SIGTTOU, SIG_DFL);
                                        
                                        // set terminal interface
                                        tio.c_line = 0;
                                        tio.c_lflag |= ECHOE; 
                                        
                                        // input: nl->nl, cr->nl
                                        tio.c_iflag &= ~(INLCR|IGNCR);
                                        tio.c_iflag |= ICRNL;
                                        tio.c_iflag &= ~ISTRIP;
                                        
                                        // output: cr->cr, nl in not retrun, no delays, ln->cr/ln
                                        tio.c_oflag &= ~(OCRNL|ONLRET|NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
                                        tio.c_oflag |= ONLCR;
                                        tio.c_oflag |= OPOST;
                                        
                                        // baud rate is 19200 (equal to beterm)
                                        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);
                                        
                                        // enable signals, canonical processing (erase, kill, etc), echo
                                        tio.c_lflag |= ISIG|ICANON|ECHO|ECHOE|ECHONL;
                                        tio.c_lflag &= ~ECHOK;
                                        
                                        tio.c_lflag &= ~IEXTEN;
                                        
                                        // set control charactors
                                        tio.c_cc[VINTR]  = 'C' & 0x1f;  /* '^C' */
                                        tio.c_cc[VQUIT]  = '\\'& 0x1f;  /* '^\' */
                                        tio.c_cc[VERASE] = 0x08;                /* '^H' */
                                        tio.c_cc[VKILL]  = 'U' & 0x1f;  /* '^U' */
                                        tio.c_cc[VEOF]   = 'D' & 0x1f;  /* '^D' */
                                        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;  /* '^S' */
                                        tio.c_cc[VSTOP]  = 'Q' & 0x1f;  /* '^Q' */
                                        tio.c_cc[VSUSP]  = '@' & 0x1f;  /* '^@' */
                                        
                                        // set terminal interface
                                        tcsetattr(fSlaveFD, TCSANOW, &tio);
                                        
                                        // set window size
                                        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)
{
        // TODO: add interrupt char handling
        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;
}