1 /* $Id: wmbiff.c,v 1.70 2005/10/07 03:07:58 bluehal Exp $ */
3 // typedef int gcry_error_t;
19 #include <sys/types.h>
25 #include <X11/cursorfont.h>
26 #include <X11/keysym.h>
31 #include "../wmgeneral/wmgeneral.h"
32 #include "../wmgeneral/misc.h"
36 #include "MessageList.h"
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;
52 #define DEFAULT_SLEEP_INTERVAL 5000
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) */
63 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
;
72 const char *certificate_filename
= NULL
;
74 /* it could be argued that a better default exists. */
75 #define DEFAULT_FONT "-*-fixed-*-r-*-*-10-*-*-*-*-*-*-*"
77 static const char *font
= NULL
;
79 int debug_default
= DEBUG_ERROR
;
81 /* color from wmbiff's xpm, down to 24 bits. */
82 const char *foreground
= "#21B3AF"; /* foreground cyan */
83 const char *background
= "#202020"; /* background gray */
84 static const char *highlight
= "yellow";
85 int SkipCertificateCheck
= 0;
86 int Relax
= 0; /* be not paranoid */
87 static int notWithdrawn
= 0;
89 static unsigned int num_mailboxes
= 1;
90 static const int x_origin
= 5;
91 static const int y_origin
= 5;
92 static int forever
= 1; /* keep running. */
95 extern Window iconwin
;
97 Cursor busy_cursor
, ready_cursor
;
99 static __inline
/*@out@ */ void *
100 malloc_ordie(size_t len
)
102 void *ret
= malloc(len
);
104 fprintf(stderr
, "unable to allocate %d bytes\n", (int) len
);
110 /* where vertically the mailbox sits for blitting characters. */
111 static int mbox_y(unsigned int mboxnum
)
113 return ((11 * mboxnum
) + y_origin
);
116 /* special shortcuts for longer shell client commands */
117 static int gicuCreate( /*@notnull@ */ Pop3 pc
, const char *path
)
120 if (isdigit(path
[5])) {
122 "shell:::echo `gnomeicu-client -u%s msgcount` new",
125 sprintf(buf
, "shell:::echo `gnomeicu-client msgcount` new");
127 return (shellCreate(pc
, buf
));
130 static int fingerCreate( /*@notnull@ */ Pop3 pc
, const char *path
)
133 sprintf(buf
, "shell:::finger -lm %s | "
134 "perl -ne '(/^new mail/i && print \"new\");' "
135 "-e '(/^mail last read/i && print \"old\");' "
136 "-e '(/^no( unread)? mail/i && print \"no\");'", path
+ 7);
137 return (shellCreate(pc
, buf
));
140 /* Read a line from a file to obtain a pair setting=value
141 skips # and leading spaces
142 NOTE: if setting finish with 0, 1, 2, 3 or 4 last char are deleted and
143 index takes its value... if not index will get -1
144 Returns -1 if no setting=value
146 static int ReadLine(FILE * fp
, /*@out@ */ char *setting
,
147 /*@out@ */ char *value
, /*@out@ */ int *mbox_index
)
160 if (!fgets(buf
, BUF_SIZE
- 1, fp
))
165 if (buf
[len
- 1] == '\n') {
166 buf
[len
- 1] = '\0'; /* strip linefeed */
171 if (!(p
= strtok(buf
, "=")))
173 if (!(q
= strtok(NULL
, "\n")))
178 * Removed for loop (which removed leading spaces)
179 * Leading & Trailing spaces need to be removed
180 * to Fix Debian bug #95849
185 /* strcpy(setting, p); nspring replaced with sscanf dec 2002 */
188 if (sscanf(p
, "%[_a-z.]%d", setting
, mbox_index
) == 2) {
189 /* mailbox-specific configuration, ends in a digit */
190 if (*mbox_index
< 0 || *mbox_index
>= MAX_NUM_MAILBOXES
) {
191 DMA(DEBUG_ERROR
, "invalid mailbox number %d\n", *mbox_index
);
194 } else if (sscanf(p
, "%[a-z]", setting
) == 1) {
195 /* global configuration, all text. */
198 /* we found an uncommented line that has an equals,
199 but is non-alphabetic. */
200 DMA(DEBUG_INFO
, "unparsed setting %s\n", p
);
204 DMA(DEBUG_INFO
, "@%s.%d=%s@\n", setting
, *mbox_index
, value
);
208 struct path_demultiplexer
{
209 const char *id
; /* followed by a colon */
210 int (*creator
) ( /*@notnull@ */ Pop3 pc
, const char *path
);
213 static struct path_demultiplexer paths
[] = {
214 {"pop3:", pop3Create
},
215 {"pop3s:", pop3Create
},
216 {"shell:", shellCreate
},
217 {"gicu:", gicuCreate
},
218 {"licq:", licqCreate
},
219 {"finger:", fingerCreate
},
220 {"imap:", imap4Create
},
221 {"imaps:", imap4Create
},
222 {"sslimap:", imap4Create
},
223 {"maildir:", maildirCreate
},
224 {"mbox:", mboxCreate
},
229 static void parse_mbox_path(unsigned int item
)
232 /* find the creator */
235 && strncasecmp(mbox
[item
].path
, paths
[i
].id
, strlen(paths
[i
].id
));
237 /* if found, execute */
238 if (paths
[i
].id
!= NULL
) {
239 if (paths
[i
].creator((&mbox
[item
]), mbox
[item
].path
) != 0) {
240 DMA(DEBUG_ERROR
, "creator for mailbox %u returned failure\n",
244 /* default are mbox */
245 mboxCreate((&mbox
[item
]), mbox
[item
].path
);
249 static int Read_Config_File(char *filename
, int *loopinterval
)
252 char setting
[BUF_SMALL
], value
[BUF_SIZE
];
256 if (!(fp
= fopen(filename
, "r"))) {
257 DMA(DEBUG_ERROR
, "Unable to open %s, no settings read: %s\n",
258 filename
, strerror(errno
));
262 /* skanky: -1 can represent an unparsed line
264 if (ReadLine(fp
, setting
, value
, &mbox_index
) == -1)
267 /* settings that can be global go here. */
268 if (!strcmp(setting
, "interval")) {
269 *loopinterval
= atoi(value
);
271 } else if (!strcmp(setting
, "askpass")) {
272 const char *askpass
= strdup_ordie(value
);
273 if (mbox_index
== -1) {
274 DMA(DEBUG_INFO
, "setting all to askpass %s\n", askpass
);
275 for (i
= 0; i
< MAX_NUM_MAILBOXES
; i
++)
276 mbox
[i
].askpass
= askpass
;
278 mbox
[mbox_index
].askpass
= askpass
;
281 } else if (!strcmp(setting
, "skinfile")) {
282 skin_filename
= strdup_ordie(value
);
284 } else if (!strcmp(setting
, "certfile")) { /* not yet supported */
285 certificate_filename
= strdup_ordie(value
);
287 } else if (!strcmp(setting
, "globalnotify")) {
288 globalnotify
= strdup_ordie(value
);
290 } else if (mbox_index
== -1) {
291 DMA(DEBUG_INFO
, "Unknown global setting '%s'\n", setting
);
292 continue; /* Didn't read any setting.[0-5] value */
295 if (mbox_index
>= MAX_NUM_MAILBOXES
) {
296 DMA(DEBUG_ERROR
, "Don't have %d mailboxes.\n", mbox_index
);
300 if (1U + mbox_index
> num_mailboxes
301 && mbox_index
+ 1 <= MAX_NUM_MAILBOXES
) {
302 num_mailboxes
= 1U + mbox_index
;
305 /* now only local settings */
306 if (!strcmp(setting
, "label.")) {
307 if (strlen(value
) + 1 > BUF_SMALL
) {
309 "Mailbox %i label string '%s' is too long.\n",
313 strncpy(mbox
[mbox_index
].label
, value
, BUF_SMALL
- 1);
315 } else if (!strcmp(setting
, "path.")) {
316 if (strlen(value
) + 1 > BUF_BIG
) {
318 "Mailbox %i path string '%s' is too long.\n",
322 strncpy(mbox
[mbox_index
].path
, value
, BUF_BIG
- 1);
324 } else if (!strcmp(setting
, "notify.")) {
325 if (strlen(value
) + 1 > BUF_BIG
) {
327 "Mailbox %i notify string '%s' is too long.\n",
331 strncpy(mbox
[mbox_index
].notify
, value
, BUF_BIG
- 1);
333 } else if (!strcmp(setting
, "action.")) {
334 if (strlen(value
) + 1 > BUF_BIG
) {
336 "Mailbox %i action string '%s' is too long.\n",
340 strncpy(mbox
[mbox_index
].action
, value
, BUF_BIG
- 1);
342 } else if (!strcmp(setting
, "action_disconnected.")) {
343 if (strlen(value
) + 1 > BUF_BIG
) {
345 "Mailbox %i action_disconnected string '%s' is too long.\n",
349 strncpy(mbox
[mbox_index
].actiondc
, value
, BUF_BIG
- 1);
351 } else if (!strcmp(setting
, "action_new_mail.")) {
352 if (strlen(value
) + 1 > BUF_BIG
) {
354 "Mailbox %i action_new_mail string '%s' is too long.\n",
358 strncpy(mbox
[mbox_index
].actionnew
, value
, BUF_BIG
- 1);
360 } else if (!strcmp(setting
, "action_no_new_mail.")) {
361 if (strlen(value
) + 1 > BUF_BIG
) {
363 "Mailbox %i action_no_new_mail string '%s' is too long.\n",
367 strncpy(mbox
[mbox_index
].actionnonew
, value
, BUF_BIG
- 1);
369 } else if (!strcmp(setting
, "interval.")) {
370 mbox
[mbox_index
].loopinterval
= atoi(value
);
371 } else if (!strcmp(setting
, "buttontwo.")) {
372 if (strlen(value
) + 1 > BUF_BIG
) {
374 "Mailbox %i buttontwo string '%s' is too long.\n",
378 strncpy(mbox
[mbox_index
].button2
, value
, BUF_BIG
- 1);
380 } else if (!strcmp(setting
, "fetchcmd.")) {
381 if (strlen(value
) + 1 > BUF_BIG
) {
383 "Mailbox %i fetchcmd string '%s' is too long.\n",
387 strncpy(mbox
[mbox_index
].fetchcmd
, value
, BUF_BIG
- 1);
389 } else if (!strcmp(setting
, "fetchinterval.")) {
390 mbox
[mbox_index
].fetchinterval
= atoi(value
);
391 } else if (!strcmp(setting
, "debug.")) {
392 int debug_value
= debug_default
;
393 if (strcasecmp(value
, "all") == 0) {
394 debug_value
= DEBUG_ALL
;
396 /* could disable debugging, but I want the command
397 line argument to provide all information
399 mbox
[mbox_index
].debug
= debug_value
;
401 DMA(DEBUG_INFO
, "Unknown setting '%s'\n", setting
);
405 for (i
= 0; i
< num_mailboxes
; i
++)
406 if (mbox
[i
].label
[0] != '\0')
412 static void init_biff(char *config_file
)
417 int loopinterval
= DEFAULT_LOOP
;
420 for (i
= 0; i
< MAX_NUM_MAILBOXES
; i
++) {
421 memset(mbox
[i
].label
, 0, BUF_SMALL
);
422 memset(mbox
[i
].path
, 0, BUF_BIG
);
423 memset(mbox
[i
].notify
, 0, BUF_BIG
);
424 memset(mbox
[i
].action
, 0, BUF_BIG
);
425 memset(mbox
[i
].actiondc
, 0, BUF_BIG
);
426 memset(mbox
[i
].actionnew
, 0, BUF_BIG
);
427 memset(mbox
[i
].actionnonew
, 0, BUF_BIG
);
428 memset(mbox
[i
].button2
, 0, BUF_BIG
);
429 memset(mbox
[i
].fetchcmd
, 0, BUF_BIG
);
430 mbox
[i
].loopinterval
= 0;
431 mbox
[i
].getHeaders
= NULL
;
432 mbox
[i
].releaseHeaders
= NULL
;
433 mbox
[i
].debug
= debug_default
;
434 mbox
[i
].askpass
= DEFAULT_ASKPASS
;
438 /* gcrypt is a little strange, in that it doesn't
439 * seem to initialize its memory pool by itself.
440 * I believe we *expect* "Warning: using insecure memory!"
442 /* gcryctl_disable_secmem gets called before check_version -- in a message on
443 gcrypt-devel august 17 2004. */
444 if ((rc
= gcry_control(GCRYCTL_DISABLE_SECMEM
, 0)) != 0) {
446 "Error: tried to disable gcrypt warning and failed: %d\n"
447 " Message: %s\n" " libgcrypt version: %s\n", rc
,
448 gcry_strerror(rc
), gcry_check_version(NULL
));
451 /* recently made a requirement, section 2.4 of gcrypt manual */
452 if (gcry_check_version("1.1.42") == NULL
) {
453 DMA(DEBUG_ERROR
, "Error: incompatible gcrypt version.\n");
457 if ((rc = gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0)) != 0) {
459 "Error: gcry_control() to initialize secure memory returned non-zero: %d\n"
461 " libgcrypt version: %s\n"
462 " recovering: will fail later if using CRAM-MD5 or APOP authentication.\n",
463 rc, gcry_strerror(rc), gcry_check_version(NULL));
468 DMA(DEBUG_INFO
, "config_file = %s.\n", config_file
);
469 if (!Read_Config_File(config_file
, &loopinterval
)) {
471 /* setup defaults if there's no config */
472 if ((m
= getenv("MAIL")) != NULL
) {
473 /* we are using MAIL environment var. type mbox */
474 if (strlen(m
) + 1 > BUF_BIG
) {
476 "MAIL environment var '%s' is too long.\n", m
);
478 DMA(DEBUG_INFO
, "Using MAIL environment var '%s'.\n", m
);
479 strncpy(mbox
[0].path
, m
, BUF_BIG
- 1);
481 } else if ((m
= getenv("USER")) != NULL
) {
482 /* we will use the USER env var to find an mbox name */
483 if (strlen(m
) + 10 + 1 > BUF_BIG
) {
485 "USER environment var '%s' is too long.\n", m
);
487 DMA(DEBUG_INFO
, "Using /var/mail/%s.\n", m
);
488 strcpy(mbox
[0].path
, "/var/mail/");
489 strncat(mbox
[0].path
, m
, BUF_BIG
- 1 - 10);
490 if (mbox
[0].path
[9] != '/') {
492 "Unexpected failure to construct /var/mail/username, please "
493 "report this with your operating system info and the version of wmbiff.");
497 DMA(DEBUG_ERROR
, "Cannot open config file '%s' nor use the "
498 "MAIL environment var.\n", config_file
);
501 if (!exists(mbox
[0].path
)) {
502 DMA(DEBUG_ERROR
, "Cannot open config file '%s', and the "
503 "default %s doesn't exist.\n", config_file
, mbox
[0].path
);
506 strcpy(mbox
[0].label
, "Spool");
507 mboxCreate((&mbox
[0]), mbox
[0].path
);
510 /* Make labels look right */
511 for (i
= 0; i
< num_mailboxes
; i
++) {
512 if (mbox
[i
].label
[0] != '\0') {
513 /* append a colon, but skip if we're using fonts. */
515 int j
= strlen(mbox
[i
].label
);
517 memset(mbox
[i
].label
+ j
, ' ', 5 - j
);
519 mbox
[i
].label
[5] = ':';
521 /* but always end after 5 characters */
522 mbox
[i
].label
[6] = '\0';
523 /* set global loopinterval to boxes with 0 loop */
524 if (!mbox
[i
].loopinterval
) {
525 mbox
[i
].loopinterval
= loopinterval
;
531 static char **LoadXPM(const char *pixmap_filename
)
535 success
= XpmReadFileToData((char *) pixmap_filename
, &xpm
);
538 DMA(DEBUG_ERROR
, "Unable to open %s\n", pixmap_filename
);
541 DMA(DEBUG_ERROR
, "%s is not a valid pixmap\n", pixmap_filename
);
544 DMA(DEBUG_ERROR
, "Insufficient memory to read %s\n",
553 /* tests as "test -f" would */
554 int exists(const char *filename
)
557 DMA(DEBUG_INFO
, "looking for %s\n", filename
);
558 if (stat(filename
, &st_buf
) == 0 && S_ISREG(st_buf
.st_mode
)) {
559 DMA(DEBUG_INFO
, "found %s\n", filename
);
565 /* acts like execvp, with code inspired by it */
567 static char *search_path(const char *path
, /*@notnull@ */
573 if (strchr(find_me
, '/') != NULL
) {
574 return (strdup_ordie(find_me
));
576 pathlen
= strlen(path
);
577 len
= strlen(find_me
) + 1;
578 buf
= malloc_ordie(pathlen
+ len
+ 1);
579 memcpy(buf
+ pathlen
+ 1, find_me
, len
);
582 for (p
= path
; p
!= NULL
; path
= p
, path
++) {
584 p
= strchr(path
, ':');
586 /* not found; p should point to the null char at the end */
588 memcpy(buf
+ pathlen
- strlen(path
), path
, strlen(path
));
589 } else if (p
== path
) {
590 /* double colon in a path apparently means try here */
591 startp
= &buf
[pathlen
+ 1];
593 /* copy the part between the colons to the buffer */
594 startp
= memcpy(buf
+ pathlen
- (p
- path
), path
, p
- path
);
596 if (exists(startp
) != 0) {
597 char *ret
= strdup_ordie(startp
);
606 /* verifies that .wmbiffrc, is a file, is owned by the user,
607 is not world writeable, and is not world readable. This
608 is just to help keep passwords secure */
609 static int wmbiffrc_permissions_check(const char *wmbiffrc_fname
)
612 if (stat(wmbiffrc_fname
, &st
) != 0) {
613 DMA(DEBUG_ERROR
, "Can't stat wmbiffrc: '%s'\n", wmbiffrc_fname
);
614 return (1); /* well, it's not a bad permission
615 problem: if you can't find it,
616 neither can the bad guys.. */
618 if ((st
.st_mode
& S_IFDIR
) != 0) {
619 DMA(DEBUG_ERROR
, ".wmbiffrc '%s' is a directory!\n"
620 "exiting. don't do that.", wmbiffrc_fname
);
623 if (st
.st_uid
!= getuid()) {
624 char *user
= getenv("USER");
626 ".wmbiffrc '%s' isn't owned by you.\n"
627 "Verify its contents, then 'chown %s %s'\n",
628 wmbiffrc_fname
, ((user
!= NULL
) ? user
: "(your username)"),
632 if ((st
.st_mode
& S_IWOTH
) != 0) {
633 DMA(DEBUG_ERROR
, ".wmbiffrc '%s' is world writable.\n"
634 "Verify its contents, then 'chmod 0600 %s'\n",
635 wmbiffrc_fname
, wmbiffrc_fname
);
638 if ((st
.st_mode
& S_IROTH
) != 0) {
639 DMA(DEBUG_ERROR
, ".wmbiffrc '%s' is world readable.\n"
640 "Please run 'chmod 0600 %s' and consider changing your passwords.\n",
641 wmbiffrc_fname
, wmbiffrc_fname
);
647 static void ClearDigits(unsigned int i
)
650 eraseRect(39, mbox_y(i
), 58, mbox_y(i
+ 1) - 1, background
);
652 /* overwrite the colon */
653 copyXPMArea((10 * (CHAR_WIDTH
+ 1)), 64, (CHAR_WIDTH
+ 1),
654 (CHAR_HEIGHT
+ 1), 35, mbox_y(i
));
655 /* blank out the number fields. */
656 copyXPMArea(39, 84, (3 * (CHAR_WIDTH
+ 1)), (CHAR_HEIGHT
+ 1), 39,
661 /* Blits a string at given co-ordinates. If a ``new''
662 parameter is nonzero, all digits will be yellow */
663 static void BlitString(const char *name
, int x
, int y
, int new)
666 /* an alternate behavior - draw the string using a font
667 instead of the pixmap. should allow pretty colors */
668 drawString(x
, y
+ CHAR_HEIGHT
+ 1, name
,
669 new ? highlight
: foreground
, background
, 0);
671 /* normal, LED-like behavior. */
673 for (i
= 0; name
[i
] != '\0'; i
++) {
674 c
= toupper(name
[i
]);
675 if (c
>= 'A' && c
<= 'Z') { /* it's a letter */
677 copyXPMArea(c
* (CHAR_WIDTH
+ 1), (new ? 95 : 74),
678 (CHAR_WIDTH
+ 1), (CHAR_HEIGHT
+ 1), k
, y
);
679 k
+= (CHAR_WIDTH
+ 1);
680 } else { /* it's a number or symbol */
683 copyXPMArea((c
* (CHAR_WIDTH
+ 1)) + 65, 0,
684 (CHAR_WIDTH
+ 1), (CHAR_HEIGHT
+ 1), k
, y
);
686 copyXPMArea((c
* (CHAR_WIDTH
+ 1)), 64,
687 (CHAR_WIDTH
+ 1), (CHAR_HEIGHT
+ 1), k
, y
);
689 k
+= (CHAR_WIDTH
+ 1);
696 /* Blits number to give coordinates.. two 0's, right justified */
697 static void BlitNum(int num
, int x
, int y
, int new)
701 sprintf(buf
, "%02i", num
);
704 const char *color
= (new) ? highlight
: foreground
;
705 drawString(x
+ (CHAR_WIDTH
* 2 + 4), y
+ CHAR_HEIGHT
+ 1, buf
,
706 color
, background
, 1);
711 newx
-= (CHAR_WIDTH
+ 1);
713 newx
-= (CHAR_WIDTH
+ 1);
715 BlitString(buf
, newx
, y
, new);
719 /* helper function for displayMsgCounters, which has outgrown its name */
720 static void blitMsgCounters(unsigned int i
)
722 int y_row
= mbox_y(i
); /* constant for each mailbox */
723 ClearDigits(i
); /* Clear digits */
724 if ((mbox
[i
].blink_stat
& 0x01) == 0) {
725 int newmail
= (mbox
[i
].UnreadMsgs
> 0) ? 1 : 0;
726 if (mbox
[i
].TextStatus
[0] != '\0') {
727 BlitString(mbox
[i
].TextStatus
, 39, y_row
, newmail
);
730 (newmail
) ? mbox
[i
].UnreadMsgs
: mbox
[i
].TotalMsgs
;
731 BlitNum(mailcount
, 45, y_row
, newmail
);
737 * void execnotify(1) : runs notify command, if given (not null)
739 static void execnotify( /*@null@ */ const char *notifycmd
)
741 if (notifycmd
!= NULL
) { /* need to call notify() ? */
742 if (!strcasecmp(notifycmd
, "beep")) { /* Internal keyword ? */
745 } else if (!strcasecmp(notifycmd
, "true")) {
748 /* Else call external notifyer, ignoring the pid */
749 (void) execCommand(notifycmd
);
756 displayMsgCounters(unsigned int i
, int mail_stat
, int *Blink_Mode
)
759 case 2: /* New mail has arrived */
760 /* Enter blink-mode for digits */
761 mbox
[i
].blink_stat
= BLINK_TIMES
* 2;
762 *Blink_Mode
|= (1 << i
); /* Global blink flag set for this mailbox */
764 execnotify(mbox
[i
].notify
);
766 /* Autofetch on new mail arrival? */
767 if (mbox
[i
].fetchinterval
== -1 && mbox
[i
].fetchcmd
[0] != '\0') {
768 (void) execCommand(mbox
[i
].fetchcmd
); /* yes */
771 case 1: /* mailbox has been rescanned/changed */
776 case -1: /* Error was detected */
777 ClearDigits(i
); /* Clear digits */
778 BlitString("XX", 45, mbox_y(i
), 0);
783 /** counts mail in spool-file
785 -1 : Error was encountered
786 0 : mailbox status wasn't changed
787 1 : mailbox was changed (NO new mail)
788 2 : mailbox was changed AND new mail has arrived
790 static int count_mail(unsigned int item
)
794 if (!mbox
[item
].checkMail
) {
798 if (mbox
[item
].checkMail(&(mbox
[item
])) < 0) {
799 /* we failed to obtain any numbers therefore set
800 * them to -1's ensuring the next pass (even if
801 * zero) will be captured correctly
803 mbox
[item
].TotalMsgs
= -1;
804 mbox
[item
].UnreadMsgs
= -1;
805 mbox
[item
].OldMsgs
= -1;
806 mbox
[item
].OldUnreadMsgs
= -1;
810 if (mbox
[item
].UnreadMsgs
> mbox
[item
].OldUnreadMsgs
&&
811 mbox
[item
].UnreadMsgs
> 0) {
812 rc
= 2; /* New mail detected */
813 } else if (mbox
[item
].UnreadMsgs
< mbox
[item
].OldUnreadMsgs
||
814 mbox
[item
].TotalMsgs
!= mbox
[item
].OldMsgs
) {
815 rc
= 1; /* mailbox was changed - NO new mail */
817 rc
= 0; /* mailbox wasn't changed */
819 mbox
[item
].OldMsgs
= mbox
[item
].TotalMsgs
;
820 mbox
[item
].OldUnreadMsgs
= mbox
[item
].UnreadMsgs
;
824 static int periodic_mail_check(void)
827 static int Blink_Mode
= 0; /* Bit mask, digits are in blinking
828 mode or not. Each bit for separate
830 int Sleep_Interval
; /* either DEFAULT_SLEEP_INTERVAL or
831 BLINK_SLEEP_INTERVAL */
832 int NewMail
= 0; /* flag for global notify */
834 time_t curtime
= time(0);
835 for (i
= 0; i
< num_mailboxes
; i
++) {
836 if (mbox
[i
].label
[0] != '\0') {
837 if (curtime
>= mbox
[i
].prevtime
+ mbox
[i
].loopinterval
) {
840 DM(&mbox
[i
], DEBUG_INFO
,
841 "working on [%u].label=>%s< [%u].path=>%s<\n", i
,
842 mbox
[i
].label
, i
, mbox
[i
].path
);
843 DM(&mbox
[i
], DEBUG_INFO
,
844 "curtime=%d, prevtime=%d, interval=%d\n",
845 (int) curtime
, (int) mbox
[i
].prevtime
,
846 mbox
[i
].loopinterval
);
847 mbox
[i
].prevtime
= curtime
;
849 XDefineCursor(display
, iconwin
, busy_cursor
);
852 mailstat
= count_mail(i
);
854 XUndefineCursor(display
, iconwin
);
856 if ((mailstat
== 2) && (mbox
[i
].notify
[0] == '\0')) {
857 /* for global notify */
860 displayMsgCounters(i
, mailstat
, &Blink_Mode
);
861 /* update our idea of current time, as it
862 may have changed as we check. */
865 if (mbox
[i
].blink_stat
> 0) {
866 if (--mbox
[i
].blink_stat
<= 0) {
867 Blink_Mode
&= ~(1 << i
);
868 mbox
[i
].blink_stat
= 0;
870 displayMsgCounters(i
, 1, &Blink_Mode
);
873 if (mbox
[i
].fetchinterval
> 0 && mbox
[i
].fetchcmd
[0] != '\0'
875 mbox
[i
].prevfetch_time
+ mbox
[i
].fetchinterval
) {
877 XDefineCursor(display
, iconwin
, busy_cursor
);
880 (void) execCommand(mbox
[i
].fetchcmd
);
882 XUndefineCursor(display
, iconwin
);
884 mbox
[i
].prevfetch_time
= curtime
;
889 /* exec globalnotify if there was any new mail */
891 execnotify(globalnotify
);
894 if (Blink_Mode
== 0) {
895 for (i
= 0; i
< num_mailboxes
; i
++) {
896 mbox
[i
].blink_stat
= 0;
898 Sleep_Interval
= DEFAULT_SLEEP_INTERVAL
;
900 Sleep_Interval
= BLINK_SLEEP_INTERVAL
;
908 return Sleep_Interval
;
911 static int findTopOfMasterXPM(const char **skin_xpm
)
914 for (i
= 0; skin_xpm
[i
] != NULL
; i
++) {
915 if (strstr(skin_xpm
[i
], "++++++++") != NULL
)
919 "couldn't find the top of the xpm file using the simple method\n");
923 static char **CreateBackingXPM(int width
, int height
,
924 const char **skin_xpm
)
926 char **ret
= malloc_ordie(sizeof(char *) * (height
+ 6)
927 + sizeof(void *) /* trailing null space */ );
928 const int colors
= 5;
929 const int base
= colors
+ 1;
930 const int margin
= 4;
932 int top
= findTopOfMasterXPM(skin_xpm
);
933 ret
[0] = malloc_ordie(30);
934 sprintf(ret
[0], "%d %d %d %d", width
, height
, colors
, 1);
935 ret
[1] = (char *) " \tc #0000FF"; /* no color */
936 ret
[2] = (char *) ".\tc #202020"; /* background gray */
937 ret
[2] = malloc_ordie(30);
938 sprintf(ret
[2], ".\tc %s", background
);
939 ret
[3] = (char *) "+\tc #000000"; /* shadowed */
940 ret
[4] = (char *) "@\tc #C7C3C7"; /* highlight */
941 ret
[5] = (char *) ":\tc #004941"; /* led off */
942 for (i
= base
; i
< base
+ height
; i
++) {
943 ret
[i
] = malloc_ordie(width
);
945 for (i
= base
; i
< base
+ margin
; i
++) {
946 memset(ret
[i
], ' ', width
);
948 for (i
= base
+ margin
; i
< height
+ base
- margin
; i
++) {
949 memset(ret
[i
], ' ', margin
);
951 if (i
== base
+ margin
) {
952 memset(ret
[i
] + margin
, '+', width
- margin
- margin
);
953 } else if (i
== base
+ height
- margin
- 1) {
954 memset(ret
[i
] + margin
, '@', width
- margin
- margin
);
956 // " +..:::...:::...:::...:::...:::.......:::...:::...:::...@ "
957 // " +.:...:.:...:.:...:.:...:.:...:..:..:...:.:...:.:...:..@ " ",
958 ret
[i
][margin
] = '+';
959 memset(ret
[i
] + margin
+ 1, '.', width
- margin
- margin
- 1);
960 ret
[i
][width
- margin
- 1] = '@';
962 skin_xpm
[((i
- (base
+ margin
) - 1) % 11) + top
+ 1],
966 memset(ret
[i
] + width
- margin
, ' ', margin
);
968 for (i
= base
+ height
- margin
; i
< height
+ base
; i
++) {
969 memset(ret
[i
], ' ', width
);
971 ret
[height
+ base
] = NULL
; /* not sure if this is necessary, it just
972 seemed like a good idea */
977 * NOTE: this function assumes that the ConnectionNumber() macro
978 * will return the file descriptor of the Display struct
979 * (it does under XFree86 and solaris' openwin X)
981 static void XSleep(int millisec
)
984 struct pollfd timeout
;
986 timeout
.fd
= ConnectionNumber(display
);
987 timeout
.events
= POLLIN
;
989 poll(&timeout
, 1, millisec
);
992 struct timeval
*timeout
= NULL
;
998 to
.tv_sec
= millisec
/ 1000;
999 to
.tv_usec
= (millisec
% 1000) * 1000;
1002 FD_SET(ConnectionNumber(display
), &readfds
);
1003 max_fd
= ConnectionNumber(display
);
1005 select(max_fd
+ 1, &readfds
, NULL
, NULL
, timeout
);
1009 const char **restart_args
;
1011 static void restart_wmbiff(int sig
1012 #ifdef HAVE___ATTRIBUTE__
1013 __attribute__ ((unused
))
1017 DMA(DEBUG_ERROR
, "exec()'ing %s\n", restart_args
[0]);
1019 execvp(restart_args
[0], (char *const *) restart_args
);
1020 DMA(DEBUG_ERROR
, "exec of %s failed: %s\n",
1021 restart_args
[0], strerror(errno
));
1025 extern int x_socket(void)
1027 return ConnectionNumber(display
);
1029 extern void ProcessPendingEvents(void)
1031 static int but_pressed_region
= -1; /* static so click can be determined */
1032 int but_released_region
= -1;
1034 while (XPending(display
)) {
1036 const char *press_action
;
1038 XNextEvent(display
, &Event
);
1040 switch (Event
.type
) {
1042 if (Event
.xany
.window
!= win
&& Event
.xany
.window
!= iconwin
) {
1049 XCloseDisplay(display
);
1053 but_pressed_region
=
1054 CheckMouseRegion(Event
.xbutton
.x
, Event
.xbutton
.y
);
1055 switch (Event
.xbutton
.button
) {
1057 press_action
= mbox
[but_pressed_region
].action
;
1060 press_action
= mbox
[but_pressed_region
].button2
;
1063 press_action
= mbox
[but_pressed_region
].fetchcmd
;
1066 press_action
= NULL
;
1070 if (press_action
&& strcmp(press_action
, "msglst") == 0) {
1071 msglst_show(&mbox
[but_pressed_region
],
1072 Event
.xbutton
.x_root
, Event
.xbutton
.y_root
);
1076 but_released_region
=
1077 CheckMouseRegion(Event
.xbutton
.x
, Event
.xbutton
.y
);
1078 if (but_released_region
== but_pressed_region
1079 && but_released_region
>= 0) {
1080 const char *click_action
, *extra_click_action
= NULL
;
1082 switch (Event
.xbutton
.button
) {
1083 case 1: /* Left mouse-click */
1084 /* C-S-left will restart wmbiff. */
1085 if ((Event
.xbutton
.state
& ControlMask
) &&
1086 (Event
.xbutton
.state
& ShiftMask
)) {
1089 /* do we need to run an extra action? */
1090 if (mbox
[but_released_region
].UnreadMsgs
== -1) {
1091 extra_click_action
=
1092 mbox
[but_released_region
].actiondc
;
1093 } else if (mbox
[but_released_region
].UnreadMsgs
> 0) {
1094 extra_click_action
=
1095 mbox
[but_released_region
].actionnew
;
1097 extra_click_action
=
1098 mbox
[but_released_region
].actionnonew
;
1100 click_action
= mbox
[but_released_region
].action
;
1102 case 2: /* Middle mouse-click */
1103 click_action
= mbox
[but_released_region
].button2
;
1105 case 3: /* Right mouse-click */
1106 click_action
= mbox
[but_released_region
].fetchcmd
;
1109 click_action
= NULL
;
1112 if (extra_click_action
!= NULL
1113 && extra_click_action
[0] != 0
1114 && strcmp(extra_click_action
, "msglst")) {
1115 DM(&mbox
[but_released_region
], DEBUG_INFO
,
1116 "runing: %s", extra_click_action
);
1117 (void) execCommand(extra_click_action
);
1119 if (click_action
!= NULL
1120 && click_action
[0] != '\0'
1121 && strcmp(click_action
, "msglst")) {
1122 DM(&mbox
[but_released_region
], DEBUG_INFO
,
1123 "running: %s", click_action
);
1124 (void) execCommand(click_action
);
1128 /* a button was released, hide the message list if open */
1131 but_pressed_region
= -1;
1132 /* RedrawWindow(); */
1137 XKeyPressedEvent
*xkpe
= (XKeyPressedEvent
*) & Event
;
1138 KeySym ks
= XKeycodeToKeysym(display
, xkpe
->keycode
, 0);
1139 if (ks
> XK_0
&& ks
< XK_0
+ min(9U, num_mailboxes
)) {
1140 const char *click_action
= mbox
[ks
- XK_1
].action
;
1141 if (click_action
!= NULL
1142 && click_action
[0] != '\0'
1143 && strcmp(click_action
, "msglst")) {
1144 DM(&mbox
[but_released_region
], DEBUG_INFO
,
1145 "running: %s", click_action
);
1146 (void) execCommand(click_action
);
1158 static void do_biff(int argc
, const char **argv
)
1163 const char **skin_xpm
= NULL
;
1164 const char **bkg_xpm
= NULL
;
1165 char *skin_file_path
= search_path(skin_search_path
, skin_filename
);
1166 int wmbiff_mask_height
= mbox_y(num_mailboxes
) + 4;
1168 DMA(DEBUG_INFO
, "running %u mailboxes w %d h %d\n", num_mailboxes
,
1169 wmbiff_mask_width
, wmbiff_mask_height
);
1171 if (skin_file_path
!= NULL
) {
1172 skin_xpm
= (const char **) LoadXPM(skin_file_path
);
1173 free(skin_file_path
);
1175 if (skin_xpm
== NULL
) {
1176 DMA(DEBUG_ERROR
, "using built-in xpm; %s wasn't found in %s\n",
1177 skin_filename
, skin_search_path
);
1178 skin_xpm
= wmbiff_master_xpm
;
1181 bkg_xpm
= (const char **) CreateBackingXPM(wmbiff_mask_width
,
1185 createXBMfromXPM(wmbiff_mask_bits
, bkg_xpm
,
1186 wmbiff_mask_width
, wmbiff_mask_height
);
1188 openXwindow(argc
, argv
, bkg_xpm
, skin_xpm
, wmbiff_mask_bits
,
1189 wmbiff_mask_width
, wmbiff_mask_height
, notWithdrawn
);
1191 /* now that display is set, we can create the cursors
1192 (mouse pointer shapes) */
1193 busy_cursor
= XCreateFontCursor(display
, XC_watch
);
1194 ready_cursor
= XCreateFontCursor(display
, XC_left_ptr
);
1197 if (loadFont(font
) < 0) {
1198 DMA(DEBUG_ERROR
, "unable to load font. exiting.\n");
1201 /* make the whole background black */
1202 // removed; seems unnecessary with CreateBackingXPM
1203 // eraseRect(x_origin, y_origin,
1204 // wmbiff_mask_width - 6, wmbiff_mask_height - 6,
1208 /* First time setup of button regions and labels */
1210 for (i
= 0; i
< num_mailboxes
; i
++) {
1211 /* make it easy to recover the mbox index from a mouse click */
1212 AddMouseRegion(i
, x_origin
, mbox_y(i
), 58, mbox_y(i
+ 1) - 1);
1213 if (mbox
[i
].label
[0] != '\0') {
1214 mbox
[i
].prevtime
= mbox
[i
].prevfetch_time
= 0;
1215 BlitString(mbox
[i
].label
, x_origin
, mbox_y(i
), 0);
1220 /* waitpid(0, NULL, WNOHANG); */
1222 Sleep_Interval
= periodic_mail_check();
1224 ProcessPendingEvents();
1226 XSleep(Sleep_Interval
);
1228 while (forever
); /* forever is usually true,
1229 but not when debugging with -exit */
1230 if (skin_xpm
!= NULL
&& skin_xpm
!= wmbiff_master_xpm
) {
1231 free(skin_xpm
); // added 3 jul 02, appeasing valgrind
1233 if (bkg_xpm
!= NULL
) {
1238 static void sigchld_handler(int sig
1239 #ifdef HAVE___ATTRIBUTE__
1240 __attribute__ ((unused
))
1244 while (waitpid(0, NULL
, WNOHANG
) > 0);
1245 signal(SIGCHLD
, sigchld_handler
);
1248 static void usage(void)
1250 printf("\nwmBiff v%s"
1251 " - incoming mail checker\n"
1252 "Gennady Belyakov and others (see the README file)\n"
1253 "Please report bugs to %s\n"
1256 " -bg <color> background color\n"
1257 " -c <filename> use specified config file\n"
1258 " -debug enable debugging\n"
1259 " -display <display name> use specified X display\n"
1260 " -fg <color> foreground color\n"
1261 " -font <font> font instead of LED\n"
1262 " -geometry +XPOS+YPOS initial window position\n"
1263 " -h this help screen\n"
1264 " -hi <color> highlight color for new mail\n"
1266 " -skip-certificate-check using TLS, don't validate the\n"
1267 " server's certificate\n"
1269 " -relax assume the configuration is \n"
1270 " correct, parse it without paranoia, \n"
1271 " and assume hostnames are okay.\n"
1272 " -v print the version number\n"
1273 " +w not withdrawn: run as a window\n"
1274 "\n", PACKAGE_VERSION
, PACKAGE_BUGREPORT
);
1277 static void printversion(void)
1279 printf("wmbiff v%s\n", PACKAGE_VERSION
);
1283 static void parse_cmd(int argc
, const char **argv
, /*@out@ */
1288 config_file
[0] = '\0';
1290 /* Parse Command Line */
1292 for (i
= 1; i
< argc
; i
++) {
1293 const char *arg
= argv
[i
];
1298 if (strcmp(arg
+ 1, "bg") == 0) {
1299 if (argc
> (i
+ 1)) {
1300 background
= strdup_ordie(argv
[i
+ 1]);
1301 DMA(DEBUG_INFO
, "new background: %s", foreground
);
1304 font
= DEFAULT_FONT
;
1309 if (strcmp(arg
+ 1, "debug") == 0) {
1310 debug_default
= DEBUG_ALL
;
1311 } else if (strcmp(arg
+ 1, "display") == 0) {
1312 /* passed to X's command line parser */
1319 if (strcmp(arg
+ 1, "fg") == 0) {
1320 if (argc
> (i
+ 1)) {
1321 foreground
= strdup_ordie(argv
[i
+ 1]);
1322 DMA(DEBUG_INFO
, "new foreground: %s", foreground
);
1325 font
= DEFAULT_FONT
;
1327 } else if (strcmp(arg
+ 1, "font") == 0) {
1328 if (argc
> (i
+ 1)) {
1329 if (strcmp(argv
[i
+ 1], "default") == 0) {
1330 font
= DEFAULT_FONT
;
1332 font
= strdup_ordie(argv
[i
+ 1]);
1334 DMA(DEBUG_INFO
, "new font: %s", font
);
1343 if (strcmp(arg
+ 1, "geometry") != 0) {
1347 i
++; /* gobble the argument */
1348 if (i
>= argc
) { /* fail if there's nothing to gobble */
1355 if (strcmp(arg
+ 1, "hi") == 0) {
1356 if (argc
> (i
+ 1)) {
1357 highlight
= strdup_ordie(argv
[i
+ 1]);
1358 DMA(DEBUG_INFO
, "new highlight: %s", highlight
);
1361 font
= DEFAULT_FONT
;
1363 } else if (strcmp(arg
+ 1, "h") == 0) {
1373 if (strcmp(arg
+ 1, "skip-certificate-check") == 0) {
1374 SkipCertificateCheck
= 1;
1382 if (strcmp(arg
+ 1, "relax") == 0) {
1391 if (argc
> (i
+ 1)) {
1392 strncpy(config_file
, argv
[i
+ 1], 255);
1396 case 'e': /* undocumented for debugging */
1397 if (strcmp(arg
+ 1, "exit") == 0) {
1406 } else if (*arg
== '+') {
1420 int main(int argc
, const char *argv
[])
1422 char uconfig_file
[256];
1424 /* hold on to the arguments we were started with; we
1425 will need them if we have to restart on sigusr1 */
1427 (const char **) malloc((argc
+ 1) * sizeof(const char *));
1428 memcpy(restart_args
, argv
, (argc
) * sizeof(const char *));
1429 restart_args
[argc
] = NULL
;
1431 parse_cmd(argc
, argv
, uconfig_file
);
1433 /* decide what the config file is */
1434 if (uconfig_file
[0] != '\0') { /* user-specified config file */
1435 DMA(DEBUG_INFO
, "Using user-specified config file '%s'.\n",
1438 const char *home
= getenv("HOME");
1441 "$HOME undefined. Use the -c option to specify a wmbiffrc\n");
1444 sprintf(uconfig_file
, "%s/.wmbiffrc", home
);
1447 if (wmbiffrc_permissions_check(uconfig_file
) == 0) {
1449 "WARNING: In future versions of WMBiff, .wmbiffrc MUST be\n"
1450 "owned by the user, and not readable or writable by others.\n\n");
1452 init_biff(uconfig_file
);
1453 signal(SIGCHLD
, sigchld_handler
);
1454 signal(SIGUSR1
, restart_wmbiff
);
1455 signal(SIGPIPE
, SIG_IGN
); /* write() may fail */
1457 do_biff(argc
, argv
);