/*
 *	KROC keyboard process
 *	Copyright (C) 1995, 1996, 1997 D.C.Wood, P.H.Welch, D.J.Beckett
 *
 *	This program 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.
 *
 *	This program 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.
 */

/*
 *	This is a UNIX process that blocks on fd 0 (the keyboard) and uses a
 *	UNIX signal to deliver notification of a new keypress to the main user
 *	process.
 *
 *	$Source: /proj/ofa/rts/RCS/kbdproc.c,v $
 *
 *	$Id: kbdproc.c,v 1.5 1997/09/03 15:43:30 djb1 Exp $
 *
 *	(C) Copyright 1995,1996,1997
 *		D.C. Wood <D.C.Wood@ukc.ac.uk>
 *		P.H. Welch <P.H.Welch@ukc.ac.uk>
 *		Dave Beckett <D.J.Beckett@ukc.ac.uk>
 *	University of Kent at Canterbury
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef OOS_BUILD
#include <unistd.h>
#include <fcntl.h>
#include <rts.h>

#ifndef BLOCKING_SYSCALLS
extern bool trace;


/* 'local' variables */

static int userproc_pid, to_userproc, from_userproc;

static char tty_char = NULL_CHAR;
static int stdin_is_tty;


/* Local function prototypes */
static void kbdproc_exit (int n);
static void kbdproc_ok_exit (void);
static void kbd_bad_signal (void);
static void get_kbd_char (void);
static void put_kbd_char (void);
static void do_occam_chars (void);
static void kbd_to_user_sync (void);

void keyboard_process (int user_pid, int to_user_fd, int from_user_fd, bool is_a_tty);


static void kbdproc_exit (int n)
{       
	#ifdef DEBUG_RTS
		if (trace) {
			/*fputs ("KBDPROC: Exit %d\n", n, stderr); fflush (stderr);*/
		}
	#endif
	_exit (n);
}	/* kbdproc_exit */


static void kbdproc_ok_exit (void)                  /* SIGTERM handler */
{
	kbdproc_exit (0);
}	/* kbdproc_ok_exit */


static void kbd_bad_signal (void)               /* handler for all bar SIGTERM and SIGCHLD */
{       
	kbdproc_exit (-1);
}	/* kbd_bad_signal */


static void get_kbd_char (void)
{
	int n;

	if (tty_char == END_CHAN) {
		#ifdef DEBUG_RTS
			if (trace) {
				fputs ("KBDPROC: Pausing\n", stderr); fflush (stderr);
			}
		#endif
		while (TRUE) {
			pause ();
		}
	} else {
		/* Use buffered I/O if reading from a non-terminal */
		if (!stdin_is_tty) {
			tty_char = getc(stdin);
			n = (tty_char == EOF) ? 0 : 1;
		} else {
			n = read (0, &tty_char, 1);
		}
      
		#ifdef DEBUG_RTS
			if (trace) {
				fprintf (stderr, "KBDPROC: Read key : %d '%c'\n", tty_char, tty_char);
			}
		#endif
		if (n == 0) {       /* assume we've read off the end of the file */
			#ifdef DEBUG_RTS
				if (trace) {
					fputs ("KBDPROC: Got EOF\n", stderr); fflush (stderr);
				}
			#endif
			tty_char = END_CHAN;
		} else if (n != 1) {
			fputs ("KBDPROC: Keyboard read failed", stderr); fflush(stderr);
			kbdproc_exit (-2);
		}
      
	}
}	/* get_kbd_char */


static void put_kbd_char (void)
{
	if (kill (userproc_pid, SIGKBD) == ERR) {
		fputs ("KBDPROC: Can't signal user process\n", stderr); fflush (stderr);
		kbdproc_exit (-3);
	}

	if (write (to_userproc, &tty_char, 1) != 1) {
		fputs ("KBDPROC: Write to pipe failed\n", stderr); fflush (stderr);
		kbdproc_exit (-4);
	}
}  /* put_kbd_char */



