/* vim: set shiftwidth=4 tabstop=8 softtabstop=4: */
/* $Id: termios.c,v 1.7 2003/12/06 18:43:01 shinra Exp $ */

#define IN_LIBSPT
#include "sptprivate.h"

/*
 * Default termios setting is basically GNU screen compatible, but also
 * influenced by xterm and rxvt.
 *
 * Removed ifdefs which are defined in FreeBSD <termios.h> when _POSIX_SOURCE
 * is defined. They are probably defined in all POSIX termios and SysV
 * termio environments.
 *
 * Does not support <cytermio.h> because it seems very few software supports
 * it. As I know only screen supports it. Even xterm does not.
 *
 * Raw mode is based on FreeBSD's cfmakeraw() and glibc's sgtty/termios
 * mapper. Prefers cfmakeraw() if exists. This raw mode is *completely*
 * raw mode. It might unsuitable to normal tty applications because you
 * can't detect ^C or ^Z asyncronously by signal. Instead it is suitable
 * to "tty on tty" such as screen and telnet.
 *
 * 8bit through.
 */

#define TIO_TYPE_TERMIOS 0
#define TIO_TYPE_TERMIO 1
#define TIO_TYPE_SGTTY 2
#if defined(HAVE_TERMIOS_H)
# include <termios.h>
# define TIO_TYPE TIO_TYPE_TERMIOS
#elif defined(HAVE_TERMIO_H)
# include <termio.h>
# define TIO_TYPE TIO_TYPE_TERMIO
#elif defined(HAVE_SYS_TERMIO_H)
# include <sys/termio.h>
# define TIO_TYPE TIO_TYPE_TERMIO
#elif defined(HAVE_SGTTY_H)
# include <sgtty.h>
# define TIO_TYPE TIO_TYPE_SGTTY
#else
# error "no termio header."
#endif

#ifdef __hpux
# include <sys/bsdtty.h>    /* to define TIOCSLTC */
#endif

#if TIO_TYPE == TIO_TYPE_SGTTY
# define USE_LTCHARS
#elif defined(__hpux)
# if !(defined(VSUSP) && defined(VDSUSP) \
	&& defined(VWERASE) && defined(VLNEXT))
#  define USE_LTCHARS
# endif
#endif
#if TIO_TYPE == TIO_TYPE_SGTTY
# if defined(TIOCLSET) && !defined(__sgi)
#  define USE_LMODE
# endif
# ifdef TIOCGETX
#  define USE_EXTRA
# endif
#endif

#if TIO_TYPE == TIO_TYPE_TERMIOS
# define TIO_VDISABLE _POSIX_VDISABLE
#else
# define TIO_VDISABLE 0xff
#endif
#define SG_VDISABLE -1
#if defined(B38400)
# define VAL_LINE_SPEED B38400
#elif defined(EXTB)
# define VAL_LINE_SPEED EXTB
#else
# define VAL_LINE_SPEED B9600
#endif
#undef CTRL /* FreeBSD defines this */
#define CTRL(x) ((x) & 0x1f)
#include "chars.h"

struct spt_termios_tag {
#if TIO_TYPE == TIO_TYPE_TERMIOS
    struct termios tio;
#elif TIO_TYPE == TIO_TYPE_TERMIO
    struct termio tio;
#else /* TIO_TYPE_SGTTY */
    struct sgttyb sg;
    struct tchars tc;
    int discipline;
#ifdef USE_EXTRA
    int extra;
#endif
#ifdef USE_LMODE
    unsigned int lmode;
#endif
#endif /* TIO_TYPE_SGTTY */
#ifdef USE_LTCHARS
    struct ltchars ltc;
#endif
#ifdef sony
    long jmode;
    struct jtchars jtc;
#endif
};

/* You might want static data area for some reasons such as signal safety */
SPT_EXPORT_VAR(spt_termios) spt_termios_buf1;
SPT_EXPORT_VAR(spt_termios) spt_termios_buf2;
SPT_EXPORT_VAR(spt_termios) spt_termios_buf3;
SPT_EXPORT_VAR(spt_termios) spt_termios_buf4;

