/* ACS - Auto Console Switching                                    */
/* What this does is allows you to specify VTs (virtual terminals, */
/* also known as consoles) to watch. When it detects new data on   */
/* this tty it will automatically switch.                          */
/*								   */
/* This allows you to do many things at once such as:              */
/*   1.) IRC on one VT                                             */
/*   2.) IRC on a different IRC network on another                 */
/*   3.) wait for a web page to load up on another                 */
/*   4.) wait for telnet to connect to a site on another           */
/*								   */
/* Compile with -DBEEPONLY to cause a beep on new activity, rather */
/* than switching to the console with new activity.                */
/*								   */
/* Note: This should work on Linux and *BSD systems.               */
/*								   */
/* So you can see the uses of this. Enjoy.                         */
/* Shok (Matt Conover), shok@dataforce.net                         */

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <linux/vt.h>
#include <sys/ioctl.h>
#include <sys/types.h>

#define ERROR -1
#define MAXVTNUM 63
          
struct vcs {
  int fd;
  char curbuf[6666], prevbuf[6666];
} vcs[MAXVTNUM];

int fd;

void clean(int val);
void syntax(char **argv);
void parse(int argc, char **argv);

void sighandler(int signum);

void main(int argc, char **argv)
{
   char buf[512];
   register int i;
   register int tty;
 
   memset(buf, 0, sizeof(buf));

   for (i = 0 ; i < MAXVTNUM; i++) 
   {
      vcs[i].fd = -1, vcs[i].curbuf[0] = 0;

      bzero(vcs[i].curbuf,  sizeof(vcs[i].curbuf));
      bzero(vcs[i].prevbuf, sizeof(vcs[i].prevbuf));
   }

   /* --------------- */

   parse(argc, argv); /* Take care of arguments (such as the VTs), */
                      /* as well as open the files.                */

   /* Turn into a daemon, exit the parent. */
   if (fork() != 0) exit(0);

   /* Need to switch to the active console. tty is what we pass */
   /* to ioctl() with VT_ACTIVATE.                              */
   if ((tty = open("/dev/tty", O_RDWR)) == ERROR) 
   {
      fprintf(stderr, "error with open(): %s\n", strerror(errno));
      exit(ERROR);
   }
 
   /* Used so that when we receive a signal to abort, we close up */
   /* all the open file descriptors, and give them a message that */
   /* we are aborting. If this is run as a daemon, then you would */
   /* have to send it with kill -2, -3 or -15.                    */
   signal(SIGINT,  sighandler);  
   signal(SIGTERM, sighandler);
   signal(SIGQUIT, sighandler);

   while (1)
   {
      for (i = 0; i < MAXVTNUM; i++) 
      {
         if (vcs[i].fd != -1) 
         {
	    /* Copy the current buffer into the previous one, */
            /* for later use.                                 */
	    strcpy(vcs[i].prevbuf, vcs[i].curbuf);

	    /* Get to the beginning of the screen. */
	    lseek(vcs[i].fd, 0, SEEK_SET); 

	    if ((read(vcs[i].fd, vcs[i].curbuf, sizeof(vcs[i].curbuf)))
	        == ERROR) {
	          perror("read");
		  clean(ERROR);
	    }

	    /* Compare the buffer of the previous screen dumb with */
	    /* this one. If they are different, new data has been  */
	    /* received and we switch consoles.                    */

	    if ((strcmp(vcs[i].curbuf, vcs[i].prevbuf) != 0) 
                        && (vcs[i].prevbuf[0] != 0))
            {
#           ifdef BEEPONLY
	       ioctl(tty, VT_ACTIVATE, i+1);
#           else
               write(tty, '\a', 1);
#           endif
	       usleep(500000);
	    }
         } 
      }
   }
}

void parse(int argc, char **argv)
{
   int i;
   char *p;
   char buf[512], bfa[512];

   if (argc < 2) syntax(argv);

   sprintf(buf, "Watching ");

   for (argc--; argc; argc--) 
   {
      if (strcasecmp(argv[argc], "all") == 0) 
      {
         for (i = 0; i < MAXVTNUM; i++) 
         {
            sprintf(buf, "/dev/vcs%d", i+1);

            vcs[i].fd = open(buf, O_RDONLY | O_NOCTTY);
            if (vcs[i].fd == ERROR) 
            {
               fprintf(stderr, "error with open(): %s\n", strerror(errno));
               clean(ERROR);
            }
         }

 	 printf("Watching all tty's...\n");
	 return;
      }

      if (strncasecmp(argv[argc], "tty", 3) != 0) syntax(argv);

      strcat(buf, argv[argc]), strcat(buf, " ");
      p = (argv[argc]+3);

      sprintf(bfa, "/dev/vcs%d", atoi(p));
      vcs[atoi(p) - 1].fd = i = open(bfa, O_RDONLY | O_NOCTTY);

      if (vcs[atoi(p) - 1].fd == ERROR) 
      {
  	 fprintf(stderr, "error with open(): %s\n", strerror(errno));
	 clean(ERROR);
      }
   }

   buf[strlen(buf) - 1] = 0;

   strcat(buf, "...\n");
   printf(buf);
}

void syntax(char **argv)
{
   printf("Syntax: %s <all | tty2 tty3 ttyX ...>\n", argv[0]);
   exit(ERROR);
}

void clean(int val)
{
   register int i;

   for (i = 0; i < MAXVTNUM; i++)
      if (vcs[i].fd != -1) close(vcs[i].fd);

   close(fd);
   exit(val);
}

void sighandler(int signum)
{
   char msg[] = "Received signal to abort. Now exiting.\n";

   close(fd);

   fd = open("/dev/tty", O_NOCTTY | O_WRONLY);
   if (fd == ERROR) 
   {
      printf(msg);
      clean(signum);
   }

   /* Give aborting message to current VT. */
   write(fd, msg, sizeof(msg)); 

   clean(signum);
}