static void do_occam_chars (void)
{
	char ack_char;

	/* read kbd chars and pass to occam on request */
	while (1) { /* for EVER */
		/* read a keyboard char */
		#ifdef DEBUG_RTS
			if (trace) {
				fputs ("KBDPROC: Wait for keyboard character\n", stderr); fflush (stderr);
			}
		#endif
		get_kbd_char ();

		#ifdef DEBUG_RTS
			if (trace) {
				fputs ("KBDPROC: Key char has been read\n", stderr); fflush (stderr);
			}
		#endif
		#ifdef DEBUG_RTS
			if (trace) {
				fputs ("KBDPROC: Signalling user with char\n", stderr); fflush (stderr);
			}
		#endif
		put_kbd_char ();
		/* sleep until kbd char taken by occam */
		#ifdef DEBUG_RTS
			if (trace) {
				fputs ("KBDPROC: Going to sleep\n", stderr); fflush (stderr);
			}
		#endif
		if ((read (from_userproc, &ack_char, 1) != 1) || (ack_char != tty_char)) {
			fprintf (stderr, "KBDPROC: Can't read user acknowledgement '%c'\n", ack_char);
			kbdproc_exit (-5);
		}
		#ifdef DEBUG_RTS
			if (trace) {
				fputs ("KBDPROC: Awake\n", stderr); fflush (stderr);
			}
		#endif
	} /* for EVER */

}  /* do_occam_chars */


static void kbd_to_user_sync (void)
{
	char go_char;

	/* wait for user's go-ahead */
	#ifdef DEBUG_RTS
		if (trace) {
			fputs ("KBDPROC: Waiting for USERPROC\n", stderr); fflush (stderr);
		}
	#endif

	if ((read (from_userproc, &go_char, 1) != 1) || (go_char != 'c')) {
		fprintf (stderr, "KBDPROC: Can't read USERPROC go-ahead '%c'\n", go_char);
		kbdproc_exit (-6);
	}

	/* give go-ahead to user process */
	#ifdef DEBUG_RTS
		if (trace) {
			fputs ("KBDPROC: Telling USERPROC to go ahead\n", stderr); fflush (stderr);
		}
	#endif

	if (write (to_userproc, "p", 1) != 1) {
		fputs ("KBDPROC: Can't write go-ahead to USERPROC\n", stderr); fflush (stderr);
		kbdproc_exit (-7);
	}

	#ifdef DEBUG_RTS
		if (trace) {
			fputs ("KBDPROC: Sent go to USERPROC OK\n", stderr); fflush (stderr);
		}
	#endif
} /* kbd_to_user_sync */


void keyboard_process (int user_pid, int to_user_fd, int from_user_fd, bool is_a_tty)
{       
	userproc_pid = user_pid;
	to_userproc = to_user_fd;
	from_userproc = from_user_fd;
	stdin_is_tty = is_a_tty;

	/* system process - read kbd chars and send to user process */
	signal (SIGTERM, (SigParam)kbdproc_ok_exit);
	/* apart from the above, nobody should ever signal us */
	/* signal (SIGEMT, (SigParam)kbd_bad_signal); not in LINUX!! */
	signal (SIGHUP, (SigParam)kbd_bad_signal);

	/* Only catch if not being ignored */
	if (signal (SIGINT, SIG_IGN) != SIG_IGN) {
		signal (SIGINT, (SigParam)kbd_bad_signal);
	}
	if (signal (SIGQUIT, SIG_IGN) != SIG_IGN) {
		signal (SIGQUIT, (SigParam)kbd_bad_signal);
	}
	signal (SIGILL, (SigParam)kbd_bad_signal);
	signal (SIGTRAP, (SigParam)kbd_bad_signal);
	signal (SIGABRT, (SigParam)kbd_bad_signal);
	signal (SIGPIPE, (SigParam)kbd_bad_signal);
  
	kbd_to_user_sync ();

	/* service occam requests */
	do_occam_chars ();            /* no return */
} /* parent */

#endif	/* !BLOCKING_SYSCALLS */
#endif	/* !OOS_BUILD */

