wmbiff: Free memory when using a custom skin.
[dockapps.git] / wmbiff / wmbiff / wmbiff.c
blob8a4fa07c2fce9b0bc44a634eb89838660409ef87
1 /* $Id: wmbiff.c,v 1.70 2005/10/07 03:07:58 bluehal Exp $ */
3 // typedef int gcry_error_t;
5 #ifdef HAVE_CONFIG_H
6 #include <config.h>
7 #endif
9 #include <time.h>
10 #include <ctype.h>
12 #ifdef HAVE_POLL
13 #include <poll.h>
14 #else
15 #include <sys/time.h>
16 #endif
18 #include <sys/wait.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <signal.h>
23 #include <X11/Xlib.h>
24 #include <X11/xpm.h>
25 #include <X11/cursorfont.h>
26 #include <X11/XKBlib.h>
28 #include <errno.h>
29 #include <string.h>
31 #include "../wmgeneral/wmgeneral.h"
32 #include "../wmgeneral/misc.h"
34 #include "Client.h"
35 #include "charutil.h"
36 #include "MessageList.h"
38 #ifdef USE_DMALLOC
39 #include <dmalloc.h>
40 #endif
43 #include "wmbiff-master-led.xpm"
44 static char wmbiff_mask_bits[64 * 64];
45 static const int wmbiff_mask_width = 64;
46 // const int wmbiff_mask_height = 64;
48 #define CHAR_WIDTH 5
49 #define CHAR_HEIGHT 7
51 #define BLINK_TIMES 8
52 #define DEFAULT_SLEEP_INTERVAL 20000
53 #define BLINK_SLEEP_INTERVAL 200
54 #define DEFAULT_LOOP 5
56 #define MAX_NUM_MAILBOXES 40
57 static mbox_t mbox[MAX_NUM_MAILBOXES];
59 /* this is the normal pixmap. */
60 static const char *skin_filename = "wmbiff-master-led.xpm";
61 /* global notify action taken (if globalnotify option is set) */
62 static const char *globalnotify = NULL;
64 /* path to search for pixmaps */
65 /* /usr/share/wmbiff should have the default pixmap. */
66 /* /usr/local/share/wmbiff if compiled locally. */
67 /* / is there in case a user wants to specify a complete path */
68 /* . is there for development. */
69 static const char *skin_search_path = DEFAULT_SKIN_PATH;
70 /* for gnutls */
71 const char *certificate_filename = NULL;
73 /* it could be argued that a better default exists. */
74 #define DEFAULT_FONT "-*-fixed-*-r-*-*-10-*-*-*-*-*-*-*"
75 static const char *font = NULL;
77 int debug_default = DEBUG_ERROR;
79 /* color from wmbiff's xpm, down to 24 bits. */
80 const char *foreground = "white"; /* foreground white */
81 const char *background = "#505075"; /* background blue */
82 static const char *highlight = "red";
83 int SkipCertificateCheck = 0;
84 int Relax = 0; /* be not paranoid */
85 static int notWithdrawn = 0;
87 static unsigned int num_mailboxes = 1;
88 static const int x_origin = 5;
89 static const int y_origin = 5;
90 static int forever = 1; /* keep running. */
91 unsigned int custom_skin = 0; /* user has choose a custom skin */
93 extern Window win;
94 extern Window iconwin;
96 Cursor busy_cursor, ready_cursor;
98 static __inline /*@out@ */ void *
99 malloc_ordie(size_t len)
101 void *ret = malloc(len);
102 if (ret == NULL) {
103 fprintf(stderr, "unable to allocate %d bytes\n", (int) len);
104 abort();
106 return (ret);
109 /* where vertically the mailbox sits for blitting characters. */
110 static int mbox_y(unsigned int mboxnum)
112 return ((11 * mboxnum) + y_origin);
115 /* Read a line from a file to obtain a pair setting=value
116 skips # and leading spaces
117 NOTE: if setting finish with 0, 1, 2, 3 or 4 last char are deleted and
118 index takes its value... if not index will get -1
119 Returns -1 if no setting=value
121 static int ReadLine(FILE * fp, /*@out@ */ char *setting,
122 /*@out@ */ char *value, /*@out@ */ int *mbox_index)
124 char buf[BUF_SIZE];
125 char *p, *q;
126 int len;
128 *setting = '\0';
129 *value = '\0';
130 *mbox_index = -1;
132 if (!fp || feof(fp))
133 return -1;
135 if (!fgets(buf, BUF_SIZE - 1, fp))
136 return -1;
138 len = strlen(buf);
140 if (buf[len - 1] == '\n') {
141 buf[len - 1] = '\0'; /* strip linefeed */
144 StripComment(buf);
146 if (!(p = strtok(buf, "=")))
147 return -1;
148 if (!(q = strtok(NULL, "\n")))
149 return -1;
151 /* Chg - Mark Hurley
152 * Date: May 8, 2001
153 * Removed for loop (which removed leading spaces)
154 * Leading & Trailing spaces need to be removed
155 * to Fix Debian bug #95849
157 FullTrim(p);
158 FullTrim(q);
160 /* strcpy(setting, p); nspring replaced with sscanf dec 2002 */
161 strcpy(value, q);
163 if (sscanf(p, "%[_a-z.]%d", setting, mbox_index) == 2) {
164 /* mailbox-specific configuration, ends in a digit */
165 if (*mbox_index < 0 || *mbox_index >= MAX_NUM_MAILBOXES) {
166 DMA(DEBUG_ERROR, "invalid mailbox number %d\n", *mbox_index);
167 exit(EXIT_FAILURE);
169 } else if (sscanf(p, "%[a-z]", setting) == 1) {
170 /* global configuration, all text. */
171 *mbox_index = -1;
172 } else {
173 /* we found an uncommented line that has an equals,
174 but is non-alphabetic. */
175 DMA(DEBUG_INFO, "unparsed setting %s\n", p);
176 return -1;
179 DMA(DEBUG_INFO, "@%s.%d=%s@\n", setting, *mbox_index, value);
180 return 1;
183 struct path_demultiplexer {
184 const char *id; /* followed by a colon */
185 int (*creator) ( /*@notnull@ */ Pop3 pc, const char *path);
188 static struct path_demultiplexer paths[] = {
189 {"pop3:", pop3Create},
190 {"pop3s:", pop3Create},
191 {"shell:", shellCreate},
192 {"imap:", imap4Create},
193 {"imaps:", imap4Create},
194 {"sslimap:", imap4Create},
195 {"maildir:", maildirCreate},
196 {"mbox:", mboxCreate},
197 {NULL, NULL}
201 static void parse_mbox_path(unsigned int item)
203 int i;
204 /* find the creator */
205 for (i = 0;
206 paths[i].id != NULL
207 && strncasecmp(mbox[item].path, paths[i].id, strlen(paths[i].id));
208 i++);
209 /* if found, execute */
210 if (paths[i].id != NULL) {
211 if (paths[i].creator((&mbox[item]), mbox[item].path) != 0) {
212 DMA(DEBUG_ERROR, "creator for mailbox %u returned failure\n",
213 item);
215 } else {
216 /* default are mbox */
217 mboxCreate((&mbox[item]), mbox[item].path);
221 static int Read_Config_File(char *filename, int *loopinterval)
223 FILE *fp;
224 char setting[BUF_SMALL], value[BUF_SIZE];
225 int mbox_index;
226 unsigned int i;
228 if (!(fp = fopen(filename, "r"))) {
229 DMA(DEBUG_ERROR, "Unable to open %s, no settings read: %s\n",
230 filename, strerror(errno));
231 return 0;
233 while (!feof(fp)) {
234 /* skanky: -1 can represent an unparsed line
235 or an error */
236 if (ReadLine(fp, setting, value, &mbox_index) == -1)
237 continue;
239 /* settings that can be global go here. */
240 if (!strcmp(setting, "interval")) {
241 *loopinterval = atoi(value);
242 continue;
243 } else if (!strcmp(setting, "askpass")) {
244 const char *askpass = strdup_ordie(value);
245 if (mbox_index == -1) {
246 DMA(DEBUG_INFO, "setting all to askpass %s\n", askpass);
247 for (i = 0; i < MAX_NUM_MAILBOXES; i++)
248 mbox[i].askpass = askpass;
249 } else {
250 mbox[mbox_index].askpass = askpass;
252 continue;
253 } else if (!strcmp(setting, "skinfile")) {
254 skin_filename = strdup_ordie(value);
255 custom_skin = 1;
256 continue;
257 } else if (!strcmp(setting, "certfile")) { /* not yet supported */
258 certificate_filename = strdup_ordie(value);
259 continue;
260 } else if (!strcmp(setting, "globalnotify")) {
261 globalnotify = strdup_ordie(value);
262 continue;
263 } else if (mbox_index == -1) {
264 DMA(DEBUG_INFO, "Unknown global setting '%s'\n", setting);
265 continue; /* Didn't read any setting.[0-5] value */
268 if (mbox_index >= MAX_NUM_MAILBOXES) {
269 DMA(DEBUG_ERROR, "Don't have %d mailboxes.\n", mbox_index);
270 continue;
273 if (1U + mbox_index > num_mailboxes
274 && mbox_index + 1 <= MAX_NUM_MAILBOXES) {
275 num_mailboxes = 1U + mbox_index;
278 /* now only local settings */
279 if (!strcmp(setting, "label.")) {
280 if (strlen(value) + 1 > BUF_SMALL) {
281 DMA(DEBUG_ERROR,
282 "Mailbox %i label string '%s' is too long.\n",
283 mbox_index, value);
284 continue;
285 } else {
286 strncpy(mbox[mbox_index].label, value, BUF_SMALL - 1);
288 } else if (!strcmp(setting, "path.")) {
289 if (strlen(value) + 1 > BUF_BIG) {
290 DMA(DEBUG_ERROR,
291 "Mailbox %i path string '%s' is too long.\n",
292 mbox_index, value);
293 continue;
294 } else {
295 strncpy(mbox[mbox_index].path, value, BUF_BIG - 1);
297 } else if (!strcmp(setting, "notify.")) {
298 if (strlen(value) + 1 > BUF_BIG) {
299 DMA(DEBUG_ERROR,
300 "Mailbox %i notify string '%s' is too long.\n",
301 mbox_index, value);
302 continue;
303 } else {
304 strncpy(mbox[mbox_index].notify, value, BUF_BIG - 1);
306 } else if (!strcmp(setting, "action.")) {
307 if (strlen(value) + 1 > BUF_BIG) {
308 DMA(DEBUG_ERROR,
309 "Mailbox %i action string '%s' is too long.\n",
310 mbox_index, value);
311 continue;
312 } else {
313 strncpy(mbox[mbox_index].action, value, BUF_BIG - 1);
315 } else if (!strcmp(setting, "action_disconnected.")) {
316 if (strlen(value) + 1 > BUF_BIG) {
317 DMA(DEBUG_ERROR,
318 "Mailbox %i action_disconnected string '%s' is too long.\n",
319 mbox_index, value);
320 continue;
321 } else {
322 strncpy(mbox[mbox_index].actiondc, value, BUF_BIG - 1);
324 } else if (!strcmp(setting, "action_new_mail.")) {
325 if (strlen(value) + 1 > BUF_BIG) {
326 DMA(DEBUG_ERROR,
327 "Mailbox %i action_new_mail string '%s' is too long.\n",
328 mbox_index, value);
329 continue;
330 } else {
331 strncpy(mbox[mbox_index].actionnew, value, BUF_BIG - 1);
333 } else if (!strcmp(setting, "action_no_new_mail.")) {
334 if (strlen(value) + 1 > BUF_BIG) {
335 DMA(DEBUG_ERROR,
336 "Mailbox %i action_no_new_mail string '%s' is too long.\n",
337 mbox_index, value);
338 continue;
339 } else {
340 strncpy(mbox[mbox_index].actionnonew, value, BUF_BIG - 1);
342 } else if (!strcmp(setting, "interval.")) {
343 mbox[mbox_index].loopinterval = atoi(value);
344 } else if (!strcmp(setting, "buttontwo.")) {
345 if (strlen(value) + 1 > BUF_BIG) {
346 DMA(DEBUG_ERROR,
347 "Mailbox %i buttontwo string '%s' is too long.\n",
348 mbox_index, value);
349 continue;
350 } else {
351 strncpy(mbox[mbox_index].button2, value, BUF_BIG - 1);
353 } else if (!strcmp(setting, "fetchcmd.")) {
354 if (strlen(value) + 1 > BUF_BIG) {
355 DMA(DEBUG_ERROR,
356 "Mailbox %i fetchcmd string '%s' is too long.\n",
357 mbox_index, value);
358 continue;
359 } else {
360 strncpy(mbox[mbox_index].fetchcmd, value, BUF_BIG - 1);
362 } else if (!strcmp(setting, "fetchinterval.")) {
363 mbox[mbox_index].fetchinterval = atoi(value);
364 } else if (!strcmp(setting, "debug.")) {
365 int debug_value = debug_default;
366 if (strcasecmp(value, "all") == 0) {
367 debug_value = DEBUG_ALL;
369 /* could disable debugging, but I want the command
370 line argument to provide all information
371 possible. */
372 mbox[mbox_index].debug = debug_value;
373 } else {
374 DMA(DEBUG_INFO, "Unknown setting '%s'\n", setting);
377 (void) fclose(fp);
378 for (i = 0; i < num_mailboxes; i++)
379 if (mbox[i].label[0] != '\0')
380 parse_mbox_path(i);
381 return 1;
385 static void init_biff(char *config_file)
387 #ifdef HAVE_GCRYPT_H
388 gcry_error_t rc;
389 #endif
390 int loopinterval = DEFAULT_LOOP;
391 unsigned int i;
393 for (i = 0; i < MAX_NUM_MAILBOXES; i++) {
394 memset(mbox[i].label, 0, BUF_SMALL);
395 memset(mbox[i].path, 0, BUF_BIG);
396 memset(mbox[i].notify, 0, BUF_BIG);
397 memset(mbox[i].action, 0, BUF_BIG);
398 memset(mbox[i].actiondc, 0, BUF_BIG);
399 memset(mbox[i].actionnew, 0, BUF_BIG);
400 memset(mbox[i].actionnonew, 0, BUF_BIG);
401 memset(mbox[i].button2, 0, BUF_BIG);
402 memset(mbox[i].fetchcmd, 0, BUF_BIG);
403 mbox[i].loopinterval = 0;
404 mbox[i].getHeaders = NULL;
405 mbox[i].releaseHeaders = NULL;
406 mbox[i].debug = debug_default;
407 mbox[i].askpass = DEFAULT_ASKPASS;
410 #ifdef HAVE_GCRYPT_H
411 /* gcrypt is a little strange, in that it doesn't
412 * seem to initialize its memory pool by itself.
413 * I believe we *expect* "Warning: using insecure memory!"
415 /* gcryctl_disable_secmem gets called before check_version -- in a message on
416 gcrypt-devel august 17 2004. */
417 if ((rc = gcry_control(GCRYCTL_DISABLE_SECMEM, 0)) != 0) {
418 DMA(DEBUG_ERROR,
419 "Error: tried to disable gcrypt warning and failed: %d\n"
420 " Message: %s\n" " libgcrypt version: %s\n", rc,
421 gcry_strerror(rc), gcry_check_version(NULL));
424 /* recently made a requirement, section 2.4 of gcrypt manual */
425 if (gcry_check_version("1.1.42") == NULL) {
426 DMA(DEBUG_ERROR, "Error: incompatible gcrypt version.\n");
428 #endif
430 DMA(DEBUG_INFO, "config_file = %s.\n", config_file);
431 if (!Read_Config_File(config_file, &loopinterval)) {
432 char *m;
433 /* setup defaults if there's no config */
434 if ((m = getenv("MAIL")) != NULL) {
435 /* we are using MAIL environment var. type mbox */
436 if (strlen(m) + 1 > BUF_BIG) {
437 DMA(DEBUG_ERROR,
438 "MAIL environment var '%s' is too long.\n", m);
439 } else {
440 DMA(DEBUG_INFO, "Using MAIL environment var '%s'.\n", m);
441 strncpy(mbox[0].path, m, BUF_BIG - 1);
443 } else if ((m = getenv("USER")) != NULL) {
444 /* we will use the USER env var to find an mbox name */
445 if (strlen(m) + 10 + 1 > BUF_BIG) {
446 DMA(DEBUG_ERROR,
447 "USER environment var '%s' is too long.\n", m);
448 } else {
449 DMA(DEBUG_INFO, "Using /var/mail/%s.\n", m);
450 strcpy(mbox[0].path, "/var/mail/");
451 strncat(mbox[0].path, m, BUF_BIG - 1 - 10);
452 if (mbox[0].path[9] != '/') {
453 DMA(DEBUG_ERROR,
454 "Unexpected failure to construct /var/mail/username, please "
455 "report this with your operating system info and the version of wmbiff.");
458 } else {
459 DMA(DEBUG_ERROR, "Cannot open config file '%s' nor use the "
460 "MAIL environment var.\n", config_file);
461 exit(EXIT_FAILURE);
463 if (!exists(mbox[0].path)) {
464 DMA(DEBUG_ERROR, "Cannot open config file '%s', and the "
465 "default %s doesn't exist.\n", config_file, mbox[0].path);
466 exit(EXIT_FAILURE);
468 strcpy(mbox[0].label, "Spool");
469 mboxCreate((&mbox[0]), mbox[0].path);
472 /* Make labels look right */
473 for (i = 0; i < num_mailboxes; i++) {
474 if (mbox[i].label[0] != '\0') {
475 /* append a colon, but skip if we're using fonts. */
476 if (font == NULL) {
477 int j = strlen(mbox[i].label);
478 if (j < 5) {
479 memset(mbox[i].label + j, ' ', 5 - j);
482 /* but always end after 5 characters */
483 mbox[i].label[6] = '\0';
484 /* set global loopinterval to boxes with 0 loop */
485 if (!mbox[i].loopinterval) {
486 mbox[i].loopinterval = loopinterval;
492 static char **LoadXPM(const char *pixmap_filename)
494 char **xpm;
495 int success;
496 success = XpmReadFileToData((char *) pixmap_filename, &xpm);
497 switch (success) {
498 case XpmOpenFailed:
499 DMA(DEBUG_ERROR, "Unable to open %s\n", pixmap_filename);
500 break;
501 case XpmFileInvalid:
502 DMA(DEBUG_ERROR, "%s is not a valid pixmap\n", pixmap_filename);
503 break;
504 case XpmNoMemory:
505 DMA(DEBUG_ERROR, "Insufficient memory to read %s\n",
506 pixmap_filename);
507 break;
508 default:
509 break;
511 return (xpm);
514 /* tests as "test -f" would */
515 int exists(const char *filename)
517 struct stat st_buf;
518 DMA(DEBUG_INFO, "looking for %s\n", filename);
519 if (stat(filename, &st_buf) == 0 && S_ISREG(st_buf.st_mode)) {
520 DMA(DEBUG_INFO, "found %s\n", filename);
521 return (1);
523 return (0);
526 /* acts like execvp, with code inspired by it */
527 /* mustfree */
528 static char *search_path(const char *path, const char *find_me)
530 char *buf;
531 const char *p;
532 int len, pathlen;
533 if (strchr(find_me, '/') != NULL) {
534 return (strdup_ordie(find_me));
536 pathlen = strlen(path);
537 len = strlen(find_me) + 1;
538 buf = malloc_ordie(pathlen + len + 1);
539 memcpy(buf + pathlen + 1, find_me, len);
540 buf[pathlen] = '/';
542 for (p = path; p != NULL; path = p, path++) {
543 char *startp;
544 p = strchr(path, ':');
545 if (p == NULL) {
546 /* not found; p should point to the null char at the end */
547 startp =
548 memcpy(buf + pathlen - strlen(path), path, strlen(path));
549 } else if (p == path) {
550 /* double colon in a path apparently means try here */
551 startp = &buf[pathlen + 1];
552 } else {
553 /* copy the part between the colons to the buffer */
554 startp = memcpy(buf + pathlen - (p - path), path, p - path);
556 if (exists(startp) != 0) {
557 char *ret = strdup_ordie(startp);
558 free(buf);
559 return (ret);
562 free(buf);
563 return (NULL);
566 /* verifies that .wmbiffrc, is a file, is owned by the user,
567 is not world writeable, and is not world readable. This
568 is just to help keep passwords secure */
569 static int wmbiffrc_permissions_check(const char *wmbiffrc_fname)
571 struct stat st;
572 if (stat(wmbiffrc_fname, &st) != 0) {
573 DMA(DEBUG_ERROR, "Can't stat wmbiffrc: '%s'\n", wmbiffrc_fname);
574 return (1); /* well, it's not a bad permission
575 problem: if you can't find it,
576 neither can the bad guys.. */
578 if ((st.st_mode & S_IFDIR) != 0) {
579 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is a directory!\n"
580 "exiting. don't do that.", wmbiffrc_fname);
581 exit(EXIT_FAILURE);
583 if (st.st_uid != getuid()) {
584 char *user = getenv("USER");
585 DMA(DEBUG_ERROR,
586 ".wmbiffrc '%s' isn't owned by you.\n"
587 "Verify its contents, then 'chown %s %s'\n",
588 wmbiffrc_fname, ((user != NULL) ? user : "(your username)"),
589 wmbiffrc_fname);
590 return (0);
592 if ((st.st_mode & S_IWOTH) != 0) {
593 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is world writable.\n"
594 "Verify its contents, then 'chmod 0600 %s'\n",
595 wmbiffrc_fname, wmbiffrc_fname);
596 return (0);
598 if ((st.st_mode & S_IROTH) != 0) {
599 DMA(DEBUG_ERROR, ".wmbiffrc '%s' is world readable.\n"
600 "Please run 'chmod 0600 %s' and consider changing your passwords.\n",
601 wmbiffrc_fname, wmbiffrc_fname);
602 return (0);
604 return (1);
607 static void ClearDigits(unsigned int i)
609 if (font) {
610 eraseRect(39, mbox_y(i), 58, mbox_y(i + 1) - 1, background);
611 } else {
612 /* overwrite the colon */
613 copyXPMArea((10 * (CHAR_WIDTH + 1)), 64, (CHAR_WIDTH + 1),
614 (CHAR_HEIGHT + 1), 35, mbox_y(i));
615 /* blank out the number fields. */
616 copyXPMArea(39, 84, (3 * (CHAR_WIDTH + 1)), (CHAR_HEIGHT + 1), 39,
617 mbox_y(i));
621 /* Blits a string at given co-ordinates. If a ``new''
622 parameter is nonzero, all digits will be yellow */
623 static void BlitString(const char *name, int x, int y, int new)
625 if (font != NULL) {
626 /* an alternate behavior - draw the string using a font
627 instead of the pixmap. should allow pretty colors */
628 drawString(x, y + CHAR_HEIGHT + 1, name,
629 new ? highlight : foreground, background, 0);
630 } else {
631 /* normal, LED-like behavior. */
632 int i, c, k = x;
633 for (i = 0; name[i] != '\0'; i++) {
634 c = toupper(name[i]);
635 if (c >= 'A' && c <= 'Z') { /* it's a letter */
636 c -= 'A';
637 copyXPMArea(c * (CHAR_WIDTH + 1), (new ? 95 : 74),
638 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
639 k += (CHAR_WIDTH + 1);
640 } else { /* it's a number or symbol */
641 c -= '0';
642 if (new) {
643 copyXPMArea((c * (CHAR_WIDTH + 1)) + 65, 0,
644 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
645 } else {
646 copyXPMArea((c * (CHAR_WIDTH + 1)), 64,
647 (CHAR_WIDTH + 1), (CHAR_HEIGHT + 1), k, y);
649 k += (CHAR_WIDTH + 1);
656 /* Blits number to give coordinates.. two 0's, right justified */
657 static void BlitNum(int num, int x, int y, int new)
659 char buf[32];
661 sprintf(buf, "%02i", num);
663 if (font != NULL) {
664 const char *color = (new) ? highlight : foreground;
665 drawString(x + (CHAR_WIDTH * 2 + 4), y + CHAR_HEIGHT + 1, buf,
666 color, background, 1);
667 } else {
668 int newx = x;
670 if (num > 99)
671 newx -= (CHAR_WIDTH + 1);
672 if (num > 999)
673 newx -= (CHAR_WIDTH + 1);
675 BlitString(buf, newx, y, new);
679 /* helper function for displayMsgCounters, which has outgrown its name */
680 static void blitMsgCounters(unsigned int i)
682 int y_row = mbox_y(i); /* constant for each mailbox */
683 ClearDigits(i); /* Clear digits */
684 if ((mbox[i].blink_stat & 0x01) == 0) {
685 int newmail = (mbox[i].UnreadMsgs > 0) ? 1 : 0;
686 if (mbox[i].TextStatus[0] != '\0') {
687 BlitString(mbox[i].TextStatus, 39, y_row, newmail);
688 } else {
689 int mailcount =
690 (newmail) ? mbox[i].UnreadMsgs : 0;
691 BlitNum(mailcount, 45, y_row, newmail);
697 * void execnotify(1) : runs notify command, if given (not null)
699 static void execnotify( /*@null@ */ const char *notifycmd)
701 if (notifycmd != NULL) { /* need to call notify() ? */
702 if (!strcasecmp(notifycmd, "true")) {
703 /* Yes, nothing */
704 } else {
705 /* Else call external notifyer, ignoring the pid */
706 (void) execCommand(notifycmd);
712 static void
713 displayMsgCounters(unsigned int i, int mail_stat, int *Blink_Mode)
715 switch (mail_stat) {
716 case 2: /* New mail has arrived */
717 /* Enter blink-mode for digits */
718 mbox[i].blink_stat = BLINK_TIMES * 2;
719 *Blink_Mode |= (1 << i); /* Global blink flag set for this mailbox */
720 blitMsgCounters(i);
721 execnotify(mbox[i].notify);
722 break;
723 case 1: /* mailbox has been rescanned/changed */
724 blitMsgCounters(i);
725 break;
726 case 0:
727 break;
728 case -1: /* Error was detected */
729 ClearDigits(i); /* Clear digits */
730 BlitString("XX", 45, mbox_y(i), 0);
731 break;
735 /** counts mail in spool-file
736 Returned value:
737 -1 : Error was encountered
738 0 : mailbox status wasn't changed
739 1 : mailbox was changed (NO new mail)
740 2 : mailbox was changed AND new mail has arrived
742 static int count_mail(unsigned int item)
744 int rc = 0;
746 if (!mbox[item].checkMail) {
747 return -1;
750 if (mbox[item].checkMail(&(mbox[item])) < 0) {
751 /* we failed to obtain any numbers therefore set
752 * them to -1's ensuring the next pass (even if
753 * zero) will be captured correctly
755 mbox[item].TotalMsgs = -1;
756 mbox[item].UnreadMsgs = -1;
757 mbox[item].OldMsgs = -1;
758 mbox[item].OldUnreadMsgs = -1;
759 return -1;
762 if (mbox[item].UnreadMsgs > mbox[item].OldUnreadMsgs &&
763 mbox[item].UnreadMsgs > 0) {
764 rc = 2; /* New mail detected */
765 } else if (mbox[item].UnreadMsgs < mbox[item].OldUnreadMsgs ||
766 mbox[item].TotalMsgs != mbox[item].OldMsgs) {
767 rc = 1; /* mailbox was changed - NO new mail */
768 } else {
769 rc = 0; /* mailbox wasn't changed */
771 mbox[item].OldMsgs = mbox[item].TotalMsgs;
772 mbox[item].OldUnreadMsgs = mbox[item].UnreadMsgs;
773 return rc;
776 static int periodic_mail_check(void)
778 int NeedRedraw = 0;
779 static int Blink_Mode = 0; /* Bit mask, digits are in blinking
780 mode or not. Each bit for separate
781 mailbox */
782 int Sleep_Interval; /* either DEFAULT_SLEEP_INTERVAL or
783 BLINK_SLEEP_INTERVAL */
784 int NewMail = 0; /* flag for global notify */
785 unsigned int i;
786 time_t curtime = time(0);
787 for (i = 0; i < num_mailboxes; i++) {
788 if (mbox[i].label[0] != '\0') {
789 if (curtime >= mbox[i].prevtime + mbox[i].loopinterval) {
790 int mailstat = 0;
791 NeedRedraw = 1;
792 DM(&mbox[i], DEBUG_INFO,
793 "working on [%u].label=>%s< [%u].path=>%s<\n", i,
794 mbox[i].label, i, mbox[i].path);
795 DM(&mbox[i], DEBUG_INFO,
796 "curtime=%d, prevtime=%d, interval=%d\n",
797 (int) curtime, (int) mbox[i].prevtime,
798 mbox[i].loopinterval);
799 mbox[i].prevtime = curtime;
801 XDefineCursor(display, iconwin, busy_cursor);
802 RedrawWindow();
804 mailstat = count_mail(i);
806 XUndefineCursor(display, iconwin);
808 /* Global notify */
809 if (mailstat == 2)
810 NewMail = 1;
812 displayMsgCounters(i, mailstat, &Blink_Mode);
813 /* update our idea of current time, as it
814 may have changed as we check. */
815 curtime = time(0);
817 if (mbox[i].blink_stat > 0) {
818 if (--mbox[i].blink_stat <= 0) {
819 Blink_Mode &= ~(1 << i);
820 mbox[i].blink_stat = 0;
822 displayMsgCounters(i, 1, &Blink_Mode);
823 NeedRedraw = 1;
825 if (mbox[i].fetchinterval > 0 && mbox[i].fetchcmd[0] != '\0'
826 && curtime >=
827 mbox[i].prevfetch_time + mbox[i].fetchinterval) {
829 XDefineCursor(display, iconwin, busy_cursor);
830 RedrawWindow();
832 (void) execCommand(mbox[i].fetchcmd);
834 XUndefineCursor(display, iconwin);
836 mbox[i].prevfetch_time = curtime;
841 /* exec globalnotify if there was any new mail */
842 if (NewMail == 1)
843 execnotify(globalnotify);
845 if (Blink_Mode == 0) {
846 for (i = 0; i < num_mailboxes; i++) {
847 mbox[i].blink_stat = 0;
849 Sleep_Interval = DEFAULT_SLEEP_INTERVAL;
850 } else {
851 Sleep_Interval = BLINK_SLEEP_INTERVAL;
854 if (NeedRedraw) {
855 NeedRedraw = 0;
856 RedrawWindow();
859 return Sleep_Interval;
862 static int findTopOfMasterXPM(const char **skin_xpm)
864 int i;
865 for (i = 0; skin_xpm[i] != NULL; i++) {
866 if (strstr(skin_xpm[i], "++++++++") != NULL)
867 return i;
869 DMA(DEBUG_ERROR,
870 "couldn't find the top of the xpm file using the simple method\n");
871 exit(EXIT_FAILURE);
874 static char **CreateBackingXPM(int width, int height,
875 const char **skin_xpm)
877 char **ret = malloc_ordie(sizeof(char *) * (height + 6)
878 + sizeof(void *) /* trailing null space */ );
879 const int colors = 5;
880 const int base = colors + 1;
881 const int margin = 4;
882 int i;
883 int top = findTopOfMasterXPM(skin_xpm);
884 ret[0] = malloc_ordie(30);
885 sprintf(ret[0], "%d %d %d %d", width, height, colors, 1);
886 ret[1] = (char *) " \tc #0000FF"; /* no color */
887 ret[2] = (char *) ".\tc #505075"; /* background gray */
888 ret[2] = malloc_ordie(30);
889 sprintf(ret[2], ".\tc %s", background);
890 ret[3] = (char *) "+\tc #000000"; /* shadowed */
891 ret[4] = (char *) "@\tc #C7C3C7"; /* highlight */
892 ret[5] = (char *) ":\tc #004941"; /* led off */
893 for (i = base; i < base + height; i++) {
894 ret[i] = malloc_ordie(width);
896 for (i = base; i < base + margin; i++) {
897 memset(ret[i], ' ', width);
899 for (i = base + margin; i < height + base - margin; i++) {
900 memset(ret[i], ' ', margin);
902 if (i == base + margin) {
903 memset(ret[i] + margin, '+', width - margin - margin);
904 } else if (i == base + height - margin - 1) {
905 memset(ret[i] + margin, '@', width - margin - margin);
906 } else {
907 // " +..:::...:::...:::...:::...:::.......:::...:::...:::...@ "
908 // " +.:...:.:...:.:...:.:...:.:...:..:..:...:.:...:.:...:..@ " ",
909 ret[i][margin] = '+';
910 memset(ret[i] + margin + 1, '.', width - margin - margin - 1);
911 ret[i][width - margin - 1] = '@';
912 memcpy(ret[i],
913 skin_xpm[((i - (base + margin) - 1) % 11) + top + 1],
914 width);
917 memset(ret[i] + width - margin, ' ', margin);
919 for (i = base + height - margin; i < height + base; i++) {
920 memset(ret[i], ' ', width);
922 ret[height + base] = NULL; /* not sure if this is necessary, it just
923 seemed like a good idea */
924 return (ret);
928 * NOTE: this function assumes that the ConnectionNumber() macro
929 * will return the file descriptor of the Display struct
930 * (it does under XFree86 and solaris' openwin X)
932 static void XSleep(int millisec)
934 #ifdef HAVE_POLL
935 struct pollfd timeout;
937 timeout.fd = ConnectionNumber(display);
938 timeout.events = POLLIN;
940 poll(&timeout, 1, millisec);
941 #else
942 struct timeval to;
943 struct timeval *timeout = NULL;
944 fd_set readfds;
945 int max_fd;
947 if (millisec >= 0) {
948 timeout = &to;
949 to.tv_sec = millisec / 1000;
950 to.tv_usec = (millisec % 1000) * 1000;
952 FD_ZERO(&readfds);
953 FD_SET(ConnectionNumber(display), &readfds);
954 max_fd = ConnectionNumber(display);
956 select(max_fd + 1, &readfds, NULL, NULL, timeout);
957 #endif
960 const char **restart_args;
962 static void restart_wmbiff(int sig
963 #ifdef HAVE___ATTRIBUTE__
964 __attribute__ ((unused))
965 #endif
968 if (restart_args) {
969 DMA(DEBUG_ERROR, "exec()'ing %s\n", restart_args[0]);
970 sleep(1);
971 execvp(restart_args[0], (char *const *) restart_args);
972 DMA(DEBUG_ERROR, "exec of %s failed: %s\n",
973 restart_args[0], strerror(errno));
974 exit(EXIT_FAILURE);
976 else
977 fprintf(stderr, "Unable to restart wmbiff: missing restart arguments (NULL)!\n");
980 extern int x_socket(void)
982 return ConnectionNumber(display);
984 extern void ProcessPendingEvents(void)
986 static int but_pressed_region = -1; /* static so click can be determined */
987 int but_released_region = -1;
988 /* X Events */
989 while (XPending(display)) {
990 XEvent Event;
991 const char *press_action;
993 XNextEvent(display, &Event);
995 switch (Event.type) {
996 case Expose:
997 if (Event.xany.window != win && Event.xany.window != iconwin) {
998 msglst_redraw();
999 } else {
1000 RedrawWindow();
1002 break;
1003 case DestroyNotify:
1004 XCloseDisplay(display);
1005 exit(EXIT_SUCCESS);
1006 break;
1007 case ButtonPress:
1008 but_pressed_region =
1009 CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
1010 switch (Event.xbutton.button) {
1011 case 1:
1012 press_action = mbox[but_pressed_region].action;
1013 break;
1014 case 2:
1015 press_action = mbox[but_pressed_region].button2;
1016 break;
1017 case 3:
1018 press_action = mbox[but_pressed_region].fetchcmd;
1019 break;
1020 default:
1021 press_action = NULL;
1022 break;
1025 if (press_action && strcmp(press_action, "msglst") == 0) {
1026 msglst_show(&mbox[but_pressed_region],
1027 Event.xbutton.x_root, Event.xbutton.y_root);
1029 break;
1030 case ButtonRelease:
1031 but_released_region =
1032 CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
1033 if (but_released_region == but_pressed_region
1034 && but_released_region >= 0) {
1035 const char *click_action, *extra_click_action = NULL;
1037 switch (Event.xbutton.button) {
1038 case 1: /* Left mouse-click */
1039 /* C-S-left will restart wmbiff. */
1040 if ((Event.xbutton.state & ControlMask) &&
1041 (Event.xbutton.state & ShiftMask)) {
1042 restart_wmbiff(0);
1044 /* do we need to run an extra action? */
1045 if (mbox[but_released_region].UnreadMsgs == -1) {
1046 extra_click_action =
1047 mbox[but_released_region].actiondc;
1048 } else if (mbox[but_released_region].UnreadMsgs > 0) {
1049 extra_click_action =
1050 mbox[but_released_region].actionnew;
1051 } else {
1052 extra_click_action =
1053 mbox[but_released_region].actionnonew;
1055 click_action = mbox[but_released_region].action;
1056 break;
1057 case 2: /* Middle mouse-click */
1058 click_action = mbox[but_released_region].button2;
1059 break;
1060 case 3: /* Right mouse-click */
1061 click_action = mbox[but_released_region].fetchcmd;
1062 break;
1063 default:
1064 click_action = NULL;
1065 break;
1067 if (extra_click_action != NULL
1068 && extra_click_action[0] != 0
1069 && strcmp(extra_click_action, "msglst")) {
1070 DM(&mbox[but_released_region], DEBUG_INFO,
1071 "runing: %s", extra_click_action);
1072 (void) execCommand(extra_click_action);
1074 if (click_action != NULL
1075 && click_action[0] != '\0'
1076 && strcmp(click_action, "msglst")) {
1077 DM(&mbox[but_released_region], DEBUG_INFO,
1078 "running: %s", click_action);
1079 (void) execCommand(click_action);
1083 /* a button was released, hide the message list if open */
1084 msglst_hide();
1086 but_pressed_region = -1;
1087 /* RedrawWindow(); */
1088 break;
1089 case MotionNotify:
1090 break;
1091 case KeyPress:{
1092 XKeyPressedEvent *xkpe = (XKeyPressedEvent *) & Event;
1093 KeySym ks = XkbKeycodeToKeysym(display, xkpe->keycode, 0, 0);
1094 if (ks > XK_0 && ks < XK_0 + min(9U, num_mailboxes)) {
1095 const char *click_action = mbox[ks - XK_1].action;
1096 if (click_action != NULL
1097 && click_action[0] != '\0'
1098 && strcmp(click_action, "msglst")) {
1099 DM(&mbox[but_released_region], DEBUG_INFO,
1100 "running: %s", click_action);
1101 (void) execCommand(click_action);
1106 break;
1107 default:
1108 break;
1113 static void do_biff(int argc, const char **argv)
1115 unsigned int i;
1116 int Sleep_Interval;
1117 const char **skin_xpm = NULL;
1118 const char **bkg_xpm = NULL;
1119 char *skin_file_path = search_path(skin_search_path, skin_filename);
1120 int wmbiff_mask_height = mbox_y(num_mailboxes) + 4;
1122 DMA(DEBUG_INFO, "running %u mailboxes w %d h %d\n", num_mailboxes,
1123 wmbiff_mask_width, wmbiff_mask_height);
1125 if (skin_file_path != NULL) {
1126 skin_xpm = (const char **) LoadXPM(skin_file_path);
1127 free(skin_file_path);
1129 if (skin_xpm == NULL) {
1130 DMA(DEBUG_ERROR, "using built-in xpm; %s wasn't found in %s\n",
1131 skin_filename, skin_search_path);
1132 skin_xpm = wmbiff_master_xpm;
1135 bkg_xpm = (const char **) CreateBackingXPM(wmbiff_mask_width, wmbiff_mask_height, skin_xpm);
1137 createXBMfromXPM(wmbiff_mask_bits, bkg_xpm,
1138 wmbiff_mask_width, wmbiff_mask_height);
1140 openXwindow(argc, argv, bkg_xpm, skin_xpm, wmbiff_mask_bits,
1141 wmbiff_mask_width, wmbiff_mask_height, notWithdrawn);
1143 /* now that display is set, we can create the cursors
1144 (mouse pointer shapes) */
1145 busy_cursor = XCreateFontCursor(display, XC_watch);
1146 ready_cursor = XCreateFontCursor(display, XC_left_ptr);
1148 if (font != NULL) {
1149 if (loadFont(font) < 0) {
1150 DMA(DEBUG_ERROR, "unable to load font. exiting.\n");
1151 exit(EXIT_FAILURE);
1155 /* First time setup of button regions and labels */
1156 for (i = 0; i < num_mailboxes; i++) {
1157 /* make it easy to recover the mbox index from a mouse click */
1158 AddMouseRegion(i, x_origin, mbox_y(i), 58, mbox_y(i + 1) - 1);
1159 if (mbox[i].label[0] != '\0') {
1160 mbox[i].prevtime = mbox[i].prevfetch_time = 0;
1161 BlitString(mbox[i].label, x_origin, mbox_y(i), 0);
1165 do {
1167 Sleep_Interval = periodic_mail_check();
1168 ProcessPendingEvents();
1169 XSleep(Sleep_Interval);
1171 while (forever); /* forever is usually true,
1172 but not when debugging with -exit */
1173 if (skin_xpm != NULL && skin_xpm != wmbiff_master_xpm) {
1174 free(skin_xpm); // added 3 jul 02, appeasing valgrind
1176 if (bkg_xpm != NULL) {
1177 // Allocated in CreateBackingXPM()
1178 free((void *)bkg_xpm[0]);
1179 free((void *)bkg_xpm[2]);
1180 int mem_block;
1181 for (mem_block = 6; mem_block < 6 + wmbiff_mask_height; mem_block++)
1182 free((void *)bkg_xpm[mem_block]);
1183 free(bkg_xpm);
1187 static void sigchld_handler(int sig
1188 #ifdef HAVE___ATTRIBUTE__
1189 __attribute__ ((unused))
1190 #endif
1193 while (waitpid(0, NULL, WNOHANG) > 0);
1194 signal(SIGCHLD, sigchld_handler);
1197 static void usage(void)
1199 printf("\nwmBiff v%s"
1200 " - incoming mail checker\n"
1201 "Gennady Belyakov and others (see the README file)\n"
1202 "Please report bugs to %s\n"
1203 "\n"
1204 "usage:\n"
1205 " -bg <color> background color\n"
1206 " -c <filename> use specified config file\n"
1207 " -debug enable debugging\n"
1208 " -display <display name> use specified X display\n"
1209 " -fg <color> foreground color\n"
1210 " -font <font> font instead of LED\n"
1211 " -geometry +XPOS+YPOS initial window position\n"
1212 " -h this help screen\n"
1213 " -hi <color> highlight color for new mail\n"
1214 #ifdef USE_GNUTLS
1215 " -skip-certificate-check using TLS, don't validate the\n"
1216 " server's certificate\n"
1217 #endif
1218 " -relax assume the configuration is \n"
1219 " correct, parse it without paranoia, \n"
1220 " and assume hostnames are okay.\n"
1221 " -v print the version number\n"
1222 " +w not withdrawn: run as a window\n"
1223 "\n", PACKAGE_VERSION, PACKAGE_BUGREPORT);
1226 static void printversion(void)
1228 printf("wmbiff v%s\n", PACKAGE_VERSION);
1232 static void parse_cmd(int argc, const char **argv, char *config_file)
1234 int i;
1236 config_file[0] = '\0';
1238 /* Parse Command Line */
1240 for (i = 1; i < argc; i++) {
1241 const char *arg = argv[i];
1243 if (*arg == '-') {
1244 switch (arg[1]) {
1245 case 'b':
1246 if (strcmp(arg + 1, "bg") == 0) {
1247 if (argc > (i + 1)) {
1248 background = strdup_ordie(argv[i + 1]);
1249 DMA(DEBUG_INFO, "new background: %s", foreground);
1250 i++;
1251 if (font == NULL)
1252 font = DEFAULT_FONT;
1255 break;
1256 case 'd':
1257 if (strcmp(arg + 1, "debug") == 0) {
1258 debug_default = DEBUG_ALL;
1259 } else if (strcmp(arg + 1, "display") == 0) {
1260 /* passed to X's command line parser */
1261 } else {
1262 usage();
1263 exit(EXIT_FAILURE);
1265 break;
1266 case 'f':
1267 if (strcmp(arg + 1, "fg") == 0) {
1268 if (argc > (i + 1)) {
1269 foreground = strdup_ordie(argv[i + 1]);
1270 DMA(DEBUG_INFO, "new foreground: %s", foreground);
1271 i++;
1272 if (font == NULL)
1273 font = DEFAULT_FONT;
1275 } else if (strcmp(arg + 1, "font") == 0) {
1276 if (argc > (i + 1)) {
1277 if (strcmp(argv[i + 1], "default") == 0) {
1278 font = DEFAULT_FONT;
1279 } else {
1280 font = strdup_ordie(argv[i + 1]);
1282 DMA(DEBUG_INFO, "new font: %s", font);
1283 i++;
1285 } else {
1286 usage();
1287 exit(EXIT_FAILURE);
1289 break;
1290 case 'g':
1291 if (strcmp(arg + 1, "geometry") != 0) {
1292 usage();
1293 exit(EXIT_FAILURE);
1294 } else {
1295 i++; /* gobble the argument */
1296 if (i >= argc) { /* fail if there's nothing to gobble */
1297 usage();
1298 exit(EXIT_FAILURE);
1301 break;
1302 case 'h':
1303 if (strcmp(arg + 1, "hi") == 0) {
1304 if (argc > (i + 1)) {
1305 highlight = strdup_ordie(argv[i + 1]);
1306 DMA(DEBUG_INFO, "new highlight: %s", highlight);
1307 i++;
1308 if (font == NULL)
1309 font = DEFAULT_FONT;
1311 } else if (strcmp(arg + 1, "h") == 0) {
1312 usage();
1313 exit(EXIT_SUCCESS);
1315 break;
1316 case 'v':
1317 printversion();
1318 exit(EXIT_SUCCESS);
1319 break;
1320 case 's':
1321 if (strcmp(arg + 1, "skip-certificate-check") == 0) {
1322 SkipCertificateCheck = 1;
1323 } else {
1324 usage();
1325 exit(EXIT_SUCCESS);
1328 break;
1329 case 'r':
1330 if (strcmp(arg + 1, "relax") == 0) {
1331 Relax = 1;
1332 } else {
1333 usage();
1334 exit(EXIT_SUCCESS);
1337 break;
1338 case 'c':
1339 if (argc > (i + 1)) {
1340 strncpy(config_file, argv[i + 1], 255);
1341 i++;
1343 break;
1344 case 'e': /* undocumented for debugging */
1345 if (strcmp(arg + 1, "exit") == 0) {
1346 forever = 0;
1348 break;
1349 default:
1350 usage();
1351 exit(EXIT_SUCCESS);
1352 break;
1354 } else if (*arg == '+') {
1355 switch (arg[1]) {
1356 case 'w':
1357 notWithdrawn = 1;
1358 break;
1359 default:
1360 usage();
1361 exit(EXIT_SUCCESS);
1362 break;
1368 int main(int argc, const char *argv[])
1370 char uconfig_file[256];
1372 /* hold on to the arguments we were started with; we
1373 will need them if we have to restart on sigusr1 */
1374 restart_args =
1375 (const char **) malloc((argc + 1) * sizeof(const char *));
1376 if (restart_args) {
1377 memcpy(restart_args, argv, (argc) * sizeof(const char *));
1378 restart_args[argc] = NULL;
1381 parse_cmd(argc, argv, uconfig_file);
1383 /* decide what the config file is */
1384 if (uconfig_file[0] != '\0') { /* user-specified config file */
1385 DMA(DEBUG_INFO, "Using user-specified config file '%s'.\n",
1386 uconfig_file);
1387 } else {
1388 const char *home = getenv("HOME");
1389 if (home == NULL) {
1390 DMA(DEBUG_ERROR,
1391 "$HOME undefined. Use the -c option to specify a wmbiffrc\n");
1392 exit(EXIT_FAILURE);
1394 sprintf(uconfig_file, "%s/.wmbiffrc", home);
1397 if (wmbiffrc_permissions_check(uconfig_file) == 0) {
1398 DMA(DEBUG_ERROR,
1399 "WARNING: In future versions of WMBiff, .wmbiffrc MUST be\n"
1400 "owned by the user, and not readable or writable by others.\n\n");
1402 init_biff(uconfig_file);
1403 signal(SIGCHLD, sigchld_handler);
1404 signal(SIGUSR1, restart_wmbiff);
1405 signal(SIGPIPE, SIG_IGN); /* write() may fail */
1407 do_biff(argc, argv);
1409 // free resources
1410 if (restart_args)
1411 free(restart_args);
1412 if (custom_skin)
1413 free((void *)skin_filename);
1415 return 0;