
/* 
	splitvt

  A fun utility designed to split your screen into twin shells.

				-Sam Lantinga  10/5/93
*/

static char *version="@(#)Splitvt 1.5.4   5/19/94  -Sam Lantinga\n";

#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/wait.h>
#include	<fcntl.h>
#include	<signal.h>
#include	<stdio.h>
#include	<errno.h>
#include	<ctype.h>
#include	<pwd.h>
#include	"vtmouse.h"

/* With xterm support, cut and paste is disabled and the title bar is
   reset to the string "xterm" after splitvt quits.  Not desirable in
   some cases.. ;^)
*/
/* Comment this next if you don't want xterm support */
#define X_SUPPORT
#define XTITLE		/* Uncomment this for an xterm title bar */

#ifdef NEED_SELECT_H
#include	<sys/select.h>
#endif

#ifdef NEED_INET_H
#include	<sys/inet.h>
#endif

#ifndef SIGCLD	/* BSD */
#define SIGCLD SIGCHLD
#endif

/* Returns true if 'ch' is a whitespace character */
#define WHITESPACE(ch)	((ch == ' ') || (ch == '\t') || (ch == '\n'))

#define toctrl(X)	(X-'@')		/* uppercase-to-control macro */
#define unctrl(X)	(X+'@')		/* control-to-uppercase macro */

#define SWITCH	toctrl('W')	/* Switch window key */
#define QUOTE	toctrl('V')	/* Quote next character key */
#define COMMAND	toctrl('O')	/* Go into command mode */

#define UPPER	0		/* For the upper window */
#define LOWER	1		/* For the lower window */


extern char *init_vt100();	/* Initialize the vt100 screen */
extern int   vt_write();	/* Write to the vt100 screen */
extern void  end_vt100();	/* Quit the vt100 screen */
extern void  set_win();		/* Set the cursor in the proper window */
extern void  vt_debug();	/* A vt100 debugging routine */
extern char  vt_prompt();	/* A user prompt routine for getting a char */
extern int UU_lines;		/* The user requested lines for the top win */
extern int WU_lines;		/* The actual lines for the top win */

/* The command prompt shown by vt_prompt() */
#define PROMPT	"\033[1;1msplitvt command: \033[m"

/* A Macro to set the window in the proper place.   */
#define SET_WIN()	set_win((thisfd == topfd) ? UPPER : LOWER)


extern char tty_name[];			/* From misc.c about get_master_pty() */
static char upper_tty[64]={'\0'};	/* tty_name of the upper window */
static char lower_tty[64]={'\0'};	/* tty_name of the lower window */	
static struct passwd *pw=NULL;		/* Our passwd entry pointer */

static void finish(), winch();
static int  insert_dash();
static int  isalive();
static void splitvtrc();

static int topok=1, bottomok=1;		/* I/O flags */
static int toppid=0, bottompid=0;	/* Children */
static int ttyfd=0, thisfd;		/* I/O file descriptors */
static int topfd, bottomfd;		/* Master file descriptors */
static char *startupfile="%s/.splitvtrc";	/* %s is replaced by $HOME */

/* Special characters */
static char 	command_c=COMMAND,
		switch_c=SWITCH, 
		quote_c=QUOTE; 
static int      setlogin=0;		/* Do the shells run as login shells? */

/* The command to run in each window */
char *upper_args[256]={NULL}, *lower_args[256]={NULL};
int upper_empty=1, lower_empty=1;

static char extract(arg)	/* get a char from x/^x format */
char *arg;
{
	if ( *arg == '^' ) {
		++arg;
		if ( islower(*arg) )
			*arg=toupper(*arg);
		return(*arg-'@');
	}
	return(*arg);
}


