/* Handling asynchronous signals.
   Copyright (C) 1992, 1993, 1994 Free Software Foundation, Inc.
   Copyright (C) 1995 Ben Wing.

This file is part of XEmacs.

XEmacs 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, or (at your option) any
later version.

XEmacs 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 XEmacs; see the file COPYING.  If not, write to the Free
Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Just to make sure we don't use any global vars below */
#define DONT_DECLARE_MAC_VARS

#include <config.h>
#include "lisp.h"

#include "device.h"
#include "frame.h"
#include "sysdep.h"
#include "syssignal.h"
#include "systime.h"

#include <errno.h>

/* Set to 1 when a SIGIO interrupt is processed.  The QUIT macro may look
   at this. */
volatile int sigio_happened;

/* Count of the number of times SIGIO has happened. */
volatile int sigio_tick_count;

/* Semi-used flag. */
int interrupt_input;


/**********************************************************************/
/*                        Control-G checking                          */
/**********************************************************************/

/* Set this for debugging, to have a way to get out */
int stop_character; /* #### not currently implemented */

/* This routine is called in response to a SIGINT or SIGQUIT.
   On TTY's, one of these two signals will get generated in response
   to C-g.  (When running under X, C-g is handled using the SIGIO
   handler, which sets a flag telling the QUIT macro to scan the
   unread events for a ^G.)

   Otherwise it sets the Lisp variable  quit-flag  not-nil.
 This causes  eval  to throw, when it gets a chance.
 If  quit-flag  is already non-nil, it stops the job right away.  */

static SIGTYPE
interrupt_signal (int sig)
{
  /* This function can GC (?!) */
  /* Must preserve main program's value of errno.  */
  int old_errno = errno;

  EMACS_REESTABLISH_SIGNAL (sig, interrupt_signal);

/* with the macroized error-checking stuff, the garbage below
   may mess things up because XDEVICE() and such can use and
   change global vars. */
#if ! (defined (ERROR_CHECK_TYPECHECK) && defined (MACROIZE_ERROR_CHECKING))
  if (!NILP (Vquit_flag) && DEVICEP (Vinitial_device) &&
      DEVICE_IS_TTY (XDEVICE (Vinitial_device)))
    {
      char c;
      fflush (stdout);
      sys_reset_device (XDEVICE (Vinitial_device));
      EMACS_UNBLOCK_SIGNAL (sig);
#ifdef SIGTSTP			/* Support possible in later USG versions */
/*
 * On systems which can suspend the current process and return to the original
 * shell, this command causes the user to end up back at the shell.
 * The "Auto-save" and "Abort" questions are not asked until
 * the user elects to return to emacs, at which point he can save the current
 * job and either dump core or continue.
 */
      sys_suspend ();
#else
#ifdef VMS
      if (sys_suspend () == -1)
	{
	  stdout_out ("Not running as a subprocess;\n");
	  stdout_out ("you can continue or abort.\n");
	}
#else /* not VMS */
      /* Perhaps should really fork an inferior shell?
	 But that would not provide any way to get back
	 to the original shell, ever.  */
      stdout_out ("No support for stopping a process on this operating system;\n");
      stdout_out ("you can continue or abort.\n");
#endif /* not VMS */
#endif /* not SIGTSTP */
      stdout_out ("Auto-save? (y or n) ");
      fflush (stdout);
      if (((c = getc (stdin)) & ~040) == 'Y')
	Fdo_auto_save (Qnil, Qnil);
      while (c != '\n')
        c = getc (stdin);
#ifdef VMS
      stdout_out ("Abort (and enter debugger)? (y or n) ");
#else /* not VMS */
      stdout_out ("Abort (and dump core)? (y or n) ");
#endif /* not VMS */
      fflush (stdout);
      if (((c = getc (stdin)) & ~040) == 'Y')
	abort ();
      while (c != '\n')
        c = getc (stdin);
      stdout_out ("Continuing...\n");
      fflush (stdout);
      sys_init_device (XDEVICE (Vinitial_device));
      MARK_FRAME_CHANGED (XFRAME (DEVICE_SELECTED_FRAME
				  (XDEVICE (Vinitial_device))));
    }
  else
#endif /* ! (defined (ERROR_CHECKING) && defined (MACROIZE_ERROR_CHECKING)) */
    {
      /* Else request quit when it's safe */
      Vquit_flag = Qt;
    }
  errno = old_errno;
  SIGRETURN;
}

