/* coolmail.c
 *
 * A cool mail notification utility.
 *
 * By Byron C. Darrah
 * Created:  1-June-1994, Wed
 * Modified: 19-Oct-1994, Tue
 * Modified: 30-Dec-1994, Fri
 * Modified: 3-Jan-1994, Tue
 * Modified: 9-Feb-1995, Thurs
 * Modified: 21-Feb-1995, Tue
 * Modified: 01-Sept-95, Fri (Added sound support --Randy Sharpe)
 * Modified: 19-Sep-1995, Tue
 * 
 */

#define Version "1.3"

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>


#ifdef AUDIO
#include <string.h>
#endif

#include "render1.h"
#include "mailbox.h"

#define DEFAULT_MAIL_DIR  "/var/mail/"

#define DEFAULT_COMMAND   "xterm -n Elm -e elm\0"
#define DEFAULT_INTERVAL  30
#define DEFAULT_FRAMES    15

#ifdef AUDIO
#define DEFAULT_VOLUME    50
#endif

#ifndef PI
#define PI 3.1415926536
#endif

polygon_t   flag[2], cube2[5], letter[1], post[6], ground[1];
int_point_t flag_ints[2][4],
            cube2_ints[5][4],
            letter_ints[1][4],
            post_ints[6][4],
            ground_ints[1][4];

char          command_str[1024]  = DEFAULT_COMMAND;
char          mailfile_str[1024] = DEFAULT_MAIL_DIR;
unsigned long interval           = DEFAULT_INTERVAL*1000;
unsigned int  frames             = DEFAULT_FRAMES;
int           verbose            = 0;

#ifdef AUDIO
char         *sndfile            = NULL;   /* default system sound */
int          cool_vol            = DEFAULT_VOLUME;
#endif

float  flag_angle = 0.0;
int    Mailbox_Is_Open  = 0,
       envelope_present = 0,
       alerted          = 0,
       onceonly         = 0;

void rotate_in(unsigned int steps);
void rotate_out(unsigned int steps);
void define_shapes(void);
void print_camera(real_point_t pos, real_point_t foc, real_point_t up);
void cool_parseargs(int argc, char *argv[]);
void cool_init(void);
void cool_checkmail(void);
int  cool_do_command(char *command);
void cool_raise_flag(unsigned int steps);
void cool_lower_flag(unsigned int steps);
void cool_put_envelope(void);
void cool_remove_envelope(void);
void cool_rotate(float *px, float *py, float cx, float cy, float angle);
void cool_get_inboxstatus(char *filename, int *anymail, int *unreadmail,
                          int *newmail);
void cool_closemailbox(int n);
void cool_alarm(void);
int  cool_help(int argc, char *argv[]);
void cool_usage(void);

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

int main(int argc, char *argv[])
{
   int reason;
#ifndef NO_CUSERID
   char username[L_cuserid];
#endif

   /* Quickly scan for the -h option -- if it is present don't do anything
    * but print out some help and exit. */
   if (cool_help(argc, argv))
      return(0);

   /* Get the username and use it to create a default mailfile name */
#ifdef SUPPORT_MAILDIR
	if (getenv("MAILDIR") && strlen(getenv("MAILDIR"))) {
		strcpy(mailfile_str,getenv("MAILDIR"));
	} else if (getenv("MAIL") && strlen(getenv("MAIL"))) {
   	strcpy(mailfile_str,getenv("MAIL"));
	} else
#endif
#ifndef NO_CUSERID
   	strcat(mailfile_str, cuserid(username));
#else
   	strcat(mailfile_str, getlogin());
#endif

   /* Initialize the renderer */
   rend_init(&argc, argv, (float)150.0);

   cool_parseargs(argc, argv);
   if (verbose)
      printf("Coolmail %s watching file: %s\n", Version, mailfile_str);

   define_shapes();
   rend_register_obj(1, 2);
   rend_register_obj(2, 5);
   rend_register_obj(3, 1);
   rend_register_obj(4, 5);
   rend_register_obj(5, 1);

   rend_submit_plist(1, flag);
   rend_submit_plist(2, cube2);
   rend_submit_plist(4, post);
   rend_submit_plist(5, ground);

   cool_init();

   reason = rend_freeze(interval);
   while(1)
   {
      switch(reason)
      {
       case TIMEOUT: 
         cool_checkmail();
         break;
       case SELECTION:
         rotate_in(frames);
         reason = cool_do_command(command_str);
         alerted = 0;
         continue;
         break;
       default:
         fprintf(stderr, "coolmail: Renderer unfroze for unknown reason.\n");
         assert(0);
      }
      reason = rend_freeze(interval);
   }

   printf("Reason for termination: %s\n", reason == TIMEOUT ?
                                        "Timed out" : "Selected");
   
   rend_end();

   return(0);
}

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

