/* unixio.c - 16:05 GMT + 10:00 Wed 4 Aug 1993 - modifier Geoffrey Tobin */

/* Add a global structure so we don't need BSD specific stuff
 * and remove that BSD specific stuff.
 *    -- Ian Dall
 */

#include "config.h"
#include "globals.h"

#include "screenio.h"  /* for MesgString, MesgLine */

#define IN_UNIXIO_C
#include "unixio.h"

/* If we are on a POSIX machine, use the tcsetattr/tcgetattr routines */
/* instead of ioctl.  (Rob Malouf, 12-Oct-1993)                       */

#ifdef _POSIX_SOURCE
# define sioctl(S) tcsetattr(DV_FD, TCSADRAIN, S)
# define gioctl(S) tcgetattr(DV_FD, S)
#else
# define sioctl(S) ioctl(DV_FD, DV_STTY, S)
# define gioctl(S) ioctl(DV_FD, DV_GTTY, S)
#endif

/* Set DV_FD (ioctl file descriptor) to 0 or 1 ? */
#ifdef _POSIX_SOURCE
# define DV_FD STDIN_FILENO
#else /* not POSIX */
# define DV_FD  0
#endif /* POSIX */

struct { unsigned int intr, tstop : 1;} sig_flags = {0, 0};

struct { unsigned int cbreak, raw, echo : 1;} cmode_flags = {0, 0, 0};

/* GT - For the purpose of this file, Aix is BSD - see the headers
        taken from Berkeley in /usr/include and /usr/include/sys !
*/

#ifdef AIX
# define BSD 1
#endif /* AIX */

/* Alex Dickinson
   Procedures for setting and resetting UNIX tty characteristics.
   Interesting functions are:
      save_init_tty;
      restore_init_tty;
      save_temp_tty;
      restore_temp_tty;
      echoon;
      echooff;
      singlecharon;
      singlecharoff;
      buffercount;
      suspend;
   A side effect of calling save_temp_tty is to set up signal handling
   to reset the terminal characteristics appropriately for the various
   interrupt signals.
*/

#ifdef _POSIX_SOURCE
# include <fcntl.h>
# include <termios.h>
  typedef struct termios dv_tty;
#else /* not POSIX */
#ifdef BSD 
# include <sgtty.h>
  typedef struct sgttyb dv_tty;
# define DV_GTTY TIOCGETP
# define DV_STTY TIOCSETN
#else /* not BSD; System V ? */
# include <fcntl.h>
# include <termio.h>
  typedef struct termio dv_tty;
# define DV_GTTY TCGETA
# define DV_STTY TCSETAW
#endif /* BSD */
#endif /* POSIX */
#include <signal.h>
#include <errno.h>

#ifdef __linux__
#include <sys/ioctl.h>
#endif /* __linux__ */

static dv_tty init_tty_state;  /* store initial terminal characteristics */
static dv_tty temp_tty_state;  /* current terminal characteristics */

#ifndef BSD /* Not BSD */
# ifdef _POSIX_SOURCE
  unsigned char saved_cc[NCCS];
# else /* Not BSD and not POSIX */
  unsigned char saved_cc[NCC];
#endif /* POSIX */
#endif /* Not BSD */

/* locally defined and used functions and data */

static Void setsignals (VOID);
static Void reportio __((int iostat));

static int iostatus;  /* return status of ioctl system function */

#ifdef __STDC__
static Void reportio (int iostat)
#else
static Void reportio (iostat)
  int iostat;
#endif
{
  if (iostat < 0)  /* an ioctl error */
  {
    char * str = "";
    string cstr;
    switch (errno)
    {
      /* generic errors */
#ifdef EBADF
      case EBADF:  str = "Not a valid, open, file descriptor!";
      break;
#endif
#ifdef EINTR
      case EINTR:  str = "Signal caught.";
      break;
#endif
#ifdef ENOTTY
      case ENOTTY:
        str = "No device driver, or doesn't accept control functions!";
      break;
#endif
      /* specialised errors */
#ifdef EFAULT
      case EFAULT:  str = "Illegal data transfer address!";
      break;
#endif
#ifdef EINVAL
      case EINVAL:  str = "Driver ignorant of this request or arg!";
      break;
#endif
#ifdef EIO
      case EIO:  str = "Physical I/O error!";
      break;
#endif
#ifdef ENOLINK
      case ENOLINK:  str = "Remote machine link no longer active!";
      break;
#endif
#ifdef ENXIO
      case ENXIO:  str = "Subdevice cannot perform requested service!";
      break;
#endif
      /* gt - should be ample space in cstr. */ 
      default:
        sprintf (cstr, "Unknown error %d !", errno);
        str = cstr;
      break;
    }
    MesgString ("ioctl: ");
    MesgString (str);
    MesgLine();
  }
} /* reportio */

Void save_init_tty (VOID)
{
  /* Save the original tty characteristics and set up the signalling. */
  save_temp_tty();
  init_tty_state = temp_tty_state;
} /* save_init_tty */

Void restore_init_tty (VOID)
{
  /* Restore the original tty characteristics. */
  temp_tty_state = init_tty_state;
  restore_temp_tty();
} /* restore_init_tty */

Void save_temp_tty (VOID)
{
  /* Save the current tty characteristics and set up the signalling. */
  iostatus = gioctl (&temp_tty_state);
  reportio (iostatus);
  setsignals();
} /* save_temp_tty */

Void restore_temp_tty (VOID)
{
  /* Restore the recent tty characteristics. */
  iostatus = sioctl (&temp_tty_state);
  reportio (iostatus);
} /* restore_temp_tty */

