1 ///////////////////////////////////////////////////////////////////////////////
3 // email indicator tool designed as docklet for Window Maker
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>.
10 // Redistribution and use in source and binary forms, with or without
11 // modification, are permitted provided that the following conditions
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 ///////////////////////////////////////////////////////////////////////////////
37 #ifndef CONFIG_H_INCLUDED
38 #include "../config.h"
39 #define CONFIG_H_INCLUDED
58 #ifdef HAVE_LIBDOCKAPP_DOCKAPP_H
59 #include <libdockapp/dockapp.h>
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"
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"
83 ///////////////////////////////////////////////////////////////////////////////
92 typedef struct _name_t
95 unsigned long checksum
;
111 STATE_QUOTED_ADDRESS
,
113 STATE_QUOTED_FULLNAME
,
114 STATE_ENCODED_FULLNAME
,
119 ///////////////////////////////////////////////////////////////////////////////
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
;
143 static XFontStruct
*tickerFS
;
144 static name_t
*names
;
145 static name_t
*curTickerName
;
152 OPT_INDEX_FAMILY_NAME
,
154 OPT_INDEX_SHORT_NAME
,
155 OPT_INDEX_SYMBOL_COLOR
,
156 OPT_INDEX_FONT_COLOR
,
157 OPT_INDEX_BACK_COLOR
,
159 OPT_INDEX_BACKGROUND
,
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",
177 .value
= { .string
= &config
.display
}
179 [OPT_INDEX_COMMAND
] =
182 .longForm
= "--command",
183 .description
= "cmd to run on btn-click (\"xterm -e mail\" is default)",
185 .value
= { .string
= &config
.runCmd
}
187 [OPT_INDEX_INTERVAL
] =
190 .longForm
= "--interval",
191 .description
= "number of secs between mail-status updates (1 is default)",
193 .value
= { .integer
= &config
.checkInterval
}
195 [OPT_INDEX_FAMILY_NAME
] =
198 .longForm
= "--familyname",
199 .description
= "tickers the family-name if available",
204 .longForm
= "--frames",
205 .description
= "ticker frames per second",
207 .value
= { .integer
= &config
.fps
}
209 [OPT_INDEX_SHORT_NAME
] =
212 .longForm
= "--shortname",
213 .description
= "tickers the nickname (all before the '@')",
215 [OPT_INDEX_SYMBOL_COLOR
] =
218 .longForm
= "--symbolcolor",
219 .description
= "symbol color-name",
221 .value
= { .string
= &config
.symbolColor
}
223 [OPT_INDEX_FONT_COLOR
] =
226 .longForm
= "--fontcolor",
227 .description
= "ticker-font color-name",
229 .value
= { .string
= &config
.fontColor
}
231 [OPT_INDEX_BACK_COLOR
] =
234 .longForm
= "--backcolor",
235 .description
= "backlight color-name",
237 .value
= { .string
= &config
.backColor
}
239 [OPT_INDEX_OFF_COLOR
] =
242 .longForm
= "--offcolor",
243 .description
= "off-light color-name",
245 .value
= { .string
= &config
.offlightColor
}
247 [OPT_INDEX_BACKGROUND
] =
250 .longForm
= "--background",
251 .description
= "frame-background for non-shaped window",
253 .value
= { .string
= &config
.backgroundColor
}
255 [OPT_INDEX_NO_SHAPE
] =
258 .longForm
= "--noshape",
259 .description
= "make the dockapp non-shaped (combine with -w)",
265 .description
= "forces wmail to show new mail exclusively",
267 [OPT_INDEX_MAILBOX
] =
270 .longForm
= "--mailbox",
271 .description
= "specify another mailbox ($MAIL is default)",
273 .value
= { .string
= &config
.mailBox
}
275 [OPT_INDEX_EXECUTE
] =
278 .longForm
= "--execute",
279 .description
= "command to execute when receiving a new mail",
281 .value
= { .string
= &config
.cmdOnMail
}
283 [OPT_INDEX_STATUS_FIELD
] =
286 .longForm
= "--statusfield",
287 .description
= "consider the status-field of the mail header to distinguish unread mails",
289 [OPT_INDEX_READ_STATUS
] =
292 .longForm
= "--readstatus",
293 .description
= "status field content that your client uses to mark read mails",
295 .value
= { .string
= &config
.readStatus
}
297 [OPT_INDEX_TICKER_FONT
] =
300 .longForm
= "--tickerfont",
301 .description
= "use specified X11 font to draw the ticker",
303 .value
= { .string
= &config
.useX11Font
}
305 [OPT_INDEX_CONFIG_FILE
] =
308 .longForm
= "--rcfile",
309 .description
= "specify another rc-file ($HOME/.wmailrc is default)",
311 .value
= { .string
= &configFile
}
316 ///////////////////////////////////////////////////////////////////////////////
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
,
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 ///////////////////////////////////////////////////////////////////////////////
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",
430 configFile
= strdup( WMAIL_RC_FILE
);
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
);
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 );
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
526 unsigned short dummy
;
534 if( config
.symbolColor
!= NULL
)
536 if( XpmColorLine( config
.symbolColor
, &symbols_xpm
[2], freeSymColor
) < 0 )
539 config
.colorsUsed
|= SYM_COLOR
;
543 if( XpmColorLine( "#20B2AA", &symbols_xpm
[2], freeSymColor
) < 0 )
546 config
.colorsUsed
|= SYM_COLOR
;
552 if( config
.fontColor
!= NULL
)
554 if( XpmColorLine( config
.fontColor
, &chars_xpm
[3], freeFntColor
) < 0 )
557 if( XpmColorLine( config
.fontColor
, &numbers_xpm
[3], freeFntColor
) < 0 )
560 config
.colorsUsed
|= FNT_COLOR
;
564 if( XpmColorLine( "#D3D3D3", &chars_xpm
[3], freeFntColor
) < 0 )
567 if( XpmColorLine( "#D3D3D3", &numbers_xpm
[3], freeFntColor
) < 0 )
570 config
.colorsUsed
|= FNT_COLOR
;
576 if( config
.backColor
!= NULL
)
578 if( XpmColorLine( config
.backColor
, &main_xpm
[3], freeBckColor
) < 0 )
581 if( XpmColorLine( config
.backColor
, &symbols_xpm
[3], freeBckColor
) < 0 )
584 if( XpmColorLine( config
.backColor
, &chars_xpm
[2], freeBckColor
) < 0 )
587 if( XpmColorLine( config
.backColor
, &numbers_xpm
[2], freeBckColor
) < 0 )
590 config
.colorsUsed
|= BCK_COLOR
;
594 if( XpmColorLine( "#282828", &main_xpm
[3], freeBckColor
) < 0 )
597 if( XpmColorLine( "#282828", &symbols_xpm
[3], freeBckColor
) < 0 )
600 if( XpmColorLine( "#282828", &chars_xpm
[2], freeBckColor
) < 0 )
603 if( XpmColorLine( "#282828", &numbers_xpm
[2], freeBckColor
) < 0 )
606 config
.colorsUsed
|= BCK_COLOR
;
612 if( config
.offlightColor
!= NULL
)
614 if( XpmColorLine( config
.offlightColor
, &main_xpm
[2], freeOffColor
) < 0 )
617 if( XpmColorLine( config
.offlightColor
, &numbers_xpm
[4], freeOffColor
) < 0 )
620 config
.colorsUsed
|= OFF_COLOR
;
624 if( XpmColorLine( "#000000", &main_xpm
[2], freeOffColor
) < 0 )
627 if( XpmColorLine( "#000000", &numbers_xpm
[4], freeOffColor
) < 0 )
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 )
641 config
.colorsUsed
|= BGR_COLOR
;
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
);
657 if( tickerFS
!= NULL
)
659 XFreeFont( DADisplay
, tickerFS
);
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
)
675 if( config
.fontColor
!= NULL
)
676 values
.foreground
= DAGetColor( config
.fontColor
);
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
,
690 clipRect
.height
= 23;
692 XSetClipRectangles( DADisplay
, tickerGC
, 0, 0, &clipRect
, 1, Unsorted
);
695 if( config
.noshape
) // non-shaped dockapp ?
698 DASetShape( mainPixmap_mask
);
703 static void MarkName( unsigned long checksum
)
707 for( name
= names
; name
!= NULL
; name
= name
->next
)
709 if( name
->checksum
== checksum
)
711 name
->flag
|= FLAG_READ
;
712 if( config
.newMailsOnly
)
719 static void DetermineState( void )
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
);
740 static void ReadChecksumFile( void )
742 FILE *f
= fopen( config
.checksumFileName
, "rb" );
746 unsigned long checksum
;
747 if( fread( &checksum
, sizeof(long), 1, f
) != 1 )
750 MarkName( checksum
);
758 static void WriteChecksumFile( bool writeAll
)
761 TRACE( "writing checksums:" );
763 if(( f
= fopen( config
.checksumFileName
, "wb" )) != NULL
)
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
);
783 static void UpdateChecksum( unsigned long *checksum
, const char *buf
)
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
)
806 static void TimedOut( void )
811 ResetConfigStrings();
812 if( !options
[OPT_INDEX_CONFIG_FILE
].used
)
814 exit( EXIT_SUCCESS
);
817 CheckTimeOut( true );
820 static void CheckTimeOut( bool force
)
822 static int checkMail
= 0;
825 gettimeofday(&now
, NULL
);
827 unsigned long nowMs
= now
.tv_sec
* 1000UL + now
.tv_usec
/ 1000UL;
829 if( !force
&& nowMs
- lastTimeOut
< 1000UL / config
.fps
)
836 readConfigFile
= false;
837 UpdateConfiguration();
844 TRACE( "checking for new mail...\n" );
852 UpdatePixmap( checkMail
% config
.fps
< config
.fps
/2 );
854 if( ++checkMail
>= config
.fps
* config
.checkInterval
)
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
868 if( state
!= STATE_NOMAIL
)
870 state
= STATE_NOMAIL
;
872 RemoveChecksumFile();
878 if( lastModifySeconds
!= fileStat
.st_mtime
|| forceRead
)
881 * File has been updated -> new mails arrived or some mails removed
884 ParseMBoxFile( &fileStat
);
885 stat( config
.mailBox
, &fileStat
);
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
)
899 SetMailFlags( FLAG_READ
);
904 lastModifySeconds
= fileStat
.st_mtime
;
905 lastAccessSeconds
= fileStat
.st_atime
;
909 static void CheckMaildir( void )
912 mail_state_t lastState
= state
;
913 unsigned lastMailCount
= numMails
;
919 TRACE( "all names cleared\n" );
922 state
= STATE_NOMAIL
;
924 if(( dir
= opendir( config
.mailBox
)) != NULL
)
926 struct dirent
*dirEnt
= NULL
;
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" );
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
967 WARNING( "can't open directory \"%s\"\n", config
.mailBox
);
969 if( lastState
!= state
|| lastMailCount
!= numMails
)
973 static int TraverseDirectory( const char *name
, bool isNewMail
)
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;
989 if( fullName
== NULL
)
991 WARNING( "Cannot allocate file/path\n" );
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
);
1009 name
->flag
= isNewMail
? FLAG_INITIAL
: FLAG_READ
;
1010 name
->visited
= true;
1023 static name_t
*GetMail( unsigned long checksum
)
1027 for( name
= names
; name
!= NULL
; name
= name
->next
)
1028 if( name
->checksum
== checksum
)
1034 static void UpdatePixmap( bool flashMailSymbol
)
1038 if( !forceRedraw
&& !HasTickerWork() )
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 );
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 )
1064 XCopyArea( DADisplay
, buttonPixmap
, outPixmap
, DAGC
,
1065 0, 0, 23, 11, 36, 48 );
1070 if( flashMailSymbol
)
1071 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
1072 13, 0, 37, 12, 20, 7 );
1074 case STATE_READMAIL
:
1075 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
1076 0, 0, 13, 12, 7, 7 );
1078 if( config
.useX11Font
== NULL
)
1079 DrawTickerBuildinFont();
1081 DrawTickerX11Font();
1083 default: // make compiler happy
1087 DASetPixmap( outPixmap
);
1090 static void ParseMBoxFile( struct stat
*fileStat
)
1093 struct utimbuf timeStruct
;
1095 FILE *f
= fopen( config
.mailBox
, "r" );
1096 unsigned long checksum
;
1098 state
= STATE_READMAIL
;
1105 WARNING( "can't open mbox \"%s\"\n", config
.mailBox
);
1109 while( fgets( buf
, sizeof buf
, f
) != NULL
)
1111 if( PREFIX_MATCHES( buf
, "From ", true ))
1119 UpdateChecksum( &checksum
, buf
);
1121 if( fromFound
&& PREFIX_MATCHES( buf
, "From:", false ))
1123 char *addr
= buf
+ sizeof "From:";
1125 if( SkipSender( addr
))
1129 if(( name
= ParseFromField( addr
)) == NULL
)
1131 WARNING( "Could not parse From field\n" );
1134 if ( InsertName( name
, checksum
, FLAG_INITIAL
) < 0 )
1136 WARNING( "Could not allocate name\n" );
1144 else if( config
.considerStatusField
&&
1145 PREFIX_MATCHES( buf
, "Status:", false ) &&
1146 strstr( buf
+ sizeof "Status:", config
.readStatus
) == NULL
)
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
)
1167 struct utimbuf timeStruct
;
1168 FILE *f
= fopen( fileName
, "r" );
1172 WARNING( "can't open maildir file \"%s\"\n", fileName
);
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
))
1186 if(( name
= ParseFromField( addr
)) == NULL
)
1188 WARNING( "Could not parse From field\n" );
1191 if ( InsertName( name
, checksum
,
1192 isNewMail
? FLAG_INITIAL
: FLAG_READ
) < 0 )
1194 WARNING( "Could not allocate name\n" );
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;
1216 char *atChar
= NULL
;
1218 size_t maxLen
= strlen( buf
) + 1;
1220 size_t fullNameLen
= 0, addressNameLen
= 0, commentLen
= 0;
1222 if(( fullName
= calloc( maxLen
, sizeof *fullName
)) == NULL
)
1224 if(( addressName
= calloc( maxLen
, sizeof *addressName
)) == NULL
)
1229 if(( comment
= calloc( maxLen
, sizeof *comment
)) == NULL
)
1232 free( addressName
);
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.
1240 for( c
= buf
; *c
!= '\0'; ++c
)
1244 case STATE_FULLNAME
:
1249 state
= STATE_QUOTED_FULLNAME
;
1252 while( fullNameLen
> 0 &&
1253 isspace( fullName
[ fullNameLen
- 1 ] ))
1255 fullName
[ fullNameLen
] = '\0';
1256 state
= STATE_ADDRESS
;
1259 saveAtCharPos
= fullNameLen
;
1260 fullName
[ fullNameLen
++ ] = *c
;
1263 state
= STATE_COMMENT
;
1269 fullNameEncoded
= 1;
1270 state
= STATE_ENCODED_FULLNAME
;
1273 /* else fall through */
1275 if( fullName
[0] != '\0' || !isspace( *c
))
1276 fullName
[ fullNameLen
++ ] = *c
;
1280 case STATE_QUOTED_FULLNAME
:
1285 fullName
[ fullNameLen
++ ] = *(++c
);
1288 state
= STATE_FULLNAME
;
1291 fullName
[ fullNameLen
++ ] = *c
;
1295 case STATE_ENCODED_FULLNAME
:
1303 state
= STATE_FULLNAME
;
1307 ; // do nothing... COMING SOON: decode at least latin1
1316 state
= STATE_QUOTED_ADDRESS
;
1319 // FIXME: Shouldn't it break here?
1320 // Since the address is finished?
1323 atChar
= addressName
+ addressNameLen
;
1324 addressName
[ addressNameLen
++ ] = *c
;
1327 addressName
[ addressNameLen
++ ] = *c
;
1331 case STATE_QUOTED_ADDRESS
:
1336 state
= STATE_ADDRESS
;
1339 addressName
[ addressNameLen
++ ] = *(++c
);
1342 addressName
[ addressNameLen
++ ] = *c
;
1349 state
= STATE_FULLNAME
;
1352 comment
[ commentLen
++ ] = *c
;
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.
1366 strcat(fullName
, "(");
1367 strcat(fullName
, comment
);
1368 strcat(fullName
, ")");
1372 strcpy(addressName
, fullName
);
1373 strcpy(fullName
, 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
);
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
)
1404 static bool SkipSender( char *address
)
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
))
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
);
1432 static int InsertName( char *name
, unsigned long checksum
, flag_t flag
)
1436 TRACE( "insertName: %X, \"%s\"\n", checksum
, name
);
1437 if(( item
= malloc( sizeof( name_t
))) == NULL
)
1444 item
->checksum
= checksum
;
1446 item
->visited
= true;
1450 namesChanged
= true;
1454 static void RemoveLastName( void )
1458 name_t
*name
= names
;
1459 names
= names
->next
;
1465 static void ClearAllNames( void )
1467 name_t
*name
, *nextName
;
1469 for( name
= names
; name
!= NULL
; name
= nextName
)
1471 nextName
= name
->next
;
1480 namesChanged
= true;
1483 static void SetMailFlags( flag_t flag
)
1487 for( name
= names
; name
!= NULL
; name
= name
->next
)
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
)
1508 namesChanged
= false;
1512 XDrawString( DADisplay
, outPixmap
, tickerGC
, insertAt
,
1513 41-tickerFS
->max_bounds
.descent
,
1514 curTickerName
->name
, strlen( curTickerName
->name
));
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
)
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
;
1541 unsigned char *currentChar
;
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
)
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) ? '?' :
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 );
1574 drawTo
+= charWidth
;
1580 if( --insertAt
< 7 )
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
)
1601 static void ButtonPressed( int button
, int state
, int x
, int y
)
1606 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 )
1608 buttonPressed
= true;
1612 // reread the config file
1613 readConfigFile
= true;
1615 CheckTimeOut( false );
1618 static void ButtonReleased( int button
, int state
, int x
, int y
)
1623 buttonPressed
= false;
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
)
1641 if( XParseColor( DADisplay
,
1642 DefaultColormap( DADisplay
, DefaultScreen( DADisplay
)),
1643 colorName
, &color
))
1644 sprintf( xpmLine
, "%02X%02X%02X", color
.red
>>8, color
.green
>>8,
1647 WARNING( "unknown colorname: \"%s\"\n", colorName
);
1650 static int XpmColorLine( const char *colorName
, char **colorLine
,
1653 char *newLine
, *from
;
1655 newLine
= strdup( *colorLine
);
1656 if ( newLine
== NULL
)
1659 from
= strrchr( newLine
, '#' );
1661 strcasecmp( &(*colorLine
)[ strlen( *colorLine
) - 4 ], "none" ) == 0 )
1664 * if no # found, it should be a None-color line
1667 newLine
= malloc( 12 );
1668 if ( newLine
!= NULL
)
1670 strcpy( newLine
, " \tc #" );
1676 if( newLine
== NULL
)
1682 GetHexColorString( colorName
, from
+ 1 );
1684 *colorLine
= newLine
;
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;
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
;
1721 for( name
= names
; name
!= NULL
; name
= nextName
)
1723 nextName
= name
->next
;
1725 if( !name
->visited
)
1730 last
->next
= name
->next
;
1735 namesChanged
= true;
1741 if( !config
.newMailsOnly
|| (name
->flag
& FLAG_READ
) == 0 )
1747 static bool HasTickerWork( void )
1749 name_t
*nextTickerName
;
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
)