wmail: added signal-handler for SIGINT and SIGTERM.
[dockapps.git] / wmail / src / wmail.c
blob71f40ea186a375090364e34438971ac1a1225f06
1 ///////////////////////////////////////////////////////////////////////////////
2 // wmail.c
3 // email indicator tool designed as docklet for Window Maker
4 // main c source-file
5 //
6 // wmail version 2.0
7 //
8 // Copyright 2000~2002, Sven Geisenhainer <sveng@informatik.uni-jena.de>.
9 // All rights reserved.
11 // Redistribution and use in source and binary forms, with or without
12 // modification, are permitted provided that the following conditions
13 // are met:
14 // 1. Redistributions of source code must retain the above copyright
15 // notice, this list of conditions, and the following disclaimer.
16 // 2. Redistributions in binary form must reproduce the above copyright
17 // notice, this list of conditions, and the following disclaimer in the
18 // documentation and/or other materials provided with the distribution.
19 // 3. The name of the author may not be used to endorse or promote products
20 // derived from this software without specific prior written permission.
22 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 ///////////////////////////////////////////////////////////////////////////////
35 // includes
37 #include <ctype.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <stdarg.h>
41 #include <string.h>
42 #include <signal.h>
43 #include <utime.h>
44 #include <fnmatch.h>
45 #include <sys/time.h>
46 #include <sys/stat.h>
47 #include <dirent.h>
48 #include <X11/Xlib.h>
49 #include <libdockapp/dockapp.h>
50 #include "common.h"
51 #include "config.h"
53 // pixmaps
54 #ifdef USE_DELT_XPMS
55 #include "xpm_delt/main.xpm"
56 #include "xpm_delt/symbols.xpm"
57 #include "xpm_delt/numbers.xpm"
58 #include "xpm_delt/button.xpm"
59 #include "xpm_delt/chars.xpm"
60 #else
61 #include "xpm/main.xpm"
62 #include "xpm/symbols.xpm"
63 #include "xpm/numbers.xpm"
64 #include "xpm/button.xpm"
65 #include "xpm/chars.xpm"
66 #endif
69 ///////////////////////////////////////////////////////////////////////////////
70 // typedefs
72 typedef enum {
73 FLAG_INITIAL = 0,
74 FLAG_READ = 1
75 } flag_t;
77 typedef struct _name_t {
78 char *name;
79 unsigned long checksum;
80 flag_t flag;
81 bool visited;
82 struct _name_t *next;
83 } name_t;
85 typedef enum {
86 STATE_NOMAIL,
87 STATE_NEWMAIL,
88 STATE_READMAIL
89 } mail_state_t;
91 typedef enum {
92 STATE_ADDRESS,
93 STATE_QUOTED_ADDRESS,
94 STATE_FULLNAME,
95 STATE_QUOTED_FULLNAME,
96 STATE_ENCODED_FULLNAME,
97 STATE_COMMENT
98 } parse_state_t;
101 ///////////////////////////////////////////////////////////////////////////////
102 // data
104 static unsigned long lastTimeOut;
105 static sig_atomic_t caughtSig;
106 mail_state_t state = STATE_NOMAIL;
107 int numMails = 0;
108 bool namesChanged = false;
109 bool buttonPressed = false;
110 bool readConfigFile = false;
111 bool isMaildir = false;
112 bool forceRead = false;
113 bool forceRedraw = true;
114 time_t lastModifySeconds = 0;
115 time_t lastAccessSeconds = 0;
116 Pixmap mainPixmap;
117 Pixmap mainPixmap_mask;
118 Pixmap symbolsPixmap;
119 Pixmap charsPixmap;
120 Pixmap numbersPixmap;
121 Pixmap buttonPixmap;
122 Pixmap outPixmap;
123 GC tickerGC;
124 XFontStruct *tickerFS = NULL;
125 name_t *names = NULL;
126 name_t *curTickerName = NULL;
128 static DAProgramOption options[] = {
129 {"-display", NULL, "display to use", DOString, False, {&config.display}},
130 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
131 DOString, False, {&config.runCmd} },
132 {"-i", "--intervall",
133 "number of secs between mail-status updates (1 is default)", DONatural,
134 False, {&config.checkInterval} },
135 {"-f", "--familyname", "tickers the family-name if available", DONone,
136 False, {NULL} },
137 {"-fps", "--frames", "ticker frames per second", DONatural,
138 False, {&config.fps} },
139 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone,
140 False, {NULL} },
141 {"-sc", "--symbolcolor", "symbol color-name",
142 DOString, False, {&config.symbolColor} },
143 {"-fc", "--fontcolor", "ticker-font color-name",
144 DOString, False, {&config.fontColor} },
145 {"-bc", "--backcolor", "backlight color-name",
146 DOString, False, {&config.backColor} },
147 {"-oc", "--offcolor", "off-light color-name",
148 DOString, False, {&config.offlightColor} },
149 {"-bg", "--background", "frame-background for non-shaped window",
150 DOString, False, {&config.backgroundColor} },
151 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
152 DONone, False, {NULL} },
153 {"-n", "--new", "forces wmail to show new mail exclusively", DONone, False, {NULL} },
154 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString, False, {&config.mailBox} },
155 {"-e", "--execute", "command to execute when receiving a new mail", DOString, False, {&config.cmdOnMail} },
156 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone, False, {NULL} },
157 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString, False, {&config.readStatus} },
158 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString, False, {&config.useX11Font} }
162 ///////////////////////////////////////////////////////////////////////////////
163 // prototypes
165 void PreparePixmaps( bool freeThemFirst );
166 static void ExitHandler( int sig );
167 void TimedOut( void );
168 void CheckTimeOut( bool force );
169 void CheckMBox( void );
170 void CheckMaildir( void );
171 int TraverseDirectory( const char *name, bool isNewMail );
172 name_t *GetMail( unsigned long checksum );
173 void UpdatePixmap( bool flashMailSymbol );
174 void ParseMBoxFile( struct stat *fileStat );
175 void ParseMaildirFile( const char *fileName, unsigned long checksum,
176 struct stat *fileStat, bool isNewMail );
177 char *ParseFromField( char *buf );
178 bool SkipSender( char *address );
179 void InsertName( char *name, unsigned long checksum, flag_t flag );
180 void RemoveLastName( void );
181 void ClearAllNames( void );
182 void DrawTickerX11Font( void );
183 void DrawTickerBuildinFont( void );
184 void ButtonPressed( int button, int state, int x, int y );
185 void ButtonReleased( int button, int state, int x, int y );
186 char *XpmColorLine( const char *colorName, char *colorLine, bool disposeLine );
187 void ReadChecksumFile( void );
188 void WriteChecksumFile( bool writeAll );
189 void UpdateChecksum( unsigned long *checksum, const char *buf );
190 void RemoveChecksumFile( void );
191 void SetMailFlags( flag_t flag );
192 void MarkName( unsigned long checksum );
193 void DetermineState( void );
194 void UpdateConfiguration( void );
195 void CleanupNames( void );
196 bool HasTickerWork( void );
199 ///////////////////////////////////////////////////////////////////////////////
200 // implementation
203 int main( int argc, char **argv )
205 char *usersHome;
206 struct sigaction sa = { .sa_handler = ExitHandler };
207 struct stat fileStat;
208 XTextProperty windowName;
209 char *name = argv[0];
210 DACallbacks callbacks = { NULL, &ButtonPressed, &ButtonReleased,
211 NULL, NULL, NULL, &TimedOut };
213 // read the config file and overide the default-settings
214 ReadConfigFile( false );
216 if( config.checksumFileName == NULL ) {
217 if(( usersHome = getenv( "HOME" )) == NULL ) {
218 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE );
219 config.checksumFileName = strdup( WMAIL_CHECKSUM_FILE );
220 } else
221 config.checksumFileName = MakePathName( usersHome, WMAIL_CHECKSUM_FILE );
224 if ( config.checksumFileName == NULL)
226 WARNING( "Cannot allocate checksum file-name.\n");
227 exit( EXIT_FAILURE );
230 TRACE( "using checksum-file \"%s\"\n", config.checksumFileName );
232 // parse cmdline-args and overide defaults and cfg-file settings
233 DAParseArguments( argc, argv, options,
234 sizeof(options) / sizeof(DAProgramOption),
235 WMAIL_NAME, WMAIL_VERSION );
237 if( options[0].used )
238 config.givenOptions |= CL_DISPLAY;
239 if( options[1].used )
240 config.givenOptions |= CL_RUNCMD;
241 if( options[2].used )
242 config.givenOptions |= CL_CHECKINTERVAL;
243 if( options[3].used ) {
244 config.givenOptions |= CL_TICKERMODE;
245 config.tickerMode = TICKER_FAMILYNAME;
247 if( options[4].used )
248 config.givenOptions |= CL_FPS;
249 if( options[5].used ) {
250 config.givenOptions |= CL_TICKERMODE;
251 config.tickerMode = TICKER_NICKNAME;
253 if( options[6].used )
254 config.givenOptions |= CL_SYMBOLCOLOR;
255 if( options[7].used )
256 config.givenOptions |= CL_FONTCOLOR;
257 if( options[8].used )
258 config.givenOptions |= CL_BACKCOLOR;
259 if( options[9].used )
260 config.givenOptions |= CL_OFFLIGHTCOLOR;
261 if( options[10].used )
262 config.givenOptions |= CL_BACKGROUNDCOLOR;
263 if( options[11].used ) {
264 config.givenOptions |= CL_NOSHAPE;
265 config.noshape = true;
267 if( options[12].used ) {
268 config.givenOptions |= CL_NEWMAILONLY;
269 config.newMailsOnly = true;
271 if( options[13].used )
272 config.givenOptions |= CL_MAILBOX;
273 if( options[14].used )
274 config.givenOptions |= CL_CMDONMAIL;
275 if( options[15].used ) {
276 config.givenOptions |= CL_CONSIDERSTATUSFIELD;
277 config.considerStatusField = true;
279 if( options[16].used )
280 config.givenOptions |= CL_READSTATUS;
281 if( options[17].used )
282 config.givenOptions |= CL_USEX11FONT;
284 if( config.mailBox == NULL )
285 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
286 else if( stat( config.mailBox, &fileStat ) == 0 )
287 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
289 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
291 // dockapp size hard wired - sorry...
292 DAInitialize( config.display, "wmail", 64, 64, argc, argv );
294 outPixmap = DAMakePixmap();
295 PreparePixmaps( false );
297 if( sigaction( SIGINT, &sa, NULL ) == -1 ) {
298 perror( "wmail error: sigaction" );
299 exit( EXIT_FAILURE );
302 if( sigaction( SIGTERM, &sa, NULL ) == -1 ) {
303 perror( "wmail error: sigaction" );
304 exit( EXIT_FAILURE );
307 DASetCallbacks( &callbacks );
308 DASetTimeout (1000 / config.fps);
310 XStringListToTextProperty( &name, 1, &windowName );
311 XSetWMName( DADisplay, DAWindow, &windowName );
313 UpdatePixmap( false );
314 DAShow();
316 DAEventLoop();
318 return 0;
321 void PreparePixmaps( bool freeMem )
323 // simple recoloring of the raw xpms befor creating Pixmaps of them
324 // this works as long as you don't "touch" the images...
326 unsigned short dummy;
327 XGCValues values;
329 if( config.symbolColor != NULL ) { // symbol color ?
330 symbols_xpm[2] = XpmColorLine( config.symbolColor, symbols_xpm[2],
331 freeMem && ( config.colorsUsed & SYM_COLOR ));
332 config.colorsUsed |= SYM_COLOR;
333 } else {
334 symbols_xpm[2] = XpmColorLine( "#20B2AA", symbols_xpm[2],
335 freeMem && ( config.colorsUsed & SYM_COLOR ));
336 config.colorsUsed |= SYM_COLOR;
339 if( config.fontColor != NULL ) { // font color ?
340 chars_xpm[3] = XpmColorLine( config.fontColor, chars_xpm[3],
341 freeMem && ( config.colorsUsed & FNT_COLOR ));
342 numbers_xpm[3] = XpmColorLine( config.fontColor, numbers_xpm[3],
343 freeMem && ( config.colorsUsed & FNT_COLOR ));
344 config.colorsUsed |= FNT_COLOR;
345 } else {
346 chars_xpm[3] = XpmColorLine( "#D3D3D3", chars_xpm[3],
347 freeMem && ( config.colorsUsed & FNT_COLOR ));
348 numbers_xpm[3] = XpmColorLine( "#D3D3D3", numbers_xpm[3],
349 freeMem && ( config.colorsUsed & FNT_COLOR ));
350 config.colorsUsed |= FNT_COLOR;
353 if( config.backColor != NULL ) { // backlight color ?
354 main_xpm[3] = XpmColorLine( config.backColor, main_xpm[3],
355 freeMem && ( config.colorsUsed & BCK_COLOR ));
356 symbols_xpm[3] = XpmColorLine( config.backColor, symbols_xpm[3],
357 freeMem && ( config.colorsUsed & BCK_COLOR ));
358 chars_xpm[2] = XpmColorLine( config.backColor, chars_xpm[2],
359 freeMem && ( config.colorsUsed & BCK_COLOR ));
360 numbers_xpm[2] = XpmColorLine( config.backColor, numbers_xpm[2],
361 freeMem && ( config.colorsUsed & BCK_COLOR ));
362 config.colorsUsed |= BCK_COLOR;
363 } else {
364 main_xpm[3] = XpmColorLine( "#282828", main_xpm[3],
365 freeMem && ( config.colorsUsed & BCK_COLOR ));
366 symbols_xpm[3] = XpmColorLine( "#282828", symbols_xpm[3],
367 freeMem && ( config.colorsUsed & BCK_COLOR ));
368 chars_xpm[2] = XpmColorLine( "#282828", chars_xpm[2],
369 freeMem && ( config.colorsUsed & BCK_COLOR ));
370 numbers_xpm[2] = XpmColorLine( "#282828", numbers_xpm[2],
371 freeMem && ( config.colorsUsed & BCK_COLOR ));
372 config.colorsUsed |= BCK_COLOR;
375 if( config.offlightColor != NULL ) { // off-light color ?
376 main_xpm[2] = XpmColorLine( config.offlightColor, main_xpm[2],
377 freeMem && ( config.colorsUsed & OFF_COLOR ));
378 numbers_xpm[4] = XpmColorLine( config.offlightColor, numbers_xpm[4],
379 freeMem && ( config.colorsUsed & OFF_COLOR ));
380 config.colorsUsed |= OFF_COLOR;
381 } else {
382 main_xpm[2] = XpmColorLine( "#000000", main_xpm[2],
383 freeMem && ( config.colorsUsed & OFF_COLOR ));
384 numbers_xpm[4] = XpmColorLine( "#000000", numbers_xpm[4],
385 freeMem && ( config.colorsUsed & OFF_COLOR ));
386 config.colorsUsed |= OFF_COLOR;
389 if( config.backgroundColor != NULL ) { // window-frame background (only seen if nonshaped) ?
390 main_xpm[1] = XpmColorLine( config.backgroundColor, main_xpm[1],
391 freeMem && ( config.colorsUsed & BGR_COLOR ));
392 config.colorsUsed |= BGR_COLOR;
395 if( freeMem )
397 XFreePixmap( DADisplay, mainPixmap );
398 XFreePixmap( DADisplay, mainPixmap_mask );
399 XFreePixmap( DADisplay, symbolsPixmap );
400 XFreePixmap( DADisplay, charsPixmap );
401 XFreePixmap( DADisplay, numbersPixmap );
402 XFreePixmap( DADisplay, buttonPixmap );
404 if( tickerGC != NULL )
406 XFreeGC( DADisplay, tickerGC );
407 tickerGC = NULL;
408 if( tickerFS != NULL ) {
409 XFreeFont( DADisplay, tickerFS );
410 tickerFS = NULL;
415 DAMakePixmapFromData( main_xpm, &mainPixmap, &mainPixmap_mask, &dummy, &dummy );
416 DAMakePixmapFromData( symbols_xpm, &symbolsPixmap, NULL, &dummy, &dummy );
417 DAMakePixmapFromData( chars_xpm, &charsPixmap, NULL, &dummy, &dummy );
418 DAMakePixmapFromData( numbers_xpm, &numbersPixmap, NULL, &dummy, &dummy );
419 DAMakePixmapFromData( button_xpm, &buttonPixmap, NULL, &dummy, &dummy );
421 if( config.useX11Font != NULL )
423 XRectangle clipRect;
425 if( config.fontColor != NULL )
426 values.foreground = DAGetColor( config.fontColor );
427 else
428 values.foreground = DAGetColor( "#D3D3D3" );
430 tickerFS = XLoadQueryFont( DADisplay, config.useX11Font );
431 if( tickerFS == NULL )
432 ABORT( "Cannot load font \"%s\"", config.useX11Font );
434 values.font = tickerFS->fid;
435 tickerGC = XCreateGC( DADisplay, DAWindow, GCForeground | GCFont,
436 &values );
437 clipRect.x = 6;
438 clipRect.y = 19;
439 clipRect.width = 52;
440 clipRect.height = 23;
442 XSetClipRectangles( DADisplay, tickerGC, 0, 0, &clipRect, 1, Unsorted );
445 if( config.noshape ) // non-shaped dockapp ?
446 DASetShape( None );
447 else
448 DASetShape( mainPixmap_mask );
451 void MarkName( unsigned long checksum )
453 name_t *name;
455 for( name = names; name != NULL; name = name->next ) {
456 if( name->checksum == checksum ) {
457 name->flag |= FLAG_READ;
458 if( config.newMailsOnly )
459 numMails--;
460 break;
465 void DetermineState( void )
467 name_t *name;
469 for( name = names; name != NULL; name = name->next )
470 if(!( name->flag & FLAG_READ )) {
471 state = STATE_NEWMAIL;
473 if( config.cmdOnMail != NULL ) {
474 int ret = system( config.cmdOnMail );
476 if( ret == 127 || ret == -1 )
477 WARNING( "execution of command \"%s\" failed.\n", config.cmdOnMail );
480 break;
484 void ReadChecksumFile( void )
486 FILE *f = fopen( config.checksumFileName, "rb" );
487 if( f != NULL ) while( !feof( f )) {
488 unsigned long checksum;
489 if( fread( &checksum, sizeof(long), 1, f ) != 1 )
490 continue;
492 MarkName( checksum );
493 } else
494 return;
496 fclose( f );
499 void WriteChecksumFile( bool writeAll )
501 FILE *f;
502 TRACE( "writing checksums:" );
504 if(( f = fopen( config.checksumFileName, "wb" )) != NULL ) {
505 name_t *name;
506 for( name = names; name != NULL; name = name->next ) {
507 if( writeAll || (name->flag & FLAG_READ)) {
508 fwrite( &name->checksum, sizeof(long), 1, f );
509 TRACE( " %X", name->checksum );
512 } else
513 return;
515 TRACE( "\n" );
517 fclose( f );
520 void UpdateChecksum( unsigned long *checksum, const char *buf )
522 if( buf != NULL ) {
523 unsigned int i, len = strlen( buf );
525 for( i = 0; i < len; ++i )
526 *checksum += buf[i] << (( i % sizeof(long) ) * 8 );
530 void RemoveChecksumFile( void )
532 TRACE( "removing checksum-file\n" );
533 remove( config.checksumFileName );
536 static void ExitHandler( int sig )
538 caughtSig = 1;
541 void TimedOut( void )
543 if (caughtSig) {
544 ClearAllNames();
545 ResetConfigStrings();
546 exit( EXIT_SUCCESS );
549 CheckTimeOut( true );
552 void CheckTimeOut( bool force )
554 static int checkMail = 0;
556 struct timeval now;
557 gettimeofday(&now, NULL);
559 unsigned long nowMs = now.tv_sec * 1000L + now.tv_usec / 1000L;
561 if (!force && nowMs - lastTimeOut < 1000L / config.fps)
562 return;
564 lastTimeOut = nowMs;
566 if( readConfigFile ) {
567 readConfigFile = false;
568 UpdateConfiguration();
569 checkMail = 0;
570 forceRead = true;
573 if( checkMail == 0 )
575 TRACE( "checking for new mail...\n" );
577 if( isMaildir )
578 CheckMaildir();
579 else
580 CheckMBox();
583 UpdatePixmap( checkMail % config.fps < config.fps/2 );
585 if( ++checkMail >= config.fps * config.checkInterval )
586 checkMail = 0;
589 void CheckMBox( void )
591 struct stat fileStat;
593 // error retrieving file-stats -> no/zero-size file and no new/read mails
594 // available
595 if( stat( config.mailBox, &fileStat ) == -1 || fileStat.st_size == 0 ) {
596 if( state != STATE_NOMAIL ) {
597 state = STATE_NOMAIL;
598 ClearAllNames();
599 RemoveChecksumFile();
600 forceRedraw = true;
602 } else {
603 // file has changed -> new mails arrived or some mails removed
604 if( lastModifySeconds != fileStat.st_mtime || forceRead ) {
605 forceRead = false;
606 ParseMBoxFile( &fileStat );
607 stat( config.mailBox, &fileStat );
608 forceRedraw = true;
609 // file has accessed (read) -> mark all mails as "read", because
610 // it cannot be decided which mails the user has read...
611 } else if( lastAccessSeconds != fileStat.st_atime ) {
612 state = STATE_READMAIL;
613 WriteChecksumFile( true );
614 if( config.newMailsOnly ) {
615 numMails = 0;
616 SetMailFlags( FLAG_READ );
618 forceRedraw = true;
621 lastModifySeconds = fileStat.st_mtime;
622 lastAccessSeconds = fileStat.st_atime;
626 void CheckMaildir( void )
628 DIR *dir = NULL;
629 int lastState = state;
630 int lastMailCount = numMails;
632 if( forceRead ) {
633 forceRead = false;
634 ClearAllNames();
635 TRACE( "all names cleared\n" );
638 state = STATE_NOMAIL;
640 if(( dir = opendir( config.mailBox )) != NULL )
642 struct dirent *dirEnt = NULL;
643 //bool writeChecksums = false;
644 name_t *name;
646 for( name = names; name != NULL; name = name->next )
647 name->visited = false;
649 while(( dirEnt = readdir( dir )) != NULL )
651 char *fullName = MakePathName( config.mailBox, dirEnt->d_name );
652 struct stat fileStat;
654 if ( fullName == NULL )
656 WARNING( "Cannot allocate file/path\n" );
657 break;
660 if( !stat( fullName, &fileStat ) == 0 )
661 WARNING( "Can't stat file/path \"%s\"\n", fullName );
662 else if( S_ISDIR( fileStat.st_mode ))
664 if( strcmp( dirEnt->d_name, "new" ) == 0 ) {
665 if( TraverseDirectory( fullName, true ) > 0 )
666 state = STATE_NEWMAIL;
668 else if( strcmp( dirEnt->d_name, "cur" ) == 0 ) {
669 if( TraverseDirectory( fullName, false ) > 0 )
670 if( state != STATE_NEWMAIL )
671 state = STATE_READMAIL;
673 // directories ".", ".." and "tmp" discarded
675 free( fullName );
677 closedir( dir );
678 CleanupNames();
679 } else
680 WARNING( "can't open directory \"%s\"\n", config.mailBox );
682 if( lastState != state || lastMailCount != numMails )
683 forceRedraw = true;
686 int TraverseDirectory( const char *name, bool isNewMail )
688 DIR *dir = NULL;
689 int mails = 0;
691 if(( dir = opendir( name )) != NULL )
693 struct dirent *dirEnt = NULL;
695 while(( dirEnt = readdir( dir )) != NULL )
697 char *fullName = MakePathName( name, dirEnt->d_name );
698 struct stat fileStat;
699 unsigned long checksum = 0;
700 name_t *name;
702 if ( fullName == NULL )
704 WARNING( "Cannot allocate file/path\n" );
705 break;
708 if( !stat( fullName, &fileStat ) == 0 )
709 WARNING( "Can't stat file/path \"%s\"\n", fullName );
710 else if( !S_ISDIR( fileStat.st_mode ))
712 TRACE( "found email-file \"%s\"\n", fullName );
713 UpdateChecksum( &checksum, dirEnt->d_name );
715 if(( name = GetMail( checksum )) == NULL )
717 TRACE( "-> new file - parsing it\n" );
718 ParseMaildirFile( fullName, checksum, &fileStat, isNewMail );
720 else {
721 name->flag = isNewMail ? FLAG_INITIAL : FLAG_READ;
722 name->visited = true;
724 ++mails;
726 free( fullName );
730 closedir( dir );
732 return mails;
735 name_t *GetMail( unsigned long checksum )
737 name_t *name;
739 for( name = names; name != NULL; name = name->next )
740 if( name->checksum == checksum )
741 return name;
743 return NULL;
746 void UpdatePixmap( bool flashMailSymbol )
748 int drawCount, i;
750 if( !forceRedraw && !HasTickerWork() )
751 return;
753 forceRedraw = false;
755 XCopyArea( DADisplay, mainPixmap, outPixmap, DAGC,
756 0, 0, 64, 64, 0, 0 );
758 if( numMails > 999 )
760 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
761 50, 0, 5, 9, 6, 49 );
762 drawCount = 999;
763 } else
764 drawCount = numMails;
766 for( i = 0; i < 3; ++i, drawCount /= 10 )
768 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
769 (drawCount%10)*5, 0, 5, 9, 24-i*6, 49 );
770 if( drawCount <= 9 )
771 break;
774 if( buttonPressed )
776 XCopyArea( DADisplay, buttonPixmap, outPixmap, DAGC,
777 0, 0, 23, 11, 36, 48 );
780 switch( state ) {
781 case STATE_NEWMAIL:
782 if( flashMailSymbol )
783 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
784 13, 0, 37, 12, 20, 7 );
785 case STATE_READMAIL:
786 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
787 0, 0, 13, 12, 7, 7 );
789 if( config.useX11Font == NULL )
790 DrawTickerBuildinFont();
791 else
792 DrawTickerX11Font();
793 default: // make compiler happy
797 DASetPixmap( outPixmap );
800 void ParseMBoxFile( struct stat *fileStat )
802 char buf[1024];
803 struct utimbuf timeStruct;
804 int fromFound = 0;
805 FILE *f = fopen( config.mailBox, "rt" );
806 unsigned long checksum;
808 state = STATE_READMAIL;
809 ClearAllNames();
811 numMails = 0;
813 if( f == NULL ) {
814 WARNING( "can't open mbox \"%s\"\n", config.mailBox );
815 return;
818 while( fgets( buf, 1024, f ) != NULL )
820 if( strncmp( buf, "From ", 5 ) == 0 ) {
821 fromFound = 1;
822 checksum = 0;
823 continue;
826 if( fromFound )
827 UpdateChecksum( &checksum, buf );
829 if( fromFound && strncasecmp( buf, "from: ", 6 ) == 0 )
831 if( SkipSender( buf+6 ))
832 goto NEXTMAIL;
834 char *name;
835 if(( name = ParseFromField( buf+6 )) == NULL )
837 WARNING( "Could not parse From field\n" );
838 break;
840 InsertName( name, checksum, FLAG_INITIAL );
842 ++numMails;
843 fromFound = 0;
844 checksum = 0;
845 } else if( config.considerStatusField && strncasecmp( buf, "status: ", 8 ) == 0 &&
846 strstr( buf+8, config.readStatus ) == NULL )
848 RemoveLastName();
849 --numMails;
851 NEXTMAIL:
855 fclose( f );
856 ReadChecksumFile();
858 DetermineState();
860 timeStruct.actime = fileStat->st_atime;
861 timeStruct.modtime = fileStat->st_mtime;
862 utime( config.mailBox, &timeStruct );
865 void ParseMaildirFile( const char *fileName, unsigned long checksum,
866 struct stat *fileStat, bool isNewMail )
868 char buf[1024];
869 struct utimbuf timeStruct;
870 FILE *f = fopen( fileName, "rt" );
872 if( f == NULL )
874 WARNING( "can't open maildir file \"%s\"\n", fileName );
875 return;
878 while( fgets( buf, 1024, f ) != NULL )
880 if( strncasecmp( buf, "from: ", 6 ) == 0 )
882 if( SkipSender( buf+6 ))
883 break;
885 char *name;
886 if(( name = ParseFromField( buf+6 )) == NULL )
888 WARNING( "Could not parse From field\n" );
889 break;
891 InsertName( name, checksum, isNewMail ? FLAG_INITIAL : FLAG_READ );
893 //++numMails;
897 fclose( f );
899 timeStruct.actime = fileStat->st_atime;
900 timeStruct.modtime = fileStat->st_mtime;
901 utime( fileName, &timeStruct );
904 char *ParseFromField( char *buf )
906 parse_state_t state = STATE_FULLNAME;
907 int fullNameEncoded = 0;
908 int saveAtCharPos = -1;
909 char *fullName;
910 char *addressName;
911 char *atChar = NULL;
912 char *c;
913 int maxLen = strlen( buf ) + 1;
914 char *comment;
916 if(( fullName = calloc( maxLen, sizeof *fullName )) == NULL )
917 return NULL;
918 if(( addressName = calloc( maxLen, sizeof *addressName )) == NULL )
920 free( fullName );
921 return NULL;
923 if(( comment = calloc( maxLen, sizeof *comment )) == NULL )
925 free( fullName );
926 free( addressName );
927 return NULL;
930 // FIXME: Don't do that "encoded" dance. It's not intended by
931 // RFC2047, and it's better to just do it in the end.
932 // Cleaner.
934 for( c = buf; *c != '\0'; ++c )
936 switch( state ) {
937 case STATE_FULLNAME:
939 switch( *c ) {
940 case '"':
941 state = STATE_QUOTED_FULLNAME;
942 continue;
943 case '<':
944 if( fullName[0] != '\0' &&
945 fullName[ strlen( fullName ) - 1 ] == ' ' )
946 fullName[ strlen( fullName ) - 1 ] = '\0';
947 state = STATE_ADDRESS;
948 continue;
949 case '@':
950 saveAtCharPos = strlen( fullName );
951 fullName[ saveAtCharPos ] = *c;
952 continue;
953 case '(':
954 state = STATE_COMMENT;
955 continue;
956 case '=':
957 if( *(c+1) == '?' ) {
958 ++c;
959 fullNameEncoded = 1;
960 state = STATE_ENCODED_FULLNAME;
961 continue;
962 } // else do the default action
963 default:
964 if( fullName[0] != '\0' || !isspace ( *c ))
965 fullName[ strlen( fullName ) ] = *c;
967 continue;
969 case STATE_QUOTED_FULLNAME:
971 switch( *c ) {
972 case '\\':
973 fullName[ strlen( fullName ) ] = *(++c);
974 continue;
975 case '"':
976 state = STATE_FULLNAME;
977 continue;
978 default:
979 fullName[ strlen( fullName ) ] = *c;
981 continue;
983 case STATE_ENCODED_FULLNAME:
985 switch( *c ) {
986 case '?':
987 if( *(c+1) == '=' ) {
988 ++c;
989 state = STATE_FULLNAME;
990 continue;
992 default:
993 ; // do nothing... COMING SOON: decode at least latin1
995 continue;
997 case STATE_ADDRESS:
999 switch( *c ) {
1000 case '"':
1001 state = STATE_QUOTED_ADDRESS;
1002 case '>':
1003 case '\n':
1004 // FIXME: Shouldn't it break here?
1005 // Since the address is finished?
1006 continue;
1007 case '@':
1008 atChar = &addressName[ strlen( addressName ) ];
1009 *atChar = *c;
1010 continue;
1011 default:
1012 addressName[ strlen( addressName ) ] = *c;
1014 continue;
1016 case STATE_QUOTED_ADDRESS:
1018 switch( *c ) {
1019 case '"':
1020 state = STATE_ADDRESS;
1021 continue;
1022 case '\\':
1023 addressName[ strlen( addressName ) ] = *(++c);
1024 continue;
1025 default:
1026 addressName[ strlen( addressName ) ] = *c;
1028 continue;
1029 case STATE_COMMENT:
1030 switch( *c ) {
1031 case ')':
1032 state = STATE_FULLNAME;
1033 continue;
1034 default:
1035 comment[ strlen( comment ) ] = *c;
1036 continue;
1038 continue;
1042 if (*comment) {
1043 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1044 // Comment seen: if there's an address, append to
1045 // fullname. If no address, copy fullname to address
1046 // and comment to fullname.
1047 if (*addressName) {
1048 strcat(fullName, "(");
1049 strcat(fullName, comment);
1050 strcat(fullName, ")");
1051 } else {
1052 strcpy(addressName, fullName);
1053 strcpy(fullName, comment);
1056 free( comment );
1058 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1060 // what name should be tickered
1061 if( config.tickerMode == TICKER_FAMILYNAME && fullName[0] != '\0' && !fullNameEncoded ) {
1062 free( addressName );
1063 return fullName;
1064 } else {
1065 if( state == STATE_FULLNAME ) {
1066 strcpy( addressName, fullName );
1067 if( saveAtCharPos != -1 )
1068 atChar = &addressName[saveAtCharPos];
1070 if( config.tickerMode == TICKER_NICKNAME ) {
1071 if( atChar != NULL )
1072 *atChar = '\0';
1074 free( fullName );
1075 return addressName;
1079 bool SkipSender( char *address )
1081 char **skipName;
1082 int len = strlen( address );
1084 // remove trailing '\n' got from fgets
1085 if( address[len-1] == '\n' )
1086 address[len-1] = '\0';
1088 for( skipName = config.skipNames;
1089 skipName != NULL && *skipName != NULL; skipName++ )
1091 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName, address );
1093 // call libc-fnmatch (wildcard-match :-) !
1094 if( !fnmatch( *skipName, address, 0 )) {
1095 TRACE( "skipping sender \"%s\"\n", *skipName );
1096 return true;
1100 return false;
1103 void InsertName( char *name, unsigned long checksum, flag_t flag )
1105 name_t *item;
1107 TRACE( "insertName: %X, \"%s\"\n", checksum, name );
1108 if (( item = malloc( sizeof( name_t ))) == NULL )
1110 free( name );
1111 return;
1114 item->name = name; /*strdup( name );*/
1115 item->checksum = checksum;
1116 item->flag = flag;
1117 item->visited = true;
1118 item->next = names;
1119 names = item;
1121 namesChanged = true;
1124 void RemoveLastName( void )
1126 if( names != NULL ) {
1127 name_t *name = names;
1128 names = names->next;
1129 free( name->name );
1130 free( name );
1134 void ClearAllNames( void )
1136 name_t *name, *nextName;
1138 for( name = names; name != NULL; name = nextName ) {
1139 nextName = name->next;
1141 free( name->name );
1142 free( name );
1145 names = NULL;
1146 numMails = 0;
1148 namesChanged = true;
1151 void SetMailFlags( flag_t flag )
1153 name_t *name;
1155 for( name = names; name != NULL; name = name->next )
1156 name->flag |= flag;
1159 void DrawTickerX11Font( void )
1161 // 49x21+7+20 out-drawable size
1163 static int insertAt;
1165 if( curTickerName == NULL || namesChanged )
1167 for( curTickerName = names;
1168 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1169 curTickerName = curTickerName->next );
1171 if( curTickerName == NULL )
1172 return;
1174 namesChanged = false;
1175 insertAt = 54;
1178 XDrawString( DADisplay, outPixmap, tickerGC, insertAt,
1179 41-tickerFS->max_bounds.descent,
1180 curTickerName->name, strlen( curTickerName->name ));
1182 --insertAt;
1184 if( insertAt < -XTextWidth( tickerFS, curTickerName->name,
1185 strlen( curTickerName->name )) + 6 )
1187 do {
1188 curTickerName = curTickerName->next;
1189 } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ));
1191 if( curTickerName != NULL ) {
1192 insertAt = 54;
1197 void DrawTickerBuildinFont( void )
1199 // 49x21+7+20 out-drawable size
1200 // 14x21 font-character size
1202 static int insertAt;
1203 static int takeItFrom;
1205 int leftSpace;
1206 int drawTo;
1207 unsigned char *currentChar;
1209 if( names == NULL )
1210 return;
1212 if( curTickerName == NULL || namesChanged ) {
1214 for( curTickerName = names;
1215 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1216 curTickerName = curTickerName->next );
1218 if( curTickerName == NULL )
1219 return;
1221 insertAt = 57;
1222 takeItFrom = 0;
1223 namesChanged = false;
1226 leftSpace = takeItFrom % 14;
1228 for( currentChar = (unsigned char *)&curTickerName->name[takeItFrom/14],
1229 drawTo = insertAt; *currentChar != '\0'; ++currentChar )
1232 int outChar = (*currentChar < 32 || *currentChar >= 128) ? '?' :
1233 *currentChar;
1234 int charWidth = 57-drawTo >= 14 ? 14 - leftSpace : 57-drawTo;
1236 XCopyArea( DADisplay, charsPixmap, outPixmap, DAGC,
1237 (outChar-32)*14+leftSpace, 0, charWidth, 21, drawTo, 20 );
1239 leftSpace = 0;
1240 drawTo += charWidth;
1242 if( drawTo > 57 )
1243 break;
1246 if( --insertAt < 7 ) {
1247 insertAt = 7;
1248 takeItFrom++;
1250 if( takeItFrom/14 >= strlen( curTickerName->name )) {
1252 do {
1253 curTickerName = curTickerName->next;
1254 } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ));
1256 if( curTickerName != NULL ) {
1257 takeItFrom = 0;
1258 insertAt = 57;
1264 void ButtonPressed( int button, int state, int x, int y )
1266 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) {
1267 buttonPressed = true;
1268 forceRedraw = true;
1269 } else
1270 // reread the config file
1271 readConfigFile = true;
1273 CheckTimeOut( false );
1276 void ButtonReleased( int button, int state, int x, int y )
1278 buttonPressed = false;
1279 forceRedraw = true;
1281 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) {
1282 int ret = system( config.runCmd );
1284 if( ret == 127 || ret == -1 )
1285 WARNING( "execution of command \"%s\" failed.\n", config.runCmd );
1288 CheckTimeOut( false );
1291 void GetHexColorString( const char *colorName, char *xpmLine )
1293 XColor color;
1295 if( XParseColor( DADisplay,
1296 DefaultColormap( DADisplay, DefaultScreen( DADisplay )),
1297 colorName, &color ))
1299 sprintf( xpmLine, "%02X%02X%02X", color.red>>8, color.green>>8,
1300 color.blue>>8 );
1301 } else
1302 WARNING( "unknown colorname: \"%s\"\n", colorName );
1305 char *XpmColorLine( const char *colorName, char *colorLine, bool disposeLine )
1307 char *newLine = strdup( colorLine );
1308 char *from = strrchr( newLine, '#' );
1310 if( from == NULL && !strcasecmp( &colorLine[ strlen( colorLine ) - 4 ], "none" )) {
1311 // if no # found, it should be a None-color line
1312 free( newLine );
1313 newLine = malloc( 12 );
1314 strcpy( newLine, " \tc #" );
1315 newLine[11] = '\0';
1316 from = newLine + 4;
1319 if( disposeLine )
1320 free( colorLine );
1322 GetHexColorString( colorName, from+1 );
1324 return newLine;
1327 void UpdateConfiguration( void )
1329 struct stat fileStat;
1331 TRACE( "reading configuration file...\n" );
1333 ReadConfigFile( true );
1335 // if no path/name to an mbox or maildir inbox directory was given,
1336 // use the environment
1337 if( config.mailBox == NULL )
1338 config.mailBox = getenv( "MAIL" );
1340 // mbox or maildir ?
1341 if( config.mailBox != NULL && stat( config.mailBox, &fileStat ) == 0 )
1342 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
1343 else
1344 isMaildir = false;
1346 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
1348 PreparePixmaps( true );
1350 DASetTimeout (1000 / config.fps);
1353 void CleanupNames( void )
1355 name_t *name, *last = NULL, *nextName;
1357 numMails = 0;
1359 for( name = names; name != NULL; name = nextName )
1361 nextName = name->next;
1363 if( !name->visited ) {
1364 if( last == NULL )
1365 names = name->next;
1366 else
1367 last->next = name->next;
1369 free( name->name );
1370 free( name );
1371 } else {
1372 last = name;
1374 if( !config.newMailsOnly || (name->flag & FLAG_READ) == 0 )
1375 ++numMails;
1380 bool HasTickerWork( void )
1382 name_t *nextTickerName;
1384 if( names == NULL )
1385 return false;
1387 if( curTickerName == NULL || namesChanged ) {
1389 for( nextTickerName = names;
1390 nextTickerName != NULL && ( config.newMailsOnly && ( nextTickerName->flag & FLAG_READ ));
1391 nextTickerName = nextTickerName->next );
1393 if( nextTickerName == NULL )
1394 return false;
1397 return true;