SPT_EXPORT(int)
spt_termios_alloc(spt_termios **ppterm)
{
    spt_termios *pterm = CALLOC(1, sizeof(spt_termios));
    if (!pterm)
	return SPT_E_NOMEM;
    *ppterm = pterm;
    return SPT_E_NONE;
}

SPT_EXPORT(void)
spt_termios_copy(spt_termios *dst, const spt_termios *src)
{
    MEMCPY(dst, src, sizeof(spt_termios));
#if TIO_TYPE == TIO_TYPE_TERMIOS && 0 /* change 0 to 1 if needed */
    cfsetispeed(&dst->tio, cfgetispeed(&src->tio));
    cfsetospeed(&dst->tio, cfgetospeed(&src->tio));
#endif
}

SPT_EXPORT(int)
spt_termios_get(spt_termios *pterm, int fd)
{
    if (
#if TIO_TYPE == TIO_TYPE_TERMIOS
	    !tcgetattr(fd, &pterm->tio)
#elif TIO_TYPE == TIO_TYPE_TERMIO
	    !ioctl(fd, TCGETA, &pterm->tio)
#else /* TIO_TYPE_SGTTY */
	    !ioctl(fd, TIOCGETP, &pterm->sg)
	    && !ioctl(fd, TIOCGETC, &pterm->tc)
	    && !ioctl(fd, TIOCGETD, &pterm->discipline)
#ifdef USE_EXTRA
	    && !ioctl(fd, TIOCGETX, &pterm->extra)
#endif
#ifdef USE_LMODE
	    && !ioctl(fd, TIOCLGET, &pterm->lmode)
#endif
#endif /* TIO_TYPE_SGTTY */
#ifdef USE_LTCHARS
	    && !ioctl(fd, TIOCGLTC, &pterm->ltc)
#endif
#ifdef sony
	    && !ioctl(fd, TIOCKGET, &pterm->jmode)
	    && !ioctl(fd, TIOCKGETC, &pterm->jtc)
#endif
	)
	return SPT_E_NONE;
    return (errno == EBADF || errno == ENOTTY) ? SPT_E_INVAL : SPT_E_STTY_FAIL;
}

SPT_EXPORT(int)
spt_termios_set(const spt_termios *pterm, int fd)
{
    if (
#if TIO_TYPE == TIO_TYPE_TERMIOS
	    !tcsetattr(fd, TCSADRAIN, &pterm->tio)
#elif TIO_TYPE == TIO_TYPE_TERMIO && defined(TCSETAW)
	    !ioctl(fd, TCSETAW, &pterm->tio)
#elif TIO_TYPE == TIO_TYPE_TERMIO /* && !defined(TCSETAW) */
	    !ioctl(fd, TCSETA, &pterm->tio)
#else /* TIO_TYPE_SGTTY */
	    !ioctl(fd, TIOCSETP, &pterm->sg)
	    && !ioctl(fd, TIOCSETC, &pterm->tc)
	    && !ioctl(fd, TIOCSETD, &pterm->discipline)
#ifdef USE_EXTRA
	    && !ioctl(fd, TIOCSETX, &pterm->extra)
#endif
#ifdef USE_LMODE
	    && !ioctl(fd, TIOCLSET, &pterm->lmode)
#endif
#endif /* TIO_TYPE_SGTTY */

#ifdef USE_LTCHARS
	    && !ioctl(fd, TIOCSLTC, &pterm->ltc)
#endif
#ifdef sony
	    && !ioctl(fd, TIOCKSET, &pterm->jmode)
	    && !ioctl(fd, TIOCKSETC, &pterm->jtc)
#endif
	)
	return SPT_E_NONE;
    return (errno == EBADF || errno == ENOTTY) ? SPT_E_INVAL : SPT_E_STTY_FAIL;
}

