/* wmymail.c - mail checking dockapp */ #include #include #include #include #include #include #include #include #include #include #include "xpm/main.xpm" #include "xpm/numbers.xpm" #include "xpm/unumbers.xpm" #include "xpm/mbox_1.xpm" #include "xpm/mbox_2.xpm" #include "xpm/mbox_3.xpm" /* * Some definitions required by libdockapp */ #define NAME "wmymail" #define VERSION "wmymail v0.3 November 6, 2004" // default mail check interval, in seconds (default) or minutes (with -F) #define CHECKINTERVAL 1 // global data char *displayName = ""; char *mailPath = NULL; char *fontColor = ""; char *background = ""; char *clickcommand = ""; char *newcommand = ""; int numMessages = 0; int lastnumMessages = 0; int numRead = 0; int numUnread = 0; int lastnumUnread = 0; int buttonpressed = 0; /* * usefetchmail means to run "fetchmail -c" and parse the output, rather * than counting up messages in an mbox file. * It will change the interval from seconds to minutes. */ int usefetchmail = 0; int flip = 1; int checkInterval = CHECKINTERVAL; time_t lastModifySeconds = 0; off_t lastSize = 0; Pixmap mainPixmap; Pixmap numbersPixmap; Pixmap unumbersPixmap; Pixmap mboxonePixmap; Pixmap mboxtwoPixmap; Pixmap mboxthreePixmap; Pixmap outPixmap1; Pixmap outPixmap2; GC defaultGC; static DAProgramOption options[] = { {"-display", NULL, "display to use", DOString, False, {&displayName}}, {"-i", "--interval", "seconds between mailbox checks (default 1)", DONatural, False, {&checkInterval} }, {"-fc", "--fontcolor", "custom font color", DOString, False, {&fontColor} }, {"-bg", "--background", "custom background color for non-shaped window", DOString, False, {&background} }, {"-ns", "--noshape", "make the dock app non-shaped (windowed)", DONone, False, {NULL} }, {"-F", "--fetchmail", "check with fetchmail -c instead of the mbox", DONone, False, {NULL} }, {"-c", "--command", "command to run when clicked", DOString, False, {&clickcommand} }, {"-n", "--newcommand", "command to run when new mail is received", DOString, False, {&newcommand} }, {"-m", "--mailbox", "mailbox to use when $MAIL is not set", DOString, False, {&mailPath} } }; // prototypes void checkForNewMail(int dummy); void updatePixmap(void); void parseMailFile( struct stat *fileStat ); char *getHexColorString( char *colorName ); void putnumber (int number, Pixmap pixmap, Pixmap numbers, int destx, int desty); void buttonpress(int button, int state, int x, int y); void buttonrelease(int button, int state, int x, int y); void checkfetchmail (void); void checkmbox (void); void launch (const char *command); // functions int main(int argc, char **argv) { Pixmap mainPixmap_mask; unsigned width, height; DACallbacks callbacks = { NULL, &buttonpress, &buttonrelease, NULL, NULL, NULL, NULL }; struct sigaction sa; sa.sa_handler = SIG_IGN; #ifdef SA_NOCLDWAIT sa.sa_flags = SA_NOCLDWAIT; #else sa.sa_flags = 0; #endif sigemptyset(&sa.sa_mask); sigaction(SIGCHLD, &sa, NULL); DAParseArguments(argc, argv, options, sizeof(options) / sizeof(DAProgramOption), NAME, VERSION); DAInitialize(displayName, "wmymail", 64, 64, argc, argv); // simple recoloring of the raw xpms befor creating Pixmaps of them // this works as long as you don't "touch" the images... if (options[2].used) { // custom font color ? char *colorLine = strdup("+ c #"); strcat(colorLine, getHexColorString(fontColor)); colorLine = strdup("+ c #"); strcat(colorLine, getHexColorString(fontColor)); numbers_xpm[3] = colorLine; } if (options[3].used && options[8].used) { // custom window background ? char *colorLine = strdup(" c #"); strcat(colorLine, getHexColorString(background)); main_xpm[1] = colorLine; } DAMakePixmapFromData(main_xpm, &mainPixmap, &mainPixmap_mask, &width, &height); DAMakePixmapFromData(numbers_xpm, &numbersPixmap, NULL, &width, &height); DAMakePixmapFromData(unumbers_xpm, &unumbersPixmap, NULL, &width, &height); DAMakePixmapFromData(mbox_1_xpm, &mboxonePixmap, NULL, &width, &height); DAMakePixmapFromData(mbox_2_xpm, &mboxtwoPixmap, NULL, &width, &height); DAMakePixmapFromData(mbox_3_xpm, &mboxthreePixmap, NULL, &width, &height); if (!options[4].used) // no shape to install DASetShape(mainPixmap_mask); if (options[5].used) // use fetchmail usefetchmail = 1; else if (mailPath == NULL) { if ((mailPath = getenv("MAIL")) == NULL) { perror("Please define your MAIL environment variable!\n"); exit(1); } } DASetCallbacks( &callbacks ); DASetTimeout(-1); outPixmap1 = DAMakePixmap(); outPixmap2 = DAMakePixmap(); defaultGC = XDefaultGC(DADisplay, 0); signal(SIGALRM, checkForNewMail); updatePixmap(); DAShow(); checkForNewMail(0); DAEventLoop(); return 0; } char *getHexColorString(char *colorName) { XColor color; char *hexColorString; if (!XParseColor(DADisplay, DefaultColormap(DADisplay, DefaultScreen( DADisplay)), colorName, &color)) { printf("unknown colorname: \"%s\"\n", colorName); exit(1); } hexColorString = (char *)malloc(7); sprintf(hexColorString, "%02X%02X%02X", color.red>>8, color.green>>8, color.blue>>8); return hexColorString; } /* * * checkForNewMail * */ void checkForNewMail(int dummy) { struct itimerval timerVal; if (usefetchmail) { checkfetchmail(); } else { checkmbox(); } if (numMessages != lastnumMessages || numUnread != lastnumUnread) { updatePixmap(); if (numUnread > lastnumUnread && strlen(newcommand) > 0) launch(newcommand); lastnumMessages = numMessages; lastnumUnread = numUnread; } memset(&timerVal, 0, sizeof(timerVal)); if (usefetchmail) { timerVal.it_value.tv_sec = checkInterval * 60; } else { timerVal.it_value.tv_sec = checkInterval; } setitimer(ITIMER_REAL, &timerVal, NULL); } /* * * checkfetchmail * */ void checkfetchmail (void) { int msgtotal = 0; int msgseen = 0; int snpret; char tmpfile[20] = "wmymail.XXXXXX"; char syscmd[120]; char line[1024]; char *s, *t; int fd; FILE *f; fd = mkstemp(tmpfile); if (fd == -1) { perror("wmymail: cannot get a temporay file"); return; } snpret = snprintf(syscmd, 120, "fetchmail -c > %s", tmpfile); if (snpret < 0) { perror("wmymail: error in snprintf() call (should not happen)"); return; } if (system(syscmd) < 0) { perror("wmymail: error when using system() to run fetchmail -c"); return; } f = fdopen(fd, "r"); if (f == NULL) { perror("wmymail: can't reread tempfile\n"); return; } /* FIXME: this assumes that fetchmail will never print a line over * 1024 characters long, which is fairly safe but you never know */ while (fgets(line, 1024, f) != NULL) { /* Every line beginning with a number is assumed to be a number of * messages on the server: * * "1 message for userfoo at mail.bar.org." * "3 messages for userfoo at mail.bar.org." */ if (line[0] >= '0' && line[0] <= '9') { /* The first number on the line may be added to the total */ msgtotal += atoi(line); /* Fetchmail may also indicate that some of the messages on the * server have already been read: * * "5 messages (3 seen) for userfoo at mail.bar.org." */ /* To get the number seen, locate the first space */ s = (char *)strstr(line, " "); if (s != NULL) { /* Skip over one character */ s++; /* And locate the second space */ t = (char *)strstr(s, " "); /* If this second space is followed by '(' and a digit, it's * a number of seen messages */ if (t != NULL && t[1] == '(' && t[2] >= '0' && t[2] <= '9') { /* Position string t on the number seen */ t += 2; /* And get the number */ msgseen += atoi(t); } } } } fclose(f); remove(tmpfile); /* Now that that's been gotten through without major errors, move the values to the global variables */ numMessages = msgtotal; numUnread = msgtotal - msgseen; } /* * * checkmbox * */ void checkmbox (void) { struct stat fileStat; if (stat(mailPath, &fileStat) == -1 || fileStat.st_size == 0) { numMessages = 0; numUnread = 0; } else if (lastModifySeconds != fileStat.st_mtime || lastSize != fileStat.st_size) { parseMailFile(&fileStat); lastModifySeconds = fileStat.st_mtime; lastSize = fileStat.st_size; } } /* * * updatePixmap * */ void updatePixmap(void) { Pixmap outPixmap = flip ? outPixmap1 : outPixmap2; flip = !flip; XCopyArea(DADisplay, mainPixmap, outPixmap, defaultGC, 0, 0, 64, 64, 0, 0); if (numMessages > 998) { putnumber(999, outPixmap, numbersPixmap, 40, 49); } else { putnumber(numMessages, outPixmap, numbersPixmap, 40, 49); } if (numUnread > 998) { putnumber(999, outPixmap, unumbersPixmap, 6, 49); } else if (!numUnread) { putnumber(0, outPixmap, numbersPixmap, 6, 49); } else { putnumber(numUnread, outPixmap, unumbersPixmap, 6, 49); } if (numUnread == 0) { // do nothing. } else if (numUnread == 1) { XCopyArea(DADisplay, mboxonePixmap, outPixmap, defaultGC, 0, 0, 40, 34, 14, 6); } else if (numUnread == 2) { XCopyArea(DADisplay, mboxtwoPixmap, outPixmap, defaultGC, 0, 0, 40, 34, 14, 6); } else { XCopyArea(DADisplay, mboxthreePixmap, outPixmap, defaultGC, 0, 0, 40, 34, 14, 6); } DASetPixmap(outPixmap); } /* * * putnumber -- draw a number * */ void putnumber ( int number, /* what value should be displayed */ Pixmap pixmap, /* pixmap to draw upon */ Pixmap numbers, /* pixmap with digit images to use */ int destx, int desty /* upper-left corner of rectangle to draw in */ ) { int digit1, digit2, digit3; /* Determine the digits */ digit1 = number / 100; digit2 = (number % 100) / 10; digit3 = number % 10; /* The 100s and 10s digits will only be displayed if the number is >99 and >9, respectively */ if (digit1) XCopyArea(DADisplay, numbers, pixmap, defaultGC, digit1 * 5, 0, 5, 9, destx, desty); if (digit2 || digit1) XCopyArea(DADisplay, numbers, pixmap, defaultGC, digit2 * 5, 0, 5, 9, destx + 6, desty); XCopyArea(DADisplay, numbers, pixmap, defaultGC, digit3 * 5, 0, 5, 9, destx + 12, desty); } /* * parseMailFile -- reads the mail file and sets the global variables: * * numMessages -- total number of messages (displayed on the right) * numRead -- messages that have been read * numUnread -- message not yet read (displayed on the left) */ void parseMailFile(struct stat *fileStat) { char buf[1024]; int inHeader = 0; int statusRead = 0; int longline = 0; FILE *f = fopen(mailPath, "r"); /* FIXME check for failure to open */ numMessages = 0; numRead = 0; while (fgets(buf, 1024, f) != NULL) { /* Keep discarding data if a line over 1024 characters long was found */ if (longline) { longline = index(buf, '\n') != NULL; } else { /* The "From" line is the marker of an individual message */ if(!strncmp(buf, "From ", 5)) { inHeader = 1; numMessages++; /* Once inside a header, it only remains to * 1) Take note, if the message appears to have been read * 2) Locate the end of the header */ } else if (inHeader) { /* A blank line indicates the end of the header */ if (!strcmp(buf, "\n")) { inHeader = 0; if (statusRead) { numRead++; statusRead = 0; } /* The "Status" line indicates that the message has been read, * if it has a "R". But since we don't trust that there will * be only one "Status" line, statusRead will be set to 1, * but numRead will only be incremented after the header has * been completely read. That way, multiple "Status" lines * would only set statusRead to 1 multiple times (having no * effect). */ } else if (!strncmp(buf, "Status: ", 8) && strchr(buf, 'R')) { statusRead = 1; } } /* The 1024 byte buffer can easily be exceeded by long lines... * when no newline is present, we must enter the state of "skipping * over the rest of a very long line". Else a line inside the body * of a message might be (starting at the 1025th character) * "From \n" thus fooling this program into parsing it * incorrectly. */ longline = index(buf, '\n') == NULL; } } fclose(f); numUnread = numMessages - numRead; } /* Take note of a mouse button being pressed inside the dock app */ void buttonpress (int button, int state, int x, int y) { buttonpressed = 1; } /* A mouse button was pressed and released. * See if it was released while the mouse was still in the bounds of * the dock app (a 64x64 square). */ void buttonrelease (int button, int state, int x, int y) { if (buttonpressed && x > 0 && x < 64 && y > 0 && y < 64 && strlen(clickcommand) > 0) { launch(clickcommand); } buttonpressed = 0; } /* Start another program */ void launch (const char *command) { int cpid; cpid = fork(); if (cpid == -1) { perror("can't fork"); } else if (cpid == 0) { system(command); exit(0); } }