/* 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 #include #include #include #include #include #include #include #include #include #include #ifdef AUDIO #include #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.\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; } /*---------------------------------------------------------------------------*/