wmail: fixed warnings about unused parameters.
[dockapps.git] / wmail / src / wmail.c
blob8eed554521963f3fd7e9ad821c629c38e7a47120
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 #ifdef HAVE_CONFIG_H
38 #ifndef CONFIG_H_INCLUDED
39 #include "../config.h"
40 #define CONFIG_H_INCLUDED
41 #endif
42 #endif
44 #include <ctype.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <stdarg.h>
48 #include <string.h>
49 #ifdef HAVE_STRINGS_H
50 #include <strings.h>
51 #endif
52 #include <signal.h>
53 #include <utime.h>
54 #include <fnmatch.h>
55 #include <sys/time.h>
56 #include <sys/stat.h>
57 #include <dirent.h>
58 #include <X11/Xlib.h>
59 #include <libdockapp/dockapp.h>
61 #include "common.h"
62 #include "config.h"
64 // pixmaps
65 #ifdef USE_DELT_XPMS
66 #include "xpm_delt/main.xpm"
67 #include "xpm_delt/symbols.xpm"
68 #include "xpm_delt/numbers.xpm"
69 #include "xpm_delt/button.xpm"
70 #include "xpm_delt/chars.xpm"
71 #else
72 #include "xpm/main.xpm"
73 #include "xpm/symbols.xpm"
74 #include "xpm/numbers.xpm"
75 #include "xpm/button.xpm"
76 #include "xpm/chars.xpm"
77 #endif
80 ///////////////////////////////////////////////////////////////////////////////
81 // typedefs
83 typedef enum {
84 FLAG_INITIAL = 0,
85 FLAG_READ = 1
86 } flag_t;
88 typedef struct _name_t {
89 char *name;
90 unsigned long checksum;
91 flag_t flag;
92 bool visited;
93 struct _name_t *next;
94 } name_t;
96 typedef enum {
97 STATE_NOMAIL,
98 STATE_NEWMAIL,
99 STATE_READMAIL
100 } mail_state_t;
102 typedef enum {
103 STATE_ADDRESS,
104 STATE_QUOTED_ADDRESS,
105 STATE_FULLNAME,
106 STATE_QUOTED_FULLNAME,
107 STATE_ENCODED_FULLNAME,
108 STATE_COMMENT
109 } parse_state_t;
112 ///////////////////////////////////////////////////////////////////////////////
113 // data
115 static unsigned long lastTimeOut;
116 static sig_atomic_t caughtSig;
117 static mail_state_t state;
118 static unsigned numMails;
119 static bool namesChanged;
120 static bool buttonPressed;
121 static bool readConfigFile;
122 static bool isMaildir;
123 static bool forceRead;
124 static bool forceRedraw = true;
125 static time_t lastModifySeconds;
126 static time_t lastAccessSeconds;
127 static Pixmap mainPixmap;
128 static Pixmap mainPixmap_mask;
129 static Pixmap symbolsPixmap;
130 static Pixmap charsPixmap;
131 static Pixmap numbersPixmap;
132 static Pixmap buttonPixmap;
133 static Pixmap outPixmap;
134 static GC tickerGC;
135 static XFontStruct *tickerFS;
136 static name_t *names;
137 static name_t *curTickerName;
139 static DAProgramOption options[] = {
140 {"-display", NULL, "display to use", DOString, False, {&config.display}},
141 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
142 DOString, False, {&config.runCmd} },
143 {"-i", "--intervall",
144 "number of secs between mail-status updates (1 is default)", DONatural,
145 False, {&config.checkInterval} },
146 {"-f", "--familyname", "tickers the family-name if available", DONone,
147 False, {NULL} },
148 {"-fps", "--frames", "ticker frames per second", DONatural,
149 False, {&config.fps} },
150 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone,
151 False, {NULL} },
152 {"-sc", "--symbolcolor", "symbol color-name",
153 DOString, False, {&config.symbolColor} },
154 {"-fc", "--fontcolor", "ticker-font color-name",
155 DOString, False, {&config.fontColor} },
156 {"-bc", "--backcolor", "backlight color-name",
157 DOString, False, {&config.backColor} },
158 {"-oc", "--offcolor", "off-light color-name",
159 DOString, False, {&config.offlightColor} },
160 {"-bg", "--background", "frame-background for non-shaped window",
161 DOString, False, {&config.backgroundColor} },
162 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
163 DONone, False, {NULL} },
164 {"-n", "--new", "forces wmail to show new mail exclusively", DONone, False, {NULL} },
165 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString, False, {&config.mailBox} },
166 {"-e", "--execute", "command to execute when receiving a new mail", DOString, False, {&config.cmdOnMail} },
167 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone, False, {NULL} },
168 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString, False, {&config.readStatus} },
169 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString, False, {&config.useX11Font} }
173 ///////////////////////////////////////////////////////////////////////////////
174 // prototypes
176 static void PreparePixmaps( bool freeThemFirst );
177 static void ExitHandler( int sig );
178 static void TimedOut( void );
179 static void CheckTimeOut( bool force );
180 static void CheckMBox( void );
181 static void CheckMaildir( void );
182 static int TraverseDirectory( const char *name, bool isNewMail );
183 static name_t *GetMail( unsigned long checksum );
184 static void UpdatePixmap( bool flashMailSymbol );
185 static void ParseMBoxFile( struct stat *fileStat );
186 static void ParseMaildirFile( const char *fileName, unsigned long checksum,
187 struct stat *fileStat, bool isNewMail );
188 static char *ParseFromField( char *buf );
189 static bool SkipSender( char *address );
190 static void InsertName( char *name, unsigned long checksum, flag_t flag );
191 static void RemoveLastName( void );
192 static void ClearAllNames( void );
193 static void DrawTickerX11Font( void );
194 static void DrawTickerBuildinFont( void );
195 static void ButtonPressed( int button, int state, int x, int y );
196 static void ButtonReleased( int button, int state, int x, int y );
197 static char *XpmColorLine( const char *colorName, char *colorLine,
198 bool disposeLine );
199 static void ReadChecksumFile( void );
200 static void WriteChecksumFile( bool writeAll );
201 static void UpdateChecksum( unsigned long *checksum, const char *buf );
202 static void RemoveChecksumFile( void );
203 static void SetMailFlags( flag_t flag );
204 static void MarkName( unsigned long checksum );
205 static void DetermineState( void );
206 static void UpdateConfiguration( void );
207 static void CleanupNames( void );
208 static bool HasTickerWork( void );
211 ///////////////////////////////////////////////////////////////////////////////
212 // implementation
215 int main( int argc, char **argv )
217 char *usersHome;
218 struct sigaction sa = { .sa_handler = ExitHandler };
219 struct stat fileStat;
220 XTextProperty windowName;
221 char *name = argv[0];
222 DACallbacks callbacks = { NULL, &ButtonPressed, &ButtonReleased,
223 NULL, NULL, NULL, &TimedOut };
225 // read the config file and overide the default-settings
226 ReadConfigFile( false );
228 if( config.checksumFileName == NULL ) {
229 if(( usersHome = getenv( "HOME" )) == NULL ) {
230 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE );
231 config.checksumFileName = strdup( WMAIL_CHECKSUM_FILE );
232 } else
233 config.checksumFileName = MakePathName( usersHome, WMAIL_CHECKSUM_FILE );
236 if( config.checksumFileName == NULL )
238 WARNING( "Cannot allocate checksum file-name.\n");
239 exit( EXIT_FAILURE );
242 TRACE( "using checksum-file \"%s\"\n", config.checksumFileName );
244 // parse cmdline-args and overide defaults and cfg-file settings
245 DAParseArguments( argc, argv, options,
246 sizeof(options) / sizeof(DAProgramOption),
247 WMAIL_NAME, WMAIL_VERSION );
249 if( options[0].used )
250 config.givenOptions |= CL_DISPLAY;
251 if( options[1].used )
252 config.givenOptions |= CL_RUNCMD;
253 if( options[2].used )
254 config.givenOptions |= CL_CHECKINTERVAL;
255 if( options[3].used ) {
256 config.givenOptions |= CL_TICKERMODE;
257 config.tickerMode = TICKER_FAMILYNAME;
259 if( options[4].used )
260 config.givenOptions |= CL_FPS;
261 if( options[5].used ) {
262 config.givenOptions |= CL_TICKERMODE;
263 config.tickerMode = TICKER_NICKNAME;
265 if( options[6].used )
266 config.givenOptions |= CL_SYMBOLCOLOR;
267 if( options[7].used )
268 config.givenOptions |= CL_FONTCOLOR;
269 if( options[8].used )
270 config.givenOptions |= CL_BACKCOLOR;
271 if( options[9].used )
272 config.givenOptions |= CL_OFFLIGHTCOLOR;
273 if( options[10].used )
274 config.givenOptions |= CL_BACKGROUNDCOLOR;
275 if( options[11].used ) {
276 config.givenOptions |= CL_NOSHAPE;
277 config.noshape = true;
279 if( options[12].used ) {
280 config.givenOptions |= CL_NEWMAILONLY;
281 config.newMailsOnly = true;
283 if( options[13].used )
284 config.givenOptions |= CL_MAILBOX;
285 if( options[14].used )
286 config.givenOptions |= CL_CMDONMAIL;
287 if( options[15].used ) {
288 config.givenOptions |= CL_CONSIDERSTATUSFIELD;
289 config.considerStatusField = true;
291 if( options[16].used )
292 config.givenOptions |= CL_READSTATUS;
293 if( options[17].used )
294 config.givenOptions |= CL_USEX11FONT;
296 if( config.mailBox == NULL )
297 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
298 else if( stat( config.mailBox, &fileStat ) == 0 )
299 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
301 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
303 // dockapp size hard wired - sorry...
304 DAInitialize( config.display, "wmail", 64, 64, argc, argv );
306 outPixmap = DAMakePixmap();
307 PreparePixmaps( false );
309 if( sigaction( SIGINT, &sa, NULL ) == -1 ) {
310 perror( "wmail error: sigaction" );
311 exit( EXIT_FAILURE );
314 if( sigaction( SIGTERM, &sa, NULL ) == -1 ) {
315 perror( "wmail error: sigaction" );
316 exit( EXIT_FAILURE );
319 DASetCallbacks( &callbacks );
320 DASetTimeout( 1000 / config.fps );
322 XStringListToTextProperty( &name, 1, &windowName );
323 XSetWMName( DADisplay, DAWindow, &windowName );
325 UpdatePixmap( false );
326 DAShow();
328 DAEventLoop();
330 return 0;
333 static void PreparePixmaps( bool freeMem )
335 // simple recoloring of the raw xpms befor creating Pixmaps of them
336 // this works as long as you don't "touch" the images...
338 unsigned short dummy;
339 XGCValues values;
341 if( config.symbolColor != NULL ) { // symbol color ?
342 symbols_xpm[2] = XpmColorLine( config.symbolColor, symbols_xpm[2],
343 freeMem && ( config.colorsUsed & SYM_COLOR ));
344 config.colorsUsed |= SYM_COLOR;
345 } else {
346 symbols_xpm[2] = XpmColorLine( "#20B2AA", symbols_xpm[2],
347 freeMem && ( config.colorsUsed & SYM_COLOR ));
348 config.colorsUsed |= SYM_COLOR;
351 if( config.fontColor != NULL ) { // font color ?
352 chars_xpm[3] = XpmColorLine( config.fontColor, chars_xpm[3],
353 freeMem && ( config.colorsUsed & FNT_COLOR ));
354 numbers_xpm[3] = XpmColorLine( config.fontColor, numbers_xpm[3],
355 freeMem && ( config.colorsUsed & FNT_COLOR ));
356 config.colorsUsed |= FNT_COLOR;
357 } else {
358 chars_xpm[3] = XpmColorLine( "#D3D3D3", chars_xpm[3],
359 freeMem && ( config.colorsUsed & FNT_COLOR ));
360 numbers_xpm[3] = XpmColorLine( "#D3D3D3", numbers_xpm[3],
361 freeMem && ( config.colorsUsed & FNT_COLOR ));
362 config.colorsUsed |= FNT_COLOR;
365 if( config.backColor != NULL ) { // backlight color ?
366 main_xpm[3] = XpmColorLine( config.backColor, main_xpm[3],
367 freeMem && ( config.colorsUsed & BCK_COLOR ));
368 symbols_xpm[3] = XpmColorLine( config.backColor, symbols_xpm[3],
369 freeMem && ( config.colorsUsed & BCK_COLOR ));
370 chars_xpm[2] = XpmColorLine( config.backColor, chars_xpm[2],
371 freeMem && ( config.colorsUsed & BCK_COLOR ));
372 numbers_xpm[2] = XpmColorLine( config.backColor, numbers_xpm[2],
373 freeMem && ( config.colorsUsed & BCK_COLOR ));
374 config.colorsUsed |= BCK_COLOR;
375 } else {
376 main_xpm[3] = XpmColorLine( "#282828", main_xpm[3],
377 freeMem && ( config.colorsUsed & BCK_COLOR ));
378 symbols_xpm[3] = XpmColorLine( "#282828", symbols_xpm[3],
379 freeMem && ( config.colorsUsed & BCK_COLOR ));
380 chars_xpm[2] = XpmColorLine( "#282828", chars_xpm[2],
381 freeMem && ( config.colorsUsed & BCK_COLOR ));
382 numbers_xpm[2] = XpmColorLine( "#282828", numbers_xpm[2],
383 freeMem && ( config.colorsUsed & BCK_COLOR ));
384 config.colorsUsed |= BCK_COLOR;
387 if( config.offlightColor != NULL ) { // off-light color ?
388 main_xpm[2] = XpmColorLine( config.offlightColor, main_xpm[2],
389 freeMem && ( config.colorsUsed & OFF_COLOR ));
390 numbers_xpm[4] = XpmColorLine( config.offlightColor, numbers_xpm[4],
391 freeMem && ( config.colorsUsed & OFF_COLOR ));
392 config.colorsUsed |= OFF_COLOR;
393 } else {
394 main_xpm[2] = XpmColorLine( "#000000", main_xpm[2],
395 freeMem && ( config.colorsUsed & OFF_COLOR ));
396 numbers_xpm[4] = XpmColorLine( "#000000", numbers_xpm[4],
397 freeMem && ( config.colorsUsed & OFF_COLOR ));
398 config.colorsUsed |= OFF_COLOR;
401 if( config.backgroundColor != NULL ) { // window-frame background (only seen if nonshaped) ?
402 main_xpm[1] = XpmColorLine( config.backgroundColor, main_xpm[1],
403 freeMem && ( config.colorsUsed & BGR_COLOR ));
404 config.colorsUsed |= BGR_COLOR;
407 if( freeMem )
409 XFreePixmap( DADisplay, mainPixmap );
410 XFreePixmap( DADisplay, mainPixmap_mask );
411 XFreePixmap( DADisplay, symbolsPixmap );
412 XFreePixmap( DADisplay, charsPixmap );
413 XFreePixmap( DADisplay, numbersPixmap );
414 XFreePixmap( DADisplay, buttonPixmap );
416 if( tickerGC != NULL )
418 XFreeGC( DADisplay, tickerGC );
419 tickerGC = NULL;
420 if( tickerFS != NULL ) {
421 XFreeFont( DADisplay, tickerFS );
422 tickerFS = NULL;
427 DAMakePixmapFromData( main_xpm, &mainPixmap, &mainPixmap_mask, &dummy, &dummy );
428 DAMakePixmapFromData( symbols_xpm, &symbolsPixmap, NULL, &dummy, &dummy );
429 DAMakePixmapFromData( chars_xpm, &charsPixmap, NULL, &dummy, &dummy );
430 DAMakePixmapFromData( numbers_xpm, &numbersPixmap, NULL, &dummy, &dummy );
431 DAMakePixmapFromData( button_xpm, &buttonPixmap, NULL, &dummy, &dummy );
433 if( config.useX11Font != NULL )
435 XRectangle clipRect;
437 if( config.fontColor != NULL )
438 values.foreground = DAGetColor( config.fontColor );
439 else
440 values.foreground = DAGetColor( "#D3D3D3" );
442 tickerFS = XLoadQueryFont( DADisplay, config.useX11Font );
443 if( tickerFS == NULL )
444 ABORT( "Cannot load font \"%s\"", config.useX11Font );
446 values.font = tickerFS->fid;
447 tickerGC = XCreateGC( DADisplay, DAWindow, GCForeground | GCFont,
448 &values );
449 clipRect.x = 6;
450 clipRect.y = 19;
451 clipRect.width = 52;
452 clipRect.height = 23;
454 XSetClipRectangles( DADisplay, tickerGC, 0, 0, &clipRect, 1, Unsorted );
457 if( config.noshape ) // non-shaped dockapp ?
458 DASetShape( None );
459 else
460 DASetShape( mainPixmap_mask );
463 static void MarkName( unsigned long checksum )
465 name_t *name;
467 for( name = names; name != NULL; name = name->next ) {
468 if( name->checksum == checksum ) {
469 name->flag |= FLAG_READ;
470 if( config.newMailsOnly )
471 numMails--;
472 break;
477 static void DetermineState( void )
479 name_t *name;
481 for( name = names; name != NULL; name = name->next )
482 if(!( name->flag & FLAG_READ )) {
483 state = STATE_NEWMAIL;
485 if( config.cmdOnMail != NULL ) {
486 int ret = system( config.cmdOnMail );
488 if( ret == 127 || ret == -1 )
489 WARNING( "execution of command \"%s\" failed.\n", config.cmdOnMail );
492 break;
496 static void ReadChecksumFile( void )
498 FILE *f = fopen( config.checksumFileName, "rb" );
499 if( f != NULL )
500 while( !feof( f )) {
501 unsigned long checksum;
502 if( fread( &checksum, sizeof(long), 1, f ) != 1 )
503 continue;
505 MarkName( checksum );
507 else
508 return;
510 fclose( f );
513 static void WriteChecksumFile( bool writeAll )
515 FILE *f;
516 TRACE( "writing checksums:" );
518 if(( f = fopen( config.checksumFileName, "wb" )) != NULL ) {
519 name_t *name;
520 for( name = names; name != NULL; name = name->next ) {
521 if( writeAll || (name->flag & FLAG_READ)) {
522 fwrite( &name->checksum, sizeof(long), 1, f );
523 TRACE( " %X", name->checksum );
526 } else
527 return;
529 TRACE( "\n" );
531 fclose( f );
534 static void UpdateChecksum( unsigned long *checksum, const char *buf )
536 if( buf != NULL ) {
537 size_t i, len = strlen( buf );
539 for( i = 0; i < len; ++i )
540 *checksum += buf[i] << (( i % sizeof(long) ) * 8 );
544 static void RemoveChecksumFile( void )
546 TRACE( "removing checksum-file\n" );
547 remove( config.checksumFileName );
550 static void ExitHandler( int sig )
552 (void) sig;
553 caughtSig = 1;
556 static void TimedOut( void )
558 if( caughtSig ) {
559 ClearAllNames();
560 ResetConfigStrings();
561 exit( EXIT_SUCCESS );
564 CheckTimeOut( true );
567 static void CheckTimeOut( bool force )
569 static int checkMail = 0;
571 struct timeval now;
572 gettimeofday(&now, NULL);
574 unsigned long nowMs = now.tv_sec * 1000UL + now.tv_usec / 1000UL;
576 if( !force && nowMs - lastTimeOut < 1000UL / config.fps )
577 return;
579 lastTimeOut = nowMs;
581 if( readConfigFile ) {
582 readConfigFile = false;
583 UpdateConfiguration();
584 checkMail = 0;
585 forceRead = true;
588 if( checkMail == 0 )
590 TRACE( "checking for new mail...\n" );
592 if( isMaildir )
593 CheckMaildir();
594 else
595 CheckMBox();
598 UpdatePixmap( checkMail % config.fps < config.fps/2 );
600 if( ++checkMail >= config.fps * config.checkInterval )
601 checkMail = 0;
604 static void CheckMBox( void )
606 struct stat fileStat;
608 // error retrieving file-stats -> no/zero-size file and no new/read mails
609 // available
610 if( stat( config.mailBox, &fileStat ) == -1 || fileStat.st_size == 0 ) {
611 if( state != STATE_NOMAIL ) {
612 state = STATE_NOMAIL;
613 ClearAllNames();
614 RemoveChecksumFile();
615 forceRedraw = true;
617 } else {
618 // file has changed -> new mails arrived or some mails removed
619 if( lastModifySeconds != fileStat.st_mtime || forceRead ) {
620 forceRead = false;
621 ParseMBoxFile( &fileStat );
622 stat( config.mailBox, &fileStat );
623 forceRedraw = true;
624 // file has accessed (read) -> mark all mails as "read", because
625 // it cannot be decided which mails the user has read...
626 } else if( lastAccessSeconds != fileStat.st_atime ) {
627 state = STATE_READMAIL;
628 WriteChecksumFile( true );
629 if( config.newMailsOnly ) {
630 numMails = 0;
631 SetMailFlags( FLAG_READ );
633 forceRedraw = true;
636 lastModifySeconds = fileStat.st_mtime;
637 lastAccessSeconds = fileStat.st_atime;
641 static void CheckMaildir( void )
643 DIR *dir = NULL;
644 int lastState = state;
645 unsigned lastMailCount = numMails;
647 if( forceRead ) {
648 forceRead = false;
649 ClearAllNames();
650 TRACE( "all names cleared\n" );
653 state = STATE_NOMAIL;
655 if(( dir = opendir( config.mailBox )) != NULL )
657 struct dirent *dirEnt = NULL;
658 //bool writeChecksums = false;
659 name_t *name;
661 for( name = names; name != NULL; name = name->next )
662 name->visited = false;
664 while(( dirEnt = readdir( dir )) != NULL )
666 char *fullName = MakePathName( config.mailBox, dirEnt->d_name );
667 struct stat fileStat;
669 if( fullName == NULL )
671 WARNING( "Cannot allocate file/path\n" );
672 break;
675 if( !stat( fullName, &fileStat ) == 0 )
676 WARNING( "Can't stat file/path \"%s\"\n", fullName );
677 else if( S_ISDIR( fileStat.st_mode ))
679 if( strcmp( dirEnt->d_name, "new" ) == 0 ) {
680 if( TraverseDirectory( fullName, true ) > 0 )
681 state = STATE_NEWMAIL;
683 else if( strcmp( dirEnt->d_name, "cur" ) == 0 ) {
684 if( TraverseDirectory( fullName, false ) > 0 )
685 if( state != STATE_NEWMAIL )
686 state = STATE_READMAIL;
688 // directories ".", ".." and "tmp" discarded
690 free( fullName );
692 closedir( dir );
693 CleanupNames();
694 } else
695 WARNING( "can't open directory \"%s\"\n", config.mailBox );
697 if( lastState != state || lastMailCount != numMails )
698 forceRedraw = true;
701 static int TraverseDirectory( const char *name, bool isNewMail )
703 DIR *dir = NULL;
704 int mails = 0;
706 if(( dir = opendir( name )) != NULL )
708 struct dirent *dirEnt = NULL;
710 while(( dirEnt = readdir( dir )) != NULL )
712 char *fullName = MakePathName( name, dirEnt->d_name );
713 struct stat fileStat;
714 unsigned long checksum = 0;
715 name_t *name;
717 if( fullName == NULL )
719 WARNING( "Cannot allocate file/path\n" );
720 break;
723 if( !stat( fullName, &fileStat ) == 0 )
724 WARNING( "Can't stat file/path \"%s\"\n", fullName );
725 else if( !S_ISDIR( fileStat.st_mode ))
727 TRACE( "found email-file \"%s\"\n", fullName );
728 UpdateChecksum( &checksum, dirEnt->d_name );
730 if(( name = GetMail( checksum )) == NULL )
732 TRACE( "-> new file - parsing it\n" );
733 ParseMaildirFile( fullName, checksum, &fileStat, isNewMail );
735 else {
736 name->flag = isNewMail ? FLAG_INITIAL : FLAG_READ;
737 name->visited = true;
739 ++mails;
741 free( fullName );
745 closedir( dir );
747 return mails;
750 static name_t *GetMail( unsigned long checksum )
752 name_t *name;
754 for( name = names; name != NULL; name = name->next )
755 if( name->checksum == checksum )
756 return name;
758 return NULL;
761 static void UpdatePixmap( bool flashMailSymbol )
763 int drawCount, i;
765 if( !forceRedraw && !HasTickerWork() )
766 return;
768 forceRedraw = false;
770 XCopyArea( DADisplay, mainPixmap, outPixmap, DAGC,
771 0, 0, 64, 64, 0, 0 );
773 if( numMails > 999 )
775 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
776 50, 0, 5, 9, 6, 49 );
777 drawCount = 999;
778 } else
779 drawCount = numMails;
781 for( i = 0; i < 3; ++i, drawCount /= 10 )
783 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
784 (drawCount%10)*5, 0, 5, 9, 24-i*6, 49 );
785 if( drawCount <= 9 )
786 break;
789 if( buttonPressed )
791 XCopyArea( DADisplay, buttonPixmap, outPixmap, DAGC,
792 0, 0, 23, 11, 36, 48 );
795 switch( state ) {
796 case STATE_NEWMAIL:
797 if( flashMailSymbol )
798 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
799 13, 0, 37, 12, 20, 7 );
800 case STATE_READMAIL:
801 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
802 0, 0, 13, 12, 7, 7 );
804 if( config.useX11Font == NULL )
805 DrawTickerBuildinFont();
806 else
807 DrawTickerX11Font();
808 default: // make compiler happy
812 DASetPixmap( outPixmap );
815 static void ParseMBoxFile( struct stat *fileStat )
817 char buf[1024];
818 struct utimbuf timeStruct;
819 int fromFound = 0;
820 FILE *f = fopen( config.mailBox, "rt" );
821 unsigned long checksum;
823 state = STATE_READMAIL;
824 ClearAllNames();
826 numMails = 0;
828 if( f == NULL ) {
829 WARNING( "can't open mbox \"%s\"\n", config.mailBox );
830 return;
833 while( fgets( buf, 1024, f ) != NULL )
835 if( strncmp( buf, "From ", 5 ) == 0 ) {
836 fromFound = 1;
837 checksum = 0;
838 continue;
841 if( fromFound )
842 UpdateChecksum( &checksum, buf );
844 if( fromFound && strncasecmp( buf, "from: ", 6 ) == 0 )
846 if( SkipSender( buf+6 ))
847 goto NEXTMAIL;
849 char *name;
850 if(( name = ParseFromField( buf+6 )) == NULL )
852 WARNING( "Could not parse From field\n" );
853 break;
855 InsertName( name, checksum, FLAG_INITIAL );
857 ++numMails;
858 fromFound = 0;
859 checksum = 0;
860 } else if( config.considerStatusField && strncasecmp( buf, "status: ", 8 ) == 0 &&
861 strstr( buf+8, config.readStatus ) == NULL )
863 RemoveLastName();
864 --numMails;
866 NEXTMAIL:
870 fclose( f );
871 ReadChecksumFile();
873 DetermineState();
875 timeStruct.actime = fileStat->st_atime;
876 timeStruct.modtime = fileStat->st_mtime;
877 utime( config.mailBox, &timeStruct );
880 static void ParseMaildirFile( const char *fileName, unsigned long checksum,
881 struct stat *fileStat, bool isNewMail )
883 char buf[1024];
884 struct utimbuf timeStruct;
885 FILE *f = fopen( fileName, "rt" );
887 if( f == NULL )
889 WARNING( "can't open maildir file \"%s\"\n", fileName );
890 return;
893 while( fgets( buf, 1024, f ) != NULL )
895 if( strncasecmp( buf, "from: ", 6 ) == 0 )
897 if( SkipSender( buf+6 ))
898 break;
900 char *name;
901 if(( name = ParseFromField( buf+6 )) == NULL )
903 WARNING( "Could not parse From field\n" );
904 break;
906 InsertName( name, checksum, isNewMail ? FLAG_INITIAL : FLAG_READ );
908 //++numMails;
912 fclose( f );
914 timeStruct.actime = fileStat->st_atime;
915 timeStruct.modtime = fileStat->st_mtime;
916 utime( fileName, &timeStruct );
919 static char *ParseFromField( char *buf )
921 parse_state_t state = STATE_FULLNAME;
922 int fullNameEncoded = 0;
923 int saveAtCharPos = -1;
924 char *fullName;
925 char *addressName;
926 char *atChar = NULL;
927 char *c;
928 size_t maxLen = strlen( buf ) + 1;
929 char *comment;
930 size_t fullNameLen = 0, addressNameLen = 0, commentLen = 0;
932 if(( fullName = calloc( maxLen, sizeof *fullName )) == NULL )
933 return NULL;
934 if(( addressName = calloc( maxLen, sizeof *addressName )) == NULL )
936 free( fullName );
937 return NULL;
939 if(( comment = calloc( maxLen, sizeof *comment )) == NULL )
941 free( fullName );
942 free( addressName );
943 return NULL;
946 // FIXME: Don't do that "encoded" dance. It's not intended by
947 // RFC2047, and it's better to just do it in the end.
948 // Cleaner.
950 for( c = buf; *c != '\0'; ++c )
952 switch( state ) {
953 case STATE_FULLNAME:
955 switch( *c ) {
956 case '"':
957 state = STATE_QUOTED_FULLNAME;
958 continue;
959 case '<':
960 while( fullNameLen > 0 &&
961 isspace( fullName[ fullNameLen - 1 ] ))
962 fullNameLen--;
963 fullName[ fullNameLen ] = '\0';
964 state = STATE_ADDRESS;
965 continue;
966 case '@':
967 saveAtCharPos = fullNameLen;
968 fullName[ fullNameLen++ ] = *c;
969 continue;
970 case '(':
971 state = STATE_COMMENT;
972 continue;
973 case '=':
974 if( *(c+1) == '?' ) {
975 ++c;
976 fullNameEncoded = 1;
977 state = STATE_ENCODED_FULLNAME;
978 continue;
979 } // else do the default action
980 default:
981 if( fullName[0] != '\0' || !isspace( *c ))
982 fullName[ fullNameLen++ ] = *c;
984 continue;
986 case STATE_QUOTED_FULLNAME:
988 switch( *c ) {
989 case '\\':
990 fullName[ fullNameLen++ ] = *(++c);
991 continue;
992 case '"':
993 state = STATE_FULLNAME;
994 continue;
995 default:
996 fullName[ fullNameLen++ ] = *c;
998 continue;
1000 case STATE_ENCODED_FULLNAME:
1002 switch( *c ) {
1003 case '?':
1004 if( *(c+1) == '=' ) {
1005 ++c;
1006 state = STATE_FULLNAME;
1007 continue;
1009 default:
1010 ; // do nothing... COMING SOON: decode at least latin1
1012 continue;
1014 case STATE_ADDRESS:
1016 switch( *c ) {
1017 case '"':
1018 state = STATE_QUOTED_ADDRESS;
1019 case '>':
1020 case '\n':
1021 // FIXME: Shouldn't it break here?
1022 // Since the address is finished?
1023 continue;
1024 case '@':
1025 atChar = addressName + addressNameLen;
1026 addressName[ addressNameLen++ ] = *c;
1027 continue;
1028 default:
1029 addressName[ addressNameLen++ ] = *c;
1031 continue;
1033 case STATE_QUOTED_ADDRESS:
1035 switch( *c ) {
1036 case '"':
1037 state = STATE_ADDRESS;
1038 continue;
1039 case '\\':
1040 addressName[ addressNameLen++ ] = *(++c);
1041 continue;
1042 default:
1043 addressName[ addressNameLen++ ] = *c;
1045 continue;
1046 case STATE_COMMENT:
1047 switch( *c ) {
1048 case ')':
1049 state = STATE_FULLNAME;
1050 continue;
1051 default:
1052 comment[ commentLen++ ] = *c;
1053 continue;
1055 continue;
1059 if( *comment ) {
1060 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1061 // Comment seen: if there's an address, append to
1062 // fullname. If no address, copy fullname to address
1063 // and comment to fullname.
1064 if( *addressName ) {
1065 strcat(fullName, "(");
1066 strcat(fullName, comment);
1067 strcat(fullName, ")");
1068 } else {
1069 strcpy(addressName, fullName);
1070 strcpy(fullName, comment);
1073 free( comment );
1075 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1077 // what name should be tickered
1078 if( config.tickerMode == TICKER_FAMILYNAME && fullName[0] != '\0' && !fullNameEncoded ) {
1079 free( addressName );
1080 return fullName;
1081 } else {
1082 if( state == STATE_FULLNAME ) {
1083 strcpy( addressName, fullName );
1084 if( saveAtCharPos != -1 )
1085 atChar = &addressName[saveAtCharPos];
1087 if( config.tickerMode == TICKER_NICKNAME ) {
1088 if( atChar != NULL )
1089 *atChar = '\0';
1091 free( fullName );
1092 return addressName;
1096 static bool SkipSender( char *address )
1098 char **skipName;
1099 size_t len = strlen( address );
1101 // remove trailing '\n' got from fgets
1102 if( address[len-1] == '\n' )
1103 address[len-1] = '\0';
1105 for( skipName = config.skipNames;
1106 skipName != NULL && *skipName != NULL; skipName++ )
1108 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName, address );
1110 // call libc-fnmatch (wildcard-match :-) !
1111 if( !fnmatch( *skipName, address, 0 )) {
1112 TRACE( "skipping sender \"%s\"\n", *skipName );
1113 return true;
1117 return false;
1120 static void InsertName( char *name, unsigned long checksum, flag_t flag )
1122 name_t *item;
1124 TRACE( "insertName: %X, \"%s\"\n", checksum, name );
1125 if(( item = malloc( sizeof( name_t ))) == NULL )
1127 free( name );
1128 return;
1131 item->name = name; /*strdup( name );*/
1132 item->checksum = checksum;
1133 item->flag = flag;
1134 item->visited = true;
1135 item->next = names;
1136 names = item;
1138 namesChanged = true;
1141 static void RemoveLastName( void )
1143 if( names != NULL ) {
1144 name_t *name = names;
1145 names = names->next;
1146 free( name->name );
1147 free( name );
1151 static void ClearAllNames( void )
1153 name_t *name, *nextName;
1155 for( name = names; name != NULL; name = nextName ) {
1156 nextName = name->next;
1158 free( name->name );
1159 free( name );
1162 names = NULL;
1163 numMails = 0;
1165 namesChanged = true;
1168 static void SetMailFlags( flag_t flag )
1170 name_t *name;
1172 for( name = names; name != NULL; name = name->next )
1173 name->flag |= flag;
1176 static void DrawTickerX11Font( void )
1178 // 49x21+7+20 out-drawable size
1180 static int insertAt;
1182 if( curTickerName == NULL || namesChanged )
1184 for( curTickerName = names;
1185 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1186 curTickerName = curTickerName->next );
1188 if( curTickerName == NULL )
1189 return;
1191 namesChanged = false;
1192 insertAt = 54;
1195 XDrawString( DADisplay, outPixmap, tickerGC, insertAt,
1196 41-tickerFS->max_bounds.descent,
1197 curTickerName->name, strlen( curTickerName->name ));
1199 --insertAt;
1201 if( insertAt < -XTextWidth( tickerFS, curTickerName->name,
1202 strlen( curTickerName->name )) + 6 )
1204 do {
1205 curTickerName = curTickerName->next;
1206 } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ));
1208 if( curTickerName != NULL ) {
1209 insertAt = 54;
1214 static void DrawTickerBuildinFont( void )
1216 // 49x21+7+20 out-drawable size
1217 // 14x21 font-character size
1219 static unsigned insertAt;
1220 static unsigned takeItFrom;
1222 unsigned leftSpace;
1223 unsigned drawTo;
1224 unsigned char *currentChar;
1226 if( names == NULL )
1227 return;
1229 if( curTickerName == NULL || namesChanged ) {
1231 for( curTickerName = names;
1232 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1233 curTickerName = curTickerName->next );
1235 if( curTickerName == NULL )
1236 return;
1238 insertAt = 57;
1239 takeItFrom = 0;
1240 namesChanged = false;
1243 leftSpace = takeItFrom % 14;
1245 for( currentChar = (unsigned char *)&curTickerName->name[takeItFrom/14],
1246 drawTo = insertAt; *currentChar != '\0'; ++currentChar )
1249 int outChar = (*currentChar < 32 || *currentChar >= 128) ? '?' :
1250 *currentChar;
1251 int charWidth = 57-drawTo >= 14 ? 14 - leftSpace : 57-drawTo;
1253 XCopyArea( DADisplay, charsPixmap, outPixmap, DAGC,
1254 (outChar-32)*14+leftSpace, 0, charWidth, 21, drawTo, 20 );
1256 leftSpace = 0;
1257 drawTo += charWidth;
1259 if( drawTo > 57 )
1260 break;
1263 if( --insertAt < 7 ) {
1264 insertAt = 7;
1265 takeItFrom++;
1267 if( takeItFrom/14 >= strlen( curTickerName->name )) {
1269 do {
1270 curTickerName = curTickerName->next;
1271 } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ));
1273 if( curTickerName != NULL ) {
1274 takeItFrom = 0;
1275 insertAt = 57;
1281 static void ButtonPressed( int button, int state, int x, int y )
1283 (void) button;
1284 (void) state;
1286 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) {
1287 buttonPressed = true;
1288 forceRedraw = true;
1289 } else
1290 // reread the config file
1291 readConfigFile = true;
1293 CheckTimeOut( false );
1296 static void ButtonReleased( int button, int state, int x, int y )
1298 (void) button;
1299 (void) state;
1301 buttonPressed = false;
1302 forceRedraw = true;
1304 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) {
1305 int ret = system( config.runCmd );
1307 if( ret == 127 || ret == -1 )
1308 WARNING( "execution of command \"%s\" failed.\n", config.runCmd );
1311 CheckTimeOut( false );
1314 static void GetHexColorString( const char *colorName, char *xpmLine )
1316 XColor color;
1318 if( XParseColor( DADisplay,
1319 DefaultColormap( DADisplay, DefaultScreen( DADisplay )),
1320 colorName, &color ))
1322 sprintf( xpmLine, "%02X%02X%02X", color.red>>8, color.green>>8,
1323 color.blue>>8 );
1324 } else
1325 WARNING( "unknown colorname: \"%s\"\n", colorName );
1328 static char *XpmColorLine( const char *colorName, char *colorLine,
1329 bool disposeLine )
1331 char *newLine = strdup( colorLine );
1332 char *from = strrchr( newLine, '#' );
1334 if( from == NULL && !strcasecmp( &colorLine[ strlen( colorLine ) - 4 ], "none" )) {
1335 // if no # found, it should be a None-color line
1336 free( newLine );
1337 newLine = malloc( 12 );
1338 strcpy( newLine, " \tc #" );
1339 newLine[11] = '\0';
1340 from = newLine + 4;
1343 if( disposeLine )
1344 free( colorLine );
1346 GetHexColorString( colorName, from+1 );
1348 return newLine;
1351 static void UpdateConfiguration( void )
1353 struct stat fileStat;
1355 TRACE( "reading configuration file...\n" );
1357 ReadConfigFile( true );
1359 // if no path/name to an mbox or maildir inbox directory was given,
1360 // use the environment
1361 if( config.mailBox == NULL )
1362 config.mailBox = getenv( "MAIL" );
1364 // mbox or maildir ?
1365 if( config.mailBox != NULL && stat( config.mailBox, &fileStat ) == 0 )
1366 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
1367 else
1368 isMaildir = false;
1370 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
1372 PreparePixmaps( true );
1374 DASetTimeout( 1000 / config.fps );
1377 static void CleanupNames( void )
1379 name_t *name, *last = NULL, *nextName;
1381 numMails = 0;
1383 for( name = names; name != NULL; name = nextName )
1385 nextName = name->next;
1387 if( !name->visited ) {
1388 if( last == NULL )
1389 names = name->next;
1390 else
1391 last->next = name->next;
1393 free( name->name );
1394 free( name );
1395 } else {
1396 last = name;
1398 if( !config.newMailsOnly || (name->flag & FLAG_READ) == 0 )
1399 ++numMails;
1404 static bool HasTickerWork( void )
1406 name_t *nextTickerName;
1408 if( names == NULL )
1409 return false;
1411 if( curTickerName == NULL || namesChanged ) {
1413 for( nextTickerName = names;
1414 nextTickerName != NULL && ( config.newMailsOnly && ( nextTickerName->flag & FLAG_READ ));
1415 nextTickerName = nextTickerName->next );
1417 if( nextTickerName == NULL )
1418 return false;
1421 return true;