void rotate_in(unsigned int steps)
{
   float theta, dist;
   int i;
   float frames   = (float)steps;
   static float mintheta = 0.4;
   static float maxtheta = 1.7;
   static float mindist  = 270.0;
   static float maxdist  = 150.0;
   real_point_t cam_pos = {60.0, 96.0, -20.0},
                cam_foc = {60.0, 96.0, 260.0},
                cam_up  = {60.0, 99.0, -20.0};

   for (i = 0; i < steps; i++)
   {
      theta = i * (maxtheta - mintheta)/ frames  + mintheta;
      dist  = i * (maxdist  - mindist) / frames  + mindist;
      cam_pos.z  = -dist * cos((double)theta) + cam_foc.z;
      cam_pos.x  =  dist * sin((double)theta) + cam_foc.x;
      cam_up.z   = cam_pos.z;
      cam_up.x   = cam_pos.x;
      rend_set_camera(&cam_pos, &cam_foc, &cam_up);
      rend_transform_update_all();
      rend_frame();
   }

}

/*---------------------------------------------------------------------------*/
void rotate_out(unsigned int steps)
{
   float theta, dist;
   int i;

   float frames   = (float)steps;
   static float mintheta = 0.4;
   static float maxtheta = 1.7;
   static float mindist  = 270.0;
   static float maxdist  = 150.0;
   real_point_t cam_pos = {60.0, 96.0, -20.0},
                cam_foc = {60.0, 96.0, 260.0},
                cam_up  = {60.0, 99.0, -20.0};

   for (i = steps - 1; i >= 0; i--)
   {
      theta = i * (maxtheta - mintheta)/ frames  + mintheta;
      dist  = i * (maxdist  - mindist) / frames  + mindist;
      cam_pos.z  = -dist * cos((double)theta) + cam_foc.z;
      cam_pos.x  =  dist * sin((double)theta) + cam_foc.x;
      cam_up.z   = cam_pos.z;
      cam_up.x   = cam_pos.x;
      rend_set_camera(&cam_pos, &cam_foc, &cam_up);
      rend_transform_update_all();
      rend_frame();
   }
}

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

void print_camera(real_point_t pos, real_point_t foc, real_point_t up)
{
   printf("cam_pos = (%6.2f, %6.2f, %6.2f)\n", pos.x, pos.y, pos.z);
   printf("cam_foc = (%6.2f, %6.2f, %6.2f)\n", foc.x, foc.y, foc.z);
   printf("cam_up  = (%6.2f, %6.2f, %6.2f)\n", up.x,  up.y,  up.z);
}

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

