wmclockmon: update change-log
[dockapps.git] / wmail / src / wmail.c
blob84bc1fdfbaa8265a199d52ca2f7f787f4351b806
1 ///////////////////////////////////////////////////////////////////////////////
2 // wmail.c
3 // email indicator tool designed as docklet for Window Maker
4 // main c source-file
5 //
6 // Copyright 2000-2002, Sven Geisenhainer <sveng@informatik.uni-jena.de>.
7 // Copyright 2016-2017, Doug Torrance <dtorrance@piedmont.edu>.
8 // Copyright 2019, Jeremy Sowden <jeremy@azazel.net>.
9 //
10 // Redistribution and use in source and binary forms, with or without
11 // modification, are permitted provided that the following conditions
12 // are met:
13 // 1. Redistributions of source code must retain the above copyright
14 // notice, this list of conditions, and the following disclaimer.
15 // 2. Redistributions in binary form must reproduce the above copyright
16 // notice, this list of conditions, and the following disclaimer in the
17 // documentation and/or other materials provided with the distribution.
18 // 3. The name of the author may not be used to endorse or promote products
19 // derived from this software without specific prior written permission.
21 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ///////////////////////////////////////////////////////////////////////////////
34 // includes
36 #ifdef HAVE_CONFIG_H
37 #ifndef CONFIG_H_INCLUDED
38 #include "../config.h"
39 #define CONFIG_H_INCLUDED
40 #endif
41 #endif
43 #include <ctype.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <stdarg.h>
47 #include <string.h>
48 #ifdef HAVE_STRINGS_H
49 #include <strings.h>
50 #endif
51 #include <signal.h>
52 #include <utime.h>
53 #include <fnmatch.h>
54 #include <sys/time.h>
55 #include <sys/stat.h>
56 #include <dirent.h>
57 #include <X11/Xlib.h>
58 #ifdef HAVE_LIBDOCKAPP_DOCKAPP_H
59 #include <libdockapp/dockapp.h>
60 #else
61 #include <dockapp.h>
62 #endif
64 #include "common.h"
65 #include "config.h"
67 // pixmaps
68 #ifdef USE_DELT_XPMS
69 #include "xpm_delt/main.xpm"
70 #include "xpm_delt/symbols.xpm"
71 #include "xpm_delt/numbers.xpm"
72 #include "xpm_delt/button.xpm"
73 #include "xpm_delt/chars.xpm"
74 #else
75 #include "xpm/main.xpm"
76 #include "xpm/symbols.xpm"
77 #include "xpm/numbers.xpm"
78 #include "xpm/button.xpm"
79 #include "xpm/chars.xpm"
80 #endif
83 ///////////////////////////////////////////////////////////////////////////////
84 // typedefs
86 typedef enum
88 FLAG_INITIAL = 0,
89 FLAG_READ = 1
90 } flag_t;
92 typedef struct _name_t
94 char *name;
95 unsigned long checksum;
96 flag_t flag;
97 bool visited;
98 struct _name_t *next;
99 } name_t;
101 typedef enum
103 STATE_NOMAIL,
104 STATE_NEWMAIL,
105 STATE_READMAIL
106 } mail_state_t;
108 typedef enum
110 STATE_ADDRESS,
111 STATE_QUOTED_ADDRESS,
112 STATE_FULLNAME,
113 STATE_QUOTED_FULLNAME,
114 STATE_ENCODED_FULLNAME,
115 STATE_COMMENT
116 } parse_state_t;
119 ///////////////////////////////////////////////////////////////////////////////
120 // data
122 static char *configFile;
123 static unsigned long lastTimeOut;
124 static sig_atomic_t caughtSig;
125 static mail_state_t state;
126 static unsigned numMails;
127 static bool namesChanged;
128 static bool buttonPressed;
129 static bool readConfigFile;
130 static bool isMaildir;
131 static bool forceRead;
132 static bool forceRedraw = true;
133 static time_t lastModifySeconds;
134 static time_t lastAccessSeconds;
135 static Pixmap mainPixmap;
136 static Pixmap mainPixmap_mask;
137 static Pixmap symbolsPixmap;
138 static Pixmap charsPixmap;
139 static Pixmap numbersPixmap;
140 static Pixmap buttonPixmap;
141 static Pixmap outPixmap;
142 static GC tickerGC;
143 static XFontStruct *tickerFS;
144 static name_t *names;
145 static name_t *curTickerName;
147 enum
149 OPT_INDEX_DISPLAY,
150 OPT_INDEX_COMMAND,
151 OPT_INDEX_INTERVAL,
152 OPT_INDEX_FAMILY_NAME,
153 OPT_INDEX_FRAMES,
154 OPT_INDEX_SHORT_NAME,
155 OPT_INDEX_SYMBOL_COLOR,
156 OPT_INDEX_FONT_COLOR,
157 OPT_INDEX_BACK_COLOR,
158 OPT_INDEX_OFF_COLOR,
159 OPT_INDEX_BACKGROUND,
160 OPT_INDEX_NO_SHAPE,
161 OPT_INDEX_NEW,
162 OPT_INDEX_MAILBOX,
163 OPT_INDEX_EXECUTE,
164 OPT_INDEX_STATUS_FIELD,
165 OPT_INDEX_READ_STATUS,
166 OPT_INDEX_TICKER_FONT,
167 OPT_INDEX_CONFIG_FILE,
170 static DAProgramOption options[] =
172 [OPT_INDEX_DISPLAY] =
174 .shortForm = "-display",
175 .description = "display to use",
176 .type = DOString,
177 .value = { .string = &config.display }
179 [OPT_INDEX_COMMAND] =
181 .shortForm = "-c",
182 .longForm = "--command",
183 .description = "cmd to run on btn-click (\"xterm -e mail\" is default)",
184 .type = DOString,
185 .value = { .string = &config.runCmd }
187 [OPT_INDEX_INTERVAL] =
189 .shortForm = "-i",
190 .longForm = "--interval",
191 .description = "number of secs between mail-status updates (1 is default)",
192 .type = DONatural,
193 .value = { .integer = &config.checkInterval }
195 [OPT_INDEX_FAMILY_NAME] =
197 .shortForm = "-f",
198 .longForm = "--familyname",
199 .description = "tickers the family-name if available",
201 [OPT_INDEX_FRAMES] =
203 .shortForm = "-fps",
204 .longForm = "--frames",
205 .description = "ticker frames per second",
206 .type = DONatural,
207 .value = { .integer = &config.fps }
209 [OPT_INDEX_SHORT_NAME] =
211 .shortForm = "-s",
212 .longForm = "--shortname",
213 .description = "tickers the nickname (all before the '@')",
215 [OPT_INDEX_SYMBOL_COLOR] =
217 .shortForm = "-sc",
218 .longForm = "--symbolcolor",
219 .description = "symbol color-name",
220 .type = DOString,
221 .value = { .string = &config.symbolColor }
223 [OPT_INDEX_FONT_COLOR] =
225 .shortForm = "-fc",
226 .longForm = "--fontcolor",
227 .description = "ticker-font color-name",
228 .type = DOString,
229 .value = { .string = &config.fontColor }
231 [OPT_INDEX_BACK_COLOR] =
233 .shortForm = "-bc",
234 .longForm = "--backcolor",
235 .description = "backlight color-name",
236 .type = DOString,
237 .value = { .string = &config.backColor }
239 [OPT_INDEX_OFF_COLOR] =
241 .shortForm = "-oc",
242 .longForm = "--offcolor",
243 .description = "off-light color-name",
244 .type = DOString,
245 .value = { .string = &config.offlightColor }
247 [OPT_INDEX_BACKGROUND] =
249 .shortForm = "-bg",
250 .longForm = "--background",
251 .description = "frame-background for non-shaped window",
252 .type = DOString,
253 .value = { .string = &config.backgroundColor }
255 [OPT_INDEX_NO_SHAPE] =
257 .shortForm = "-ns",
258 .longForm = "--noshape",
259 .description = "make the dockapp non-shaped (combine with -w)",
261 [OPT_INDEX_NEW] =
263 .shortForm = "-n",
264 .longForm = "--new",
265 .description = "forces wmail to show new mail exclusively",
267 [OPT_INDEX_MAILBOX] =
269 .shortForm = "-mb",
270 .longForm = "--mailbox",
271 .description = "specify another mailbox ($MAIL is default)",
272 .type = DOString,
273 .value = { .string = &config.mailBox }
275 [OPT_INDEX_EXECUTE] =
277 .shortForm = "-e",
278 .longForm = "--execute",
279 .description = "command to execute when receiving a new mail",
280 .type = DOString,
281 .value = { .string = &config.cmdOnMail }
283 [OPT_INDEX_STATUS_FIELD] =
285 .shortForm = "-sf",
286 .longForm = "--statusfield",
287 .description = "consider the status-field of the mail header to distinguish unread mails",
289 [OPT_INDEX_READ_STATUS] =
291 .shortForm = "-rs",
292 .longForm = "--readstatus",
293 .description = "status field content that your client uses to mark read mails",
294 .type = DOString,
295 .value = { .string = &config.readStatus }
297 [OPT_INDEX_TICKER_FONT] =
299 .shortForm = "-fn",
300 .longForm = "--tickerfont",
301 .description = "use specified X11 font to draw the ticker",
302 .type = DOString,
303 .value = { .string = &config.useX11Font }
305 [OPT_INDEX_CONFIG_FILE] =
307 .shortForm = "-rc",
308 .longForm = "--rcfile",
309 .description = "specify another rc-file ($HOME/.wmailrc is default)",
310 .type = DOString,
311 .value = { .string = &configFile }
316 ///////////////////////////////////////////////////////////////////////////////
317 // prototypes
319 static int PreparePixmaps( bool freeMem );
320 static void ExitHandler( int sig );
321 static void TimedOut( void );
322 static void CheckTimeOut( bool force );
323 static void CheckMBox( void );
324 static void CheckMaildir( void );
325 static int TraverseDirectory( const char *name, bool isNewMail );
326 static name_t *GetMail( unsigned long checksum );
327 static void UpdatePixmap( bool flashMailSymbol );
328 static void ParseMBoxFile( struct stat *fileStat );
329 static void ParseMaildirFile( const char *fileName, unsigned long checksum,
330 struct stat *fileStat, bool isNewMail );
331 static char *ParseFromField( char *buf );
332 static bool SkipSender( char *address );
333 static int InsertName( char *name, unsigned long checksum, flag_t flag );
334 static void RemoveLastName( void );
335 static void ClearAllNames( void );
336 static void DrawTickerX11Font( void );
337 static void DrawTickerBuildinFont( void );
338 static void ButtonPressed( int button, int state, int x, int y );
339 static void ButtonReleased( int button, int state, int x, int y );
340 static int XpmColorLine( const char *colorName, char **colorLine,
341 bool disposeLine );
342 static void ReadChecksumFile( void );
343 static void WriteChecksumFile( bool writeAll );
344 static void UpdateChecksum( unsigned long *checksum, const char *buf );
345 static void RemoveChecksumFile( void );
346 static void SetMailFlags( flag_t flag );
347 static void MarkName( unsigned long checksum );
348 static void DetermineState( void );
349 static void UpdateConfiguration( void );
350 static void CleanupNames( void );
351 static bool HasTickerWork( void );
354 ///////////////////////////////////////////////////////////////////////////////
355 // implementation
358 int main( int argc, char **argv )
360 char *usersHome = getenv( "HOME" );
361 struct sigaction sa = { .sa_handler = ExitHandler };
362 struct stat fileStat;
363 XTextProperty windowName;
364 char *name = argv[0];
365 DACallbacks callbacks = { NULL, &ButtonPressed, &ButtonReleased,
366 NULL, NULL, NULL, &TimedOut };
368 // parse cmdline-args
369 DAParseArguments( argc, argv, options, sizeof options / sizeof *options,
370 PACKAGE_NAME, PACKAGE_STRING );
372 if( options[OPT_INDEX_DISPLAY].used )
373 config.givenOptions |= CL_DISPLAY;
374 if( options[OPT_INDEX_COMMAND].used )
375 config.givenOptions |= CL_RUNCMD;
376 if( options[OPT_INDEX_INTERVAL].used )
377 config.givenOptions |= CL_CHECKINTERVAL;
378 if( options[OPT_INDEX_FAMILY_NAME].used )
380 config.givenOptions |= CL_TICKERMODE;
381 config.tickerMode = TICKER_FAMILYNAME;
383 if( options[OPT_INDEX_FRAMES].used )
384 config.givenOptions |= CL_FPS;
385 if( options[OPT_INDEX_SHORT_NAME].used )
387 config.givenOptions |= CL_TICKERMODE;
388 config.tickerMode = TICKER_NICKNAME;
390 if( options[OPT_INDEX_SYMBOL_COLOR].used )
391 config.givenOptions |= CL_SYMBOLCOLOR;
392 if( options[OPT_INDEX_FONT_COLOR].used )
393 config.givenOptions |= CL_FONTCOLOR;
394 if( options[OPT_INDEX_BACK_COLOR].used )
395 config.givenOptions |= CL_BACKCOLOR;
396 if( options[OPT_INDEX_OFF_COLOR].used )
397 config.givenOptions |= CL_OFFLIGHTCOLOR;
398 if( options[OPT_INDEX_BACK_COLOR].used )
399 config.givenOptions |= CL_BACKGROUNDCOLOR;
400 if( options[OPT_INDEX_NO_SHAPE].used )
402 config.givenOptions |= CL_NOSHAPE;
403 config.noshape = true;
405 if( options[OPT_INDEX_NEW].used )
407 config.givenOptions |= CL_NEWMAILONLY;
408 config.newMailsOnly = true;
410 if( options[OPT_INDEX_MAILBOX].used )
411 config.givenOptions |= CL_MAILBOX;
412 if( options[OPT_INDEX_EXECUTE].used )
413 config.givenOptions |= CL_CMDONMAIL;
414 if( options[OPT_INDEX_STATUS_FIELD].used )
416 config.givenOptions |= CL_CONSIDERSTATUSFIELD;
417 config.considerStatusField = true;
419 if( options[OPT_INDEX_READ_STATUS].used )
420 config.givenOptions |= CL_READSTATUS;
421 if( options[OPT_INDEX_TICKER_FONT].used )
422 config.givenOptions |= CL_USEX11FONT;
424 if( configFile == NULL)
426 if( usersHome == NULL)
428 WARNING( "HOME environment-variable is not set, looking for %s in current directory!\n",
429 WMAIL_RC_FILE );
430 configFile = strdup( WMAIL_RC_FILE );
432 else
433 configFile = MakePathName( usersHome, WMAIL_RC_FILE );
435 if( configFile == NULL )
437 WARNING( "Cannot allocate config file-name.\n");
438 exit( EXIT_FAILURE );
443 TRACE( "%s: configFile = %s\n", __func__, configFile );
445 // read the config file
446 ReadConfigFile( configFile, false );
448 if( config.checksumFileName == NULL )
450 if( usersHome == NULL )
452 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n",
453 WMAIL_CHECKSUM_FILE );
454 config.checksumFileName = strdup( WMAIL_CHECKSUM_FILE );
456 else
457 config.checksumFileName = MakePathName( usersHome, WMAIL_CHECKSUM_FILE );
459 if( config.checksumFileName == NULL )
461 WARNING( "Cannot allocate checksum file-name.\n");
462 exit( EXIT_FAILURE );
466 TRACE( "using checksum-file \"%s\"\n", config.checksumFileName );
468 if( config.mailBox == NULL )
469 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
470 else if( stat( config.mailBox, &fileStat ) == 0 )
471 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
473 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
475 // dockapp size hard wired - sorry...
476 DAInitialize( config.display, "wmail", 64, 64, argc, argv );
478 outPixmap = DAMakePixmap();
479 if( PreparePixmaps( false ) < 0 )
481 WARNING( "Cannot allocate color.\n" );
482 exit( EXIT_FAILURE );
485 if( sigaction( SIGINT, &sa, NULL ) == -1 )
487 perror( "wmail error: sigaction" );
488 exit( EXIT_FAILURE );
491 if( sigaction( SIGTERM, &sa, NULL ) == -1 )
493 perror( "wmail error: sigaction" );
494 exit( EXIT_FAILURE );
497 DASetCallbacks( &callbacks );
498 DASetTimeout( 1000 / config.fps );
500 XStringListToTextProperty( &name, 1, &windowName );
501 XSetWMName( DADisplay, DAWindow, &windowName );
502 XSetCommand( DADisplay, DAWindow, argv, argc );
504 UpdatePixmap( false );
505 DAShow();
507 DAEventLoop();
509 return 0;
512 static int PreparePixmaps( bool freeMem )
514 // simple recoloring of the raw xpms befor creating Pixmaps of them
515 // this works as long as you don't "touch" the images...
517 bool freeSymColor = freeMem && ( config.colorsUsed & SYM_COLOR );
518 bool freeFntColor = freeMem && ( config.colorsUsed & FNT_COLOR );
519 bool freeBckColor = freeMem && ( config.colorsUsed & BCK_COLOR );
520 bool freeOffColor = freeMem && ( config.colorsUsed & OFF_COLOR );
521 bool freeBgrColor = freeMem && ( config.colorsUsed & BGR_COLOR );
523 #if DA_VERSION < 20030126
524 unsigned dummy;
525 #else
526 unsigned short dummy;
527 #endif
529 XGCValues values;
532 * Symbol color?
534 if( config.symbolColor != NULL )
536 if( XpmColorLine( config.symbolColor, &symbols_xpm[2], freeSymColor) < 0 )
537 return -1;
539 config.colorsUsed |= SYM_COLOR;
541 else
543 if( XpmColorLine( "#20B2AA", &symbols_xpm[2], freeSymColor) < 0 )
544 return -1;
546 config.colorsUsed |= SYM_COLOR;
550 * Font color?
552 if( config.fontColor != NULL )
554 if( XpmColorLine( config.fontColor, &chars_xpm[3], freeFntColor) < 0 )
555 return -1;
557 if( XpmColorLine( config.fontColor, &numbers_xpm[3], freeFntColor) < 0 )
558 return -1;
560 config.colorsUsed |= FNT_COLOR;
562 else
564 if( XpmColorLine( "#D3D3D3", &chars_xpm[3], freeFntColor) < 0 )
565 return -1;
567 if( XpmColorLine( "#D3D3D3", &numbers_xpm[3], freeFntColor) < 0 )
568 return -1;
570 config.colorsUsed |= FNT_COLOR;
574 * Backlight color?
576 if( config.backColor != NULL )
578 if( XpmColorLine( config.backColor, &main_xpm[3], freeBckColor) < 0 )
579 return -1;
581 if( XpmColorLine( config.backColor, &symbols_xpm[3], freeBckColor) < 0 )
582 return -1;
584 if( XpmColorLine( config.backColor, &chars_xpm[2], freeBckColor) < 0 )
585 return -1;
587 if( XpmColorLine( config.backColor, &numbers_xpm[2], freeBckColor) < 0 )
588 return -1;
590 config.colorsUsed |= BCK_COLOR;
592 else
594 if( XpmColorLine( "#282828", &main_xpm[3], freeBckColor) < 0 )
595 return -1;
597 if( XpmColorLine( "#282828", &symbols_xpm[3], freeBckColor) < 0 )
598 return -1;
600 if( XpmColorLine( "#282828", &chars_xpm[2], freeBckColor) < 0 )
601 return -1;
603 if( XpmColorLine( "#282828", &numbers_xpm[2], freeBckColor) < 0 )
604 return -1;
606 config.colorsUsed |= BCK_COLOR;
610 * Off-light color?
612 if( config.offlightColor != NULL )
614 if( XpmColorLine( config.offlightColor, &main_xpm[2], freeOffColor) < 0 )
615 return -1;
617 if( XpmColorLine( config.offlightColor, &numbers_xpm[4], freeOffColor) < 0 )
618 return -1;
620 config.colorsUsed |= OFF_COLOR;
622 else
624 if( XpmColorLine( "#000000", &main_xpm[2], freeOffColor) < 0 )
625 return -1;
627 if( XpmColorLine( "#000000", &numbers_xpm[4], freeOffColor) < 0 )
628 return -1;
630 config.colorsUsed |= OFF_COLOR;
634 * Window-frame background (only seen if nonshaped)?
636 if( config.backgroundColor != NULL )
638 if( XpmColorLine( config.backgroundColor, &main_xpm[1], freeBgrColor) < 0 )
639 return -1;
641 config.colorsUsed |= BGR_COLOR;
644 if( freeMem )
646 XFreePixmap( DADisplay, mainPixmap );
647 XFreePixmap( DADisplay, mainPixmap_mask );
648 XFreePixmap( DADisplay, symbolsPixmap );
649 XFreePixmap( DADisplay, charsPixmap );
650 XFreePixmap( DADisplay, numbersPixmap );
651 XFreePixmap( DADisplay, buttonPixmap );
653 if( tickerGC != NULL )
655 XFreeGC( DADisplay, tickerGC );
656 tickerGC = NULL;
657 if( tickerFS != NULL )
659 XFreeFont( DADisplay, tickerFS );
660 tickerFS = NULL;
665 DAMakePixmapFromData( main_xpm, &mainPixmap, &mainPixmap_mask, &dummy, &dummy );
666 DAMakePixmapFromData( symbols_xpm, &symbolsPixmap, NULL, &dummy, &dummy );
667 DAMakePixmapFromData( chars_xpm, &charsPixmap, NULL, &dummy, &dummy );
668 DAMakePixmapFromData( numbers_xpm, &numbersPixmap, NULL, &dummy, &dummy );
669 DAMakePixmapFromData( button_xpm, &buttonPixmap, NULL, &dummy, &dummy );
671 if( config.useX11Font != NULL )
673 XRectangle clipRect;
675 if( config.fontColor != NULL )
676 values.foreground = DAGetColor( config.fontColor );
677 else
678 values.foreground = DAGetColor( "#D3D3D3" );
680 tickerFS = XLoadQueryFont( DADisplay, config.useX11Font );
681 if( tickerFS == NULL )
682 ABORT( "Cannot load font \"%s\"", config.useX11Font );
684 values.font = tickerFS->fid;
685 tickerGC = XCreateGC( DADisplay, DAWindow, GCForeground | GCFont,
686 &values );
687 clipRect.x = 6;
688 clipRect.y = 19;
689 clipRect.width = 52;
690 clipRect.height = 23;
692 XSetClipRectangles( DADisplay, tickerGC, 0, 0, &clipRect, 1, Unsorted );
695 if( config.noshape ) // non-shaped dockapp ?
696 DASetShape( None );
697 else
698 DASetShape( mainPixmap_mask );
700 return 0;
703 static void MarkName( unsigned long checksum )
705 name_t *name;
707 for( name = names; name != NULL; name = name->next )
709 if( name->checksum == checksum )
711 name->flag |= FLAG_READ;
712 if( config.newMailsOnly )
713 numMails--;
714 break;
719 static void DetermineState( void )
721 name_t *name;
723 for( name = names; name != NULL; name = name->next )
724 if(!( name->flag & FLAG_READ ))
726 state = STATE_NEWMAIL;
728 if( config.cmdOnMail != NULL )
730 int ret = system( config.cmdOnMail );
732 if( ret == 127 || ret == -1 )
733 WARNING( "execution of command \"%s\" failed.\n", config.cmdOnMail );
736 break;
740 static void ReadChecksumFile( void )
742 FILE *f = fopen( config.checksumFileName, "rb" );
743 if( f != NULL )
744 while( !feof( f ))
746 unsigned long checksum;
747 if( fread( &checksum, sizeof(long), 1, f ) != 1 )
748 continue;
750 MarkName( checksum );
752 else
753 return;
755 fclose( f );
758 static void WriteChecksumFile( bool writeAll )
760 FILE *f;
761 TRACE( "writing checksums:" );
763 if(( f = fopen( config.checksumFileName, "wb" )) != NULL )
765 name_t *name;
766 for( name = names; name != NULL; name = name->next )
768 if( writeAll || (name->flag & FLAG_READ))
770 fwrite( &name->checksum, sizeof(long), 1, f );
771 TRACE( " %X", name->checksum );
775 else
776 return;
778 TRACE( "\n" );
780 fclose( f );
783 static void UpdateChecksum( unsigned long *checksum, const char *buf )
785 if( buf != NULL )
787 size_t i, len = strlen( buf );
789 for( i = 0; i < len; ++i )
790 *checksum += buf[i] << (( i % sizeof(long) ) * 8 );
794 static void RemoveChecksumFile( void )
796 TRACE( "removing checksum-file\n" );
797 remove( config.checksumFileName );
800 static void ExitHandler( int sig )
802 (void) sig;
803 caughtSig = 1;
806 static void TimedOut( void )
808 if( caughtSig )
810 ClearAllNames();
811 ResetConfigStrings();
812 if( !options[OPT_INDEX_CONFIG_FILE].used )
813 free( configFile );
814 exit( EXIT_SUCCESS );
817 CheckTimeOut( true );
820 static void CheckTimeOut( bool force )
822 static int checkMail = 0;
824 struct timeval now;
825 gettimeofday(&now, NULL);
827 unsigned long nowMs = now.tv_sec * 1000UL + now.tv_usec / 1000UL;
829 if( !force && nowMs - lastTimeOut < 1000UL / config.fps )
830 return;
832 lastTimeOut = nowMs;
834 if( readConfigFile )
836 readConfigFile = false;
837 UpdateConfiguration();
838 checkMail = 0;
839 forceRead = true;
842 if( checkMail == 0 )
844 TRACE( "checking for new mail...\n" );
846 if( isMaildir )
847 CheckMaildir();
848 else
849 CheckMBox();
852 UpdatePixmap( checkMail % config.fps < config.fps/2 );
854 if( ++checkMail >= config.fps * config.checkInterval )
855 checkMail = 0;
858 static void CheckMBox( void )
860 struct stat fileStat;
862 if( stat( config.mailBox, &fileStat ) == -1 || fileStat.st_size == 0 )
865 * Error retrieving file-stats or file is empty -> no new/read mails
866 * available.
868 if( state != STATE_NOMAIL )
870 state = STATE_NOMAIL;
871 ClearAllNames();
872 RemoveChecksumFile();
873 forceRedraw = true;
876 else
878 if( lastModifySeconds != fileStat.st_mtime || forceRead )
881 * File has been updated -> new mails arrived or some mails removed
883 forceRead = false;
884 ParseMBoxFile( &fileStat );
885 stat( config.mailBox, &fileStat );
886 forceRedraw = true;
888 else if( lastAccessSeconds != fileStat.st_atime )
891 * File has been accessed (read) -> mark all mails as "read", because it
892 * cannot be decided which mails the user has read...
894 state = STATE_READMAIL;
895 WriteChecksumFile( true );
896 if( config.newMailsOnly )
898 numMails = 0;
899 SetMailFlags( FLAG_READ );
901 forceRedraw = true;
904 lastModifySeconds = fileStat.st_mtime;
905 lastAccessSeconds = fileStat.st_atime;
909 static void CheckMaildir( void )
911 DIR *dir = NULL;
912 mail_state_t lastState = state;
913 unsigned lastMailCount = numMails;
915 if( forceRead )
917 forceRead = false;
918 ClearAllNames();
919 TRACE( "all names cleared\n" );
922 state = STATE_NOMAIL;
924 if(( dir = opendir( config.mailBox )) != NULL )
926 struct dirent *dirEnt = NULL;
927 name_t *name;
929 for( name = names; name != NULL; name = name->next )
930 name->visited = false;
932 while(( dirEnt = readdir( dir )) != NULL )
934 char *fullName = MakePathName( config.mailBox, dirEnt->d_name );
935 struct stat fileStat;
937 if( fullName == NULL )
939 WARNING( "Cannot allocate file/path\n" );
940 break;
943 if( stat( fullName, &fileStat ) == -1 )
944 WARNING( "Can't stat file/path \"%s\"\n", fullName );
945 else if( S_ISDIR( fileStat.st_mode ))
947 if( strcmp( dirEnt->d_name, "new" ) == 0 )
949 if( TraverseDirectory( fullName, true ) > 0 )
950 state = STATE_NEWMAIL;
952 else if( strcmp( dirEnt->d_name, "cur" ) == 0 )
954 if( TraverseDirectory( fullName, false ) > 0 )
955 if( state != STATE_NEWMAIL )
956 state = STATE_READMAIL;
958 // directories ".", ".." and "tmp" discarded
960 free( fullName );
963 closedir( dir );
964 CleanupNames();
966 else
967 WARNING( "can't open directory \"%s\"\n", config.mailBox );
969 if( lastState != state || lastMailCount != numMails )
970 forceRedraw = true;
973 static int TraverseDirectory( const char *name, bool isNewMail )
975 DIR *dir = NULL;
976 int mails = 0;
978 if(( dir = opendir( name )) != NULL )
980 struct dirent *dirEnt = NULL;
982 while(( dirEnt = readdir( dir )) != NULL )
984 char *fullName = MakePathName( name, dirEnt->d_name );
985 struct stat fileStat;
986 unsigned long checksum = 0;
987 name_t *name;
989 if( fullName == NULL )
991 WARNING( "Cannot allocate file/path\n" );
992 break;
995 if( stat( fullName, &fileStat ) == -1 )
996 WARNING( "Can't stat file/path \"%s\"\n", fullName );
997 else if( !S_ISDIR( fileStat.st_mode ))
999 TRACE( "found email-file \"%s\"\n", fullName );
1000 UpdateChecksum( &checksum, dirEnt->d_name );
1002 if(( name = GetMail( checksum )) == NULL )
1004 TRACE( "-> new file - parsing it\n" );
1005 ParseMaildirFile( fullName, checksum, &fileStat, isNewMail );
1007 else
1009 name->flag = isNewMail ? FLAG_INITIAL : FLAG_READ;
1010 name->visited = true;
1012 ++mails;
1014 free( fullName );
1017 closedir( dir );
1020 return mails;
1023 static name_t *GetMail( unsigned long checksum )
1025 name_t *name;
1027 for( name = names; name != NULL; name = name->next )
1028 if( name->checksum == checksum )
1029 return name;
1031 return NULL;
1034 static void UpdatePixmap( bool flashMailSymbol )
1036 int drawCount, i;
1038 if( !forceRedraw && !HasTickerWork() )
1039 return;
1041 forceRedraw = false;
1043 XCopyArea( DADisplay, mainPixmap, outPixmap, DAGC,
1044 0, 0, 64, 64, 0, 0 );
1046 if( numMails > 999 )
1048 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
1049 50, 0, 5, 9, 6, 49 );
1050 drawCount = 999;
1052 else
1053 drawCount = numMails;
1055 for( i = 0; i < 3; ++i, drawCount /= 10 )
1057 XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC,
1058 (drawCount%10)*5, 0, 5, 9, 24-i*6, 49 );
1059 if( drawCount <= 9 )
1060 break;
1063 if( buttonPressed )
1064 XCopyArea( DADisplay, buttonPixmap, outPixmap, DAGC,
1065 0, 0, 23, 11, 36, 48 );
1067 switch( state )
1069 case STATE_NEWMAIL:
1070 if( flashMailSymbol )
1071 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
1072 13, 0, 37, 12, 20, 7 );
1073 /* fall through */
1074 case STATE_READMAIL:
1075 XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC,
1076 0, 0, 13, 12, 7, 7 );
1078 if( config.useX11Font == NULL )
1079 DrawTickerBuildinFont();
1080 else
1081 DrawTickerX11Font();
1082 break;
1083 default: // make compiler happy
1084 break;
1087 DASetPixmap( outPixmap );
1090 static void ParseMBoxFile( struct stat *fileStat )
1092 char buf[1024];
1093 struct utimbuf timeStruct;
1094 int fromFound = 0;
1095 FILE *f = fopen( config.mailBox, "r" );
1096 unsigned long checksum;
1098 state = STATE_READMAIL;
1099 ClearAllNames();
1101 numMails = 0;
1103 if( f == NULL )
1105 WARNING( "can't open mbox \"%s\"\n", config.mailBox );
1106 return;
1109 while( fgets( buf, sizeof buf, f ) != NULL )
1111 if( PREFIX_MATCHES( buf, "From ", true ))
1113 fromFound = 1;
1114 checksum = 0;
1115 continue;
1118 if( fromFound )
1119 UpdateChecksum( &checksum, buf );
1121 if( fromFound && PREFIX_MATCHES( buf, "From:", false ))
1123 char *addr = buf + sizeof "From:";
1125 if( SkipSender( addr ))
1126 continue;
1128 char *name;
1129 if(( name = ParseFromField( addr )) == NULL )
1131 WARNING( "Could not parse From field\n" );
1132 break;
1134 if ( InsertName( name, checksum, FLAG_INITIAL ) < 0 )
1136 WARNING( "Could not allocate name\n" );
1137 break;
1140 ++numMails;
1141 fromFound = 0;
1142 checksum = 0;
1144 else if( config.considerStatusField &&
1145 PREFIX_MATCHES( buf, "Status:", false ) &&
1146 strstr( buf + sizeof "Status:", config.readStatus ) == NULL )
1148 RemoveLastName();
1149 --numMails;
1153 fclose( f );
1154 ReadChecksumFile();
1156 DetermineState();
1158 timeStruct.actime = fileStat->st_atime;
1159 timeStruct.modtime = fileStat->st_mtime;
1160 utime( config.mailBox, &timeStruct );
1163 static void ParseMaildirFile( const char *fileName, unsigned long checksum,
1164 struct stat *fileStat, bool isNewMail )
1166 char buf[1024];
1167 struct utimbuf timeStruct;
1168 FILE *f = fopen( fileName, "r" );
1170 if( f == NULL )
1172 WARNING( "can't open maildir file \"%s\"\n", fileName );
1173 return;
1176 while( fgets( buf, sizeof buf, f ) != NULL )
1178 if( PREFIX_MATCHES( buf, "From:", false ))
1180 char *addr = buf + sizeof "From:";
1182 if( SkipSender( addr ))
1183 break;
1185 char *name;
1186 if(( name = ParseFromField( addr )) == NULL )
1188 WARNING( "Could not parse From field\n" );
1189 break;
1191 if ( InsertName( name, checksum,
1192 isNewMail ? FLAG_INITIAL : FLAG_READ ) < 0 )
1194 WARNING( "Could not allocate name\n" );
1195 break;
1198 //++numMails;
1202 fclose( f );
1204 timeStruct.actime = fileStat->st_atime;
1205 timeStruct.modtime = fileStat->st_mtime;
1206 utime( fileName, &timeStruct );
1209 static char *ParseFromField( char *buf )
1211 parse_state_t state = STATE_FULLNAME;
1212 int fullNameEncoded = 0;
1213 int saveAtCharPos = -1;
1214 char *fullName;
1215 char *addressName;
1216 char *atChar = NULL;
1217 char *c;
1218 size_t maxLen = strlen( buf ) + 1;
1219 char *comment;
1220 size_t fullNameLen = 0, addressNameLen = 0, commentLen = 0;
1222 if(( fullName = calloc( maxLen, sizeof *fullName )) == NULL )
1223 return NULL;
1224 if(( addressName = calloc( maxLen, sizeof *addressName )) == NULL )
1226 free( fullName );
1227 return NULL;
1229 if(( comment = calloc( maxLen, sizeof *comment )) == NULL )
1231 free( fullName );
1232 free( addressName );
1233 return NULL;
1236 // FIXME: Don't do that "encoded" dance. It's not intended by
1237 // RFC2047, and it's better to just do it in the end.
1238 // Cleaner.
1240 for( c = buf; *c != '\0'; ++c )
1242 switch( state )
1244 case STATE_FULLNAME:
1246 switch( *c )
1248 case '"':
1249 state = STATE_QUOTED_FULLNAME;
1250 continue;
1251 case '<':
1252 while( fullNameLen > 0 &&
1253 isspace( fullName[ fullNameLen - 1 ] ))
1254 fullNameLen--;
1255 fullName[ fullNameLen ] = '\0';
1256 state = STATE_ADDRESS;
1257 continue;
1258 case '@':
1259 saveAtCharPos = fullNameLen;
1260 fullName[ fullNameLen++ ] = *c;
1261 continue;
1262 case '(':
1263 state = STATE_COMMENT;
1264 continue;
1265 case '=':
1266 if( *(c+1) == '?' )
1268 ++c;
1269 fullNameEncoded = 1;
1270 state = STATE_ENCODED_FULLNAME;
1271 continue;
1273 /* else fall through */
1274 default:
1275 if( fullName[0] != '\0' || !isspace( *c ))
1276 fullName[ fullNameLen++ ] = *c;
1278 continue;
1280 case STATE_QUOTED_FULLNAME:
1282 switch( *c )
1284 case '\\':
1285 fullName[ fullNameLen++ ] = *(++c);
1286 continue;
1287 case '"':
1288 state = STATE_FULLNAME;
1289 continue;
1290 default:
1291 fullName[ fullNameLen++ ] = *c;
1293 continue;
1295 case STATE_ENCODED_FULLNAME:
1297 switch( *c )
1299 case '?':
1300 if( *(c+1) == '=' )
1302 ++c;
1303 state = STATE_FULLNAME;
1304 continue;
1306 default:
1307 ; // do nothing... COMING SOON: decode at least latin1
1309 continue;
1311 case STATE_ADDRESS:
1313 switch( *c )
1315 case '"':
1316 state = STATE_QUOTED_ADDRESS;
1317 case '>':
1318 case '\n':
1319 // FIXME: Shouldn't it break here?
1320 // Since the address is finished?
1321 continue;
1322 case '@':
1323 atChar = addressName + addressNameLen;
1324 addressName[ addressNameLen++ ] = *c;
1325 continue;
1326 default:
1327 addressName[ addressNameLen++ ] = *c;
1329 continue;
1331 case STATE_QUOTED_ADDRESS:
1333 switch( *c )
1335 case '"':
1336 state = STATE_ADDRESS;
1337 continue;
1338 case '\\':
1339 addressName[ addressNameLen++ ] = *(++c);
1340 continue;
1341 default:
1342 addressName[ addressNameLen++ ] = *c;
1344 continue;
1345 case STATE_COMMENT:
1346 switch( *c )
1348 case ')':
1349 state = STATE_FULLNAME;
1350 continue;
1351 default:
1352 comment[ commentLen++ ] = *c;
1354 continue;
1358 if( *comment )
1360 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1361 // Comment seen: if there's an address, append to
1362 // fullname. If no address, copy fullname to address
1363 // and comment to fullname.
1364 if( *addressName )
1366 strcat(fullName, "(");
1367 strcat(fullName, comment);
1368 strcat(fullName, ")");
1370 else
1372 strcpy(addressName, fullName);
1373 strcpy(fullName, comment);
1376 free( comment );
1378 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1380 // what name should be tickered
1381 if( config.tickerMode == TICKER_FAMILYNAME && fullName[0] != '\0' && !fullNameEncoded )
1383 free( addressName );
1384 return fullName;
1386 else
1388 if( state == STATE_FULLNAME )
1390 strcpy( addressName, fullName );
1391 if( saveAtCharPos != -1 )
1392 atChar = &addressName[saveAtCharPos];
1394 if( config.tickerMode == TICKER_NICKNAME )
1396 if( atChar != NULL )
1397 *atChar = '\0';
1399 free( fullName );
1400 return addressName;
1404 static bool SkipSender( char *address )
1406 char **skipName;
1407 size_t len = strlen( address );
1409 // remove trailing '\n' got from fgets
1410 if( address[len - 1] == '\n' )
1411 address[len - 1] = '\0';
1413 while( isspace( *address ))
1414 address++;
1416 for( skipName = config.skipNames;
1417 skipName != NULL && *skipName != NULL; skipName++ )
1419 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName, address );
1421 // call libc-fnmatch (wildcard-match :-) !
1422 if( !fnmatch( *skipName, address, 0 ))
1424 TRACE( "skipping sender \"%s\"\n", *skipName );
1425 return true;
1429 return false;
1432 static int InsertName( char *name, unsigned long checksum, flag_t flag )
1434 name_t *item;
1436 TRACE( "insertName: %X, \"%s\"\n", checksum, name );
1437 if(( item = malloc( sizeof( name_t ))) == NULL )
1439 free( name );
1440 return -1;
1443 item->name = name;
1444 item->checksum = checksum;
1445 item->flag = flag;
1446 item->visited = true;
1447 item->next = names;
1448 names = item;
1450 namesChanged = true;
1451 return 0;
1454 static void RemoveLastName( void )
1456 if( names != NULL )
1458 name_t *name = names;
1459 names = names->next;
1460 free( name->name );
1461 free( name );
1465 static void ClearAllNames( void )
1467 name_t *name, *nextName;
1469 for( name = names; name != NULL; name = nextName )
1471 nextName = name->next;
1473 free( name->name );
1474 free( name );
1477 names = NULL;
1478 numMails = 0;
1480 namesChanged = true;
1483 static void SetMailFlags( flag_t flag )
1485 name_t *name;
1487 for( name = names; name != NULL; name = name->next )
1488 name->flag |= flag;
1491 static void DrawTickerX11Font( void )
1493 // 49 x 21 + 7 + 20 out-drawable size
1495 static int insertAt;
1497 if( curTickerName == NULL || namesChanged )
1499 for( curTickerName = names;
1500 curTickerName != NULL && config.newMailsOnly &&
1501 ( curTickerName->flag & FLAG_READ );
1502 curTickerName = curTickerName->next )
1505 if( curTickerName == NULL )
1506 return;
1508 namesChanged = false;
1509 insertAt = 54;
1512 XDrawString( DADisplay, outPixmap, tickerGC, insertAt,
1513 41-tickerFS->max_bounds.descent,
1514 curTickerName->name, strlen( curTickerName->name ));
1516 --insertAt;
1518 if( insertAt < -XTextWidth( tickerFS, curTickerName->name,
1519 strlen( curTickerName->name )) + 6 )
1522 curTickerName = curTickerName->next;
1523 while( curTickerName != NULL && config.newMailsOnly &&
1524 ( curTickerName->flag & FLAG_READ ));
1526 if( curTickerName != NULL )
1527 insertAt = 54;
1531 static void DrawTickerBuildinFont( void )
1533 // 49 x 21 + 7 + 20 out-drawable size
1534 // 14 x 21 font-character size
1536 static unsigned insertAt;
1537 static unsigned takeItFrom;
1539 unsigned leftSpace;
1540 unsigned drawTo;
1541 unsigned char *currentChar;
1543 if( names == NULL )
1544 return;
1546 if( curTickerName == NULL || namesChanged )
1548 for( curTickerName = names;
1549 curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ );
1550 curTickerName = curTickerName->next )
1553 if( curTickerName == NULL )
1554 return;
1556 insertAt = 57;
1557 takeItFrom = 0;
1558 namesChanged = false;
1561 leftSpace = takeItFrom % 14;
1563 for( currentChar = (unsigned char *)&curTickerName->name[takeItFrom/14],
1564 drawTo = insertAt; *currentChar != '\0'; ++currentChar )
1566 int outChar = (*currentChar < 32 || *currentChar >= 128) ? '?' :
1567 *currentChar;
1568 int charWidth = 57 - drawTo >= 14 ? 14 - leftSpace : 57 - drawTo;
1570 XCopyArea( DADisplay, charsPixmap, outPixmap, DAGC,
1571 (outChar - 32) * 14 + leftSpace, 0, charWidth, 21, drawTo, 20 );
1573 leftSpace = 0;
1574 drawTo += charWidth;
1576 if( drawTo > 57 )
1577 break;
1580 if( --insertAt < 7 )
1582 insertAt = 7;
1583 takeItFrom++;
1585 if( takeItFrom/14 >= strlen( curTickerName->name ))
1588 curTickerName = curTickerName->next;
1589 while( curTickerName != NULL && config.newMailsOnly &&
1590 ( curTickerName->flag & FLAG_READ ));
1592 if( curTickerName != NULL )
1594 takeItFrom = 0;
1595 insertAt = 57;
1601 static void ButtonPressed( int button, int state, int x, int y )
1603 (void) button;
1604 (void) state;
1606 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 )
1608 buttonPressed = true;
1609 forceRedraw = true;
1611 else
1612 // reread the config file
1613 readConfigFile = true;
1615 CheckTimeOut( false );
1618 static void ButtonReleased( int button, int state, int x, int y )
1620 (void) button;
1621 (void) state;
1623 buttonPressed = false;
1624 forceRedraw = true;
1626 if( x >= 35 && x <= 59 && y >= 47 && y <= 59 )
1628 int ret = system( config.runCmd );
1630 if( ret == 127 || ret == -1 )
1631 WARNING( "execution of command \"%s\" failed.\n", config.runCmd );
1634 CheckTimeOut( false );
1637 static void GetHexColorString( const char *colorName, char *xpmLine )
1639 XColor color;
1641 if( XParseColor( DADisplay,
1642 DefaultColormap( DADisplay, DefaultScreen( DADisplay )),
1643 colorName, &color ))
1644 sprintf( xpmLine, "%02X%02X%02X", color.red>>8, color.green>>8,
1645 color.blue>>8 );
1646 else
1647 WARNING( "unknown colorname: \"%s\"\n", colorName );
1650 static int XpmColorLine( const char *colorName, char **colorLine,
1651 bool disposeLine )
1653 char *newLine, *from;
1655 newLine = strdup( *colorLine );
1656 if ( newLine == NULL )
1657 return -1;
1659 from = strrchr( newLine, '#' );
1660 if( from == NULL &&
1661 strcasecmp( &(*colorLine)[ strlen( *colorLine ) - 4 ], "none" ) == 0 )
1664 * if no # found, it should be a None-color line
1666 free( newLine );
1667 newLine = malloc( 12 );
1668 if ( newLine != NULL )
1670 strcpy( newLine, " \tc #" );
1671 newLine[11] = '\0';
1672 from = newLine + 4;
1676 if( newLine == NULL)
1677 return -1;
1679 if( disposeLine )
1680 free( *colorLine );
1682 GetHexColorString( colorName, from + 1 );
1684 *colorLine = newLine;
1685 return 0;
1688 static void UpdateConfiguration( void )
1690 struct stat fileStat;
1692 TRACE( "reading configuration file...\n" );
1694 ReadConfigFile( configFile, true );
1696 // if no path/name to an mbox or maildir inbox directory was given,
1697 // use the environment
1698 if( config.mailBox == NULL )
1699 config.mailBox = getenv( "MAIL" );
1701 // mbox or maildir ?
1702 if( config.mailBox != NULL && stat( config.mailBox, &fileStat ) == 0 )
1703 isMaildir = S_ISDIR( fileStat.st_mode ) != 0;
1704 else
1705 isMaildir = false;
1707 TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" );
1709 if( PreparePixmaps( true ) < 0 )
1710 WARNING( "Cannot allocate color.\n" );
1712 DASetTimeout( 1000 / config.fps );
1715 static void CleanupNames( void )
1717 name_t *name, *last = NULL, *nextName;
1719 numMails = 0;
1721 for( name = names; name != NULL; name = nextName )
1723 nextName = name->next;
1725 if( !name->visited )
1727 if( last == NULL )
1728 names = name->next;
1729 else
1730 last->next = name->next;
1732 free( name->name );
1733 free( name );
1735 namesChanged = true;
1737 else
1739 last = name;
1741 if( !config.newMailsOnly || (name->flag & FLAG_READ) == 0 )
1742 ++numMails;
1747 static bool HasTickerWork( void )
1749 name_t *nextTickerName;
1751 if( names == NULL )
1752 return false;
1754 if( curTickerName == NULL || namesChanged )
1757 for( nextTickerName = names;
1758 nextTickerName != NULL && config.newMailsOnly &&
1759 ( nextTickerName->flag & FLAG_READ );
1760 nextTickerName = nextTickerName->next )
1763 if( nextTickerName == NULL )
1764 return false;
1767 return true;