void print_usage(argv)
char *argv;
{
	fprintf(stderr, "\nUsage: %s [options] [shell]\n\n", argv);
	fprintf(stderr, "Options:\n");
	fprintf(stderr, "\t-s numlines\t\tSets 'numlines' to the number of lines\n");
	fprintf(stderr, "\t\t\t\tin the top window.  This number will\n");
	fprintf(stderr, "\t\t\t\tbe modified if the screen isn't big\n");
	fprintf(stderr, "\t\t\t\tenough to handle the full size.\n\n");
	fprintf(stderr, "\t-upper command\t\tRuns 'command' in the upper window\n");
	fprintf(stderr, "\t-lower command\t\tRuns 'command' in the lower window\n");
	fprintf(stderr, "\t-login\t\t\tRuns programs as if they were login shells\n");
	fprintf(stderr, "\t-nologin\t\tOverrides \"set login on\" in startup file\n");
	fprintf(stderr, "\t-rcfile file\t\tLoads 'file' at startup instead of ~/.splitvtrc\n");
	fprintf(stderr, "\t-norc\t\t\tSuppresses loading your startup file\n");
	fprintf(stderr, "\t-v\t\t\tPrint out the version number\n");
	fprintf(stderr, "\n");
	exit(1);
}

 
main(argc, argv)
int argc;
char *argv[];
{
	extern int errno, optind;
	extern char *optarg;

	int i, len, maxfds, numready;
	char buffer[BUFSIZ], *ptr;
	struct timeval tv, *tvptr;
	fd_set read_mask;
	static struct passwd pwdata;	/* Our passwd entry */

	static int debugflag=0;		/* Do we allow debug key? */
	struct event X_event;

#ifdef NEED_INET_H
        /* There is a bug in the Wallabong Group's implementation
           of select().  It will not work properly with fd 0 */

        if ( (ttyfd=dup(0)) < 0 )
        {
                perror("dup() error");
                exit(2);
        }
#endif

	/* First, get default options from ~/.splitvtrc */
	for ( i=1; argv[i]; ++i ) {
		if ( strcmp(argv[i], "-norc") == 0 ) {
			i=0;
			break;
		} else if ( strcmp(argv[i], "-rcfile") == 0 ) {
			if ( ! argv[++i] )
				print_usage(argv[0]);
			startupfile=argv[i];
		}
	}
	if ( i )
		splitvtrc();

	/* Parse command line options */
	while ( (i=getopt(argc, argv, "n:u:l:r:s:vh")) != EOF )
	{
		switch (i)
		{
			case 'n': if ( strcmp(optarg, "ologin") == 0 )
					setlogin=0;
				  else if ( strcmp(optarg, "orc") != 0 )
					print_usage(argv[0]);
				  /* Already handled above */
				  break;
			case 'u': if ( strcmp(optarg, "pper") != 0 )
					print_usage(argv[0]);
				  upper_args[0]=argv[optind++];
				  upper_args[1]=NULL;
				  upper_empty=0;
				  break;
			case 'l': if ( strcmp(optarg, "ower") == 0 ) {
				  	lower_args[0]=argv[optind++];
				  	lower_args[1]=NULL;
				  	lower_empty=0;
				  } else if ( strcmp(optarg, "ogin") == 0 ) {
					setlogin=1;
				  } else
					print_usage(argv[0]);
				  break;
			case 'r': if ( strcmp(optarg, "cfile") != 0 )
					print_usage(argv[0]);
				  else /* Already handled above */
					++optind;
				  break;
			case 's': UU_lines=atoi(optarg);
				  break;
			case 'v': printf("%s", version+4);
				  exit(0);
				  break;
			case 'h':
			default:  print_usage(argv[0]);
				  break;
		}
	}
	argv+=(optind-1);

	/* Retrieve and save our passwd entry */
	if ( (pw=(struct passwd *)getpwuid(getuid())) == NULL ) {
		fprintf(stderr, 
		"Warning: Can't find your passwd entry; no utmp logging.\n");
		sleep(2);
	} else { /* Save the passwd entry for future reference */
		d_copy((char *)pw, (char *)&pwdata, sizeof(pwdata));
		pw=(&pwdata);
	}

	if ( tty_getmode(ttyfd) < 0 ) 
	{
		fprintf(stderr, "Can't get terminal settings.\n");
		exit(2);
	}
	(void) tty_raw(0);   /* Set the tty raw here to prevent lost input */

	if ( (ptr=init_vt100()) != NULL )
	{
		if ( tty_reset(0) < 0 )	
			(void) tty_sane(0);

		fprintf(stderr, "\rCan't initialize screen: %s\n", ptr);
		exit(3);
	}
#ifdef X_SUPPORT
#ifdef XTITLE
	strcpy(buffer, version+4);
	buffer[strlen(buffer)-1]='\0';
	(void) event_init(stdin, stdout, buffer);
#else
	(void) event_init(stdin, stdout, NULL);
#endif
#endif /* X_SUPPORT */

	if ( argv[1] ) {
		if ( upper_empty ) {
			for ( i=0; argv[i+1]; ++i )
				upper_args[i]=argv[i+1];
			upper_args[i]=NULL;
		}
		if ( lower_empty ) {
			for ( i=0; argv[i+1]; ++i )
				lower_args[i]=argv[i+1];
			lower_args[i]=NULL;
		}
	} else {
		if ( upper_args[0] == NULL ) {
			if ( ((upper_args[0]=(char *)getenv("SHELL")) == NULL) ||
			     (*upper_args[0] == '\0') )
				upper_args[0]="/bin/csh";
			upper_args[1]=NULL;
		}
		if ( lower_args[0] == NULL ) {
			if ( ((lower_args[0]=(char *)getenv("SHELL")) == NULL) ||
			     (*lower_args[0] == '\0') )
				lower_args[0]="/bin/csh";
			lower_args[1]=NULL;
		}
	}
	if ( setlogin ) {
		(void) insert_dash(upper_args);
		(void) insert_dash(lower_args);
	}
#ifdef NEED_INET_H
	signal(SIGCLD, SIG_IGN);
#endif

	(void) remove_me();
	if ( (topfd=pty_open(upper_args, &toppid, UPPER)) < 0 )
	{
		end_vt100();
		switch (errno) {
		   case EIO:
		   case EPERM:
		   case ENOENT:
			fprintf(stderr, "No available pseudo terminals.\n");
			break;
			case EAGAIN:
			fprintf(stderr, "No more processes, try again later.\n");
			break;
		   default:
			perror("pty_open() error");
		}
		finish(0);
	} else if ( pw ) {
		(void) strcpy(upper_tty, tty_name);
		(void) addutmp(pw->pw_name, pw->pw_uid, upper_tty);
	}

	if ( (bottomfd=pty_open(lower_args, &bottompid, LOWER)) < 0 )
	{
		end_vt100();
		switch (errno) {
		   case EIO:
		   case EPERM:
		   case ENOENT:
			fprintf(stderr, "No available pseudo terminals.\n");
			break;
			case EAGAIN:
			fprintf(stderr, "No more processes, try again later.\n");
			break;
		   default:
			perror("pty_open() error");
		}
		finish(0);
	} else if ( pw ) {
		(void) strcpy(lower_tty, tty_name);
		(void) addutmp(pw->pw_name, pw->pw_uid, lower_tty); 
	}
	thisfd=topfd;

#if defined(SOLARIS) || defined(HAVE_BSDTTY_H) || defined(HP_UX)
        maxfds=32;              /* Any comments?  This is a WAG */
#else
        maxfds=getdtablesize();
#endif
        /* Set select() timeout, and zero out the read mask */
#ifdef NEED_INET_H
        tv.tv_sec=3;
        tv.tv_usec=0;
        tvptr=&tv;
#else
        tvptr=NULL;
#endif
	signal(SIGHUP, finish);
	signal(SIGINT, finish);
	signal(SIGQUIT, finish);
	signal(SIGTERM, finish);
	signal(SIGBUS, finish);
	signal(SIGSEGV, finish);
#ifdef SIGWINCH
	signal(SIGWINCH, winch);
#endif
	for ( SET_WIN(); (topok || bottomok); )
	{
		FD_ZERO(&read_mask);
		FD_SET(ttyfd, &read_mask);

		/* Make sure the children are still alive */
		if ( ! isalive() )
			break;

		if ( topok )
			FD_SET(topfd, &read_mask);

		if ( bottomok )
			FD_SET(bottomfd, &read_mask);

                if ( (numready=select(maxfds, &read_mask, NULL, NULL, tvptr))
                                                                        <= 0 )
                {
#ifndef NEED_INET_H                     /* Wallabong select() is buggy */
                        switch (errno)
                        {
                                case EIO:    /* The program is finished. */
                                                break;
                                case EINTR:  /* Probably SIGWINCH */
                                                break;
                                default:        perror("select() error");
                                                fprintf(stderr, "\r");
                                                break;
                        }
                        if ( errno != EINTR )
                                finish(0);
			else
				continue;
#endif
                }


		if ( FD_ISSET(ttyfd, &read_mask) )
		{
			if ( (buffer[0]=event_getc(&X_event)) == EOF )
				finish(0);

			if ( X_event.happening ) {
				/* Work only on button release */
				if ( BUTTON_ISSET(X_event, RELEASE) ) { 
					/* Mouse click in which window? */
					if ( X_event.y > WU_lines ) { 
						if ( bottomok )
							thisfd=bottomfd;
						SET_WIN();
					} else if ( X_event.y < WU_lines ) { 
						if ( topok )
							thisfd=topfd;
						SET_WIN();
					}
				}
			} else {
				if ( buffer[0] == quote_c ) {
					read(ttyfd, buffer, 1);
					goto writeit;
				} else if ( buffer[0] == switch_c ) {
					if ( (thisfd == topfd) && bottomok )
						thisfd=bottomfd;
					else if ( topok )
						thisfd=topfd;
					SET_WIN();
				} else if ( buffer[0] == command_c ) {
					switch (vt_prompt(PROMPT)) {
						/* Print debug info */
						case 'p': if ( debugflag )
								vt_debug(stderr);
							  break;
						/* Quick exit */
						case 'q': finish(0);
							  break;
						default:  break;
					}
				}
				else { writeit:
					write(thisfd, buffer, 1);
				}
			}
		}

		if  ( FD_ISSET(bottomfd, &read_mask) )
		{
			if ( (len=read(bottomfd, buffer, BUFSIZ)) <= 0 )
			{
				switch (errno)
				{
					case EIO: /*break;*/
					default:  if ( isalive() < 0 )
							finish(0);
				}
			}
			else {
				(void) vt_write(LOWER, buffer, len);
				if ( thisfd == topfd )
					SET_WIN();
			}
		}

		if  ( FD_ISSET(topfd, &read_mask) )
		{
			if ( (len=read(topfd, buffer, BUFSIZ)) <= 0 )
			{
				switch (errno)
				{
					case EIO: /*break;*/
					default:  if ( isalive() < 0 )
							finish(0);
				}
			}
			else {
				(void) vt_write(UPPER, buffer, len);
				if ( thisfd == bottomfd ) 
					SET_WIN();
			}
		}
	}
	finish(0);
}