/* Ugly goto chain: how to avoid both awful indent and goto? */
SPT_EXPORT(int)
spt_termios_sset(const spt_termios *pterm, int fd, const spt_termios *pold)
{
    if (!pold)
	return SPT_E_INVAL;
#if TIO_TYPE == TIO_TYPE_TERMIOS
    if (tcsetattr(fd, TCSADRAIN, &pterm->tio))
	goto err1;
#define LASTERR 1

#elif TIO_TYPE == TIO_TYPE_TERMIO && defined(TCSETAW)
    if (ioctl(fd, TCSETAW, &pterm->tio))
	goto err1;
#define LASTERR 1

#elif TIO_TYPE == TIO_TYPE_TERMIO /* && !defined(TCSETAW) */
    if (ioctl(fd, TCSETA, &pterm->tio))
	goto err1;
#define LASTERR 1

#else /* TIO_TYPE_SGTTY */
    if (ioctl(fd, TIOCSETP, &pterm->sg))
	goto err1;
    if (ioctl(fd, TIOCSETC, &pterm->tc))
	goto err2;
    if (ioctl(fd, TIOCSETD, &pterm->discipline))
	goto err3;
#define LASTERR 3
#ifdef USE_EXTRA
    if (ioctl(fd, TIOCSETX, &pterm->extra))
	goto err4;
#undef LASTERR
#define LASTERR 4
#endif /* USE_EXTRA */
#ifdef USE_LMODE
    if (ioctl(fd, TIOCLSET, &pterm->lmode))
	goto err5;
#undef LASTERR
#define LASTERR 5
#endif /* USE_LMODE */
#endif /* TIO_TYPE_SGTTY */

#ifdef USE_LTCHARS
    if (ioctl(fd, TIOCSLTC, &pterm->ltc))
	goto err6;
#undef LASTERR
#define LASTERR 6
#endif /* USE_LTCHARS */

#ifdef sony
    if (ioctl(fd, TIOCKSET, &pterm->jmode))
	goto err7;
    if (ioctl(fd, TIOCKSETC, &pterm->jtc))
	goto err8;
#undef LASTERR
#define LASTERR 8
#endif /* sony */

    return SPT_E_NONE;

    /* Start error recovery */

#ifdef sony
#if LASTERR > 8
    ioctl(fd, TIOCKSETC, &pold->jtc);
#endif
err8:
    ioctl(fd, TIOCKSET, &pold->jmode);
err7:
#endif /* sony */

#ifdef USE_LTCHARS
#if LASTERR > 6
    ioctl(fd, TIOCSLTC, &pold->ltc);
#endif
err6:
#endif /* USE_LTCHARS */

#if TIO_TYPE == TIO_TYPE_SGTTY
#ifdef USE_LMODE
#if LASTERR > 5
    ioctl(fd, TIOCLSET, &pold->lmode);
#endif
err5:
#endif /* USE_LMODE */
#ifdef USE_EXTRA
#if LASTERR > 4
    ioctl(fd, TIOCSETX, &pold->extra);
#endif
err4:
#endif /* USE_EXTRA */
#if LASTERR > 3
    ioctl(fd, TIOCSETD, &pold->discipline);
#endif
err3:
    ioctl(fd, TIOCSETC, &pold->tc);
err2:
    ioctl(fd, TIOCSETP, &pold->sg);

#elif TIO_TYPE == TIO_TYPE_TERMIO && !defined(TCSETAW)
#if LASTERR > 1
    ioctl(fd, TCSETA, &pold->tio);
#endif

#elif TIO_TYPE == TIO_TYPE_TERMIO /* && defined(TCSETAW) */
#if LASTERR > 1
    ioctl(fd, TCSETAW, &pold->tio);
#endif

#else /* TIO_TYPE_TERMIOS */
#if LASTERR > 1
    tcsetattr(fd, TCSADRAIN, &pold->tio);
#endif

#endif /* TIO_TYPE_TERMIOS */
err1:
    return (errno == EBADF || errno == ENOTTY) ? SPT_E_INVAL : SPT_E_STTY_FAIL;
#undef LASTERR
}