#ifdef POLL_FOR_INPUT

/* Number of seconds between polling for input.  */
double polling_period;

int polling_for_input;

/* Nonzero means polling for input is temporarily suppressed.  */
int poll_suppress_count;

#ifdef HAVE_SETITIMER
/* Nonzero means the periodic itimer may have been lost. */
int reactivate_itimer;
#endif

#ifndef SIGIO

static void
set_periodic_timer (double interval)
{
#ifdef HAVE_SETITIMER
  struct itimerval it;
  it.it_value.tv_sec = 0;
  while (interval >= 1)
    {
      it.it_value.tv_sec++;
      interval -= 1;
    }
  it.it_value.tv_usec = (long) (interval * 1000000);
  it.it_interval = it.it_value;
  setitimer (ITIMER_REAL, &it, 0);
  reactivate_itimer = 0;
#else
  if (interval < 1)
    alarm (1);
  else
    alarm ((int) interval);
#endif
}

#ifdef HAVE_SETITIMER
unsigned int
alarm (unsigned int howlong) {
  struct itimerval old_it, new_it;

#if 0
  /* #### Don't want to enable this so close to the 19.12 release--
   *      should enable it starting with the 19.13 betas.  I think
   *      some of the NAS audio calls may need to be protected
   *	  since there are some alarm() calls in the NAS library.
   *      -dkindred@cs.cmu.edu
   */
  /* If alarm() gets called when polling isn't disabled, it can mess
     up the periodic timer. */
  assert (poll_suppress_count > 0);
#endif

  new_it.it_value.tv_sec = howlong;
  new_it.it_value.tv_usec = 0;
  new_it.it_interval.tv_sec = 0;
  new_it.it_interval.tv_usec = 0;
  setitimer (ITIMER_REAL, &new_it, &old_it);

  if (poll_suppress_count == 0) 
    /* at the next poll, explicitly reenable the polling itimer */
    reactivate_itimer = 1;
  
  /* Never return zero if there was a timer outstanding. */
  return old_it.it_value.tv_sec + (old_it.it_value.tv_usec > 0 ? 1 : 0);
}
#endif

static void
reestablish_periodic_timer (double interval)
{
#ifdef HAVE_SETITIMER
  /* automatically reestablished; no need to do anything unless
     somebody has been playing with alarm() behind our back. */
  if (reactivate_itimer)
    set_periodic_timer (interval);
#else
  set_periodic_timer (interval);
#endif
}

/* Handle an alarm once each second and read pending input
   so as to handle a C-g if it comes in.  */

static SIGTYPE
input_poll_signal (int signo)
{
  something_happened = 1; /* tell QUIT to wake up */
  sigio_happened = 1;
  sigio_tick_count++;
  EMACS_REESTABLISH_SIGNAL (signo, input_poll_signal);
  reestablish_periodic_timer (polling_period);
  SIGRETURN;
}

/* Begin signals to poll for input, if they are appropriate. */
static void
start_polling (void)
{
  poll_suppress_count--;
  if (poll_suppress_count == 0)
    {
      /* Some callers turn off polling and then use the alarm
	 for their own purposes; so reinitialize everything. */
      signal (SIGALRM, input_poll_signal);
      polling_for_input = 1;
      set_periodic_timer (polling_period);
    }
}

/* Turn off polling.  */

static void
stop_polling (void)
{
  if (poll_suppress_count == 0)
    {
      polling_for_input = 0;
      set_periodic_timer (0);
    }
  poll_suppress_count++;
}

static void
init_polling (void)
{
  /* #### This is just a guess.  Some investigation will have to be
     done to see what the best value is.  The best value is the
     smallest possible value that doesn't cause a significant amount
     of running time to be spent in C-g checking. */
  polling_period = 0.25; /* check for C-g every 1/4 of a second */
  signal (SIGALRM, input_poll_signal);
  set_periodic_timer (polling_period);
  polling_for_input = 1;
}
#endif /* !SIGIO */
#endif /* POLL_FOR_INPUT */

