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 ///////////////////////////////////////////////////////////////////////////////
91 typedef struct _name_t
{
93 unsigned long checksum
;
107 STATE_QUOTED_ADDRESS
,
109 STATE_QUOTED_FULLNAME
,
110 STATE_ENCODED_FULLNAME
,
115 ///////////////////////////////////////////////////////////////////////////////
118 static unsigned long lastTimeOut
;
119 static sig_atomic_t caughtSig
;
120 static mail_state_t state
;
121 static unsigned numMails
;
122 static bool namesChanged
;
123 static bool buttonPressed
;
124 static bool readConfigFile
;
125 static bool isMaildir
;
126 static bool forceRead
;
127 static bool forceRedraw
= true;
128 static time_t lastModifySeconds
;
129 static time_t lastAccessSeconds
;
130 static Pixmap mainPixmap
;
131 static Pixmap mainPixmap_mask
;
132 static Pixmap symbolsPixmap
;
133 static Pixmap charsPixmap
;
134 static Pixmap numbersPixmap
;
135 static Pixmap buttonPixmap
;
136 static Pixmap outPixmap
;
138 static XFontStruct
*tickerFS
;
139 static name_t
*names
;
140 static name_t
*curTickerName
;
142 static DAProgramOption options
[] = {
143 {"-display", NULL
, "display to use", DOString
, False
, {&config
.display
}},
144 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
145 DOString
, False
, {&config
.runCmd
} },
146 {"-i", "--intervall",
147 "number of secs between mail-status updates (1 is default)", DONatural
,
148 False
, {&config
.checkInterval
} },
149 {"-f", "--familyname", "tickers the family-name if available", DONone
,
151 {"-fps", "--frames", "ticker frames per second", DONatural
,
152 False
, {&config
.fps
} },
153 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone
,
155 {"-sc", "--symbolcolor", "symbol color-name",
156 DOString
, False
, {&config
.symbolColor
} },
157 {"-fc", "--fontcolor", "ticker-font color-name",
158 DOString
, False
, {&config
.fontColor
} },
159 {"-bc", "--backcolor", "backlight color-name",
160 DOString
, False
, {&config
.backColor
} },
161 {"-oc", "--offcolor", "off-light color-name",
162 DOString
, False
, {&config
.offlightColor
} },
163 {"-bg", "--background", "frame-background for non-shaped window",
164 DOString
, False
, {&config
.backgroundColor
} },
165 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
166 DONone
, False
, {NULL
} },
167 {"-n", "--new", "forces wmail to show new mail exclusively", DONone
, False
, {NULL
} },
168 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString
, False
, {&config
.mailBox
} },
169 {"-e", "--execute", "command to execute when receiving a new mail", DOString
, False
, {&config
.cmdOnMail
} },
170 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone
, False
, {NULL
} },
171 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString
, False
, {&config
.readStatus
} },
172 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString
, False
, {&config
.useX11Font
} }
176 ///////////////////////////////////////////////////////////////////////////////
179 static void PreparePixmaps( bool freeThemFirst
);
180 static void ExitHandler( int sig
);
181 static void TimedOut( void );
182 static void CheckTimeOut( bool force
);
183 static void CheckMBox( void );
184 static void CheckMaildir( void );
185 static int TraverseDirectory( const char *name
, bool isNewMail
);
186 static name_t
*GetMail( unsigned long checksum
);
187 static void UpdatePixmap( bool flashMailSymbol
);
188 static void ParseMBoxFile( struct stat
*fileStat
);
189 static void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
190 struct stat
*fileStat
, bool isNewMail
);
191 static char *ParseFromField( char *buf
);
192 static bool SkipSender( char *address
);
193 static void InsertName( char *name
, unsigned long checksum
, flag_t flag
);
194 static void RemoveLastName( void );
195 static void ClearAllNames( void );
196 static void DrawTickerX11Font( void );
197 static void DrawTickerBuildinFont( void );
198 static void ButtonPressed( int button
, int state
, int x
, int y
);
199 static void ButtonReleased( int button
, int state
, int x
, int y
);
200 static char *XpmColorLine( const char *colorName
, char *colorLine
,
202 static void ReadChecksumFile( void );
203 static void WriteChecksumFile( bool writeAll
);
204 static void UpdateChecksum( unsigned long *checksum
, const char *buf
);
205 static void RemoveChecksumFile( void );
206 static void SetMailFlags( flag_t flag
);
207 static void MarkName( unsigned long checksum
);
208 static void DetermineState( void );
209 static void UpdateConfiguration( void );
210 static void CleanupNames( void );
211 static bool HasTickerWork( void );
214 ///////////////////////////////////////////////////////////////////////////////
218 int main( int argc
, char **argv
)
221 struct sigaction sa
= { .sa_handler
= ExitHandler
};
222 struct stat fileStat
;
223 XTextProperty windowName
;
224 char *name
= argv
[0];
225 DACallbacks callbacks
= { NULL
, &ButtonPressed
, &ButtonReleased
,
226 NULL
, NULL
, NULL
, &TimedOut
};
228 // read the config file and override the default-settings
229 ReadConfigFile( false );
231 if( config
.checksumFileName
== NULL
) {
232 if(( usersHome
= getenv( "HOME" )) == NULL
) {
233 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE
);
234 config
.checksumFileName
= strdup( WMAIL_CHECKSUM_FILE
);
236 config
.checksumFileName
= MakePathName( usersHome
, WMAIL_CHECKSUM_FILE
);
239 if( config
.checksumFileName
== NULL
)
241 WARNING( "Cannot allocate checksum file-name.\n");
242 exit( EXIT_FAILURE
);
245 TRACE( "using checksum-file \"%s\"\n", config
.checksumFileName
);
247 // parse cmdline-args and override defaults and cfg-file settings
248 DAParseArguments( argc
, argv
, options
,
249 sizeof(options
) / sizeof(DAProgramOption
),
250 PACKAGE_NAME
, PACKAGE_STRING
);
252 if( options
[0].used
)
253 config
.givenOptions
|= CL_DISPLAY
;
254 if( options
[1].used
)
255 config
.givenOptions
|= CL_RUNCMD
;
256 if( options
[2].used
)
257 config
.givenOptions
|= CL_CHECKINTERVAL
;
258 if( options
[3].used
) {
259 config
.givenOptions
|= CL_TICKERMODE
;
260 config
.tickerMode
= TICKER_FAMILYNAME
;
262 if( options
[4].used
)
263 config
.givenOptions
|= CL_FPS
;
264 if( options
[5].used
) {
265 config
.givenOptions
|= CL_TICKERMODE
;
266 config
.tickerMode
= TICKER_NICKNAME
;
268 if( options
[6].used
)
269 config
.givenOptions
|= CL_SYMBOLCOLOR
;
270 if( options
[7].used
)
271 config
.givenOptions
|= CL_FONTCOLOR
;
272 if( options
[8].used
)
273 config
.givenOptions
|= CL_BACKCOLOR
;
274 if( options
[9].used
)
275 config
.givenOptions
|= CL_OFFLIGHTCOLOR
;
276 if( options
[10].used
)
277 config
.givenOptions
|= CL_BACKGROUNDCOLOR
;
278 if( options
[11].used
) {
279 config
.givenOptions
|= CL_NOSHAPE
;
280 config
.noshape
= true;
282 if( options
[12].used
) {
283 config
.givenOptions
|= CL_NEWMAILONLY
;
284 config
.newMailsOnly
= true;
286 if( options
[13].used
)
287 config
.givenOptions
|= CL_MAILBOX
;
288 if( options
[14].used
)
289 config
.givenOptions
|= CL_CMDONMAIL
;
290 if( options
[15].used
) {
291 config
.givenOptions
|= CL_CONSIDERSTATUSFIELD
;
292 config
.considerStatusField
= true;
294 if( options
[16].used
)
295 config
.givenOptions
|= CL_READSTATUS
;
296 if( options
[17].used
)
297 config
.givenOptions
|= CL_USEX11FONT
;
299 if( config
.mailBox
== NULL
)
300 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
301 else if( stat( config
.mailBox
, &fileStat
) == 0 )
302 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
304 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
306 // dockapp size hard wired - sorry...
307 DAInitialize( config
.display
, "wmail", 64, 64, argc
, argv
);
309 outPixmap
= DAMakePixmap();
310 PreparePixmaps( false );
312 if( sigaction( SIGINT
, &sa
, NULL
) == -1 ) {
313 perror( "wmail error: sigaction" );
314 exit( EXIT_FAILURE
);
317 if( sigaction( SIGTERM
, &sa
, NULL
) == -1 ) {
318 perror( "wmail error: sigaction" );
319 exit( EXIT_FAILURE
);
322 DASetCallbacks( &callbacks
);
323 DASetTimeout( 1000 / config
.fps
);
325 XStringListToTextProperty( &name
, 1, &windowName
);
326 XSetWMName( DADisplay
, DAWindow
, &windowName
);
328 UpdatePixmap( false );
336 static void PreparePixmaps( bool freeMem
)
338 // simple recoloring of the raw xpms befor creating Pixmaps of them
339 // this works as long as you don't "touch" the images...
341 #if DA_VERSION < 20030126
344 unsigned short dummy
;
349 if( config
.symbolColor
!= NULL
) { // symbol color ?
350 symbols_xpm
[2] = XpmColorLine( config
.symbolColor
, symbols_xpm
[2],
351 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
352 config
.colorsUsed
|= SYM_COLOR
;
354 symbols_xpm
[2] = XpmColorLine( "#20B2AA", symbols_xpm
[2],
355 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
356 config
.colorsUsed
|= SYM_COLOR
;
359 if( config
.fontColor
!= NULL
) { // font color ?
360 chars_xpm
[3] = XpmColorLine( config
.fontColor
, chars_xpm
[3],
361 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
362 numbers_xpm
[3] = XpmColorLine( config
.fontColor
, numbers_xpm
[3],
363 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
364 config
.colorsUsed
|= FNT_COLOR
;
366 chars_xpm
[3] = XpmColorLine( "#D3D3D3", chars_xpm
[3],
367 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
368 numbers_xpm
[3] = XpmColorLine( "#D3D3D3", numbers_xpm
[3],
369 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
370 config
.colorsUsed
|= FNT_COLOR
;
373 if( config
.backColor
!= NULL
) { // backlight color ?
374 main_xpm
[3] = XpmColorLine( config
.backColor
, main_xpm
[3],
375 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
376 symbols_xpm
[3] = XpmColorLine( config
.backColor
, symbols_xpm
[3],
377 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
378 chars_xpm
[2] = XpmColorLine( config
.backColor
, chars_xpm
[2],
379 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
380 numbers_xpm
[2] = XpmColorLine( config
.backColor
, numbers_xpm
[2],
381 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
382 config
.colorsUsed
|= BCK_COLOR
;
384 main_xpm
[3] = XpmColorLine( "#282828", main_xpm
[3],
385 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
386 symbols_xpm
[3] = XpmColorLine( "#282828", symbols_xpm
[3],
387 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
388 chars_xpm
[2] = XpmColorLine( "#282828", chars_xpm
[2],
389 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
390 numbers_xpm
[2] = XpmColorLine( "#282828", numbers_xpm
[2],
391 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
392 config
.colorsUsed
|= BCK_COLOR
;
395 if( config
.offlightColor
!= NULL
) { // off-light color ?
396 main_xpm
[2] = XpmColorLine( config
.offlightColor
, main_xpm
[2],
397 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
398 numbers_xpm
[4] = XpmColorLine( config
.offlightColor
, numbers_xpm
[4],
399 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
400 config
.colorsUsed
|= OFF_COLOR
;
402 main_xpm
[2] = XpmColorLine( "#000000", main_xpm
[2],
403 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
404 numbers_xpm
[4] = XpmColorLine( "#000000", numbers_xpm
[4],
405 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
406 config
.colorsUsed
|= OFF_COLOR
;
409 if( config
.backgroundColor
!= NULL
) { // window-frame background (only seen if nonshaped) ?
410 main_xpm
[1] = XpmColorLine( config
.backgroundColor
, main_xpm
[1],
411 freeMem
&& ( config
.colorsUsed
& BGR_COLOR
));
412 config
.colorsUsed
|= BGR_COLOR
;
417 XFreePixmap( DADisplay
, mainPixmap
);
418 XFreePixmap( DADisplay
, mainPixmap_mask
);
419 XFreePixmap( DADisplay
, symbolsPixmap
);
420 XFreePixmap( DADisplay
, charsPixmap
);
421 XFreePixmap( DADisplay
, numbersPixmap
);
422 XFreePixmap( DADisplay
, buttonPixmap
);
424 if( tickerGC
!= NULL
)
426 XFreeGC( DADisplay
, tickerGC
);
428 if( tickerFS
!= NULL
) {
429 XFreeFont( DADisplay
, tickerFS
);
435 DAMakePixmapFromData( main_xpm
, &mainPixmap
, &mainPixmap_mask
, &dummy
, &dummy
);
436 DAMakePixmapFromData( symbols_xpm
, &symbolsPixmap
, NULL
, &dummy
, &dummy
);
437 DAMakePixmapFromData( chars_xpm
, &charsPixmap
, NULL
, &dummy
, &dummy
);
438 DAMakePixmapFromData( numbers_xpm
, &numbersPixmap
, NULL
, &dummy
, &dummy
);
439 DAMakePixmapFromData( button_xpm
, &buttonPixmap
, NULL
, &dummy
, &dummy
);
441 if( config
.useX11Font
!= NULL
)
445 if( config
.fontColor
!= NULL
)
446 values
.foreground
= DAGetColor( config
.fontColor
);
448 values
.foreground
= DAGetColor( "#D3D3D3" );
450 tickerFS
= XLoadQueryFont( DADisplay
, config
.useX11Font
);
451 if( tickerFS
== NULL
)
452 ABORT( "Cannot load font \"%s\"", config
.useX11Font
);
454 values
.font
= tickerFS
->fid
;
455 tickerGC
= XCreateGC( DADisplay
, DAWindow
, GCForeground
| GCFont
,
460 clipRect
.height
= 23;
462 XSetClipRectangles( DADisplay
, tickerGC
, 0, 0, &clipRect
, 1, Unsorted
);
465 if( config
.noshape
) // non-shaped dockapp ?
468 DASetShape( mainPixmap_mask
);
471 static void MarkName( unsigned long checksum
)
475 for( name
= names
; name
!= NULL
; name
= name
->next
) {
476 if( name
->checksum
== checksum
) {
477 name
->flag
|= FLAG_READ
;
478 if( config
.newMailsOnly
)
485 static void DetermineState( void )
489 for( name
= names
; name
!= NULL
; name
= name
->next
)
490 if(!( name
->flag
& FLAG_READ
)) {
491 state
= STATE_NEWMAIL
;
493 if( config
.cmdOnMail
!= NULL
) {
494 int ret
= system( config
.cmdOnMail
);
496 if( ret
== 127 || ret
== -1 )
497 WARNING( "execution of command \"%s\" failed.\n", config
.cmdOnMail
);
504 static void ReadChecksumFile( void )
506 FILE *f
= fopen( config
.checksumFileName
, "rb" );
509 unsigned long checksum
;
510 if( fread( &checksum
, sizeof(long), 1, f
) != 1 )
513 MarkName( checksum
);
521 static void WriteChecksumFile( bool writeAll
)
524 TRACE( "writing checksums:" );
526 if(( f
= fopen( config
.checksumFileName
, "wb" )) != NULL
) {
528 for( name
= names
; name
!= NULL
; name
= name
->next
) {
529 if( writeAll
|| (name
->flag
& FLAG_READ
)) {
530 fwrite( &name
->checksum
, sizeof(long), 1, f
);
531 TRACE( " %X", name
->checksum
);
542 static void UpdateChecksum( unsigned long *checksum
, const char *buf
)
545 size_t i
, len
= strlen( buf
);
547 for( i
= 0; i
< len
; ++i
)
548 *checksum
+= buf
[i
] << (( i
% sizeof(long) ) * 8 );
552 static void RemoveChecksumFile( void )
554 TRACE( "removing checksum-file\n" );
555 remove( config
.checksumFileName
);
558 static void ExitHandler( int sig
)
564 static void TimedOut( void )
568 ResetConfigStrings();
569 exit( EXIT_SUCCESS
);
572 CheckTimeOut( true );
575 static void CheckTimeOut( bool force
)
577 static int checkMail
= 0;
580 gettimeofday(&now
, NULL
);
582 unsigned long nowMs
= now
.tv_sec
* 1000UL + now
.tv_usec
/ 1000UL;
584 if( !force
&& nowMs
- lastTimeOut
< 1000UL / config
.fps
)
589 if( readConfigFile
) {
590 readConfigFile
= false;
591 UpdateConfiguration();
598 TRACE( "checking for new mail...\n" );
606 UpdatePixmap( checkMail
% config
.fps
< config
.fps
/2 );
608 if( ++checkMail
>= config
.fps
* config
.checkInterval
)
612 static void CheckMBox( void )
614 struct stat fileStat
;
616 // error retrieving file-stats -> no/zero-size file and no new/read mails
618 if( stat( config
.mailBox
, &fileStat
) == -1 || fileStat
.st_size
== 0 ) {
619 if( state
!= STATE_NOMAIL
) {
620 state
= STATE_NOMAIL
;
622 RemoveChecksumFile();
626 // file has changed -> new mails arrived or some mails removed
627 if( lastModifySeconds
!= fileStat
.st_mtime
|| forceRead
) {
629 ParseMBoxFile( &fileStat
);
630 stat( config
.mailBox
, &fileStat
);
632 // file has accessed (read) -> mark all mails as "read", because
633 // it cannot be decided which mails the user has read...
634 } else if( lastAccessSeconds
!= fileStat
.st_atime
) {
635 state
= STATE_READMAIL
;
636 WriteChecksumFile( true );
637 if( config
.newMailsOnly
) {
639 SetMailFlags( FLAG_READ
);
644 lastModifySeconds
= fileStat
.st_mtime
;
645 lastAccessSeconds
= fileStat
.st_atime
;
649 static void CheckMaildir( void )
652 mail_state_t lastState
= state
;
653 unsigned lastMailCount
= numMails
;
658 TRACE( "all names cleared\n" );
661 state
= STATE_NOMAIL
;
663 if(( dir
= opendir( config
.mailBox
)) != NULL
)
665 struct dirent
*dirEnt
= NULL
;
666 //bool writeChecksums = false;
669 for( name
= names
; name
!= NULL
; name
= name
->next
)
670 name
->visited
= false;
672 while(( dirEnt
= readdir( dir
)) != NULL
)
674 char *fullName
= MakePathName( config
.mailBox
, dirEnt
->d_name
);
675 struct stat fileStat
;
677 if( fullName
== NULL
)
679 WARNING( "Cannot allocate file/path\n" );
683 if( stat( fullName
, &fileStat
) == -1 )
684 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
685 else if( S_ISDIR( fileStat
.st_mode
))
687 if( strcmp( dirEnt
->d_name
, "new" ) == 0 ) {
688 if( TraverseDirectory( fullName
, true ) > 0 )
689 state
= STATE_NEWMAIL
;
691 else if( strcmp( dirEnt
->d_name
, "cur" ) == 0 ) {
692 if( TraverseDirectory( fullName
, false ) > 0 )
693 if( state
!= STATE_NEWMAIL
)
694 state
= STATE_READMAIL
;
696 // directories ".", ".." and "tmp" discarded
703 WARNING( "can't open directory \"%s\"\n", config
.mailBox
);
705 if( lastState
!= state
|| lastMailCount
!= numMails
)
709 static int TraverseDirectory( const char *name
, bool isNewMail
)
714 if(( dir
= opendir( name
)) != NULL
)
716 struct dirent
*dirEnt
= NULL
;
718 while(( dirEnt
= readdir( dir
)) != NULL
)
720 char *fullName
= MakePathName( name
, dirEnt
->d_name
);
721 struct stat fileStat
;
722 unsigned long checksum
= 0;
725 if( fullName
== NULL
)
727 WARNING( "Cannot allocate file/path\n" );
731 if( stat( fullName
, &fileStat
) == -1 )
732 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
733 else if( !S_ISDIR( fileStat
.st_mode
))
735 TRACE( "found email-file \"%s\"\n", fullName
);
736 UpdateChecksum( &checksum
, dirEnt
->d_name
);
738 if(( name
= GetMail( checksum
)) == NULL
)
740 TRACE( "-> new file - parsing it\n" );
741 ParseMaildirFile( fullName
, checksum
, &fileStat
, isNewMail
);
744 name
->flag
= isNewMail
? FLAG_INITIAL
: FLAG_READ
;
745 name
->visited
= true;
758 static name_t
*GetMail( unsigned long checksum
)
762 for( name
= names
; name
!= NULL
; name
= name
->next
)
763 if( name
->checksum
== checksum
)
769 static void UpdatePixmap( bool flashMailSymbol
)
773 if( !forceRedraw
&& !HasTickerWork() )
778 XCopyArea( DADisplay
, mainPixmap
, outPixmap
, DAGC
,
779 0, 0, 64, 64, 0, 0 );
783 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
784 50, 0, 5, 9, 6, 49 );
787 drawCount
= numMails
;
789 for( i
= 0; i
< 3; ++i
, drawCount
/= 10 )
791 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
792 (drawCount
%10)*5, 0, 5, 9, 24-i
*6, 49 );
799 XCopyArea( DADisplay
, buttonPixmap
, outPixmap
, DAGC
,
800 0, 0, 23, 11, 36, 48 );
805 if( flashMailSymbol
)
806 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
807 13, 0, 37, 12, 20, 7 );
810 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
811 0, 0, 13, 12, 7, 7 );
813 if( config
.useX11Font
== NULL
)
814 DrawTickerBuildinFont();
818 default: // make compiler happy
822 DASetPixmap( outPixmap
);
825 static void ParseMBoxFile( struct stat
*fileStat
)
828 struct utimbuf timeStruct
;
830 FILE *f
= fopen( config
.mailBox
, "rt" );
831 unsigned long checksum
;
833 state
= STATE_READMAIL
;
839 WARNING( "can't open mbox \"%s\"\n", config
.mailBox
);
843 while( fgets( buf
, 1024, f
) != NULL
)
845 if( strncmp( buf
, "From ", 5 ) == 0 ) {
852 UpdateChecksum( &checksum
, buf
);
854 if( fromFound
&& strncasecmp( buf
, "from: ", 6 ) == 0 )
856 if( SkipSender( buf
+6 ))
860 if(( name
= ParseFromField( buf
+6 )) == NULL
)
862 WARNING( "Could not parse From field\n" );
865 InsertName( name
, checksum
, FLAG_INITIAL
);
870 } else if( config
.considerStatusField
&& strncasecmp( buf
, "status: ", 8 ) == 0 &&
871 strstr( buf
+8, config
.readStatus
) == NULL
)
885 timeStruct
.actime
= fileStat
->st_atime
;
886 timeStruct
.modtime
= fileStat
->st_mtime
;
887 utime( config
.mailBox
, &timeStruct
);
890 static void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
891 struct stat
*fileStat
, bool isNewMail
)
894 struct utimbuf timeStruct
;
895 FILE *f
= fopen( fileName
, "rt" );
899 WARNING( "can't open maildir file \"%s\"\n", fileName
);
903 while( fgets( buf
, 1024, f
) != NULL
)
905 if( strncasecmp( buf
, "from: ", 6 ) == 0 )
907 if( SkipSender( buf
+6 ))
911 if(( name
= ParseFromField( buf
+6 )) == NULL
)
913 WARNING( "Could not parse From field\n" );
916 InsertName( name
, checksum
, isNewMail
? FLAG_INITIAL
: FLAG_READ
);
924 timeStruct
.actime
= fileStat
->st_atime
;
925 timeStruct
.modtime
= fileStat
->st_mtime
;
926 utime( fileName
, &timeStruct
);
929 static char *ParseFromField( char *buf
)
931 parse_state_t state
= STATE_FULLNAME
;
932 int fullNameEncoded
= 0;
933 int saveAtCharPos
= -1;
938 size_t maxLen
= strlen( buf
) + 1;
940 size_t fullNameLen
= 0, addressNameLen
= 0, commentLen
= 0;
942 if(( fullName
= calloc( maxLen
, sizeof *fullName
)) == NULL
)
944 if(( addressName
= calloc( maxLen
, sizeof *addressName
)) == NULL
)
949 if(( comment
= calloc( maxLen
, sizeof *comment
)) == NULL
)
956 // FIXME: Don't do that "encoded" dance. It's not intended by
957 // RFC2047, and it's better to just do it in the end.
960 for( c
= buf
; *c
!= '\0'; ++c
)
967 state
= STATE_QUOTED_FULLNAME
;
970 while( fullNameLen
> 0 &&
971 isspace( fullName
[ fullNameLen
- 1 ] ))
973 fullName
[ fullNameLen
] = '\0';
974 state
= STATE_ADDRESS
;
977 saveAtCharPos
= fullNameLen
;
978 fullName
[ fullNameLen
++ ] = *c
;
981 state
= STATE_COMMENT
;
984 if( *(c
+1) == '?' ) {
987 state
= STATE_ENCODED_FULLNAME
;
990 /* else fall through */
992 if( fullName
[0] != '\0' || !isspace( *c
))
993 fullName
[ fullNameLen
++ ] = *c
;
997 case STATE_QUOTED_FULLNAME
:
1001 fullName
[ fullNameLen
++ ] = *(++c
);
1004 state
= STATE_FULLNAME
;
1007 fullName
[ fullNameLen
++ ] = *c
;
1011 case STATE_ENCODED_FULLNAME
:
1015 if( *(c
+1) == '=' ) {
1017 state
= STATE_FULLNAME
;
1021 ; // do nothing... COMING SOON: decode at least latin1
1029 state
= STATE_QUOTED_ADDRESS
;
1032 // FIXME: Shouldn't it break here?
1033 // Since the address is finished?
1036 atChar
= addressName
+ addressNameLen
;
1037 addressName
[ addressNameLen
++ ] = *c
;
1040 addressName
[ addressNameLen
++ ] = *c
;
1044 case STATE_QUOTED_ADDRESS
:
1048 state
= STATE_ADDRESS
;
1051 addressName
[ addressNameLen
++ ] = *(++c
);
1054 addressName
[ addressNameLen
++ ] = *c
;
1060 state
= STATE_FULLNAME
;
1063 comment
[ commentLen
++ ] = *c
;
1071 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1072 // Comment seen: if there's an address, append to
1073 // fullname. If no address, copy fullname to address
1074 // and comment to fullname.
1075 if( *addressName
) {
1076 strcat(fullName
, "(");
1077 strcat(fullName
, comment
);
1078 strcat(fullName
, ")");
1080 strcpy(addressName
, fullName
);
1081 strcpy(fullName
, comment
);
1086 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1088 // what name should be tickered
1089 if( config
.tickerMode
== TICKER_FAMILYNAME
&& fullName
[0] != '\0' && !fullNameEncoded
) {
1090 free( addressName
);
1093 if( state
== STATE_FULLNAME
) {
1094 strcpy( addressName
, fullName
);
1095 if( saveAtCharPos
!= -1 )
1096 atChar
= &addressName
[saveAtCharPos
];
1098 if( config
.tickerMode
== TICKER_NICKNAME
) {
1099 if( atChar
!= NULL
)
1107 static bool SkipSender( char *address
)
1110 size_t len
= strlen( address
);
1112 // remove trailing '\n' got from fgets
1113 if( address
[len
-1] == '\n' )
1114 address
[len
-1] = '\0';
1116 for( skipName
= config
.skipNames
;
1117 skipName
!= NULL
&& *skipName
!= NULL
; skipName
++ )
1119 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName
, address
);
1121 // call libc-fnmatch (wildcard-match :-) !
1122 if( !fnmatch( *skipName
, address
, 0 )) {
1123 TRACE( "skipping sender \"%s\"\n", *skipName
);
1131 static void InsertName( char *name
, unsigned long checksum
, flag_t flag
)
1135 TRACE( "insertName: %X, \"%s\"\n", checksum
, name
);
1136 if(( item
= malloc( sizeof( name_t
))) == NULL
)
1142 item
->name
= name
; /*strdup( name );*/
1143 item
->checksum
= checksum
;
1145 item
->visited
= true;
1149 namesChanged
= true;
1152 static void RemoveLastName( void )
1154 if( names
!= NULL
) {
1155 name_t
*name
= names
;
1156 names
= names
->next
;
1162 static void ClearAllNames( void )
1164 name_t
*name
, *nextName
;
1166 for( name
= names
; name
!= NULL
; name
= nextName
) {
1167 nextName
= name
->next
;
1176 namesChanged
= true;
1179 static void SetMailFlags( flag_t flag
)
1183 for( name
= names
; name
!= NULL
; name
= name
->next
)
1187 static void DrawTickerX11Font( void )
1189 // 49x21+7+20 out-drawable size
1191 static int insertAt
;
1193 if( curTickerName
== NULL
|| namesChanged
)
1195 for( curTickerName
= names
;
1196 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1197 curTickerName
= curTickerName
->next
);
1199 if( curTickerName
== NULL
)
1202 namesChanged
= false;
1206 XDrawString( DADisplay
, outPixmap
, tickerGC
, insertAt
,
1207 41-tickerFS
->max_bounds
.descent
,
1208 curTickerName
->name
, strlen( curTickerName
->name
));
1212 if( insertAt
< -XTextWidth( tickerFS
, curTickerName
->name
,
1213 strlen( curTickerName
->name
)) + 6 )
1216 curTickerName
= curTickerName
->next
;
1217 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1219 if( curTickerName
!= NULL
) {
1225 static void DrawTickerBuildinFont( void )
1227 // 49x21+7+20 out-drawable size
1228 // 14x21 font-character size
1230 static unsigned insertAt
;
1231 static unsigned takeItFrom
;
1235 unsigned char *currentChar
;
1240 if( curTickerName
== NULL
|| namesChanged
) {
1242 for( curTickerName
= names
;
1243 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1244 curTickerName
= curTickerName
->next
);
1246 if( curTickerName
== NULL
)
1251 namesChanged
= false;
1254 leftSpace
= takeItFrom
% 14;
1256 for( currentChar
= (unsigned char *)&curTickerName
->name
[takeItFrom
/14],
1257 drawTo
= insertAt
; *currentChar
!= '\0'; ++currentChar
)
1260 int outChar
= (*currentChar
< 32 || *currentChar
>= 128) ? '?' :
1262 int charWidth
= 57-drawTo
>= 14 ? 14 - leftSpace
: 57-drawTo
;
1264 XCopyArea( DADisplay
, charsPixmap
, outPixmap
, DAGC
,
1265 (outChar
-32)*14+leftSpace
, 0, charWidth
, 21, drawTo
, 20 );
1268 drawTo
+= charWidth
;
1274 if( --insertAt
< 7 ) {
1278 if( takeItFrom
/14 >= strlen( curTickerName
->name
)) {
1281 curTickerName
= curTickerName
->next
;
1282 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1284 if( curTickerName
!= NULL
) {
1292 static void ButtonPressed( int button
, int state
, int x
, int y
)
1297 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1298 buttonPressed
= true;
1301 // reread the config file
1302 readConfigFile
= true;
1304 CheckTimeOut( false );
1307 static void ButtonReleased( int button
, int state
, int x
, int y
)
1312 buttonPressed
= false;
1315 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1316 int ret
= system( config
.runCmd
);
1318 if( ret
== 127 || ret
== -1 )
1319 WARNING( "execution of command \"%s\" failed.\n", config
.runCmd
);
1322 CheckTimeOut( false );
1325 static void GetHexColorString( const char *colorName
, char *xpmLine
)
1329 if( XParseColor( DADisplay
,
1330 DefaultColormap( DADisplay
, DefaultScreen( DADisplay
)),
1331 colorName
, &color
))
1333 sprintf( xpmLine
, "%02X%02X%02X", color
.red
>>8, color
.green
>>8,
1336 WARNING( "unknown colorname: \"%s\"\n", colorName
);
1339 static char *XpmColorLine( const char *colorName
, char *colorLine
,
1342 char *newLine
= strdup( colorLine
);
1343 char *from
= strrchr( newLine
, '#' );
1345 if( from
== NULL
&& !strcasecmp( &colorLine
[ strlen( colorLine
) - 4 ], "none" )) {
1346 // if no # found, it should be a None-color line
1348 newLine
= malloc( 12 );
1349 strcpy( newLine
, " \tc #" );
1357 GetHexColorString( colorName
, from
+1 );
1362 static void UpdateConfiguration( void )
1364 struct stat fileStat
;
1366 TRACE( "reading configuration file...\n" );
1368 ReadConfigFile( true );
1370 // if no path/name to an mbox or maildir inbox directory was given,
1371 // use the environment
1372 if( config
.mailBox
== NULL
)
1373 config
.mailBox
= getenv( "MAIL" );
1375 // mbox or maildir ?
1376 if( config
.mailBox
!= NULL
&& stat( config
.mailBox
, &fileStat
) == 0 )
1377 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
1381 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
1383 PreparePixmaps( true );
1385 DASetTimeout( 1000 / config
.fps
);
1388 static void CleanupNames( void )
1390 name_t
*name
, *last
= NULL
, *nextName
;
1394 for( name
= names
; name
!= NULL
; name
= nextName
)
1396 nextName
= name
->next
;
1398 if( !name
->visited
) {
1402 last
->next
= name
->next
;
1409 if( !config
.newMailsOnly
|| (name
->flag
& FLAG_READ
) == 0 )
1415 static bool HasTickerWork( void )
1417 name_t
*nextTickerName
;
1422 if( curTickerName
== NULL
|| namesChanged
) {
1424 for( nextTickerName
= names
;
1425 nextTickerName
!= NULL
&& ( config
.newMailsOnly
&& ( nextTickerName
->flag
& FLAG_READ
));
1426 nextTickerName
= nextTickerName
->next
);
1428 if( nextTickerName
== NULL
)