void define_shapes(void)
{
   /* Define the letter */
   letter[0].color      = WHITE;
   letter[0].sim_points = envelope;
   letter[0].npoints    = 4;
   letter[0].int_points = letter_ints[0];

   /* Define the ground */
   ground[0].color      = GREEN;
   ground[0].sim_points = ground_plane;
   ground[0].npoints    = 4;
   ground[0].int_points = ground_ints[0];

   /* Define the mailbox flag */
   flag[0].color      = RED;
   flag[0].sim_points = flag_1;
   flag[0].npoints    = 4;
   flag[0].int_points = flag_ints[0];

   flag[1].color      = RED;
   flag[1].sim_points = flag_2;
   flag[1].npoints    = 4;
   flag[1].int_points = flag_ints[1];

   /* Define the mailbox itself */
   cube2[0].color      = DARKCYAN;
   cube2[0].sim_points = mailbox_front;   /* Front */
   cube2[0].npoints    = 4;
   cube2[0].int_points = cube2_ints[0];

   cube2[1].color      = GREY2;
   cube2[1].sim_points = mailbox_back;   /* Back */
   cube2[1].npoints    = 4;
   cube2[1].int_points = cube2_ints[1];

   cube2[2].color      = CYAN;
   cube2[2].sim_points = mailbox_top;   /* Top */
   cube2[2].npoints    = 4;
   cube2[2].int_points = cube2_ints[2];

   cube2[3].color      = DARKBLUE;
   cube2[3].sim_points = mailbox_bottom;   /* Bottom */
   cube2[3].npoints    = 4;
   cube2[3].int_points = cube2_ints[3];

   cube2[4].color      = GREY1;
   cube2[4].sim_points = mailbox_left;   /* Left */
   cube2[4].npoints    = 4;
   cube2[4].int_points = cube2_ints[4];

   /* Define the post */
   post[0].color      = DARKBROWN;
   post[0].sim_points = post_bottom; /* Bottom face */
   post[0].npoints    = 4;
   post[0].int_points = post_ints[1];

   post[1].color      = DARKBROWN;
   post[1].sim_points = post_front; /* Front face */
   post[1].npoints    = 4;
   post[1].int_points = post_ints[2];

   post[2].color      = DARKBROWN;
   post[2].sim_points = post_back; /* Back face */
   post[2].npoints    = 4;
   post[2].int_points = post_ints[3];

   post[3].color      = BROWN;
   post[3].sim_points = post_right; /* Right face */
   post[3].npoints    = 4;
   post[3].int_points = post_ints[4];

   post[4].color      = DARKBROWN;
   post[4].sim_points = post_left; /* Left face */
   post[4].npoints    = 4;
   post[4].int_points = post_ints[5];

}

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

void cool_init(void)
{
   rotate_out(2);      /* Initialize the display */
   cool_checkmail();  /* Check whether to raise flag & beep */
}

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

/* Check for the existence of new mail.
 * If there is new mail, raise the flag and sound the alarm.
 *
 * This function could probably be made a lot smarter.
 */

void cool_checkmail(void)
{

   time_t newsize;
   int anymail, newmail, unreadmail;

   cool_get_inboxstatus(mailfile_str, &anymail, &unreadmail, &newmail);
   if (anymail)
      cool_put_envelope();
   else
      cool_remove_envelope();

   if (unreadmail)
      cool_raise_flag(frames);
   else
      cool_lower_flag(frames);

   if (newmail)
      cool_alarm();
}

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

void cool_parseargs(int argc, char *argv[])
{
   int i = 1;
   while (i < argc)
   {
      if (!strcmp(argv[i], "-f"))
      {
         i++;
         strncpy(mailfile_str, argv[i], 1023);
      }
      else if (!strcmp(argv[i], "-e"))
      {
         i++;
         strncpy(command_str, argv[i], 1023);
      }
      else if (!strcmp(argv[i], "-fr"))
      {
         i++;
         sscanf(argv[i], "%u", &frames);
         if(frames < 2) frames = 2;
      }
      else if (!strcmp(argv[i], "-int") || !strcmp(argv[i], "-update"))
      {
         i++;
         sscanf(argv[i], "%lu", &interval);
         interval *= 1000;
      }
      else if (!strcmp(argv[i], "-once"))
      {
         onceonly = 1;
      }
      else if (!strcmp(argv[i], "-v"))
      {
         verbose++;
      }
#ifdef AUDIO
      else if (!strcmp(argv[i], "-vol"))
      {
         i++;
         sscanf(argv[i], "%u", &cool_vol);
         if      (cool_vol < 0)   cool_vol = 0;
         else if (cool_vol > 100) cool_vol = 100;
      }
      else if (!strcmp(argv[i], "-af"))
      {
         i++;
         sndfile = strdup(argv[i]);
      }
#endif
      else
      {
         fprintf(stderr, "Unknown argument: %s\n", argv[i]);
         fprintf(stderr, "Type coolmail -h for help.\n");
         exit(0);
      }
      i++;
   }
}

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