#ifdef SIGIO

/* Note SIGIO has been undef'd if FIONREAD is missing.  */

static void
input_available_signal (int signo)
{
  something_happened = 1; /* tell QUIT to wake up */
  sigio_happened = 1;
  sigio_tick_count++;
  EMACS_REESTABLISH_SIGNAL (signo, input_available_signal);

#ifdef BSD4_1
  select_alarmed = 1;  /* Force the select emulator back to life */
  EMACS_UNBLOCK_SIGNAL (signo);
#endif

  SIGRETURN;
}
#endif /* SIGIO */

void
stop_interrupts (void)
{
#ifdef SIGIO
  unrequest_sigio ();
#elif defined (POLL_FOR_INPUT)
  stop_polling ();
#endif
}

void
start_interrupts (void)
{
#ifdef SIGIO
  request_sigio ();
#elif defined (POLL_FOR_INPUT)
  start_polling ();
#endif
}

void
init_interrupts_late (void)
{
  if (!noninteractive)
    {
      signal (SIGINT, interrupt_signal);
#ifdef HAVE_TERMIO
      /* On  systems with TERMIO, C-g is set up for both SIGINT and SIGQUIT
	 and we can't tell which one it will give us.  */
      signal (SIGQUIT, interrupt_signal);
#endif /* HAVE_TERMIO */
/* Note SIGIO has been undef'd if FIONREAD is missing.  */
#ifdef SIGIO
      signal (SIGIO, input_available_signal);
      interrupt_input = 1;
#else
      interrupt_input = 0;
#ifdef POLL_FOR_INPUT
      init_polling ();
#endif      
#endif /* SIGIO */
    }

  EMACS_UNBLOCK_ALL_SIGNALS ();
}

void
init_signals_very_early (void)
{
  if (! noninteractive || initialized)
    {
      /* Don't catch these signals in batch mode if not initialized.
	 On some machines, this sets static data that would make
	 signal fail to work right when the dumped Emacs is run.  */
      signal (SIGHUP, fatal_error_signal);
      signal (SIGQUIT, fatal_error_signal);
      signal (SIGILL, fatal_error_signal);
      signal (SIGTRAP, fatal_error_signal);
#ifdef SIGIOT
      /* This is missing on some systems - OS/2, for example.  */
      signal (SIGIOT, fatal_error_signal);
#endif
#ifdef SIGEMT
      signal (SIGEMT, fatal_error_signal);
#endif
      signal (SIGFPE, fatal_error_signal);
#ifdef SIGBUS
      signal (SIGBUS, fatal_error_signal);
#endif
      signal (SIGSEGV, fatal_error_signal);
#ifdef SIGSYS
      signal (SIGSYS, fatal_error_signal);
#endif
      signal (SIGPIPE, fatal_error_signal);
      signal (SIGTERM, fatal_error_signal);
#ifdef SIGXCPU
      signal (SIGXCPU, fatal_error_signal);
#endif
#ifdef SIGXFSZ
      signal (SIGXFSZ, fatal_error_signal);
#endif /* SIGXFSZ */

#ifdef SIGDANGER
      /* This just means available memory is getting low.  */
      signal (SIGDANGER, memory_warning_signal);
#endif

#ifdef SIGLOST
      signal (SIGLOST, fatal_error_signal);
#endif

#ifdef AIX
/* 20 is SIGCHLD, 21 is SIGTTIN, 22 is SIGTTOU.  */
      signal (SIGXCPU, fatal_error_signal);
#ifndef _I386
      signal (SIGIOINT, fatal_error_signal);
#endif
      signal (SIGGRANT, fatal_error_signal);
      signal (SIGRETRACT, fatal_error_signal);
      signal (SIGSOUND, fatal_error_signal);
      signal (SIGMSG, fatal_error_signal);
#endif /* AIX */
    }
}

void
syms_of_signal (void)
{
  sigio_happened = 0;
  sigio_tick_count = 0;
}
