1 ///////////////////////////////////////////////////////////////////////////////
3 // email indicator tool designed as docklet for Window Maker
8 // Copyright 2000~2002, Sven Geisenhainer <sveng@informatik.uni-jena.de>.
9 // All rights reserved.
11 // Redistribution and use in source and binary forms, with or without
12 // modification, are permitted provided that the following conditions
14 // 1. Redistributions of source code must retain the above copyright
15 // notice, this list of conditions, and the following disclaimer.
16 // 2. Redistributions in binary form must reproduce the above copyright
17 // notice, this list of conditions, and the following disclaimer in the
18 // documentation and/or other materials provided with the distribution.
19 // 3. The name of the author may not be used to endorse or promote products
20 // derived from this software without specific prior written permission.
22 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 ///////////////////////////////////////////////////////////////////////////////
38 #ifndef CONFIG_H_INCLUDED
39 #include "../config.h"
40 #define CONFIG_H_INCLUDED
59 #include <libdockapp/dockapp.h>
66 #include "xpm_delt/main.xpm"
67 #include "xpm_delt/symbols.xpm"
68 #include "xpm_delt/numbers.xpm"
69 #include "xpm_delt/button.xpm"
70 #include "xpm_delt/chars.xpm"
72 #include "xpm/main.xpm"
73 #include "xpm/symbols.xpm"
74 #include "xpm/numbers.xpm"
75 #include "xpm/button.xpm"
76 #include "xpm/chars.xpm"
80 ///////////////////////////////////////////////////////////////////////////////
88 typedef struct _name_t
{
90 unsigned long checksum
;
104 STATE_QUOTED_ADDRESS
,
106 STATE_QUOTED_FULLNAME
,
107 STATE_ENCODED_FULLNAME
,
112 ///////////////////////////////////////////////////////////////////////////////
115 static unsigned long lastTimeOut
;
116 static sig_atomic_t caughtSig
;
117 static mail_state_t state
;
118 static unsigned numMails
;
119 static bool namesChanged
;
120 static bool buttonPressed
;
121 static bool readConfigFile
;
122 static bool isMaildir
;
123 static bool forceRead
;
124 static bool forceRedraw
= true;
125 static time_t lastModifySeconds
;
126 static time_t lastAccessSeconds
;
127 static Pixmap mainPixmap
;
128 static Pixmap mainPixmap_mask
;
129 static Pixmap symbolsPixmap
;
130 static Pixmap charsPixmap
;
131 static Pixmap numbersPixmap
;
132 static Pixmap buttonPixmap
;
133 static Pixmap outPixmap
;
135 static XFontStruct
*tickerFS
;
136 static name_t
*names
;
137 static name_t
*curTickerName
;
139 static DAProgramOption options
[] = {
140 {"-display", NULL
, "display to use", DOString
, False
, {&config
.display
}},
141 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
142 DOString
, False
, {&config
.runCmd
} },
143 {"-i", "--intervall",
144 "number of secs between mail-status updates (1 is default)", DONatural
,
145 False
, {&config
.checkInterval
} },
146 {"-f", "--familyname", "tickers the family-name if available", DONone
,
148 {"-fps", "--frames", "ticker frames per second", DONatural
,
149 False
, {&config
.fps
} },
150 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone
,
152 {"-sc", "--symbolcolor", "symbol color-name",
153 DOString
, False
, {&config
.symbolColor
} },
154 {"-fc", "--fontcolor", "ticker-font color-name",
155 DOString
, False
, {&config
.fontColor
} },
156 {"-bc", "--backcolor", "backlight color-name",
157 DOString
, False
, {&config
.backColor
} },
158 {"-oc", "--offcolor", "off-light color-name",
159 DOString
, False
, {&config
.offlightColor
} },
160 {"-bg", "--background", "frame-background for non-shaped window",
161 DOString
, False
, {&config
.backgroundColor
} },
162 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
163 DONone
, False
, {NULL
} },
164 {"-n", "--new", "forces wmail to show new mail exclusively", DONone
, False
, {NULL
} },
165 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString
, False
, {&config
.mailBox
} },
166 {"-e", "--execute", "command to execute when receiving a new mail", DOString
, False
, {&config
.cmdOnMail
} },
167 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone
, False
, {NULL
} },
168 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString
, False
, {&config
.readStatus
} },
169 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString
, False
, {&config
.useX11Font
} }
173 ///////////////////////////////////////////////////////////////////////////////
176 static void PreparePixmaps( bool freeThemFirst
);
177 static void ExitHandler( int sig
);
178 static void TimedOut( void );
179 static void CheckTimeOut( bool force
);
180 static void CheckMBox( void );
181 static void CheckMaildir( void );
182 static int TraverseDirectory( const char *name
, bool isNewMail
);
183 static name_t
*GetMail( unsigned long checksum
);
184 static void UpdatePixmap( bool flashMailSymbol
);
185 static void ParseMBoxFile( struct stat
*fileStat
);
186 static void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
187 struct stat
*fileStat
, bool isNewMail
);
188 static char *ParseFromField( char *buf
);
189 static bool SkipSender( char *address
);
190 static void InsertName( char *name
, unsigned long checksum
, flag_t flag
);
191 static void RemoveLastName( void );
192 static void ClearAllNames( void );
193 static void DrawTickerX11Font( void );
194 static void DrawTickerBuildinFont( void );
195 static void ButtonPressed( int button
, int state
, int x
, int y
);
196 static void ButtonReleased( int button
, int state
, int x
, int y
);
197 static char *XpmColorLine( const char *colorName
, char *colorLine
,
199 static void ReadChecksumFile( void );
200 static void WriteChecksumFile( bool writeAll
);
201 static void UpdateChecksum( unsigned long *checksum
, const char *buf
);
202 static void RemoveChecksumFile( void );
203 static void SetMailFlags( flag_t flag
);
204 static void MarkName( unsigned long checksum
);
205 static void DetermineState( void );
206 static void UpdateConfiguration( void );
207 static void CleanupNames( void );
208 static bool HasTickerWork( void );
211 ///////////////////////////////////////////////////////////////////////////////
215 int main( int argc
, char **argv
)
218 struct sigaction sa
= { .sa_handler
= ExitHandler
};
219 struct stat fileStat
;
220 XTextProperty windowName
;
221 char *name
= argv
[0];
222 DACallbacks callbacks
= { NULL
, &ButtonPressed
, &ButtonReleased
,
223 NULL
, NULL
, NULL
, &TimedOut
};
225 // read the config file and overide the default-settings
226 ReadConfigFile( false );
228 if( config
.checksumFileName
== NULL
) {
229 if(( usersHome
= getenv( "HOME" )) == NULL
) {
230 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE
);
231 config
.checksumFileName
= strdup( WMAIL_CHECKSUM_FILE
);
233 config
.checksumFileName
= MakePathName( usersHome
, WMAIL_CHECKSUM_FILE
);
236 if( config
.checksumFileName
== NULL
)
238 WARNING( "Cannot allocate checksum file-name.\n");
239 exit( EXIT_FAILURE
);
242 TRACE( "using checksum-file \"%s\"\n", config
.checksumFileName
);
244 // parse cmdline-args and overide defaults and cfg-file settings
245 DAParseArguments( argc
, argv
, options
,
246 sizeof(options
) / sizeof(DAProgramOption
),
247 WMAIL_NAME
, WMAIL_VERSION
);
249 if( options
[0].used
)
250 config
.givenOptions
|= CL_DISPLAY
;
251 if( options
[1].used
)
252 config
.givenOptions
|= CL_RUNCMD
;
253 if( options
[2].used
)
254 config
.givenOptions
|= CL_CHECKINTERVAL
;
255 if( options
[3].used
) {
256 config
.givenOptions
|= CL_TICKERMODE
;
257 config
.tickerMode
= TICKER_FAMILYNAME
;
259 if( options
[4].used
)
260 config
.givenOptions
|= CL_FPS
;
261 if( options
[5].used
) {
262 config
.givenOptions
|= CL_TICKERMODE
;
263 config
.tickerMode
= TICKER_NICKNAME
;
265 if( options
[6].used
)
266 config
.givenOptions
|= CL_SYMBOLCOLOR
;
267 if( options
[7].used
)
268 config
.givenOptions
|= CL_FONTCOLOR
;
269 if( options
[8].used
)
270 config
.givenOptions
|= CL_BACKCOLOR
;
271 if( options
[9].used
)
272 config
.givenOptions
|= CL_OFFLIGHTCOLOR
;
273 if( options
[10].used
)
274 config
.givenOptions
|= CL_BACKGROUNDCOLOR
;
275 if( options
[11].used
) {
276 config
.givenOptions
|= CL_NOSHAPE
;
277 config
.noshape
= true;
279 if( options
[12].used
) {
280 config
.givenOptions
|= CL_NEWMAILONLY
;
281 config
.newMailsOnly
= true;
283 if( options
[13].used
)
284 config
.givenOptions
|= CL_MAILBOX
;
285 if( options
[14].used
)
286 config
.givenOptions
|= CL_CMDONMAIL
;
287 if( options
[15].used
) {
288 config
.givenOptions
|= CL_CONSIDERSTATUSFIELD
;
289 config
.considerStatusField
= true;
291 if( options
[16].used
)
292 config
.givenOptions
|= CL_READSTATUS
;
293 if( options
[17].used
)
294 config
.givenOptions
|= CL_USEX11FONT
;
296 if( config
.mailBox
== NULL
)
297 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
298 else if( stat( config
.mailBox
, &fileStat
) == 0 )
299 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
301 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
303 // dockapp size hard wired - sorry...
304 DAInitialize( config
.display
, "wmail", 64, 64, argc
, argv
);
306 outPixmap
= DAMakePixmap();
307 PreparePixmaps( false );
309 if( sigaction( SIGINT
, &sa
, NULL
) == -1 ) {
310 perror( "wmail error: sigaction" );
311 exit( EXIT_FAILURE
);
314 if( sigaction( SIGTERM
, &sa
, NULL
) == -1 ) {
315 perror( "wmail error: sigaction" );
316 exit( EXIT_FAILURE
);
319 DASetCallbacks( &callbacks
);
320 DASetTimeout( 1000 / config
.fps
);
322 XStringListToTextProperty( &name
, 1, &windowName
);
323 XSetWMName( DADisplay
, DAWindow
, &windowName
);
325 UpdatePixmap( false );
333 static void PreparePixmaps( bool freeMem
)
335 // simple recoloring of the raw xpms befor creating Pixmaps of them
336 // this works as long as you don't "touch" the images...
338 unsigned short dummy
;
341 if( config
.symbolColor
!= NULL
) { // symbol color ?
342 symbols_xpm
[2] = XpmColorLine( config
.symbolColor
, symbols_xpm
[2],
343 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
344 config
.colorsUsed
|= SYM_COLOR
;
346 symbols_xpm
[2] = XpmColorLine( "#20B2AA", symbols_xpm
[2],
347 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
348 config
.colorsUsed
|= SYM_COLOR
;
351 if( config
.fontColor
!= NULL
) { // font color ?
352 chars_xpm
[3] = XpmColorLine( config
.fontColor
, chars_xpm
[3],
353 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
354 numbers_xpm
[3] = XpmColorLine( config
.fontColor
, numbers_xpm
[3],
355 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
356 config
.colorsUsed
|= FNT_COLOR
;
358 chars_xpm
[3] = XpmColorLine( "#D3D3D3", chars_xpm
[3],
359 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
360 numbers_xpm
[3] = XpmColorLine( "#D3D3D3", numbers_xpm
[3],
361 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
362 config
.colorsUsed
|= FNT_COLOR
;
365 if( config
.backColor
!= NULL
) { // backlight color ?
366 main_xpm
[3] = XpmColorLine( config
.backColor
, main_xpm
[3],
367 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
368 symbols_xpm
[3] = XpmColorLine( config
.backColor
, symbols_xpm
[3],
369 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
370 chars_xpm
[2] = XpmColorLine( config
.backColor
, chars_xpm
[2],
371 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
372 numbers_xpm
[2] = XpmColorLine( config
.backColor
, numbers_xpm
[2],
373 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
374 config
.colorsUsed
|= BCK_COLOR
;
376 main_xpm
[3] = XpmColorLine( "#282828", main_xpm
[3],
377 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
378 symbols_xpm
[3] = XpmColorLine( "#282828", symbols_xpm
[3],
379 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
380 chars_xpm
[2] = XpmColorLine( "#282828", chars_xpm
[2],
381 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
382 numbers_xpm
[2] = XpmColorLine( "#282828", numbers_xpm
[2],
383 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
384 config
.colorsUsed
|= BCK_COLOR
;
387 if( config
.offlightColor
!= NULL
) { // off-light color ?
388 main_xpm
[2] = XpmColorLine( config
.offlightColor
, main_xpm
[2],
389 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
390 numbers_xpm
[4] = XpmColorLine( config
.offlightColor
, numbers_xpm
[4],
391 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
392 config
.colorsUsed
|= OFF_COLOR
;
394 main_xpm
[2] = XpmColorLine( "#000000", main_xpm
[2],
395 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
396 numbers_xpm
[4] = XpmColorLine( "#000000", numbers_xpm
[4],
397 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
398 config
.colorsUsed
|= OFF_COLOR
;
401 if( config
.backgroundColor
!= NULL
) { // window-frame background (only seen if nonshaped) ?
402 main_xpm
[1] = XpmColorLine( config
.backgroundColor
, main_xpm
[1],
403 freeMem
&& ( config
.colorsUsed
& BGR_COLOR
));
404 config
.colorsUsed
|= BGR_COLOR
;
409 XFreePixmap( DADisplay
, mainPixmap
);
410 XFreePixmap( DADisplay
, mainPixmap_mask
);
411 XFreePixmap( DADisplay
, symbolsPixmap
);
412 XFreePixmap( DADisplay
, charsPixmap
);
413 XFreePixmap( DADisplay
, numbersPixmap
);
414 XFreePixmap( DADisplay
, buttonPixmap
);
416 if( tickerGC
!= NULL
)
418 XFreeGC( DADisplay
, tickerGC
);
420 if( tickerFS
!= NULL
) {
421 XFreeFont( DADisplay
, tickerFS
);
427 DAMakePixmapFromData( main_xpm
, &mainPixmap
, &mainPixmap_mask
, &dummy
, &dummy
);
428 DAMakePixmapFromData( symbols_xpm
, &symbolsPixmap
, NULL
, &dummy
, &dummy
);
429 DAMakePixmapFromData( chars_xpm
, &charsPixmap
, NULL
, &dummy
, &dummy
);
430 DAMakePixmapFromData( numbers_xpm
, &numbersPixmap
, NULL
, &dummy
, &dummy
);
431 DAMakePixmapFromData( button_xpm
, &buttonPixmap
, NULL
, &dummy
, &dummy
);
433 if( config
.useX11Font
!= NULL
)
437 if( config
.fontColor
!= NULL
)
438 values
.foreground
= DAGetColor( config
.fontColor
);
440 values
.foreground
= DAGetColor( "#D3D3D3" );
442 tickerFS
= XLoadQueryFont( DADisplay
, config
.useX11Font
);
443 if( tickerFS
== NULL
)
444 ABORT( "Cannot load font \"%s\"", config
.useX11Font
);
446 values
.font
= tickerFS
->fid
;
447 tickerGC
= XCreateGC( DADisplay
, DAWindow
, GCForeground
| GCFont
,
452 clipRect
.height
= 23;
454 XSetClipRectangles( DADisplay
, tickerGC
, 0, 0, &clipRect
, 1, Unsorted
);
457 if( config
.noshape
) // non-shaped dockapp ?
460 DASetShape( mainPixmap_mask
);
463 static void MarkName( unsigned long checksum
)
467 for( name
= names
; name
!= NULL
; name
= name
->next
) {
468 if( name
->checksum
== checksum
) {
469 name
->flag
|= FLAG_READ
;
470 if( config
.newMailsOnly
)
477 static void DetermineState( void )
481 for( name
= names
; name
!= NULL
; name
= name
->next
)
482 if(!( name
->flag
& FLAG_READ
)) {
483 state
= STATE_NEWMAIL
;
485 if( config
.cmdOnMail
!= NULL
) {
486 int ret
= system( config
.cmdOnMail
);
488 if( ret
== 127 || ret
== -1 )
489 WARNING( "execution of command \"%s\" failed.\n", config
.cmdOnMail
);
496 static void ReadChecksumFile( void )
498 FILE *f
= fopen( config
.checksumFileName
, "rb" );
501 unsigned long checksum
;
502 if( fread( &checksum
, sizeof(long), 1, f
) != 1 )
505 MarkName( checksum
);
513 static void WriteChecksumFile( bool writeAll
)
516 TRACE( "writing checksums:" );
518 if(( f
= fopen( config
.checksumFileName
, "wb" )) != NULL
) {
520 for( name
= names
; name
!= NULL
; name
= name
->next
) {
521 if( writeAll
|| (name
->flag
& FLAG_READ
)) {
522 fwrite( &name
->checksum
, sizeof(long), 1, f
);
523 TRACE( " %X", name
->checksum
);
534 static void UpdateChecksum( unsigned long *checksum
, const char *buf
)
537 size_t i
, len
= strlen( buf
);
539 for( i
= 0; i
< len
; ++i
)
540 *checksum
+= buf
[i
] << (( i
% sizeof(long) ) * 8 );
544 static void RemoveChecksumFile( void )
546 TRACE( "removing checksum-file\n" );
547 remove( config
.checksumFileName
);
550 static void ExitHandler( int sig
)
556 static void TimedOut( void )
560 ResetConfigStrings();
561 exit( EXIT_SUCCESS
);
564 CheckTimeOut( true );
567 static void CheckTimeOut( bool force
)
569 static int checkMail
= 0;
572 gettimeofday(&now
, NULL
);
574 unsigned long nowMs
= now
.tv_sec
* 1000UL + now
.tv_usec
/ 1000UL;
576 if( !force
&& nowMs
- lastTimeOut
< 1000UL / config
.fps
)
581 if( readConfigFile
) {
582 readConfigFile
= false;
583 UpdateConfiguration();
590 TRACE( "checking for new mail...\n" );
598 UpdatePixmap( checkMail
% config
.fps
< config
.fps
/2 );
600 if( ++checkMail
>= config
.fps
* config
.checkInterval
)
604 static void CheckMBox( void )
606 struct stat fileStat
;
608 // error retrieving file-stats -> no/zero-size file and no new/read mails
610 if( stat( config
.mailBox
, &fileStat
) == -1 || fileStat
.st_size
== 0 ) {
611 if( state
!= STATE_NOMAIL
) {
612 state
= STATE_NOMAIL
;
614 RemoveChecksumFile();
618 // file has changed -> new mails arrived or some mails removed
619 if( lastModifySeconds
!= fileStat
.st_mtime
|| forceRead
) {
621 ParseMBoxFile( &fileStat
);
622 stat( config
.mailBox
, &fileStat
);
624 // file has accessed (read) -> mark all mails as "read", because
625 // it cannot be decided which mails the user has read...
626 } else if( lastAccessSeconds
!= fileStat
.st_atime
) {
627 state
= STATE_READMAIL
;
628 WriteChecksumFile( true );
629 if( config
.newMailsOnly
) {
631 SetMailFlags( FLAG_READ
);
636 lastModifySeconds
= fileStat
.st_mtime
;
637 lastAccessSeconds
= fileStat
.st_atime
;
641 static void CheckMaildir( void )
644 int lastState
= state
;
645 unsigned lastMailCount
= numMails
;
650 TRACE( "all names cleared\n" );
653 state
= STATE_NOMAIL
;
655 if(( dir
= opendir( config
.mailBox
)) != NULL
)
657 struct dirent
*dirEnt
= NULL
;
658 //bool writeChecksums = false;
661 for( name
= names
; name
!= NULL
; name
= name
->next
)
662 name
->visited
= false;
664 while(( dirEnt
= readdir( dir
)) != NULL
)
666 char *fullName
= MakePathName( config
.mailBox
, dirEnt
->d_name
);
667 struct stat fileStat
;
669 if( fullName
== NULL
)
671 WARNING( "Cannot allocate file/path\n" );
675 if( !stat( fullName
, &fileStat
) == 0 )
676 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
677 else if( S_ISDIR( fileStat
.st_mode
))
679 if( strcmp( dirEnt
->d_name
, "new" ) == 0 ) {
680 if( TraverseDirectory( fullName
, true ) > 0 )
681 state
= STATE_NEWMAIL
;
683 else if( strcmp( dirEnt
->d_name
, "cur" ) == 0 ) {
684 if( TraverseDirectory( fullName
, false ) > 0 )
685 if( state
!= STATE_NEWMAIL
)
686 state
= STATE_READMAIL
;
688 // directories ".", ".." and "tmp" discarded
695 WARNING( "can't open directory \"%s\"\n", config
.mailBox
);
697 if( lastState
!= state
|| lastMailCount
!= numMails
)
701 static int TraverseDirectory( const char *name
, bool isNewMail
)
706 if(( dir
= opendir( name
)) != NULL
)
708 struct dirent
*dirEnt
= NULL
;
710 while(( dirEnt
= readdir( dir
)) != NULL
)
712 char *fullName
= MakePathName( name
, dirEnt
->d_name
);
713 struct stat fileStat
;
714 unsigned long checksum
= 0;
717 if( fullName
== NULL
)
719 WARNING( "Cannot allocate file/path\n" );
723 if( !stat( fullName
, &fileStat
) == 0 )
724 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
725 else if( !S_ISDIR( fileStat
.st_mode
))
727 TRACE( "found email-file \"%s\"\n", fullName
);
728 UpdateChecksum( &checksum
, dirEnt
->d_name
);
730 if(( name
= GetMail( checksum
)) == NULL
)
732 TRACE( "-> new file - parsing it\n" );
733 ParseMaildirFile( fullName
, checksum
, &fileStat
, isNewMail
);
736 name
->flag
= isNewMail
? FLAG_INITIAL
: FLAG_READ
;
737 name
->visited
= true;
750 static name_t
*GetMail( unsigned long checksum
)
754 for( name
= names
; name
!= NULL
; name
= name
->next
)
755 if( name
->checksum
== checksum
)
761 static void UpdatePixmap( bool flashMailSymbol
)
765 if( !forceRedraw
&& !HasTickerWork() )
770 XCopyArea( DADisplay
, mainPixmap
, outPixmap
, DAGC
,
771 0, 0, 64, 64, 0, 0 );
775 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
776 50, 0, 5, 9, 6, 49 );
779 drawCount
= numMails
;
781 for( i
= 0; i
< 3; ++i
, drawCount
/= 10 )
783 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
784 (drawCount
%10)*5, 0, 5, 9, 24-i
*6, 49 );
791 XCopyArea( DADisplay
, buttonPixmap
, outPixmap
, DAGC
,
792 0, 0, 23, 11, 36, 48 );
797 if( flashMailSymbol
)
798 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
799 13, 0, 37, 12, 20, 7 );
801 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
802 0, 0, 13, 12, 7, 7 );
804 if( config
.useX11Font
== NULL
)
805 DrawTickerBuildinFont();
808 default: // make compiler happy
812 DASetPixmap( outPixmap
);
815 static void ParseMBoxFile( struct stat
*fileStat
)
818 struct utimbuf timeStruct
;
820 FILE *f
= fopen( config
.mailBox
, "rt" );
821 unsigned long checksum
;
823 state
= STATE_READMAIL
;
829 WARNING( "can't open mbox \"%s\"\n", config
.mailBox
);
833 while( fgets( buf
, 1024, f
) != NULL
)
835 if( strncmp( buf
, "From ", 5 ) == 0 ) {
842 UpdateChecksum( &checksum
, buf
);
844 if( fromFound
&& strncasecmp( buf
, "from: ", 6 ) == 0 )
846 if( SkipSender( buf
+6 ))
850 if(( name
= ParseFromField( buf
+6 )) == NULL
)
852 WARNING( "Could not parse From field\n" );
855 InsertName( name
, checksum
, FLAG_INITIAL
);
860 } else if( config
.considerStatusField
&& strncasecmp( buf
, "status: ", 8 ) == 0 &&
861 strstr( buf
+8, config
.readStatus
) == NULL
)
875 timeStruct
.actime
= fileStat
->st_atime
;
876 timeStruct
.modtime
= fileStat
->st_mtime
;
877 utime( config
.mailBox
, &timeStruct
);
880 static void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
881 struct stat
*fileStat
, bool isNewMail
)
884 struct utimbuf timeStruct
;
885 FILE *f
= fopen( fileName
, "rt" );
889 WARNING( "can't open maildir file \"%s\"\n", fileName
);
893 while( fgets( buf
, 1024, f
) != NULL
)
895 if( strncasecmp( buf
, "from: ", 6 ) == 0 )
897 if( SkipSender( buf
+6 ))
901 if(( name
= ParseFromField( buf
+6 )) == NULL
)
903 WARNING( "Could not parse From field\n" );
906 InsertName( name
, checksum
, isNewMail
? FLAG_INITIAL
: FLAG_READ
);
914 timeStruct
.actime
= fileStat
->st_atime
;
915 timeStruct
.modtime
= fileStat
->st_mtime
;
916 utime( fileName
, &timeStruct
);
919 static char *ParseFromField( char *buf
)
921 parse_state_t state
= STATE_FULLNAME
;
922 int fullNameEncoded
= 0;
923 int saveAtCharPos
= -1;
928 size_t maxLen
= strlen( buf
) + 1;
930 size_t fullNameLen
= 0, addressNameLen
= 0, commentLen
= 0;
932 if(( fullName
= calloc( maxLen
, sizeof *fullName
)) == NULL
)
934 if(( addressName
= calloc( maxLen
, sizeof *addressName
)) == NULL
)
939 if(( comment
= calloc( maxLen
, sizeof *comment
)) == NULL
)
946 // FIXME: Don't do that "encoded" dance. It's not intended by
947 // RFC2047, and it's better to just do it in the end.
950 for( c
= buf
; *c
!= '\0'; ++c
)
957 state
= STATE_QUOTED_FULLNAME
;
960 while( fullNameLen
> 0 &&
961 isspace( fullName
[ fullNameLen
- 1 ] ))
963 fullName
[ fullNameLen
] = '\0';
964 state
= STATE_ADDRESS
;
967 saveAtCharPos
= fullNameLen
;
968 fullName
[ fullNameLen
++ ] = *c
;
971 state
= STATE_COMMENT
;
974 if( *(c
+1) == '?' ) {
977 state
= STATE_ENCODED_FULLNAME
;
979 } // else do the default action
981 if( fullName
[0] != '\0' || !isspace( *c
))
982 fullName
[ fullNameLen
++ ] = *c
;
986 case STATE_QUOTED_FULLNAME
:
990 fullName
[ fullNameLen
++ ] = *(++c
);
993 state
= STATE_FULLNAME
;
996 fullName
[ fullNameLen
++ ] = *c
;
1000 case STATE_ENCODED_FULLNAME
:
1004 if( *(c
+1) == '=' ) {
1006 state
= STATE_FULLNAME
;
1010 ; // do nothing... COMING SOON: decode at least latin1
1018 state
= STATE_QUOTED_ADDRESS
;
1021 // FIXME: Shouldn't it break here?
1022 // Since the address is finished?
1025 atChar
= addressName
+ addressNameLen
;
1026 addressName
[ addressNameLen
++ ] = *c
;
1029 addressName
[ addressNameLen
++ ] = *c
;
1033 case STATE_QUOTED_ADDRESS
:
1037 state
= STATE_ADDRESS
;
1040 addressName
[ addressNameLen
++ ] = *(++c
);
1043 addressName
[ addressNameLen
++ ] = *c
;
1049 state
= STATE_FULLNAME
;
1052 comment
[ commentLen
++ ] = *c
;
1060 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1061 // Comment seen: if there's an address, append to
1062 // fullname. If no address, copy fullname to address
1063 // and comment to fullname.
1064 if( *addressName
) {
1065 strcat(fullName
, "(");
1066 strcat(fullName
, comment
);
1067 strcat(fullName
, ")");
1069 strcpy(addressName
, fullName
);
1070 strcpy(fullName
, comment
);
1075 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1077 // what name should be tickered
1078 if( config
.tickerMode
== TICKER_FAMILYNAME
&& fullName
[0] != '\0' && !fullNameEncoded
) {
1079 free( addressName
);
1082 if( state
== STATE_FULLNAME
) {
1083 strcpy( addressName
, fullName
);
1084 if( saveAtCharPos
!= -1 )
1085 atChar
= &addressName
[saveAtCharPos
];
1087 if( config
.tickerMode
== TICKER_NICKNAME
) {
1088 if( atChar
!= NULL
)
1096 static bool SkipSender( char *address
)
1099 size_t len
= strlen( address
);
1101 // remove trailing '\n' got from fgets
1102 if( address
[len
-1] == '\n' )
1103 address
[len
-1] = '\0';
1105 for( skipName
= config
.skipNames
;
1106 skipName
!= NULL
&& *skipName
!= NULL
; skipName
++ )
1108 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName
, address
);
1110 // call libc-fnmatch (wildcard-match :-) !
1111 if( !fnmatch( *skipName
, address
, 0 )) {
1112 TRACE( "skipping sender \"%s\"\n", *skipName
);
1120 static void InsertName( char *name
, unsigned long checksum
, flag_t flag
)
1124 TRACE( "insertName: %X, \"%s\"\n", checksum
, name
);
1125 if(( item
= malloc( sizeof( name_t
))) == NULL
)
1131 item
->name
= name
; /*strdup( name );*/
1132 item
->checksum
= checksum
;
1134 item
->visited
= true;
1138 namesChanged
= true;
1141 static void RemoveLastName( void )
1143 if( names
!= NULL
) {
1144 name_t
*name
= names
;
1145 names
= names
->next
;
1151 static void ClearAllNames( void )
1153 name_t
*name
, *nextName
;
1155 for( name
= names
; name
!= NULL
; name
= nextName
) {
1156 nextName
= name
->next
;
1165 namesChanged
= true;
1168 static void SetMailFlags( flag_t flag
)
1172 for( name
= names
; name
!= NULL
; name
= name
->next
)
1176 static void DrawTickerX11Font( void )
1178 // 49x21+7+20 out-drawable size
1180 static int insertAt
;
1182 if( curTickerName
== NULL
|| namesChanged
)
1184 for( curTickerName
= names
;
1185 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1186 curTickerName
= curTickerName
->next
);
1188 if( curTickerName
== NULL
)
1191 namesChanged
= false;
1195 XDrawString( DADisplay
, outPixmap
, tickerGC
, insertAt
,
1196 41-tickerFS
->max_bounds
.descent
,
1197 curTickerName
->name
, strlen( curTickerName
->name
));
1201 if( insertAt
< -XTextWidth( tickerFS
, curTickerName
->name
,
1202 strlen( curTickerName
->name
)) + 6 )
1205 curTickerName
= curTickerName
->next
;
1206 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1208 if( curTickerName
!= NULL
) {
1214 static void DrawTickerBuildinFont( void )
1216 // 49x21+7+20 out-drawable size
1217 // 14x21 font-character size
1219 static unsigned insertAt
;
1220 static unsigned takeItFrom
;
1224 unsigned char *currentChar
;
1229 if( curTickerName
== NULL
|| namesChanged
) {
1231 for( curTickerName
= names
;
1232 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1233 curTickerName
= curTickerName
->next
);
1235 if( curTickerName
== NULL
)
1240 namesChanged
= false;
1243 leftSpace
= takeItFrom
% 14;
1245 for( currentChar
= (unsigned char *)&curTickerName
->name
[takeItFrom
/14],
1246 drawTo
= insertAt
; *currentChar
!= '\0'; ++currentChar
)
1249 int outChar
= (*currentChar
< 32 || *currentChar
>= 128) ? '?' :
1251 int charWidth
= 57-drawTo
>= 14 ? 14 - leftSpace
: 57-drawTo
;
1253 XCopyArea( DADisplay
, charsPixmap
, outPixmap
, DAGC
,
1254 (outChar
-32)*14+leftSpace
, 0, charWidth
, 21, drawTo
, 20 );
1257 drawTo
+= charWidth
;
1263 if( --insertAt
< 7 ) {
1267 if( takeItFrom
/14 >= strlen( curTickerName
->name
)) {
1270 curTickerName
= curTickerName
->next
;
1271 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1273 if( curTickerName
!= NULL
) {
1281 static void ButtonPressed( int button
, int state
, int x
, int y
)
1286 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1287 buttonPressed
= true;
1290 // reread the config file
1291 readConfigFile
= true;
1293 CheckTimeOut( false );
1296 static void ButtonReleased( int button
, int state
, int x
, int y
)
1301 buttonPressed
= false;
1304 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1305 int ret
= system( config
.runCmd
);
1307 if( ret
== 127 || ret
== -1 )
1308 WARNING( "execution of command \"%s\" failed.\n", config
.runCmd
);
1311 CheckTimeOut( false );
1314 static void GetHexColorString( const char *colorName
, char *xpmLine
)
1318 if( XParseColor( DADisplay
,
1319 DefaultColormap( DADisplay
, DefaultScreen( DADisplay
)),
1320 colorName
, &color
))
1322 sprintf( xpmLine
, "%02X%02X%02X", color
.red
>>8, color
.green
>>8,
1325 WARNING( "unknown colorname: \"%s\"\n", colorName
);
1328 static char *XpmColorLine( const char *colorName
, char *colorLine
,
1331 char *newLine
= strdup( colorLine
);
1332 char *from
= strrchr( newLine
, '#' );
1334 if( from
== NULL
&& !strcasecmp( &colorLine
[ strlen( colorLine
) - 4 ], "none" )) {
1335 // if no # found, it should be a None-color line
1337 newLine
= malloc( 12 );
1338 strcpy( newLine
, " \tc #" );
1346 GetHexColorString( colorName
, from
+1 );
1351 static void UpdateConfiguration( void )
1353 struct stat fileStat
;
1355 TRACE( "reading configuration file...\n" );
1357 ReadConfigFile( true );
1359 // if no path/name to an mbox or maildir inbox directory was given,
1360 // use the environment
1361 if( config
.mailBox
== NULL
)
1362 config
.mailBox
= getenv( "MAIL" );
1364 // mbox or maildir ?
1365 if( config
.mailBox
!= NULL
&& stat( config
.mailBox
, &fileStat
) == 0 )
1366 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
1370 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
1372 PreparePixmaps( true );
1374 DASetTimeout( 1000 / config
.fps
);
1377 static void CleanupNames( void )
1379 name_t
*name
, *last
= NULL
, *nextName
;
1383 for( name
= names
; name
!= NULL
; name
= nextName
)
1385 nextName
= name
->next
;
1387 if( !name
->visited
) {
1391 last
->next
= name
->next
;
1398 if( !config
.newMailsOnly
|| (name
->flag
& FLAG_READ
) == 0 )
1404 static bool HasTickerWork( void )
1406 name_t
*nextTickerName
;
1411 if( curTickerName
== NULL
|| namesChanged
) {
1413 for( nextTickerName
= names
;
1414 nextTickerName
!= NULL
&& ( config
.newMailsOnly
&& ( nextTickerName
->flag
& FLAG_READ
));
1415 nextTickerName
= nextTickerName
->next
);
1417 if( nextTickerName
== NULL
)