/* Check for the -h option.  If found, print help and return nonzero. */

int  cool_help(int argc, char *argv[])
{
   int i;

   for(i = 1; i < argc; i++)
      if (!strcmp(argv[i], "-h"))
      {
         cool_usage();
         return(1);
      }

   return(0);
}

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

void cool_usage(void)
{
   printf("Usage:  coolmail [options]\n\n");
   printf("OPTIONS:\n");
   printf("  -af filename Specifies an audio file (.au format) to use\n");
   printf("               instead of the console bell.\n\n");
   printf("  -e command   Specifies a command (usually in quotes) which\n");
   printf("               is used to invoke your favorite mail-reading\n");
   printf("               program.\n\n");
   printf("  -f filename  Watch filename/maildir, instead of the default mail\n");
   printf("               file, %s<username>.\n\n", DEFAULT_MAIL_DIR);
   printf("  -fr n        Number of frames to generate for each animation.\n");
   printf("               Set to an appropriate value for your machine's.\n");
   printf("               graphics performance.  The default is %d.\n\n",
          DEFAULT_FRAMES);
   printf("  -h           Print some help then exit.\n\n");
   printf("  -int n       Check mail every n seconds, instead of the\n");
   printf("               default, %d seconds.  Note: Xbiff fans may use\n",
          DEFAULT_INTERVAL);
   printf("               -update instead of -int.\n\n");
   printf("  -mono        Monochrome mode.\n\n");
   printf("  -once        Ring bell when new mail is first detected, but\n");
   printf("               not when more new mail is detected.\n\n");
   printf("  -v           Verbose mode - Coolmail prints some information\n");
   printf("               on startup.\n\n");
   printf("  -vol n       Value between 0 and 100 for the volume setting\n");
   printf("               for both the console bell and the audio file.\n");
   printf("               The default is 50.\n\n");
   printf(" In addition, standard X intrinsic arguments are supported.\n");
}

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

/* Fork off the renderer update routine, to keep the mailbox window
 * up to date while we execute the command.
 */

int cool_do_command(char *command)
{
   int exists, reason;
   time_t newsize;
   pid_t child;

   disp_sync();

   if (child = fork())
   {  /* Parent Process */
      Mailbox_Is_Open = 1;
      signal(SIGCHLD, cool_closemailbox);
      while(Mailbox_Is_Open)
      {
         reason = rend_freeze(interval);
         cool_checkmail();
      }
      wait(NULL);
      return(reason);
   }
   else
   {  /* Child Process */
      pid_t parent = getppid();

      system(command);
      kill(parent, SIGCHLD);
      exit(0);
   }
}

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

void cool_raise_flag(unsigned int steps)
{
   static float pivot_x = 34.0;
   static float pivot_y = 80.0;
   float inc = (PI/2.0)/(float)steps;
   int   poly, point;

   if (flag_angle > PI/2.0 - inc) return;

   for (; flag_angle < PI/2.0; flag_angle += inc)
   {
      for(poly = 0; poly < 2; poly++)
         for(point = 0; point < 4; point++)
         {
            cool_rotate(&(flag[poly].sim_points[point].x),
                        &(flag[poly].sim_points[point].y),
                        pivot_x, pivot_y, inc);
         }
      rend_rm_plist(1);
      rend_submit_plist(1, flag);
      rend_frame();
   }

   return;
}

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

