wmail: Add version 2.0 to repository.
[dockapps.git] / wmail / src / wmail.c
blob1ef93b3cb0aa671754cbf60391ccdef02d0c73a5
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.
10 //
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.
21 //
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 <stdio.h>
38 #include <stdlib.h>
39 #include <stdarg.h>
40 #include <string.h>
41 #include <signal.h>
42 #include <utime.h>
43 #include <fnmatch.h>
44 #include <sys/time.h>
45 #include <sys/stat.h>
46 #include <dirent.h>
47 #include <X11/Xlib.h>
48 #include <dockapp.h>
49 #include "common.h"
50 #include "config.h"
52 // pixmaps
53 #ifdef USE_DELT_XPMS
54 #include "xpm_delt/main.xpm"
55 #include "xpm_delt/symbols.xpm"
56 #include "xpm_delt/numbers.xpm"
57 #include "xpm_delt/button.xpm"
58 #include "xpm_delt/chars.xpm"
59 #else
60 #include "xpm/main.xpm"
61 #include "xpm/symbols.xpm"
62 #include "xpm/numbers.xpm"
63 #include "xpm/button.xpm"
64 #include "xpm/chars.xpm"
65 #endif
68 ///////////////////////////////////////////////////////////////////////////////
69 // typedefs
71 typedef enum {
72 FLAG_INITIAL = 0,
73 FLAG_READ = 1
74 } flag_t;
76 typedef struct _name_t {
77 char *name;
78 unsigned long checksum;
79 flag_t flag;
80 bool visited;
81 struct _name_t *next;
82 } name_t;
84 typedef enum {
85 STATE_NOMAIL,
86 STATE_NEWMAIL,
87 STATE_READMAIL
88 } mail_state_t;
90 typedef enum {
91 STATE_ADDRESS,
92 STATE_QUOTED_ADDRESS,
93 STATE_FULLNAME,
94 STATE_QUOTED_FULLNAME,
95 STATE_ENCODED_FULLNAME,
96 STATE_COMMENT
97 } parse_state_t;
100 ///////////////////////////////////////////////////////////////////////////////
101 // data
103 mail_state_t state = STATE_NOMAIL;
104 int numMails = 0;
105 bool namesChanged = false;
106 bool buttonPressed = false;
107 bool readConfigFile = false;
108 bool isMaildir = false;
109 bool forceRead = false;
110 bool forceRedraw = true;
111 time_t lastModifySeconds = 0;
112 time_t lastAccessSeconds = 0;
113 Pixmap mainPixmap;
114 Pixmap mainPixmap_mask;
115 Pixmap symbolsPixmap;
116 Pixmap charsPixmap;
117 Pixmap numbersPixmap;
118 Pixmap buttonPixmap;
119 Pixmap outPixmap;
120 GC tickerGC;
121 XFontStruct *tickerFS = NULL;
122 name_t *names = NULL;
123 name_t *curTickerName = NULL;
125 static DAProgramOption options[] = {
126 {"-display", NULL, "display to use", DOString, False, {&config.display}},
127 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
128 DOString, False, {&config.runCmd} },
129 {"-i", "--intervall",
130 "number of secs between mail-status updates (1 is default)", DONatural,
131 False, {&config.checkInterval} },
132 {"-f", "--familyname", "tickers the family-name if available", DONone,
133 False, {NULL} },
134 {"-fps", "--frames", "ticker frames per second", DONatural,
135 False, {&config.fps} },
136 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone,
137 False, {NULL} },
138 {"-sc", "--symbolcolor", "symbol color-name",
139 DOString, False, {&config.symbolColor} },
140 {"-fc", "--fontcolor", "ticker-font color-name",
141 DOString, False, {&config.fontColor} },
142 {"-bc", "--backcolor", "backlight color-name",
143 DOString, False, {&config.backColor} },
144 {"-oc", "--offcolor", "off-light color-name",
145 DOString, False, {&config.offlightColor} },
146 {"-bg", "--background", "frame-background for non-shaped window",
147 DOString, False, {&config.backgroundColor} },
148 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
149 DONone, False, {NULL} },
150 {"-n", "--new", "forces wmail to show new mail exclusively", DONone, False, {NULL} },
151 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString, False, {&config.mailBox} },
152 {"-e", "--execute", "command to execute when receiving a new mail", DOString, False, {&config.cmdOnMail} },
153 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone, False, {NULL} },
154 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString, False, {&config.readStatus} },
155 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString, False, {&config.useX11Font} }
159 ///////////////////////////////////////////////////////////////////////////////
160 // prototypes
162 void PreparePixmaps( bool freeThemFirst );
163 void TimerHandler( int dummy );
164 void CheckMBox();
165 void CheckMaildir();
166 int TraverseDirectory( const char *name, bool isNewMail );
167 name_t *GetMail( unsigned long checksum );
168 void UpdatePixmap( bool flashMailSymbol );
169 void ParseMBoxFile( struct stat *fileStat );
170 void ParseMaildirFile( const char *fileName, unsigned long checksum,
171 struct stat *fileStat, bool isNewMail );
172 char *ParseFromField( char *buf );
173 bool SkipSender( char *address );
174 void InsertName( char *name, unsigned long checksum, flag_t flag );
175 void RemoveLastName();
176 void ClearAllNames();
177 void DrawTickerX11Font();
178 void DrawTickerBuildinFont();
179 void ButtonPressed( int button, int state, int x, int y );
180 void ButtonReleased( int button, int state, int x, int y );
181 char *XpmColorLine( const char *colorName, char *colorLine, bool disposeLine );
182 void ReadChecksumFile();
183 void WriteChecksumFile( bool writeAll );
184 void UpdateChecksum( unsigned long *checksum, const char *buf );
185 void RemoveChecksumFile();
186 void SetMailFlags( flag_t flag );
187 void MarkName( unsigned long checksum );
188 void DetermineState();
189 void UpdateConfiguration();
190 void CleanupNames();
191 char *FileNameConcat( const char *path, const char *fileName );
192 bool HasTickerWork();
195 ///////////////////////////////////////////////////////////////////////////////
196 // implementation
199 void SetTimer()
201 struct itimerval timerVal;
203 timerVal.it_interval.tv_sec = 0;
204 timerVal.it_interval.tv_usec = 1000000/config.fps;
205 timerVal.it_value.tv_sec = 0;
206 timerVal.it_value.tv_usec = 1000000/config.fps;
208 setitimer( ITIMER_REAL, &timerVal, NULL );
211 int main( int argc, char **argv )
213 char *usersHome;
214 struct sigaction sa;
215 int ret;
216 struct stat fileStat;
217 XTextProperty windowName;
218 char *name = argv[0];
219 DACallbacks callbacks = { NULL, &ButtonPressed, &ButtonReleased,
220 NULL, NULL, NULL, NULL };
222 // read the config file and overide the default-settings
223 ReadConfigFile( false );
225 if( config.checksumFileName == NULL ) {
226 if(( usersHome = getenv( "HOME" )) == NULL ) {
227 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE );
228 config.checksumFileName = WMAIL_CHECKSUM_FILE;
229 } else
230 config.checksumFileName = MakePathName( usersHome, WMAIL_CHECKSUM_FILE );
233 TRACE( "using checksum-file \"%s\"\n", config.checksumFileName );
235 // parse cmdline-args and overide defaults and cfg-file settings
236 DAParseArguments( argc, argv, options,
237 sizeof(options) / sizeof(DAProgramOption),
238 WMAIL_NAME, WMAIL_VERSION );
240 if( options[0].used )
241 config.givenOptions |= CL_DISPLAY;
242 if( options[1].used )
243 config.givenOptions |= CL_RUNCMD;
244 if( options[2].used )
245 config.givenOptions |= CL_CHECKINTERVAL;
246 if( options[3].used ) {
247 config.givenOptions |= CL_TICKERMODE;
248 config.tickerMode = TICKER_FAMILYNAME;
250 if( options[4].used )
251 config.givenOptions |= CL_FPS;
252 if( options[5].used ) {
253 config.givenOptions |= CL_TICKERMODE;
254 config.tickerMode = TICKER_NICKNAME;
256 if( options[6].used )
257 config.givenOptions |= CL_SYMBOLCOLOR;
258 if( options[7].used )
259 config.givenOptions |= CL_FONTCOLOR;
260 if( options[8].used )
261 config.givenOptions |= CL_BACKCOLOR;
262 if( options[9].used )
263 config.givenOptions |= CL_OFFLIGHTCOLOR;
264 if( options[10].used )
265 config.givenOptions |= CL_BACKGROUNDCOLOR;
266 if( options[11].used ) {
267 config.givenOptions |= CL_NOSHAPE;
268 config.noshape = true;
270 if( options[12].used ) {
271 config.givenOptions |= CL_NEWMAILONLY;
272 config.newMailsOnly = true;
274 if( options[13].used )
275 config.givenOptions |= CL_MAILBOX;
276 if( options[14].used )
277 config.givenOptions |= CL_CMDONMAIL;
278 if( options[15].used ) {
279 config.givenOptions |= CL_CONSIDERSTATUSFIELD;
280 config.considerStatusField = true;
282 if( options[16].used )
283 config.givenOptions |= CL_READSTATUS;
284 if( options[17].used )
285 config.givenOptions |= CL_USEX11FONT;
287 if( config.mailBox == NULL )
288 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
289 else if( stat( config.mailBox, &fileStat ) == 0 )
290 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
292 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
294 // dockapp size hard wired - sorry...
295 DAInitialize( config.display, "wmail", 64, 64, argc, argv );
297 outPixmap = DAMakePixmap();
298 PreparePixmaps( false );
300 DASetCallbacks( &callbacks );
301 DASetTimeout( -1 );
303 sa.sa_handler = TimerHandler;
304 sigemptyset( &sa.sa_mask );
305 sa.sa_flags = SA_RESTART;
306 ret = sigaction( SIGALRM, &sa, 0 );
308 if( ret ) {
309 perror( "wmail error: sigaction" );
310 exit( 1 );
313 XStringListToTextProperty( &name, 1, &windowName );
314 XSetWMName( DADisplay, DAWindow, &windowName );
316 UpdatePixmap( false );
317 DAShow();
318 SetTimer();
320 DAEventLoop();
322 return 0;
325 void PreparePixmaps( bool freeMem )
327 // simple recoloring of the raw xpms befor creating Pixmaps of them
328 // this works as long as you don't "touch" the images...
330 unsigned dummy;
331 XGCValues values;
333 if( config.symbolColor != NULL ) { // symbol color ?
334 symbols_xpm[2] = XpmColorLine( config.symbolColor, symbols_xpm[2],
335 freeMem && ( config.colorsUsed & SYM_COLOR ));
336 config.colorsUsed |= SYM_COLOR;
337 } else {
338 symbols_xpm[2] = XpmColorLine( "#20B2AA", symbols_xpm[2],
339 freeMem && ( config.colorsUsed & SYM_COLOR ));
340 config.colorsUsed |= SYM_COLOR;
343 if( config.fontColor != NULL ) { // font color ?
344 chars_xpm[3] = XpmColorLine( config.fontColor, chars_xpm[3],
345 freeMem && ( config.colorsUsed & FNT_COLOR ));
346 numbers_xpm[3] = XpmColorLine( config.fontColor, numbers_xpm[3],
347 freeMem && ( config.colorsUsed & FNT_COLOR ));
348 config.colorsUsed |= FNT_COLOR;
349 } else {
350 chars_xpm[3] = XpmColorLine( "#D3D3D3", chars_xpm[3],
351 freeMem && ( config.colorsUsed & FNT_COLOR ));
352 numbers_xpm[3] = XpmColorLine( "#D3D3D3", numbers_xpm[3],
353 freeMem && ( config.colorsUsed & FNT_COLOR ));
354 config.colorsUsed |= FNT_COLOR;
357 if( config.backColor != NULL ) { // backlight color ?
358 main_xpm[3] = XpmColorLine( config.backColor, main_xpm[3],
359 freeMem && ( config.colorsUsed & BCK_COLOR ));
360 symbols_xpm[3] = XpmColorLine( config.backColor, symbols_xpm[3],
361 freeMem && ( config.colorsUsed & BCK_COLOR ));
362 chars_xpm[2] = XpmColorLine( config.backColor, chars_xpm[2],
363 freeMem && ( config.colorsUsed & BCK_COLOR ));
364 numbers_xpm[2] = XpmColorLine( config.backColor, numbers_xpm[2],
365 freeMem && ( config.colorsUsed & BCK_COLOR ));
366 config.colorsUsed |= BCK_COLOR;
367 } else {
368 main_xpm[3] = XpmColorLine( "#282828", main_xpm[3],
369 freeMem && ( config.colorsUsed & BCK_COLOR ));
370 symbols_xpm[3] = XpmColorLine( "#282828", symbols_xpm[3],
371 freeMem && ( config.colorsUsed & BCK_COLOR ));
372 chars_xpm[2] = XpmColorLine( "#282828", chars_xpm[2],
373 freeMem && ( config.colorsUsed & BCK_COLOR ));
374 numbers_xpm[2] = XpmColorLine( "#282828", numbers_xpm[2],
375 freeMem && ( config.colorsUsed & BCK_COLOR ));
376 config.colorsUsed |= BCK_COLOR;
379 if( config.offlightColor != NULL ) { // off-light color ?
380 main_xpm[2] = XpmColorLine( config.offlightColor, main_xpm[2],
381 freeMem && ( config.colorsUsed & OFF_COLOR ));
382 numbers_xpm[4] = XpmColorLine( config.offlightColor, numbers_xpm[4],
383 freeMem && ( config.colorsUsed & OFF_COLOR ));
384 config.colorsUsed |= OFF_COLOR;
385 } else {
386 main_xpm[2] = XpmColorLine( "#000000", main_xpm[2],
387 freeMem && ( config.colorsUsed & OFF_COLOR ));
388 numbers_xpm[4] = XpmColorLine( "#000000", numbers_xpm[4],
389 freeMem && ( config.colorsUsed & OFF_COLOR ));
390 config.colorsUsed |= OFF_COLOR;
393 if( config.backgroundColor != NULL ) { // window-frame background (only seen if nonshaped) ?
394 main_xpm[1] = XpmColorLine( config.backgroundColor, main_xpm[1],
395 freeMem && ( config.colorsUsed & BGR_COLOR ));
396 config.colorsUsed |= BGR_COLOR;
399 if( freeMem )
401 XFreePixmap( DADisplay, mainPixmap );
402 XFreePixmap( DADisplay, mainPixmap_mask );
403 XFreePixmap( DADisplay, symbolsPixmap );
404 XFreePixmap( DADisplay, charsPixmap );
405 XFreePixmap( DADisplay, numbersPixmap );
406 XFreePixmap( DADisplay, buttonPixmap );
408 if( tickerGC != NULL )
410 XFreeGC( DADisplay, tickerGC );
411 tickerGC = NULL;
412 if( tickerFS != NULL ) {
413 XFreeFont( DADisplay, tickerFS );
414 tickerFS = NULL;
419 DAMakePixmapFromData( main_xpm, &mainPixmap, &mainPixmap_mask, &dummy, &dummy );
420 DAMakePixmapFromData( symbols_xpm, &symbolsPixmap, NULL, &dummy, &dummy );
421 DAMakePixmapFromData( chars_xpm, &charsPixmap, NULL, &dummy, &dummy );
422 DAMakePixmapFromData( numbers_xpm, &numbersPixmap, NULL, &dummy, &dummy );
423 DAMakePixmapFromData( button_xpm, &buttonPixmap, NULL, &dummy, &dummy );
425 if( config.useX11Font != NULL )
427 XRectangle clipRect;
429 if( config.fontColor != NULL )
430 values.foreground = DAGetColor( config.fontColor );
431 else
432 values.foreground = DAGetColor( "#D3D3D3" );
434 tickerFS = XLoadQueryFont( DADisplay, config.useX11Font );
435 if( tickerFS == NULL )
436 ABORT( "Cannot load font \"%s\"", config.useX11Font );
438 values.font = tickerFS->fid;
439 tickerGC = XCreateGC( DADisplay, DAWindow, GCForeground | GCFont,
440 &values );
441 clipRect.x = 6;
442 clipRect.y = 19;
443 clipRect.width = 52;
444 clipRect.height = 23;
446 XSetClipRectangles( DADisplay, tickerGC, 0, 0, &clipRect, 1, Unsorted );
449 if( config.noshape ) // non-shaped dockapp ?
450 DASetShape( None );
451 else
452 DASetShape( mainPixmap_mask );
455 void MarkName( unsigned long checksum )
457 name_t *name;
459 for( name = names; name != NULL; name = name->next ) {
460 if( name->checksum == checksum ) {
461 name->flag |= FLAG_READ;
462 if( config.newMailsOnly )
463 numMails--;
464 break;
469 void DetermineState()
471 name_t *name;
473 for( name = names; name != NULL; name = name->next )
474 if(!( name->flag & FLAG_READ )) {
475 state = STATE_NEWMAIL;
477 if( config.cmdOnMail != NULL ) {
478 int ret = system( config.cmdOnMail );
480 if( ret == 127 || ret == -1 )
481 WARNING( "execution of command \"%s\" failed.\n", config.cmdOnMail );
484 break;
488 void ReadChecksumFile()
490 FILE *f = fopen( config.checksumFileName, "rb" );
491 if( f != NULL ) while( !feof( f )) {
492 unsigned long checksum;
493 if( fread( &checksum, sizeof(long), 1, f ) != 1 )
494 continue;
496 MarkName( checksum );
497 } else
498 return;
500 fclose( f );
503 void WriteChecksumFile( bool writeAll )
505 FILE *f;
506 TRACE( "writing checksums:" );
508 if(( f = fopen( config.checksumFileName, "wb" )) != NULL ) {
509 name_t *name;
510 for( name = names; name != NULL; name = name->next ) {
511 if( writeAll || (name->flag & FLAG_READ)) {
512 fwrite( &name->checksum, sizeof(long), 1, f );
513 TRACE( " %X", name->checksum );
516 } else
517 return;
519 TRACE( "\n" );
521 fclose( f );
524 void UpdateChecksum( unsigned long *checksum, const char *buf )
526 if( buf != NULL ) {
527 unsigned int i, len = strlen( buf );
529 for( i = 0; i < len; ++i )
530 *checksum += buf[i] << (( i % sizeof(long) ) * 8 );
534 void RemoveChecksumFile()
536 TRACE( "removing checksum-file\n" );
537 remove( config.checksumFileName );
540 // dummy needed because this func is a signal-handler
541 void TimerHandler( int dummy )
543 static int checkMail = 0;
545 if( readConfigFile ) {
546 readConfigFile = false;
547 UpdateConfiguration();
548 checkMail = 0;
549 forceRead = true;
552 if( checkMail == 0 )
554 TRACE( "checking for new mail...\n" );
556 if( isMaildir )
557 CheckMaildir();
558 else
559 CheckMBox();
562 UpdatePixmap( checkMail % config.fps < config.fps/2 );
564 if( ++checkMail >= config.fps * config.checkInterval )
565 checkMail = 0;
568 void CheckMBox()
570 struct stat fileStat;
572 // error retrieving file-stats -> no/zero-size file and no new/read mails
573 // available
574 if( stat( config.mailBox, &fileStat ) == -1 || fileStat.st_size == 0 ) {
575 if( state != STATE_NOMAIL ) {
576 state = STATE_NOMAIL;
577 ClearAllNames();
578 RemoveChecksumFile();
579 forceRedraw = true;
581 } else {
582 // file has changed -> new mails arrived or some mails removed
583 if( lastModifySeconds != fileStat.st_mtime || forceRead ) {
584 forceRead = false;
585 ParseMBoxFile( &fileStat );
586 stat( config.mailBox, &fileStat );
587 forceRedraw = true;
588 // file has accessed (read) -> mark all mails as "read", because
589 // it cannot be decided which mails the user has read...
590 } else if( lastAccessSeconds != fileStat.st_atime ) {
591 state = STATE_READMAIL;
592 WriteChecksumFile( true );
593 if( config.newMailsOnly ) {
594 numMails = 0;
595 SetMailFlags( FLAG_READ );
597 forceRedraw = true;
600 lastModifySeconds = fileStat.st_mtime;
601 lastAccessSeconds = fileStat.st_atime;
605 void CheckMaildir()
607 DIR *dir = NULL;
608 int lastState = state;
609 int lastMailCount = numMails;
611 if( forceRead ) {
612 forceRead = false;
613 ClearAllNames();
614 TRACE( "all names cleared\n" );
617 state = STATE_NOMAIL;
619 if(( dir = opendir( config.mailBox )) != NULL )
621 struct dirent *dirEnt = NULL;
622 //bool writeChecksums = false;
623 name_t *name;
625 for( name = names; name != NULL; name = name->next )
626 name->visited = false;
628 while(( dirEnt = readdir( dir )) != NULL )
630 char *fullName = FileNameConcat( config.mailBox, dirEnt->d_name );
631 struct stat fileStat;
633 if( !stat( fullName, &fileStat ) == 0 ) {
634 WARNING( "Can't stat file/path \"%s\"\n", fullName );
635 free( fullName );
636 continue;
639 if( S_ISDIR( fileStat.st_mode ))
641 if( strcmp( dirEnt->d_name, "new" ) == 0 ) {
642 if( TraverseDirectory( fullName, true ) > 0 )
643 state = STATE_NEWMAIL;
645 else if( strcmp( dirEnt->d_name, "cur" ) == 0 ) {
646 if( TraverseDirectory( fullName, false ) > 0 )
647 if( state != STATE_NEWMAIL )
648 state = STATE_READMAIL;
650 // directories ".", ".." and "tmp" discarded
653 closedir( dir );
654 CleanupNames();
655 } else
656 WARNING( "can't open directory \"%s\"\n", config.mailBox );
658 if( lastState != state || lastMailCount != numMails )
659 forceRedraw = true;
662 int TraverseDirectory( const char *name, bool isNewMail )
664 DIR *dir = NULL;
665 int mails = 0;
667 if(( dir = opendir( name )) != NULL )
669 struct dirent *dirEnt = NULL;
671 while(( dirEnt = readdir( dir )) != NULL )
673 char *fullName = FileNameConcat( name, dirEnt->d_name );
674 struct stat fileStat;
675 unsigned long checksum = 0;
676 name_t *name;
678 if( !stat( fullName, &fileStat ) == 0 ) {
679 WARNING( "Can't stat file/path \"%s\"\n", fullName );
680 free( fullName );
681 continue;
684 if( !S_ISDIR( fileStat.st_mode ))
686 TRACE( "found email-file \"%s\"\n", fullName );
687 UpdateChecksum( &checksum, dirEnt->d_name );
689 if(( name = GetMail( checksum )) == NULL )
691 TRACE( "-> new file - parsing it\n" );
692 ParseMaildirFile( fullName, checksum, &fileStat, isNewMail );
694 else {
695 name->flag = isNewMail ? FLAG_INITIAL : FLAG_READ;
696 name->visited = true;
698 ++mails;
703 closedir( dir );
705 return mails;
708 char *FileNameConcat( const char *path, const char *fileName )
710 int len1 = strlen( path );
711 int len2 = strlen( fileName );
712 char *buf;
714 if( path[len1-1] == '/' )
715 --len1;
717 buf = (char *)malloc( len1 + len2 + 2 );
719 memcpy( buf, path, len1 );
720 buf[len1] = '/';
721 memcpy( &buf[len1+1], fileName, len2 );
722 buf[len1+len2+1] = '\0';
724 return buf;
727 name_t *GetMail( unsigned long checksum )
729 name_t *name;
731 for( name = names; name != NULL; name = name->next )
732 if( name->checksum == checksum )
733 return name;
735 return NULL;
738 void UpdatePixmap( bool flashMailSymbol )
740 int drawCount, i;
742 if( !forceRedraw && !HasTickerWork() )
743 return;
745 forceRedraw = false;
747 XCopyArea( DADisplay, mainPixmap, outPixmap, DAGC,
748 0, 0, 64, 64, 0, 0 );
750 if( numMails > 999 )
752 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
753 50, 0, 5, 9, 6, 49 );
754 drawCount = 999;
755 } else
756 drawCount = numMails;
758 for( i = 0; i < 3; ++i, drawCount /= 10 )
760 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
761 (drawCount%10)*5, 0, 5, 9, 24-i*6, 49 );
762 if( drawCount <= 9 )
763 break;
766 if( buttonPressed )
768 XCopyArea( DADisplay, buttonPixmap, outPixmap, DAGC,
769 0, 0, 23, 11, 36, 48 );
772 switch( state ) {
773 case STATE_NEWMAIL:
774 if( flashMailSymbol )
775 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
776 13, 0, 37, 12, 20, 7 );
777 case STATE_READMAIL:
778 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
779 0, 0, 13, 12, 7, 7 );
781 if( config.useX11Font == NULL )
782 DrawTickerBuildinFont();
783 else
784 DrawTickerX11Font();
785 default: // make compiler happy
789 DASetPixmap( outPixmap );
792 void ParseMBoxFile( struct stat *fileStat )
794 char buf[1024];
795 struct utimbuf timeStruct;
796 int fromFound = 0;
797 FILE *f = fopen( config.mailBox, "rt" );
798 unsigned long checksum;
800 state = STATE_READMAIL;
801 ClearAllNames();
803 numMails = 0;
805 if( f == NULL ) {
806 WARNING( "can't open mbox \"%s\"\n", config.mailBox );
807 return;
810 while( fgets( buf, 1024, f ) != NULL )
812 if( strncmp( buf, "From ", 5 ) == 0 ) {
813 fromFound = 1;
814 checksum = 0;
815 continue;
818 if( fromFound )
819 UpdateChecksum( &checksum, buf );
821 if( fromFound && strncasecmp( buf, "from: ", 6 ) == 0 )
823 if( SkipSender( buf+6 ))
824 goto NEXTMAIL;
826 InsertName( ParseFromField( buf+6 ), checksum, FLAG_INITIAL );
828 ++numMails;
829 fromFound = 0;
830 checksum = 0;
831 } else if( config.considerStatusField && strncasecmp( buf, "status: ", 8 ) == 0 &&
832 strstr( buf+8, config.readStatus ) == NULL )
834 RemoveLastName();
835 --numMails;
837 NEXTMAIL:
841 fclose( f );
842 ReadChecksumFile();
844 DetermineState();
846 timeStruct.actime = fileStat->st_atime;
847 timeStruct.modtime = fileStat->st_mtime;
848 utime( config.mailBox, &timeStruct );
851 void ParseMaildirFile( const char *fileName, unsigned long checksum,
852 struct stat *fileStat, bool isNewMail )
854 char buf[1024];
855 struct utimbuf timeStruct;
856 FILE *f = fopen( fileName, "rt" );
858 if( f == NULL )
860 WARNING( "can't open maildir file \"%s\"\n", fileName );
861 return;
864 while( fgets( buf, 1024, f ) != NULL )
866 if( strncasecmp( buf, "from: ", 6 ) == 0 )
868 if( SkipSender( buf+6 ))
869 break;
871 InsertName( ParseFromField( buf+6 ), checksum,
872 isNewMail ? FLAG_INITIAL : FLAG_READ );
874 //++numMails;
878 fclose( f );
880 timeStruct.actime = fileStat->st_atime;
881 timeStruct.modtime = fileStat->st_mtime;
882 utime( fileName, &timeStruct );
885 char *ParseFromField( char *buf )
887 parse_state_t state = STATE_FULLNAME;
888 int fullNameEncoded = 0;
889 int saveAtCharPos = -1;
890 char *fullName;
891 char *addressName;
892 char *atChar = NULL;
893 char *c;
894 int maxLen = strlen( buf ) + 1;
895 char *comment;
897 // FIXME: Uhm, those mallocs might fail...
899 fullName = malloc( maxLen );
900 addressName = malloc( maxLen );
901 comment = malloc( maxLen );
903 memset( fullName, '\0', maxLen );
904 memset( addressName, '\0', maxLen );
905 memset( comment, '\0', maxLen );
907 // FIXME: Don't do that "encoded" dance. It's not intended by
908 // RFC2047, and it's better to just do it in the end.
909 // Cleaner.
911 for( c = buf; *c != '\0'; ++c )
913 switch( state ) {
914 case STATE_FULLNAME:
916 switch( *c ) {
917 case '"':
918 state = STATE_QUOTED_FULLNAME;
919 continue;
920 case '<':
921 if( fullName[0] != '\0' &&
922 fullName[ strlen( fullName ) - 1 ] == ' ' )
923 fullName[ strlen( fullName ) - 1 ] = '\0';
924 state = STATE_ADDRESS;
925 continue;
926 case '@':
927 saveAtCharPos = strlen( fullName );
928 fullName[ saveAtCharPos ] = *c;
929 continue;
930 case '(':
931 state = STATE_COMMENT;
932 continue;
933 case '=':
934 if( *(c+1) == '?' ) {
935 ++c;
936 fullNameEncoded = 1;
937 state = STATE_ENCODED_FULLNAME;
938 continue;
939 } // else do the default action
940 default:
941 fullName[ strlen( fullName ) ] = *c;
943 continue;
945 case STATE_QUOTED_FULLNAME:
947 switch( *c ) {
948 case '\\':
949 fullName[ strlen( fullName ) ] = *(++c);
950 continue;
951 case '"':
952 state = STATE_FULLNAME;
953 continue;
954 default:
955 fullName[ strlen( fullName ) ] = *c;
957 continue;
959 case STATE_ENCODED_FULLNAME:
961 switch( *c ) {
962 case '?':
963 if( *(c+1) == '=' ) {
964 ++c;
965 state = STATE_FULLNAME;
966 continue;
968 default:
969 ; // do nothing... COMING SOON: decode at least latin1
971 continue;
973 case STATE_ADDRESS:
975 switch( *c ) {
976 case '"':
977 state = STATE_QUOTED_ADDRESS;
978 case '>':
979 case '\n':
980 // FIXME: Shouldn't it break here?
981 // Since the address is finished?
982 continue;
983 case '@':
984 atChar = &addressName[ strlen( addressName ) ];
985 *atChar = *c;
986 continue;
987 default:
988 addressName[ strlen( addressName ) ] = *c;
990 continue;
992 case STATE_QUOTED_ADDRESS:
994 switch( *c ) {
995 case '"':
996 state = STATE_ADDRESS;
997 continue;
998 case '\\':
999 addressName[ strlen( addressName ) ] = *(++c);
1000 continue;
1001 default:
1002 addressName[ strlen( addressName ) ] = *c;
1004 continue;
1005 case STATE_COMMENT:
1006 switch( *c ) {
1007 case ')':
1008 state = STATE_FULLNAME;
1009 continue;
1010 default:
1011 comment[ strlen( comment ) ] = *c;
1012 continue;
1014 continue;
1018 if (*comment) {
1019 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1020 // Comment seen: if there's an address, append to
1021 // fullname. If no address, copy fullname to address
1022 // and comment to fullname.
1023 if (*addressName) {
1024 strcat(fullName, "(");
1025 strcat(fullName, comment);
1026 strcat(fullName, ")");
1027 } else {
1028 strcpy(addressName, fullName);
1029 strcpy(fullName, comment);
1033 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1035 // what name should be tickered
1036 if( config.tickerMode == TICKER_FAMILYNAME && fullName[0] != '\0' && !fullNameEncoded ) {
1037 free( addressName );
1038 return fullName;
1039 } else {
1040 if( state == STATE_FULLNAME ) {
1041 strcpy( addressName, fullName );
1042 if( saveAtCharPos != -1 )
1043 atChar = &addressName[saveAtCharPos];
1045 if( config.tickerMode == TICKER_NICKNAME ) {
1046 if( atChar != NULL )
1047 *atChar = '\0';
1049 free( fullName );
1050 return addressName;
1054 bool SkipSender( char *address )
1056 char **skipName;
1057 int len = strlen( address );
1059 // remove trailing '\n' got from fgets
1060 if( address[len-1] == '\n' )
1061 address[len-1] = '\0';
1063 for( skipName = config.skipNames;
1064 skipName != NULL && *skipName != NULL; skipName++ )
1066 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName, address );
1068 // call libc-fnmatch (wildcard-match :-) !
1069 if( !fnmatch( *skipName, address, 0 )) {
1070 TRACE( "skipping sender \"%s\"\n", *skipName );
1071 return true;
1075 return false;
1078 void InsertName( char *name, unsigned long checksum, flag_t flag )
1080 name_t *item;
1082 TRACE( "insertName: %X, \"%s\"\n", checksum, name );
1083 item = (name_t *)malloc( sizeof( name_t ));
1084 item->name = name; /*strdup( name );*/
1085 item->checksum = checksum;
1086 item->flag = flag;
1087 item->visited = true;
1088 item->next = names;
1089 names = item;
1091 namesChanged = true;
1094 void RemoveLastName()
1096 if( names != NULL ) {
1097 name_t *name = names;
1098 names = names->next;
1099 free( name );
1103 void ClearAllNames()
1105 name_t *name, *nextName;
1107 for( name = names; name != NULL; name = nextName ) {
1108 nextName = name->next;
1110 free( name->name );
1111 free( name );
1114 names = NULL;
1115 numMails = 0;
1117 namesChanged = true;
1120 void SetMailFlags( flag_t flag )
1122 name_t *name;
1124 for( name = names; name != NULL; name = name->next )
1125 name->flag |= flag;
1128 void DrawTickerX11Font()
1130 // 49x21+7+20 out-drawable size
1132 static int insertAt;
1134 if( curTickerName == NULL || namesChanged )
1136 for( curTickerName = names;
1137 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1138 curTickerName = curTickerName->next );
1140 if( curTickerName == NULL )
1141 return;
1143 namesChanged = false;
1144 insertAt = 54;
1147 XDrawString( DADisplay, outPixmap, tickerGC, insertAt,
1148 41-tickerFS->max_bounds.descent,
1149 curTickerName->name, strlen( curTickerName->name ));
1151 --insertAt;
1153 if( insertAt < -XTextWidth( tickerFS, curTickerName->name,
1154 strlen( curTickerName->name )) + 6 )
1156 do {
1157 curTickerName = curTickerName->next;
1158 } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ));
1160 if( curTickerName != NULL ) {
1161 insertAt = 54;
1166 void DrawTickerBuildinFont()
1168 // 49x21+7+20 out-drawable size
1169 // 14x21 font-character size
1171 static int insertAt;
1172 static int takeItFrom;
1174 int leftSpace;
1175 int drawTo;
1176 unsigned char *currentChar;
1178 if( names == NULL )
1179 return;
1181 if( curTickerName == NULL || namesChanged ) {
1183 for( curTickerName = names;
1184 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1185 curTickerName = curTickerName->next );
1187 if( curTickerName == NULL )
1188 return;
1190 insertAt = 57;
1191 takeItFrom = 0;
1192 namesChanged = false;
1195 leftSpace = takeItFrom % 14;
1197 for( currentChar = (unsigned char *)&curTickerName->name[takeItFrom/14],
1198 drawTo = insertAt; *currentChar != '\0'; ++currentChar )
1201 int outChar = (*currentChar < 32 || *currentChar >= 128) ? '?' :
1202 *currentChar;
1203 int charWidth = 57-drawTo >= 14 ? 14 - leftSpace : 57-drawTo;
1205 XCopyArea( DADisplay, charsPixmap, outPixmap, DAGC,
1206 (outChar-32)*14+leftSpace, 0, charWidth, 21, drawTo, 20 );
1208 leftSpace = 0;
1209 drawTo += charWidth;
1211 if( drawTo > 57 )
1212 break;
1215 if( --insertAt < 7 ) {
1216 insertAt = 7;
1217 takeItFrom++;
1219 if( takeItFrom/14 >= strlen( curTickerName->name )) {
1221 do {
1222 curTickerName = curTickerName->next;
1223 } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ));
1225 if( curTickerName != NULL ) {
1226 takeItFrom = 0;
1227 insertAt = 57;
1233 void ButtonPressed( int button, int state, int x, int y )
1235 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) {
1236 buttonPressed = true;
1237 forceRedraw = true;
1238 } else
1239 // reread the config file
1240 readConfigFile = true;
1243 void ButtonReleased( int button, int state, int x, int y )
1245 buttonPressed = false;
1246 forceRedraw = true;
1248 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) {
1249 int ret = system( config.runCmd );
1251 if( ret == 127 || ret == -1 )
1252 WARNING( "execution of command \"%s\" failed.\n", config.runCmd );
1256 void GetHexColorString( const char *colorName, char *xpmLine )
1258 XColor color;
1260 if( XParseColor( DADisplay,
1261 DefaultColormap( DADisplay, DefaultScreen( DADisplay )),
1262 colorName, &color ))
1264 sprintf( xpmLine, "%02X%02X%02X", color.red>>8, color.green>>8,
1265 color.blue>>8 );
1266 } else
1267 WARNING( "unknown colorname: \"%s\"\n", colorName );
1270 char *XpmColorLine( const char *colorName, char *colorLine, bool disposeLine )
1272 char *newLine = strdup( colorLine );
1273 char *from = strrchr( newLine, '#' );
1275 if( from == NULL && !strcasecmp( &colorLine[ strlen( colorLine ) - 4 ], "none" )) {
1276 // if no # found, it should be a None-color line
1277 free( newLine );
1278 newLine = malloc( 12 );
1279 strcpy( newLine, " \tc #" );
1280 newLine[11] = '\0';
1281 from = newLine + 4;
1284 if( disposeLine )
1285 free( colorLine );
1287 GetHexColorString( colorName, from+1 );
1289 return newLine;
1292 void UpdateConfiguration()
1294 struct stat fileStat;
1296 TRACE( "reading configuration file...\n" );
1298 ReadConfigFile( true );
1300 // if no path/name to an mbox or maildir inbox directory was given,
1301 // use the environment
1302 if( config.mailBox == NULL )
1303 config.mailBox = getenv( "MAIL" );
1305 // mbox or maildir ?
1306 if( config.mailBox != NULL && stat( config.mailBox, &fileStat ) == 0 )
1307 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
1308 else
1309 isMaildir = false;
1311 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
1313 PreparePixmaps( true );
1315 SetTimer();
1318 void CleanupNames()
1320 name_t *name, *last = NULL, *nextName;
1322 numMails = 0;
1324 for( name = names; name != NULL; name = nextName )
1326 nextName = name->next;
1328 if( !name->visited ) {
1329 if( last == NULL )
1330 names = name->next;
1331 else
1332 last->next = name->next;
1334 free( name );
1335 } else {
1336 last = name;
1338 if( !config.newMailsOnly || (name->flag & FLAG_READ) == 0 )
1339 ++numMails;
1344 bool HasTickerWork()
1346 name_t *nextTickerName;
1348 if( names == NULL )
1349 return false;
1351 if( curTickerName == NULL || namesChanged ) {
1353 for( nextTickerName = names;
1354 nextTickerName != NULL && ( config.newMailsOnly && ( nextTickerName->flag & FLAG_READ ));
1355 nextTickerName = nextTickerName->next );
1357 if( nextTickerName == NULL )
1358 return false;
1361 return true;