/*
 * KON - Kanji ON Linux Console - Copyright (C) 1992, 1993, 1994 Takashi
 * MANABE (manabe@Roy.dsl.tutics.tut.ac.jp)
 * 
 * KON is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * KON is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 675
 * Mass Ave, Cambridge, MA 02139, USA.
 */
/*
 * modified for Big5Con by Hung-Chi Chu <hcchu@r350.ee.ntu.edu.tw>
 */

#include	"big5con.h"

#if defined(__FreeBSD__)
#define TCSETA     TIOCSETA
#define TCGETA     TIOCGETA
#define SIGCLD     SIGCHLD
#define XCASE 0
#endif

int             masterPty;	/* master pseudo-tty file descriptor */

#define	MAX_TTYNAME	10

static int      childPid, sockFd, slavePty;
static struct termio oldTio;
static char     ptyName[MAX_TTYNAME + 1];
static int      orgVtNum = -1;

struct initInfo {
    bool            display;	/* display initialized */
    bool            utmp;	/* utmp set */
    bool            socket;	/* socket opened */
    bool            termio;	/* termio saved */
};

static struct initInfo init;

extern int      kbd_leds;
extern struct keymap vckmap;

static void
CleanUp(void)
{
    if (init.display && con.active) {
	TextMode();
    }
    if (init.utmp)
	ResetUtmp(ptyName);
    if (init.socket)
	SocketKill(sockFd);
    if (init.termio)
	ioctl(0, TCSETA, &oldTio);
    signal(SIGCHLD, SIG_DFL);
    signal(SIGHUP, SIG_DFL);
    signal(SIGTERM, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    signal(SIGUSR1, SIG_DFL);
    signal(SIGUSR2, SIG_DFL);
    FontDetach(TRUE);
}

static void
ExitTerm(int signum)
{
    fatal(sys_siglist[signum]);
}

static void
ExitPty(int signum)
{
    int             stat;

#if defined(__FreeBSD__)
    signal(SIGCLD, SIG_DFL);
#endif
    if (wait3(&stat, WNOHANG, 0) != childPid) {
	TextMode();
	kill(0, SIGTSTP);
	GraphMode();
	kill(childPid, SIGCONT);
	signal(SIGCLD, ExitPty);
	return;
    }
    if (WEXITSTATUS(stat) & 0x7f)
	if (WIFSIGNALED(stat))
	    fatal("child died with signal -- %s\r\n", sys_siglist[WTERMSIG(stat)]);
	else
	    fatal("child exited with status %d\r\n", WEXITSTATUS(stat) & 0x7f);
    else if (signum == SIGHUP) {
	fprintf(stderr, "\r\nBig5Con> switched to new VC\r\n");
	exit(EXIT_SUCCESS);
    } else {
	fprintf(stderr, "\r\nBig5Con> finished\r\n");
	exit(EXIT_SUCCESS);
    }
}

static fd_set   orgReadFds;
static int      numFds;

void
MouseSetRfd(int mfd)
{
    if (mfd > 0)
	FD_SET(mfd, &orgReadFds);
    if (mfd > sockFd)
	numFds = mfd + 1;
    else
	numFds = sockFd + 1;
}

void
MouseResetRfd(int mfd)
{
    if (mfd > 0)
	FD_CLR(mfd, &orgReadFds);
    numFds = sockFd + 1;
}

static void
ConsoleHandler(void)
{
    static u_char   buff[BUFSIZ + 1];
    fd_set          readFds;
    int             i = 0;
    struct timeval  tv;

    FD_ZERO(&orgReadFds);
    FD_SET(0, &orgReadFds);
    FD_SET(masterPty, &orgReadFds);
    FD_SET(sockFd, &orgReadFds);
    if (mInfo.has_mouse && mouseFd > 0)
	MouseSetRfd(mouseFd);
    else
	MouseResetRfd(mouseFd);
    /*
     * Note: we use timeout on select call even if cursor blink is off
     * because of screen saver and mouse cursor timeout.
     */
    while (1) {
	int             v;

	do {
	    /* Idle loop. */
	    PollCursor(FALSE);
	    readFds = orgReadFds;
	    tv.tv_sec = 0;
	    tv.tv_usec = 100000;/* 0.1 sec */
	    v = select(numFds, &readFds, NULL, NULL, &tv);
	} while (v == 0 || (v < 0 && (errno == EINTR || mouseFd < 0)));
	if (v < 0) {
	    PerrorExit("select");
	}
	if (FD_ISSET(masterPty, &readFds)) {
	    i = read(masterPty, buff, BUFSIZ);
	    if (i > 0) {
		if (con.text_mode) {
		    write(1, buff, i);
		} else {
		    /* buff[i] = 0; */
		    VtEmu(buff, i);
		    TextRefresh();
		}
	    }
	}
	if (FD_ISSET(0, &readFds)) {
	    int             j;
	    i = read(0, buff, BUFSIZ);
	    for (j = 0; j < i; j++)
		trans_key(buff[j]);
	    PollCursor(TRUE);
	}
	if (FD_ISSET(sockFd, &readFds))
	    SocketInterface(sockFd);
	if (mInfo.has_mouse && mouseFd > 0) {
	    if (FD_ISSET(mouseFd, &readFds) && con.active) {
		i = read(mouseFd, buff, BUFSIZ);
		if (i > 0)
		    MouseGetPacket(buff, i);
		PollCursor(TRUE);
	    }
	}
    }
}

static void
ProcessArgs(int argc, const char *argv[])
{
    int             i = 0;
    const char     *video = "NORMAL";

    if (argc > 0 && argv[0][0] != '-') {
	video = argv[0];
	i++;
    }
    ConsoleInit(video);
    while (i < argc) {
	const char     *arg;

	if (argv[i][0] != '-') {
	    warn("bad arg `%s'; assumed `-%s'\r\n", argv[i]);
	    arg = (char *)argv[i];
	} else
	    arg = (char *)argv[i] + 1;
	i++;
	if (i >= argc) {
	    error("no value for `%s'\r\n", arg);
	    break;
	}
	if (!strcasecmp(arg, "e"))
	    ConfigExecProg(argv[i]);
	else if (SetCapArg(arg, argv[i]) < 0)
	    warn("invalid capability `%s' ignored\r\n", arg);
	i++;
    }
}

static int      savedArgc;	/* argc of startup time */
static          const char **savedArgv;	/* argv of startup time */

/* Do initialization before reading config file */
void
TermInit(int argc, const char *argv[])
{
    int             i;

    init.display = init.utmp = init.socket = init.termio = FALSE;
    /* Initialize subsystems. */

    CapInit();
    ChildInit();
    MouseInit();
    VtInit();
    ProcessArgs(argc, argv);
    savedArgc = argc;
    savedArgv = malloc(argc * sizeof(const char *));
    for (i = 0; i < argc; i++) {
	savedArgv[i] = strdup(argv[i]);
    }
}

static int
TryTermReset(int argc, const char *argv[])
{
    int             i;

    fprintf(stderr, "Big5Con> resetting kon for args [");
    for (i = 0; i < argc; i++) {
	fprintf(stderr, " %s", argv[i]);
    }
    fprintf(stderr, " ]...\r\n");
    CapInit();
    ConsoleCleanup();
    if (mInfo.has_mouse)
	MouseCleanup();
#if 0
    VtCleanup();
#endif
    init.display = FALSE;
    MouseInit();
    VtInit();
    ProcessArgs(argc, argv);
    return ReadConfig(CONFIG_NAME);
}

/* Called from SocketInterface with stream fd. */
void
TermRestart(int fd)
{
    int             i;
    int             argc;
    char          **argv;

    read(fd, &argc, sizeof(argc));
    argv = alloca(argc * sizeof(char *));
    for (i = 0; i < argc; i++) {
	int             len;

	read(fd, &len, sizeof(len));
	argv[i] = alloca(len + 1);	/* +1 for '\0' */
	read(fd, argv[i], (size_t) len);
	argv[i][len] = '\0';
    }
    TextMode();
    if (TryTermReset(argc, (const char **)argv) < 0 &&
	TryTermReset(savedArgc, savedArgv) < 0 &&
	TryTermReset(0, (const char **)NULL) < 0)
	fatal("giving up\r\n");
    if (mInfo.has_mouse)
	mouseFd = MouseStart();
    VtStart();
    ConsoleStart(savedArgc, savedArgv); /* by b5c: add argc, argv */
    init.display = TRUE;
    message("reset done\r\n");
}

/* Start processing */
void
TermStart(int argc, const char *argv[])
{
    struct termio   newTio;
    char            ls, ln;

    /* Open PTY(master) */
    for (ls = 'p'; ls <= 's'; ls++) {
	for (ln = 0; ln <= 0xF; ln++) {
	    sprintf(ptyName, "/dev/pty%1c%1x", ls, ln);
	    if ((masterPty = open(ptyName, O_RDWR)) >= 0)
		break;
	}
	if (masterPty >= 0)
	    break;
    }
    if (masterPty < 0) {
	message("can not get master pty\r\n");
	PerrorExit(ptyName);
    }
    ptyName[5] = 't';

    if (mInfo.has_mouse) {
	mouseFd = MouseStart();
    }
    chown("/dev/tty0", getuid(), getgid());
#if defined(linux)
    sockFd = SocketInit(ttyname(0) + 8);
#elif defined(__FreeBSD__)
    sockFd = SocketInit(ttyname(0) + 9);
#endif
    init.socket = TRUE;

    /* Get old tio of 0 */
    ioctl(0, TCGETA, &oldTio);
    init.termio = TRUE;

    /* by b5c: add check GIO_KEYMAP KDGETLED */
    if (ioctl(0, GIO_KEYMAP, &vckmap) < 0) {
	PerrorExit("ioctl");
    }
    if (ioctl(0, KDGETLED, &kbd_leds) < 0) {
	PerrorExit("ioctl");
    }

    SetUtmp(ptyName);
    init.utmp = TRUE;

    /* fork handler */
    if ((childPid = fork()) < 0) {
	PerrorExit("fork");
    }
    if (childPid != 0) {
	/* I'm parent. */
	atexit(CleanUp);
	ChildCleanup();

	/* Signal Setting */
	signal(SIGCHLD, ExitPty);
	signal(SIGHUP, ExitTerm);
	signal(SIGTERM, ExitTerm);
	signal(SIGSEGV, ExitTerm);
	signal(SIGQUIT, ExitTerm); /* by b5c */
	/* Set new tio of 0 */
	newTio = oldTio;
	newTio.c_lflag &= ~(ECHO | ISIG | ICANON | XCASE);
	newTio.c_iflag = 0;
	newTio.c_oflag &= ~OPOST;
	newTio.c_cc[VMIN] = 1;
	newTio.c_cc[VTIME] = 0;
#if defined(__FreeBSD__)
	newTio.c_cc[VDISCARD] = _POSIX_VDISABLE;
	newTio.c_cc[VLNEXT] = _POSIX_VDISABLE;
	newTio.c_cc[VSTART] = _POSIX_VDISABLE;
	newTio.c_cc[VSTOP] = _POSIX_VDISABLE;
	newTio.c_cc[VINTR] = _POSIX_VDISABLE;
	newTio.c_cc[VSUSP] = _POSIX_VDISABLE;
	newTio.c_cc[VDSUSP] = _POSIX_VDISABLE;
	newTio.c_cc[VQUIT] = _POSIX_VDISABLE;
#endif
	newTio.c_cflag |= CS8;
#if defined(linux)
	newTio.c_line = 0;
#endif
	ioctl(0, TCSETA, &newTio);
	/* VGA initialize */
	VtStart();
	ConsoleStart(argc, argv);
	init.display = TRUE;
	FontAttach();
	ConsoleHandler();
    } else {
	int             efd;
	FILE           *errfp;

	efd = dup(STDERR_FILENO);
	errfp = fdopen(efd, "w");
	/* I'm child */
	/* Make me process leader */
	setsid();
#if defined(__FreeBSD__)
	{
	    int             devtty;
	    if ((devtty = open("/dev/tty", O_RDWR | O_NDELAY)) >= 0) {
		ioctl(devtty, TIOCNOTTY, (char *)0);
		close(devtty);
	    }
	}
#endif
	/* Open TTY(slave) */
	if ((slavePty = open(ptyName, O_RDWR)) < 0) {
	    PerrorExit(ptyName);
	}
	close(masterPty);
	/* Set old tio to TTY */
	ioctl(slavePty, TCSETA, &oldTio);
#if defined(__FreeBSD__)
	ioctl(slavePty, TIOCSCTTY, (char *)0);
#endif
	/* Set std??? to pty */
	dup2(slavePty, STDIN_FILENO);
	dup2(slavePty, STDOUT_FILENO);
	dup2(slavePty, STDERR_FILENO);
	ChildStart(errfp);
    }
}

void
ChangeOrgConsole()
{
    int             cfd;

#if defined(linux)
    cfd = open("/dev/console", O_WRONLY);
    if (cfd < 0 && (cfd = open("/dev/console", O_RDONLY)) < 0) {
        PerrorExit("/dev/console");
    }
#elif defined(__FreeBSD__)
    cfd = open("/dev/vga", O_WRONLY);
    if (cfd < 0 && (cfd = open("/dev/vga", O_RDONLY)) < 0) {
        PerrorExit("/dev/vga");
    }
#endif
    ioctl(cfd, VT_ACTIVATE, orgVtNum);
    close(cfd);
}

void 
ChangeNewConsole()
{
#if defined(linux)
    struct vt_stat  vts;
#endif
    int             cfd, vfd, vtNum, child, parent, mode;
    char            vtty[MAX_TTYNAME + 1];

#if defined(linux)
    cfd = open("/dev/console", O_WRONLY);
    if (cfd < 0 && (cfd = open("/dev/console", O_RDONLY)) < 0)
        fatal("can't open /dev/console");
#elif defined(__FreeBSD__)
    cfd = open("/dev/vga", O_WRONLY);
    if (cfd < 0 && (cfd = open("/dev/vga", O_RDONLY)) < 0)
        fatal("can't open /dev/vga");
#endif
    ioctl(cfd, KDGETMODE, &mode);
    if (mode == KD_TEXT) {
	close(cfd);
	return;
    }
#if defined(linux)
    ioctl(cfd, VT_GETSTATE, &vts);
    orgVtNum = vts.v_active;
#endif
    ioctl(cfd, VT_OPENQRY, &vtNum);
    if (vtNum < 0)
	fatal("can't get free VC");
    parent = getpid();
    if ((child = fork()) == -1)
	PerrorExit("fork");
    if (child) {
	signal(SIGHUP, ExitPty);
	pause();
    }
    setsid();
#if defined(linux)
    sprintf(vtty, "/dev/tty%d", vtNum);
#elif defined(__FreeBSD__)
    sprintf(vtty, "/dev/ttyv%d", vtNum);
#endif
    if ((vfd = open(vtty, O_RDWR)) < 0)
	fatal("can't open %s", vtty);
    if (ioctl(cfd, VT_ACTIVATE, vtNum) != 0)
	fatal("can't activate VC(%d)", vtNum);
    atexit(ChangeOrgConsole);
    close(cfd);
    dup2(vfd, STDIN_FILENO);
    dup2(vfd, STDOUT_FILENO);
    dup2(vfd, STDERR_FILENO);
    kill(parent, SIGHUP);
}
