1 /* wmymail.c - mail checking dockapp */
12 #include <libdockapp/dockapp.h>
14 #include "xpm/main.xpm"
15 #include "xpm/numbers.xpm"
16 #include "xpm/unumbers.xpm"
17 #include "xpm/mbox_1.xpm"
18 #include "xpm/mbox_2.xpm"
19 #include "xpm/mbox_3.xpm"
22 * Some definitions required by libdockapp
25 #define NAME "wmymail"
26 #define VERSION "wmymail v0.3 November 6, 2004"
28 // default mail check interval, in seconds (default) or minutes (with -F)
29 #define CHECKINTERVAL 1
33 char *displayName
= "";
34 char *mailPath
= NULL
;
36 char *background
= "";
37 char *clickcommand
= "";
38 char *newcommand
= "";
40 int lastnumMessages
= 0;
43 int lastnumUnread
= 0;
44 int buttonpressed
= 0;
47 * usefetchmail means to run "fetchmail -c" and parse the output, rather
48 * than counting up messages in an mbox file.
50 * It will change the interval from seconds to minutes.
55 int checkInterval
= CHECKINTERVAL
;
56 time_t lastModifySeconds
= 0;
61 Pixmap unumbersPixmap
;
64 Pixmap mboxthreePixmap
;
69 static DAProgramOption options
[] = {
71 {"-display", NULL
, "display to use",
72 DOString
, False
, {&displayName
}},
74 {"-i", "--interval", "seconds between mailbox checks (default 1)",
75 DONatural
, False
, {&checkInterval
} },
77 {"-fc", "--fontcolor", "custom font color",
78 DOString
, False
, {&fontColor
} },
80 {"-bg", "--background", "custom background color for non-shaped window",
81 DOString
, False
, {&background
} },
83 {"-ns", "--noshape", "make the dock app non-shaped (windowed)",
84 DONone
, False
, {NULL
} },
86 {"-F", "--fetchmail", "check with fetchmail -c instead of the mbox",
87 DONone
, False
, {NULL
} },
89 {"-c", "--command", "command to run when clicked",
90 DOString
, False
, {&clickcommand
} },
92 {"-n", "--newcommand", "command to run when new mail is received",
93 DOString
, False
, {&newcommand
} },
95 {"-m", "--mailbox", "mailbox to use when $MAIL is not set",
96 DOString
, False
, {&mailPath
} }
102 void checkForNewMail(int dummy
);
103 void updatePixmap(void);
104 void parseMailFile( struct stat
*fileStat
);
105 char *getHexColorString( char *colorName
);
106 void putnumber (int number
, Pixmap pixmap
, Pixmap numbers
,
107 int destx
, int desty
);
108 void buttonpress(int button
, int state
, int x
, int y
);
109 void buttonrelease(int button
, int state
, int x
, int y
);
110 void checkfetchmail (void);
111 void checkmbox (void);
112 void launch (const char *command
);
116 int main(int argc
, char **argv
) {
117 Pixmap mainPixmap_mask
;
119 unsigned width
, height
;
121 DACallbacks callbacks
= { NULL
, &buttonpress
, &buttonrelease
,
122 NULL
, NULL
, NULL
, NULL
};
126 sa
.sa_handler
= SIG_IGN
;
128 sa
.sa_flags
= SA_NOCLDWAIT
;
132 sigemptyset(&sa
.sa_mask
);
133 sigaction(SIGCHLD
, &sa
, NULL
);
136 DAParseArguments(argc
, argv
, options
,
137 sizeof(options
) / sizeof(DAProgramOption
),
140 DAInitialize(displayName
, "wmymail", 64, 64, argc
, argv
);
142 // simple recoloring of the raw xpms befor creating Pixmaps of them
143 // this works as long as you don't "touch" the images...
145 if (options
[2].used
) { // custom font color ?
146 char *colorLine
= strdup("+ c #");
148 strcat(colorLine
, getHexColorString(fontColor
));
150 colorLine
= strdup("+ c #");
151 strcat(colorLine
, getHexColorString(fontColor
));
152 numbers_xpm
[3] = colorLine
;
155 if (options
[3].used
&& options
[8].used
) { // custom window background ?
156 char *colorLine
= strdup(" c #");
158 strcat(colorLine
, getHexColorString(background
));
159 main_xpm
[1] = colorLine
;
162 DAMakePixmapFromData(main_xpm
, &mainPixmap
, &mainPixmap_mask
, &width
, &height
);
163 DAMakePixmapFromData(numbers_xpm
, &numbersPixmap
, NULL
, &width
, &height
);
164 DAMakePixmapFromData(unumbers_xpm
, &unumbersPixmap
, NULL
, &width
, &height
);
166 DAMakePixmapFromData(mbox_1_xpm
, &mboxonePixmap
, NULL
, &width
, &height
);
167 DAMakePixmapFromData(mbox_2_xpm
, &mboxtwoPixmap
, NULL
, &width
, &height
);
168 DAMakePixmapFromData(mbox_3_xpm
, &mboxthreePixmap
, NULL
, &width
, &height
);
170 if (!options
[4].used
) // no shape to install
171 DASetShape(mainPixmap_mask
);
173 if (options
[5].used
) // use fetchmail
175 else if (mailPath
== NULL
) {
176 if ((mailPath
= getenv("MAIL")) == NULL
) {
177 perror("Please define your MAIL environment variable!\n");
182 DASetCallbacks( &callbacks
);
185 outPixmap1
= DAMakePixmap();
186 outPixmap2
= DAMakePixmap();
187 defaultGC
= XDefaultGC(DADisplay
, 0);
189 signal(SIGALRM
, checkForNewMail
);
202 char *getHexColorString(char *colorName
) {
204 char *hexColorString
;
206 if (!XParseColor(DADisplay
,
207 DefaultColormap(DADisplay
, DefaultScreen( DADisplay
)),
210 printf("unknown colorname: \"%s\"\n", colorName
);
214 hexColorString
= (char *)malloc(7);
215 sprintf(hexColorString
, "%02X%02X%02X", color
.red
>>8, color
.green
>>8,
218 return hexColorString
;
227 void checkForNewMail(int dummy
) {
228 struct itimerval timerVal
;
236 if (numMessages
!= lastnumMessages
||
237 numUnread
!= lastnumUnread
) {
239 if (numUnread
> lastnumUnread
&& strlen(newcommand
) > 0)
241 lastnumMessages
= numMessages
;
242 lastnumUnread
= numUnread
;
245 memset(&timerVal
, 0, sizeof(timerVal
));
248 timerVal
.it_value
.tv_sec
= checkInterval
* 60;
250 timerVal
.it_value
.tv_sec
= checkInterval
;
253 setitimer(ITIMER_REAL
, &timerVal
, NULL
);
262 void checkfetchmail (void) {
266 char tmpfile
[20] = "wmymail.XXXXXX";
274 fd
= mkstemp(tmpfile
);
276 perror("wmymail: cannot get a temporay file");
280 snpret
= snprintf(syscmd
, 120, "fetchmail -c > %s", tmpfile
);
282 perror("wmymail: error in snprintf() call (should not happen)");
286 if (system(syscmd
) < 0) {
287 perror("wmymail: error when using system() to run fetchmail -c");
293 perror("wmymail: can't reread tempfile\n");
297 /* FIXME: this assumes that fetchmail will never print a line over
298 * 1024 characters long, which is fairly safe but you never know */
299 while (fgets(line
, 1024, f
) != NULL
) {
301 /* Every line beginning with a number is assumed to be a number of
302 * messages on the server:
304 * "1 message for userfoo at mail.bar.org."
305 * "3 messages for userfoo at mail.bar.org." */
306 if (line
[0] >= '0' && line
[0] <= '9') {
308 /* The first number on the line may be added to the total */
309 msgtotal
+= atoi(line
);
311 /* Fetchmail may also indicate that some of the messages on the
312 * server have already been read:
314 * "5 messages (3 seen) for userfoo at mail.bar.org." */
316 /* To get the number seen, locate the first space */
317 s
= (char *)strstr(line
, " ");
320 /* Skip over one character */
323 /* And locate the second space */
324 t
= (char *)strstr(s
, " ");
326 /* If this second space is followed by '(' and a digit, it's
327 * a number of seen messages */
328 if (t
!= NULL
&& t
[1] == '(' && t
[2] >= '0' && t
[2] <= '9') {
330 /* Position string t on the number seen */
333 /* And get the number */
343 /* Now that that's been gotten through without major errors,
344 move the values to the global variables */
346 numMessages
= msgtotal
;
347 numUnread
= msgtotal
- msgseen
;
355 void checkmbox (void) {
356 struct stat fileStat
;
358 if (stat(mailPath
, &fileStat
) == -1 || fileStat
.st_size
== 0) {
361 } else if (lastModifySeconds
!= fileStat
.st_mtime
||
362 lastSize
!= fileStat
.st_size
) {
364 parseMailFile(&fileStat
);
366 lastModifySeconds
= fileStat
.st_mtime
;
367 lastSize
= fileStat
.st_size
;
377 void updatePixmap(void) {
378 Pixmap outPixmap
= flip
? outPixmap1
: outPixmap2
;
382 XCopyArea(DADisplay
, mainPixmap
, outPixmap
, defaultGC
,
385 if (numMessages
> 998) {
386 putnumber(999, outPixmap
, numbersPixmap
, 40, 49);
388 putnumber(numMessages
, outPixmap
, numbersPixmap
, 40, 49);
391 if (numUnread
> 998) {
392 putnumber(999, outPixmap
, unumbersPixmap
, 6, 49);
393 } else if (!numUnread
) {
394 putnumber(0, outPixmap
, numbersPixmap
, 6, 49);
396 putnumber(numUnread
, outPixmap
, unumbersPixmap
, 6, 49);
399 if (numUnread
== 0) {
401 } else if (numUnread
== 1) {
402 XCopyArea(DADisplay
, mboxonePixmap
, outPixmap
, defaultGC
,
403 0, 0, 40, 34, 14, 6);
404 } else if (numUnread
== 2) {
405 XCopyArea(DADisplay
, mboxtwoPixmap
, outPixmap
, defaultGC
,
406 0, 0, 40, 34, 14, 6);
408 XCopyArea(DADisplay
, mboxthreePixmap
, outPixmap
, defaultGC
,
409 0, 0, 40, 34, 14, 6);
412 DASetPixmap(outPixmap
);
417 * putnumber -- draw a number
422 int number
, /* what value should be displayed */
423 Pixmap pixmap
, /* pixmap to draw upon */
424 Pixmap numbers
, /* pixmap with digit images to use */
425 int destx
, int desty
/* upper-left corner of rectangle to draw in */
428 int digit1
, digit2
, digit3
;
430 /* Determine the digits */
431 digit1
= number
/ 100;
432 digit2
= (number
% 100) / 10;
433 digit3
= number
% 10;
435 /* The 100s and 10s digits will only be displayed if the number
436 is >99 and >9, respectively */
438 if (digit1
) XCopyArea(DADisplay
, numbers
, pixmap
, defaultGC
,
439 digit1
* 5, 0, 5, 9, destx
, desty
);
441 if (digit2
|| digit1
) XCopyArea(DADisplay
, numbers
, pixmap
, defaultGC
,
442 digit2
* 5, 0, 5, 9, destx
+ 6, desty
);
444 XCopyArea(DADisplay
, numbers
, pixmap
, defaultGC
,
445 digit3
* 5, 0, 5, 9, destx
+ 12, desty
);
449 * parseMailFile -- reads the mail file and sets the global variables:
451 * numMessages -- total number of messages (displayed on the right)
452 * numRead -- messages that have been read
453 * numUnread -- message not yet read (displayed on the left)
456 void parseMailFile(struct stat
*fileStat
) {
461 FILE *f
= fopen(mailPath
, "r"); /* FIXME check for failure to open */
466 while (fgets(buf
, 1024, f
) != NULL
) {
468 /* Keep discarding data if a line over 1024 characters long was found */
470 longline
= index(buf
, '\n') != NULL
;
473 /* The "From" line is the marker of an individual message */
474 if(!strncmp(buf
, "From ", 5)) {
478 /* Once inside a header, it only remains to
479 * 1) Take note, if the message appears to have been read
480 * 2) Locate the end of the header */
481 } else if (inHeader
) {
483 /* A blank line indicates the end of the header */
484 if (!strcmp(buf
, "\n")) {
491 /* The "Status" line indicates that the message has been read,
492 * if it has a "R". But since we don't trust that there will
493 * be only one "Status" line, statusRead will be set to 1,
494 * but numRead will only be incremented after the header has
495 * been completely read. That way, multiple "Status" lines
496 * would only set statusRead to 1 multiple times (having no
498 } else if (!strncmp(buf
, "Status: ", 8) && strchr(buf
, 'R')) {
503 /* The 1024 byte buffer can easily be exceeded by long lines...
504 * when no newline is present, we must enter the state of "skipping
505 * over the rest of a very long line". Else a line inside the body
506 * of a message might be (starting at the 1025th character)
507 * "From <foo@bar.org>\n" thus fooling this program into parsing it
509 longline
= index(buf
, '\n') == NULL
;
514 numUnread
= numMessages
- numRead
;
517 /* Take note of a mouse button being pressed inside the dock app */
518 void buttonpress (int button
, int state
, int x
, int y
) {
522 /* A mouse button was pressed and released.
523 * See if it was released while the mouse was still in the bounds of
524 * the dock app (a 64x64 square). */
525 void buttonrelease (int button
, int state
, int x
, int y
) {
526 if (buttonpressed
&& x
> 0 && x
< 64 && y
> 0 && y
< 64 &&
527 strlen(clickcommand
) > 0) {
528 launch(clickcommand
);
533 /* Start another program */
534 void launch (const char *command
) {
539 perror("can't fork");
540 } else if (cpid
== 0) {