SPT_EXPORT(void)
spt_termios_default(spt_termios *pterm)
{
    BZERO(pterm, sizeof(spt_termios));
#if TIO_TYPE == TIO_TYPE_TERMIOS || TIO_TYPE == TIO_TYPE_TERMIO
    /*
     * set: (POSIX) BRKINT, IGNPAR, IXON, ICRNL
     * cleard: (POSIX) IGNBRK, PARMRK, INPCK, ISTRIP, INLCR, IGNCR, IXOFF
     * (XSI) IXANY, (others) IMAXBEL, IUCLC
     */
    pterm->tio.c_iflag = BRKINT | IGNPAR | IXON | ICRNL;

    /*
     * set: (POSIX) OPOST, (XSI) ONLCR, (others) OXTABS, TAB3
     * cleard: (POSIX 2001) OCRNL, ONOCR, ONLRET, OFILL, NLDLY, CRDLY,
     * TABDLY, BSDLY, VTDLY, FFDLY
     * (others) ONOEOT, OFDEL
     */
    pterm->tio.c_oflag = (OPOST
#ifdef ONLCR
	    | ONLCR
#endif
#ifdef TAB3
	    | TAB3
#endif
#ifdef OXTABS
	    | OXTABS
#endif
	    );

    /*
     * set: (POSIX) CS8, CREAD, CLOCAL, (TERMIO only) VAL_LINE_SPEED
     * cleard: (POSIX) CSTOPB, PARENB, PARODD, HUPCL
     * (others) CIGNORE, CCTS_OFLOW, CRTS_IFLOW, CDTR_IFLOW, CDSR_OFLOW,
     * CCAR_OFLOW, MDMBUF, LOBRK, CIBAUD, CRTSCTS
     */
    pterm->tio.c_cflag = CS8 | CREAD | CLOCAL
#if  TIO_TYPE == TIO_TYPE_TERMIO
	| VAL_LINE_SPEED
#endif
	;
#if TIO_TYPE == TIO_TYPE_TERMIOS
    cfsetispeed(&pterm->tio, VAL_LINE_SPEED);
    cfsetospeed(&pterm->tio, VAL_LINE_SPEED);
#endif

    /*
     * set: (POSIX) ISIG, ICANON, ECHO, ECHOE, ECHOK, IEXTEN
     * (others) ECHOCTL, ECHOKE
     * cleard: (POSIX) ECHONL, EXTPROC, TOSTOP, NOFLSH
     * (others) ECHOPRT, ALTWERASE, FLUSHO, NOKERNINFO, PENDIN, XCASE, DEFECHO
     */
    pterm->tio.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | IEXTEN
#ifdef ECHOCTL
	| ECHOCTL
#endif
#ifdef ECHOKE
	| ECHOKE
#endif
	;

    /*
     * This part differs to GNU screen depending on Cxxx definitions.
     * Especially "KILL" is normally '^U' while screen's is '^H'.
     * '^H' may set to "ERASE2", which screen does not set.
     * Based on FreeBSD <sys/ttydefaults.h> and rxvt.
     */
#if TIO_TYPE == TIO_TYPE_TERMIOS
# if !(defined(VEOF) && defined(VEOL) && defined(VERASE) && defined(VINTR) \
	&& defined(VKILL) && defined(VQUIT) && defined(VSUSP) \
	&& defined(VSTART) && defined(VSTOP) && defined(NCCS))
#  error "Standard termios macros are not defined"
# endif
# define MAXCC NCCS
#elif defined(NCC)
# define MAXCC NCC
#else
# define MAXCC 256
#endif
#define VDISABLE TIO_VDISABLE
#include "tioccc.h"
#undef VDISABLE
#undef MAXCC

    /* VMIN == VEOF and VTIME == VEOL on old System V. */
#if VMIN != VEOF
    pterm->tio.c_cc[VMIN] = 1;
#endif
#if VTIME != VEOL
    pterm->tio.c_cc[VTIME] = 0;
#endif