/* A better child checker. :)  It gathers the status of the child,
   rendering it free and un-zombied. :) */

static int isalive()
{
	int status;

#if ! defined(WNOHANG) || defined(NEED_INET_H)
	if ( topok )
		if (  kill(toppid, 0) < 0 )
		{
			if ( pw ) 
				(void) delutmp(pw->pw_name, upper_tty);
			if ( thisfd == topfd )
				thisfd=bottomfd;
			(void) close(topfd);
			topok=0;
		}

	if ( bottomok )
		if ( kill(bottompid, 0) < 0 )
		{
			if ( pw ) 
				(void) delutmp(pw->pw_name, lower_tty);
			if ( thisfd == bottomfd )
				thisfd=topfd;
			(void) close(bottomfd);
			bottomok=0;
		}

#else
#ifdef HAVE_WAIT4	/* For BSD 4.3 */
#define waitpid(pid, status, options)	wait4(pid, status, options, NULL)
#endif /* HAVE_WAIT4 */

	if ( topok )
		if ( waitpid(toppid, &status, WNOHANG) != 0 )
		{
			if ( pw ) 
				(void) delutmp(pw->pw_name, upper_tty);
			if ( thisfd == topfd )
				thisfd=bottomfd;
			(void) close(topfd);
			topok=0;
		}

	if ( bottomok )
		if ( waitpid(bottompid, &status, WNOHANG) != 0 )
		{
			if ( pw ) 
				(void) delutmp(pw->pw_name, lower_tty);
			if ( thisfd == bottomfd )
				thisfd=topfd;
			(void) close(bottomfd);
			bottomok=0;
		}
#endif
	if ( topok || bottomok )
		return(1);
	return(0);
}

	
/* Cleanup routine */
static void finish(sig)
int sig;
{
	/* Only call this routine after tty_getmode() has been called */
	/* The tty_reset() call flushes the tty's input buffers. */
	if ( tty_reset(0) < 0 )	
		(void) tty_sane(0);

	/* Reset the vt100 window */
	end_vt100();
	event_quit();

	/* Reset our utmp entries */
	if ( pw && topok && upper_tty[0] )
		(void) delutmp(pw->pw_name, upper_tty);
	if ( pw && bottomok && lower_tty[0] )
		(void) delutmp(pw->pw_name, lower_tty);
	(void) replace_me();

	if ( sig )
		printf("Exiting due to signal: %d\n", sig);
	exit(sig);
}