void cool_lower_flag(unsigned int steps)
{
   static float pivot_x = 34.0;
   static float pivot_y = 80.0;
   float inc = (PI/2.0)/(float)steps;
   int   poly, point;

   if (flag_angle < inc) return;

   for (; flag_angle > 0.0; flag_angle -= inc)
   {
      for(poly = 0; poly < 2; poly++)
         for(point = 0; point < 4; point++)
         {
            cool_rotate(&(flag[poly].sim_points[point].x),
                        &(flag[poly].sim_points[point].y),
                        pivot_x, pivot_y, -inc);
         }
      rend_rm_plist(1);
      rend_submit_plist(1, flag);
      rend_frame();
   }

   return;
}

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

/* Put the envelope in the mailbox */

void cool_put_envelope(void)
{
   if (envelope_present) return;
   rend_submit_plist(3, letter);
   rend_frame();
   envelope_present = 1;
}

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

/* Remove the envelope from the mailbox */

void cool_remove_envelope(void)
{
   if (!envelope_present) return;
   rend_rm_plist(3);
   rend_frame();
   envelope_present = 0;
}

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

/* Sound an alarm to alert the user of new mail */

void cool_alarm(void)
{
   if (!alerted || !onceonly)
   {
      rend_bell();
      alerted++;
   }
}

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

/* Rotate (px,py) about the point (cx,cy).
 */

void cool_rotate(float *px, float *py, float cx, float cy, float angle)
{
   double theta;
   double r;

   double xdist = (double)(*px-cx),
          ydist = (double)(*py-cy);

   r     = sqrt((double)(xdist*xdist + ydist * ydist));
   if (r == 0) return;

   theta = acos(xdist/r);
   if (asin(ydist/r) < 0.0)
      theta = (2.0*PI - theta);

   theta += (double)angle;

   *px =  r*cos(theta) + cx;
   *py =  r*sin(theta) + cy;
}
/*---------------------------------------------------------------------------*/

/* This is called upon receiving a signal from the child process, that the
 * mail application has completed.
 */

void cool_closemailbox(int n)
{
   cool_checkmail();
   rotate_out(frames);     /* Do the animation of the mailbox closing */
   signal(SIGCHLD, SIG_DFL);
   Mailbox_Is_Open = 0;
}

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

/* Get file modification time */


/* Maildir notes (aqua@sonoma.net, Sun Jan 18 19:42:27 PST 1998):
 *
 * The maildir mail-storage standard is a replacement for the traditional
 * 'mbox' format, intended to remove problems with file contention, locking,
 * reduce corruption in the case of a program or system crash, etc, etc.
 * Fairly detailed description of it can be had as part of the Qmail MTA
 * documentation, http://www.qmail.org/qmail-manual-html/man5/maildir.html.
 *
 * The general gist of the maildir approach is that mail is stored, one
 # message per file, in a subtree of ~/Maildir.  New mail goes in /new,
 * the "spool" goes in /cur, and /tmp is available to MUAs &c.  Mail is
 * theoretically supposed to be removed from /new immediately by the
 * MUA, but I've observed that with mutt 0.88, at least, it isn't if
 * the mailfile was generated by an import script (e.g. mbox2maildir)
 * rather than the normal delivery agent, presumably due to naming
 * differences.
 *
 * Checking for new mail mostly entails checking the mtime vs. atime of
 * every file in /new, and the number of messages in /new; if the latter
 * increases, new mail was delivered -- if not, but the files' atimes
 * are all later than their mtimes, the MUA read the /new spool.
 *
 * The specifications suggest skipping over every .file, but reading all
 * the others -- I've extended this to include skipping of all non-regular
 * files, which seemed to make sense -- define SUPPORT_MAILDIR_STRICTER to
 * override this behavior.
 *
 * This process is more resource-intensive than the old scheme of merely
 * calling stat() for a single file -- it's an O(n) rather than O(1)
 * operation.
 *
 */