#else /* TIO_TYPE_SGTTY */
#define VDISABLE SG_VDISABLE
    /*
     * Character settings are based on FreeBSD and rxvt while others
     * are screen compatible, as in termio[s] environment.
     * I turn off ANYP to pass 8bit though screen sets it.
     */
    pterm->sg.sg_erase = MYCERASE;
    pterm->sg.sg_kill = MYCKILL;
    pterm->sg.sg_ispeed = VAL_LINE_SPEED;
    pterm->sg.sg_ospeed = VAL_LINE_SPEED;
    pterm->sg.sg_flags = CRMOD | ECHO;

    pterm->tc.t_intrc = MYCINTR;
    pterm->tc.t_quitc = MYCQUIT;
    pterm->tc.t_startc = MYCSTART;
    pterm->tc.t_stopc = MYCSTOP;
    pterm->tc.t_eofc = MYCEOF;
    pterm->tc.t_brkc = MYCBRK;
#undef VDISABLE

#if defined(NTTYDISC)
    pterm->discipline = NTTYDISC;
#endif

#ifdef USE_LMODE
    /* I don't know what they mean. Copy xterm's ifdefs. */
    pterm->lmode = LCTLECH | LCRTKIL | LCRTERA | LCRTBS
#ifdef LPASS8
	| LPASS8
#endif
#ifdef LDECCTQ
	| LDECCTQ
#endif
	;
#endif /* USE_LMODE */

#endif /* TIO_TYPE_SGTTY */

#ifdef USE_LTCHARS
#define VDISABLE SG_VDISABLE
    pterm->ltc.t_suspc = MYCSUSP;
    pterm->ltc.t_dsuspc = MYCDSUSP;
    pterm->ltc.t_rprntc = MYCRPRNT;
    pterm->ltc.t_flushc = MYCFLUSH;
    pterm->ltc.t_werasc = MYCWERASE;
    pterm->ltc.t_lnextc = MYCLNEXT;
#undef VDISABLE
#endif /* USE_LTCHARS */

#ifdef sony
    pterm->jtc.t_ascii = 'J';
    pterm->jtc.t_kanji = 'B';
    pterm->jmode = KM_ASCII | KM_SYSSJIS;
#endif /* sony */
}

