546 lines
14 KiB
C
546 lines
14 KiB
C
|
/* wmymail.c - mail checking dockapp */
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <signal.h>
|
||
|
#include <utime.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
#include <X11/Xlib.h>
|
||
|
#include <dockapp.h>
|
||
|
|
||
|
#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 <foo@bar.org>\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);
|
||
|
}
|
||
|
}
|
||
|
|