/* Resize the window... unfortunately, this means that the
   current window is erased.  Oh well.. :) */

static void winch(sig)
int sig;
{
	char *ptr;

	signal(sig, winch);

	if ( (ptr=init_vt100()) != NULL )
	{
		fprintf(stderr, "Can't resize window: %s. (exiting)\n", ptr);
		finish(0);
	}
	else
		SET_WIN();
	if ( topfd )
		pty_setwin(topfd, UPPER);
	if ( bottomfd )
		pty_setwin(bottomfd, LOWER);
}


static int insert_dash(args)
char *args[];
{
	char *temp;

	if ( (temp=(char *)malloc(strlen(args[0])+2)) == NULL )
		return(-1);
	sprintf(temp, "-%s", args[0]);
	args[0]=temp;		/* Possible memory leak.. who cares? */
	return(0);
}

/* A routine to parse the ~/.splitvtrc file and set default options */
#define WARN(X)	fprintf(stderr, X, lineno); sleep(2);
static void splitvtrc()
{
	char *home, splitvtrc[256];
	FILE *rcfile;
	char line[BUFSIZ], *parsed[256];
	char *head, *tail;
	int  i, lineno=0;

	if ( (home=(char *)getenv("HOME")) == NULL )
		home="";

	sprintf(splitvtrc, startupfile, home);
	if ( (rcfile=fopen(splitvtrc, "r")) == NULL )
		return;

	while ( fgets(line, BUFSIZ-1, rcfile) ) {
		++lineno;
		/* Clean up leading and tailing whitespace */
		for ( head=line; WHITESPACE(*head); ++head );
		for ( tail=(head+strlen(head)); 
			(WHITESPACE(*(tail-1)) && (tail > head)); --tail );
		*tail='\0';
		if ( (*head == '\0') || (*head == '#') )
			continue;

		/* Parse the line */
		for ( i=0, tail=head; *tail; ++tail ) {
			if ( WHITESPACE(*tail) ) {
				*tail='\0';
				parsed[i++]=head;
				while ( WHITESPACE(*(tail+1)) )
					++tail;
				head=(tail+1);
			}
		}
		parsed[i++]=head;
		parsed[i++]=NULL;

		if ( strcmp(parsed[0], "set") == 0 ) {
			if ( ! parsed[1] ) {
				WARN("Warning: No argument to 'set' in ~/.splitvtrc line %d\n");
				continue;
			}
			if ( strcmp(parsed[1], "command_char") == 0 ) {
				if ( ! parsed[2] ) {
					WARN("Warning: No command_char to set in ~/.splitvtrc line %d\n");
					continue;
				}
				command_c=extract(parsed[2]);
			} else if ( strcmp(parsed[1], "switch_char") == 0 ) {
				if ( ! parsed[2] ) {
					WARN("Warning: No switch_char to set in ~/.splitvtrc line %d\n");
					continue;
				}
				switch_c=extract(parsed[2]);
			} else if ( strcmp(parsed[1], "quote_char") == 0 ) {
				if ( ! parsed[2] ) {
					WARN("Warning: No switch_char to set in ~/.splitvtrc line %d\n");
					continue;
				}
				quote_c=extract(parsed[2]);
			} else if ( strcmp(parsed[1], "upper_lines") == 0 ) {
				if ( ! parsed[2] ) {
					WARN("Warning: No number of lines to set in ~/.splitvtrc line %d\n");
					continue;
				}
				if ( (i=atoi(parsed[2])) == 0 ) {
					WARN("Warning: Invalid number of lines in ~/.splitvtrc line %d\n");
					continue;
				} else
					UU_lines=i;
			} else if ( strcmp(parsed[1], "login") == 0 ) {
				if ( ! parsed[2] ) {
					WARN("Syntax error in ~/.splitvtrc line %d (Usage: set login [on|off])\n");
				} else if ( strcmp(parsed[2], "on") == 0 )
					setlogin=1;
				else if ( strcmp(parsed[2], "off") == 0 )
					setlogin=0;
				else {
					WARN("Syntax error in ~/.splitvtrc line %d (Usage: set login [on|off])\n");
				}
			} else {
				WARN("Warning: Invalid parameter to 'set' in ~/.splitvtrc line %d\n");
			}
		} else if ( strcmp(parsed[0], "run") == 0 ) {
			if ( ! parsed[1] ) {
				WARN("Warning: Nothing to run in ~/.splitvtrc line %d\n");
			}
			if ( *parsed[1] == '-' ) {
				if ( strcmp(&parsed[1][1], "upper") == 0 ) {
					for ( i=2; parsed[i]; ++i ) {
						if ( (upper_args[i-2]=(char *)malloc(strlen(parsed[i])+1)) == NULL ) {
							perror("malloc");
							exit(5);
						}
						strcpy(upper_args[i-2], parsed[i]);
					}
					upper_args[i-2]=NULL;
				} else if ( strcmp(&parsed[1][1], "lower") == 0 ) {
					for ( i=2; parsed[i]; ++i ) {
						if ( (lower_args[i-2]=(char *)malloc(strlen(parsed[i])+1)) == NULL ) {
							perror("malloc");
							exit(5);
						}
						strcpy(lower_args[i-2], parsed[i]);
					}
					lower_args[i-2]=NULL;
				} else {
					WARN("Warning: Invalid argument to 'run' in ~/.splitvtrc line %d\n");
				}
			} else {
				for ( i=1; parsed[i]; ++i ) {
						if ( ((upper_args[i-1]=(char *)malloc(strlen(parsed[i])+1)) == NULL) ||
						     ((lower_args[i-1]=(char *)malloc(strlen(parsed[i])+1)) == NULL) ) {
							perror("malloc");
							exit(5);
						}
						strcpy(upper_args[i-1], parsed[i]);
						strcpy(lower_args[i-1], parsed[i]);
				}
				upper_args[i-1]=NULL;
				lower_args[i-1]=NULL;
			}
		} else {
			WARN("Warning: Invalid directive in ~/.splitvtrc line %d\n");
		}
	}
	return;
}