SPT_EXPORT(void)
spt_termios_raw(spt_termios *pterm)
{
#if TIO_TYPE == TIO_TYPE_TERMIOS && defined(HAVE_CFMAKERAW)
    cfmakeraw(&pterm->tio);
#elif TIO_TYPE == TIO_TYPE_TERMIOS || TIO_TYPE == TIO_TYPE_TERMIO
    /*
     * set: (POSIX) IGNBRK
     * cleard: (POSIX) BRKINT, IGNPAR, PARMRK, INPCK, ISTRIP, INLCR, IGNCR,
     * ICRNL, IXON, IXOFF
     * (others) IMAXBEL
     * unchanged: (XSI) IXANY, (others) IUCLC
     *
     * FreeBSD's and glibc's cfmakeraw() keep IXANY and IUCLC unchanged.
     * FreeBSD's telnet deliberately saves IXANY.
     * I don't know why they do but I follow them.
     */
    pterm->tio.c_iflag &= ~BRKINT & ~IGNPAR & ~PARMRK & ~INPCK & ~ISTRIP
	& ~INLCR & ~IGNCR & ~ICRNL & ~IXON & ~IXOFF
#ifdef IMAXBEL
	& ~IMAXBEL
#endif
	;
    pterm->tio.c_iflag |= IGNBRK;

    /*
     * cleard: (POSIX) OPOST
     * uncanged: all other flags
     *
     * Above is all we have to do.
     */
    pterm->tio.c_oflag &= ~OPOST;

    /*
     * set: (POSIX) CS8, CREAD
     * cleard: (POSIX) CSIZE, PARENB
     * unchanged: (POSIX) CSTOPB, PARODD, HUPCL, CLOCAL
     * (TERMIO only) CBAUD, CIBAUD
     * (others) CCTS_OFLOW, CRTSCTS, CRTS_IFLOW, CDTR_IFLOW, CDSR_OFLOW,
     * CCAR_OFLOW, MDMBUF, LOBLK
     *
     * This order is important; CSIZE might be CS5|CS6|CS7|CS8.
     * I don't know well about unchanged flags.
     */
    pterm->tio.c_cflag &= ~CSIZE & ~PARENB;
    pterm->tio.c_cflag |= CS8 | CREAD;

    /*
     * cleard: (POSIX) ECHOE, ECHOK, ECHO, ECHONL, ISIG, ICANON, IEXTEN,
     * TOSTOP, NOFLSH
     * (others) PENDIN
     * unchanged: (others) EXTPROC, ECHOKE, ECHOPRT, ECHOCTL, ALTWERASE,
     * FLUSHO, NOKERNINFO, IXCASE, DEFECHO
     *
     * ECHOKE, ECHOPRT, NOKERNINFO, ALTWERASE and IXCASE are meaningless in
     * noncanonical mode.
     * ECHOPRT, ECHOCTL and probably DEFECHO are meaningless when ECHO is
     * not set.
     * I don't know about FLUSHO and EXTPROC.
     */
    pterm->tio.c_lflag &= ~ECHOE & ~ECHOK & ~ECHO & ~ECHONL & ~ISIG
	& ~ICANON & ~IEXTEN & ~TOSTOP & ~NOFLSH
#ifdef PENDIN
	& ~PENDIN
#endif
	;
    
    pterm->tio.c_cc[VMIN] = 1;
    pterm->tio.c_cc[VTIME] = 0;

#else /* TIO_TYPE_SGTTY */

    /*
     * set: RAW(!ICANON,!ISIG)
     * cleard: CBREAK(!ICANON,ISIG), CRMOD(ICRNL), TANDEM(IXOFF), ECHO(ECHO),
     * ANYP(see below)
     * unchanged: none?
     * 
     * When !RAW && !LITOUT FreeBSD sgtty emulator interprets:
     *
     * PASS8 && ODDP && EVENP -> !PARENB && CS8 && !INPCK && ISTRIP
     * PASS8 && !ODDP && !EVENP -> !PARENB && CS8 && !INPCK && !ISTRIP
     * !PASS8 && ODDP && EVENP -> PARENB && CS7 && !INPCK && ISTRIP
     * !PASS8 && !ODDP && !EVENP -> PARENB && CS7 && !INPCK && ISTRIP
     *
     * CS8 && ISTRIP -> PASS8 && ODDP && EVENP
     * CS8 && !ISTRIP -> PASS8 && !ODDP && !EVENP
     * CS7 && PARENB && !INPCK && ISTRIP -> !PASS8 && ODDP && EVENP
     * CS7 && PARENB && !INPCK && !ISTRIP -> !PASS8 && !ODDP && !EVENP
     *
     * We must be aware to these pitfalls to keep our ttys 8bit through.
     */
    pterm->sg.sg_flags &= ~CBREAK & ~CRMOD & ~TANDEM & ~ECHO & ~LCRTERA
	& ~LCRTKIL
#ifdef ANYP
	& ~ANYP
#else
	& ~ODDP & ~EVENP
#endif
	;
    pterm->sg.sg_flags |= RAW;
    
#ifdef USE_LMODE
    /*
     * set: LPASS8(CS8), LLITOUT(!OPOST)
     * cleard: LNOFLSH(NOFLSH), LCRTERA(ECHOE), LCRTKIL(ECHOK),
     * LTOSTOP(TOSTOP)
     * unchanged: none?
     */
    pterm->lmode &= ~LNOFLSH & ~LCRTERA & ~LCRTKIL & ~LTOSTOP;
    pterm->lmode |= LLITOUT
#ifdef LPASS8
	| LPASS8
#endif
	;
#endif /* USE_LMODE */

#ifdef USE_EXTRA
    /*
     * set: NOISIG(!ISIG)
     * unchanged: STOPB(CSTOPB)
     */
    pterm->extra |= NOISIG;
#endif

    /* sg_erase, sg_kill, sg_ispeed, sg_ospeed, tc, ltc unchanged */

#endif /* TIO_TYPE_SGTTY */
}

SPT_EXPORT(int)
spt_termios_fd_default(int fd)
{
    spt_termios new, old;
    if (spt_termios_get(&old, fd))
	spt_termios_default(&old);
    spt_termios_default(&new);
    return spt_termios_sset(&new, fd, &old);
}