void cool_get_inboxstatus(char *filename, int *anymail, int *unreadmail,
                          int *newmail)
{
   static off_t oldsize = 0;
   off_t  newsize;
   struct stat st;
   int fd;
#ifdef SUPPORT_MAILDIR
   DIR *d;
   struct dirent *de;
   char maildir[256],mfn[256];
#endif


#ifdef SUPPORT_MAILDIR_DEBUG
   printf("B anymail=%d, newmail=%d, unreadmail=%d, oldsize=%d, newsize=%d\n",
   	*anymail,*newmail,*unreadmail,oldsize,newsize);
#endif
#ifdef SUPPORT_MAILDIR
   if (stat(filename,&st)==-1) {
  	   *anymail = *newmail = *unreadmail = 0;
     	newsize = oldsize = 0;   
      perror(filename);
      return;
   }
   if (S_ISDIR(st.st_mode)) {
      /* likely a maildir */
   	strcpy(maildir,filename);
	   if (maildir[strlen(maildir)-1]!='/')
   	  strcat(maildir,"/");
	   strcat(maildir,"new");
   	if (stat(maildir,&st)==-1) {
      	perror(maildir);
	      printf("%s is not a maildir (missing/inaccessible %s)\n",filename,maildir);
   	   *anymail = *newmail = *unreadmail = 0;
      	newsize = oldsize = 0;
	      return;
   	}
	   if (!S_ISDIR(st.st_mode)) {
   	   printf("%s is not a directory (mode %d)\n",maildir,st.st_mode);
      	*anymail = *newmail = *unreadmail = 0;
	      newsize = oldsize = 0;
   	   return;
	   }   
	   d=opendir(maildir);
	   newsize=0;
   	*unreadmail = 0;
	   while ((de=readdir(d))) {
   	   if (de->d_name[0]=='.') /* dotfiles ignored per the maildir specs */
      	   continue;
	      strcpy(mfn,maildir);
   	   if (mfn[strlen(mfn)-1]!='/')
      	  strcat(mfn,"/");
	      strcat(mfn,de->d_name);
   	   if (stat(mfn,&st)==-1) {
      	  perror(mfn);
	        continue;
   	   }
#ifndef SUPPORT_MAILDIR_STRICTER
      	if (S_ISREG(st.st_mode))
#endif
         	newsize++;
	      if (st.st_mtime>=st.st_atime) {
#ifdef SUPPORT_MAILDIR_DEBUG
   	     printf("unread: %s mtime = %d, atime = %d\n",de->d_name,st.st_mtime,st.st_atime);
#endif
	        *unreadmail = 1;
   	   }
	   }
	   closedir(d);
	   if (newsize) {
	     *anymail = 1;
   	  if (newsize>oldsize && *unreadmail)
      	 *newmail = 1;
	     else
   	    *newmail = 0;
	   } else {
   	  *anymail = *newmail = *unreadmail = 0;
	     newsize = 0;
	   }
#ifdef SUPPORT_MAILDIR_DEBUG
   	printf("A anymail=%d, newmail=%d, unreadmail=%d, oldsize=%d, newsize=%d\n",
		  	*anymail,*newmail,*unreadmail,oldsize,newsize);
#endif
	   oldsize=newsize;
	} else {
#endif /* SUPPORT_MAILDIR */
	   fd = open (filename, O_RDONLY, 0);
   	if (fd < 0)
	   {
   	   *anymail    = 0;
      	*newmail    = 0;
	      *unreadmail = 0;
   	   newsize = 0;
	   }
	   else
	   {
   	   fstat(fd, &st);
	      close(fd);
   	   newsize = st.st_size;

	      if (newsize > 0)
   	      *anymail = 1;
      	else
	         *anymail = 0;

   	   if (st.st_mtime >= st.st_atime && newsize > 0)
      	   *unreadmail = 1;
	      else
   	      *unreadmail = 0;

	      if (newsize > oldsize && *unreadmail)
   	      *newmail = 1;
	      else
   	      *newmail = 0;
	   }
#ifdef SUPPORT_MAILDIR
	}
#endif
	oldsize = newsize;	
}

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


syntax highlighted by Code2HTML, v. 0.9.1