Void singlecharon (VOID)
{
  /* Set driver to read characters as they are typed without waiting for a
     terminator. Echo remains unchanged. */
  dv_tty s;

  if (cmode_flags.cbreak != 1)
  {
    /* First argument to ioctl is a file descriptor; */
    /* our sysadm, Kevin Pye, says it should be standard input; */
    /* so 0, not 1. */

    iostatus = gioctl (&s);
    reportio (iostatus);
#ifdef BSD
    s.sg_flags |= CBREAK;
#else /* not BSD */
    saved_cc[VEOL] = s.c_cc[VEOL];
    saved_cc[VEOF] = s.c_cc[VEOF];
    s.c_lflag &= ~ICANON;    /* Disable erase/kill processing */
/*    s.c_lflag |= ISIG; */   /* Turn on signal handling */
    s.c_cc[VMIN] = 1;     /* Input should wait for at least 1 char */
    s.c_cc[VTIME] = 0;    /* no matter how long that takes.  */
#endif /* BSD */
    iostatus = sioctl (&s);
    reportio (iostatus);
#ifdef FLUSH_STDIN
    fflush (stdin);
#endif /* FLUSH_STDIN */

    cmode_flags.cbreak = 1;
  }
} /* singlecharon */

Void singlecharoff (VOID)
{
  /* Turn off single character read mode. */
  dv_tty s;

  if (cmode_flags.cbreak != 0)
  {
    iostatus = gioctl (&s);
    reportio (iostatus);
#ifdef BSD
    s.sg_flags &= ~CBREAK;
#else /* not BSD */
    s.c_lflag |= ICANON;    /* Enable erase/kill processing */
    s.c_cc[VEOL] = saved_cc[VEOL];
    s.c_cc[VEOF] = saved_cc[VEOF];
#endif /* BSD */
    iostatus = sioctl (&s);
    reportio (iostatus);

    cmode_flags.cbreak = 0;
  }
} /* singlecharoff */

Void echoon (VOID)
{
  /* Turn character echoing on. */
  dv_tty s;

  iostatus = gioctl (&s);
  reportio (iostatus);
#ifdef BSD
  s.sg_flags |= ECHO;
#else /* not BSD */
  s.c_lflag |= ECHO;
#endif /* BSD */
  iostatus = sioctl (&s);
  reportio (iostatus);

  cmode_flags.echo = 1;
} /* echoon */

Void echooff (VOID)
{
  /* Turn character echoing off. */
  dv_tty s;

  iostatus = gioctl (&s);
  reportio (iostatus);
#ifdef BSD
  s.sg_flags &= ~ECHO;
#else /* not BSD */
  s.c_lflag &= ~ECHO;
#endif /* BSD */
  iostatus = sioctl (&s);
  reportio (iostatus);

  cmode_flags.echo = 0;
 } /* echooff */

Void rawouton (VOID)
{
  dv_tty s;

  iostatus = gioctl (&s);
  reportio (iostatus);
#ifndef BSD /* Not BSD */
  s.c_oflag &= ~OPOST;    /* Disables tab interpretation etc. */
#endif /* Not BSD */
  iostatus = sioctl (&s);
  reportio (iostatus);

  cmode_flags.raw = 1;
} /* rawouton */

Void rawoutoff (VOID)
{
  dv_tty s;

  iostatus = gioctl (&s);
  reportio (iostatus);
#ifndef BSD /* Not BSD */
  s.c_oflag |= OPOST;
#endif /* Not BSD */
  iostatus = sioctl (&s);
  reportio (iostatus);

  cmode_flags.raw = 0;
} /* rawoutoff */

int buffercount (VOID)
{
  /* Return true if there are any characters currently in the input buffer. */
#if defined(BSD)||defined(__linux__)
  long count;
  iostatus = ioctl (DV_FD, FIONREAD, &count);
  reportio (iostatus);
  return (count > 0);
#else /* not BSD or Linux */
  int c, flags, ret;

  /* Save the current fcntl flags and make reads return without waiting */
  flags = fcntl (0, F_GETFL, 0);
  fcntl (0, F_SETFL, flags | O_NDELAY); /* This might be SysV specific */
  if ((c = getchar()) != EOF)
  {
    ungetc (c, stdin);
    ret = 1;
  }
  else
  {
    ret = 0;
  }
  fcntl (0, F_SETFL, flags);    /* This might be SysV specific */
  return ret;
#endif /* BSD */
} /* buffercount */

#ifdef __STDC__
Void handleint (int sig)
#else
Void handleint (sig)
  int sig;
#endif
{
  /* Catch signals from tty.
     If sig is an interrupt, set the intr flag,
     Otherwise it was a suspend, so set the tstop flag. */
  fflush (stdin);
  if (sig == SIGINT)
  {
    sig_flags.intr = 1;
  }
  else
  {
    sig_flags.tstop = 1;
  }
#ifndef BSD /* Not BSD */
  setsignals();    /* Because SysV forgets its handlers */
#endif /* Not BSD */
} /* handleint */

static Void setsignals (VOID)
{
  /* Signal initialization. */
  signal (SIGINT, handleint);
#ifdef BSD
  signal (SIGTSTP, handleint);
#endif /* BSD */
} /* setsignals */

Void suspend (VOID)
{
  /* Suspend the process */
#ifdef BSD
  signal (SIGTSTP, SIG_DFL);
  kill (0, SIGTSTP);
  /* resumed again, goody! */
  setsignals();
#else
  /* don't know what to do */
#endif /* BSD */
} /* suspend */

#undef IN_UNIXIO_C
