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 ///////////////////////////////////////////////////////////////////////////////
49 #include <libdockapp/dockapp.h>
55 #include "xpm_delt/main.xpm"
56 #include "xpm_delt/symbols.xpm"
57 #include "xpm_delt/numbers.xpm"
58 #include "xpm_delt/button.xpm"
59 #include "xpm_delt/chars.xpm"
61 #include "xpm/main.xpm"
62 #include "xpm/symbols.xpm"
63 #include "xpm/numbers.xpm"
64 #include "xpm/button.xpm"
65 #include "xpm/chars.xpm"
69 ///////////////////////////////////////////////////////////////////////////////
77 typedef struct _name_t
{
79 unsigned long checksum
;
95 STATE_QUOTED_FULLNAME
,
96 STATE_ENCODED_FULLNAME
,
101 ///////////////////////////////////////////////////////////////////////////////
104 static unsigned long lastTimeOut
;
105 static sig_atomic_t caughtSig
;
106 mail_state_t state
= STATE_NOMAIL
;
108 bool namesChanged
= false;
109 bool buttonPressed
= false;
110 bool readConfigFile
= false;
111 bool isMaildir
= false;
112 bool forceRead
= false;
113 bool forceRedraw
= true;
114 time_t lastModifySeconds
= 0;
115 time_t lastAccessSeconds
= 0;
117 Pixmap mainPixmap_mask
;
118 Pixmap symbolsPixmap
;
120 Pixmap numbersPixmap
;
124 XFontStruct
*tickerFS
= NULL
;
125 name_t
*names
= NULL
;
126 name_t
*curTickerName
= NULL
;
128 static DAProgramOption options
[] = {
129 {"-display", NULL
, "display to use", DOString
, False
, {&config
.display
}},
130 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
131 DOString
, False
, {&config
.runCmd
} },
132 {"-i", "--intervall",
133 "number of secs between mail-status updates (1 is default)", DONatural
,
134 False
, {&config
.checkInterval
} },
135 {"-f", "--familyname", "tickers the family-name if available", DONone
,
137 {"-fps", "--frames", "ticker frames per second", DONatural
,
138 False
, {&config
.fps
} },
139 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone
,
141 {"-sc", "--symbolcolor", "symbol color-name",
142 DOString
, False
, {&config
.symbolColor
} },
143 {"-fc", "--fontcolor", "ticker-font color-name",
144 DOString
, False
, {&config
.fontColor
} },
145 {"-bc", "--backcolor", "backlight color-name",
146 DOString
, False
, {&config
.backColor
} },
147 {"-oc", "--offcolor", "off-light color-name",
148 DOString
, False
, {&config
.offlightColor
} },
149 {"-bg", "--background", "frame-background for non-shaped window",
150 DOString
, False
, {&config
.backgroundColor
} },
151 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
152 DONone
, False
, {NULL
} },
153 {"-n", "--new", "forces wmail to show new mail exclusively", DONone
, False
, {NULL
} },
154 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString
, False
, {&config
.mailBox
} },
155 {"-e", "--execute", "command to execute when receiving a new mail", DOString
, False
, {&config
.cmdOnMail
} },
156 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone
, False
, {NULL
} },
157 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString
, False
, {&config
.readStatus
} },
158 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString
, False
, {&config
.useX11Font
} }
162 ///////////////////////////////////////////////////////////////////////////////
165 void PreparePixmaps( bool freeThemFirst
);
166 static void ExitHandler( int sig
);
167 void TimedOut( void );
168 void CheckTimeOut( bool force
);
169 void CheckMBox( void );
170 void CheckMaildir( void );
171 int TraverseDirectory( const char *name
, bool isNewMail
);
172 name_t
*GetMail( unsigned long checksum
);
173 void UpdatePixmap( bool flashMailSymbol
);
174 void ParseMBoxFile( struct stat
*fileStat
);
175 void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
176 struct stat
*fileStat
, bool isNewMail
);
177 char *ParseFromField( char *buf
);
178 bool SkipSender( char *address
);
179 void InsertName( char *name
, unsigned long checksum
, flag_t flag
);
180 void RemoveLastName( void );
181 void ClearAllNames( void );
182 void DrawTickerX11Font( void );
183 void DrawTickerBuildinFont( void );
184 void ButtonPressed( int button
, int state
, int x
, int y
);
185 void ButtonReleased( int button
, int state
, int x
, int y
);
186 char *XpmColorLine( const char *colorName
, char *colorLine
, bool disposeLine
);
187 void ReadChecksumFile( void );
188 void WriteChecksumFile( bool writeAll
);
189 void UpdateChecksum( unsigned long *checksum
, const char *buf
);
190 void RemoveChecksumFile( void );
191 void SetMailFlags( flag_t flag
);
192 void MarkName( unsigned long checksum
);
193 void DetermineState( void );
194 void UpdateConfiguration( void );
195 void CleanupNames( void );
196 bool HasTickerWork( void );
199 ///////////////////////////////////////////////////////////////////////////////
203 int main( int argc
, char **argv
)
206 struct sigaction sa
= { .sa_handler
= ExitHandler
};
207 struct stat fileStat
;
208 XTextProperty windowName
;
209 char *name
= argv
[0];
210 DACallbacks callbacks
= { NULL
, &ButtonPressed
, &ButtonReleased
,
211 NULL
, NULL
, NULL
, &TimedOut
};
213 // read the config file and overide the default-settings
214 ReadConfigFile( false );
216 if( config
.checksumFileName
== NULL
) {
217 if(( usersHome
= getenv( "HOME" )) == NULL
) {
218 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE
);
219 config
.checksumFileName
= strdup( WMAIL_CHECKSUM_FILE
);
221 config
.checksumFileName
= MakePathName( usersHome
, WMAIL_CHECKSUM_FILE
);
224 if ( config
.checksumFileName
== NULL
)
226 WARNING( "Cannot allocate checksum file-name.\n");
227 exit( EXIT_FAILURE
);
230 TRACE( "using checksum-file \"%s\"\n", config
.checksumFileName
);
232 // parse cmdline-args and overide defaults and cfg-file settings
233 DAParseArguments( argc
, argv
, options
,
234 sizeof(options
) / sizeof(DAProgramOption
),
235 WMAIL_NAME
, WMAIL_VERSION
);
237 if( options
[0].used
)
238 config
.givenOptions
|= CL_DISPLAY
;
239 if( options
[1].used
)
240 config
.givenOptions
|= CL_RUNCMD
;
241 if( options
[2].used
)
242 config
.givenOptions
|= CL_CHECKINTERVAL
;
243 if( options
[3].used
) {
244 config
.givenOptions
|= CL_TICKERMODE
;
245 config
.tickerMode
= TICKER_FAMILYNAME
;
247 if( options
[4].used
)
248 config
.givenOptions
|= CL_FPS
;
249 if( options
[5].used
) {
250 config
.givenOptions
|= CL_TICKERMODE
;
251 config
.tickerMode
= TICKER_NICKNAME
;
253 if( options
[6].used
)
254 config
.givenOptions
|= CL_SYMBOLCOLOR
;
255 if( options
[7].used
)
256 config
.givenOptions
|= CL_FONTCOLOR
;
257 if( options
[8].used
)
258 config
.givenOptions
|= CL_BACKCOLOR
;
259 if( options
[9].used
)
260 config
.givenOptions
|= CL_OFFLIGHTCOLOR
;
261 if( options
[10].used
)
262 config
.givenOptions
|= CL_BACKGROUNDCOLOR
;
263 if( options
[11].used
) {
264 config
.givenOptions
|= CL_NOSHAPE
;
265 config
.noshape
= true;
267 if( options
[12].used
) {
268 config
.givenOptions
|= CL_NEWMAILONLY
;
269 config
.newMailsOnly
= true;
271 if( options
[13].used
)
272 config
.givenOptions
|= CL_MAILBOX
;
273 if( options
[14].used
)
274 config
.givenOptions
|= CL_CMDONMAIL
;
275 if( options
[15].used
) {
276 config
.givenOptions
|= CL_CONSIDERSTATUSFIELD
;
277 config
.considerStatusField
= true;
279 if( options
[16].used
)
280 config
.givenOptions
|= CL_READSTATUS
;
281 if( options
[17].used
)
282 config
.givenOptions
|= CL_USEX11FONT
;
284 if( config
.mailBox
== NULL
)
285 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
286 else if( stat( config
.mailBox
, &fileStat
) == 0 )
287 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
289 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
291 // dockapp size hard wired - sorry...
292 DAInitialize( config
.display
, "wmail", 64, 64, argc
, argv
);
294 outPixmap
= DAMakePixmap();
295 PreparePixmaps( false );
297 if( sigaction( SIGINT
, &sa
, NULL
) == -1 ) {
298 perror( "wmail error: sigaction" );
299 exit( EXIT_FAILURE
);
302 if( sigaction( SIGTERM
, &sa
, NULL
) == -1 ) {
303 perror( "wmail error: sigaction" );
304 exit( EXIT_FAILURE
);
307 DASetCallbacks( &callbacks
);
308 DASetTimeout (1000 / config
.fps
);
310 XStringListToTextProperty( &name
, 1, &windowName
);
311 XSetWMName( DADisplay
, DAWindow
, &windowName
);
313 UpdatePixmap( false );
321 void PreparePixmaps( bool freeMem
)
323 // simple recoloring of the raw xpms befor creating Pixmaps of them
324 // this works as long as you don't "touch" the images...
326 unsigned short dummy
;
329 if( config
.symbolColor
!= NULL
) { // symbol color ?
330 symbols_xpm
[2] = XpmColorLine( config
.symbolColor
, symbols_xpm
[2],
331 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
332 config
.colorsUsed
|= SYM_COLOR
;
334 symbols_xpm
[2] = XpmColorLine( "#20B2AA", symbols_xpm
[2],
335 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
336 config
.colorsUsed
|= SYM_COLOR
;
339 if( config
.fontColor
!= NULL
) { // font color ?
340 chars_xpm
[3] = XpmColorLine( config
.fontColor
, chars_xpm
[3],
341 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
342 numbers_xpm
[3] = XpmColorLine( config
.fontColor
, numbers_xpm
[3],
343 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
344 config
.colorsUsed
|= FNT_COLOR
;
346 chars_xpm
[3] = XpmColorLine( "#D3D3D3", chars_xpm
[3],
347 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
348 numbers_xpm
[3] = XpmColorLine( "#D3D3D3", numbers_xpm
[3],
349 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
350 config
.colorsUsed
|= FNT_COLOR
;
353 if( config
.backColor
!= NULL
) { // backlight color ?
354 main_xpm
[3] = XpmColorLine( config
.backColor
, main_xpm
[3],
355 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
356 symbols_xpm
[3] = XpmColorLine( config
.backColor
, symbols_xpm
[3],
357 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
358 chars_xpm
[2] = XpmColorLine( config
.backColor
, chars_xpm
[2],
359 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
360 numbers_xpm
[2] = XpmColorLine( config
.backColor
, numbers_xpm
[2],
361 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
362 config
.colorsUsed
|= BCK_COLOR
;
364 main_xpm
[3] = XpmColorLine( "#282828", main_xpm
[3],
365 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
366 symbols_xpm
[3] = XpmColorLine( "#282828", symbols_xpm
[3],
367 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
368 chars_xpm
[2] = XpmColorLine( "#282828", chars_xpm
[2],
369 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
370 numbers_xpm
[2] = XpmColorLine( "#282828", numbers_xpm
[2],
371 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
372 config
.colorsUsed
|= BCK_COLOR
;
375 if( config
.offlightColor
!= NULL
) { // off-light color ?
376 main_xpm
[2] = XpmColorLine( config
.offlightColor
, main_xpm
[2],
377 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
378 numbers_xpm
[4] = XpmColorLine( config
.offlightColor
, numbers_xpm
[4],
379 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
380 config
.colorsUsed
|= OFF_COLOR
;
382 main_xpm
[2] = XpmColorLine( "#000000", main_xpm
[2],
383 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
384 numbers_xpm
[4] = XpmColorLine( "#000000", numbers_xpm
[4],
385 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
386 config
.colorsUsed
|= OFF_COLOR
;
389 if( config
.backgroundColor
!= NULL
) { // window-frame background (only seen if nonshaped) ?
390 main_xpm
[1] = XpmColorLine( config
.backgroundColor
, main_xpm
[1],
391 freeMem
&& ( config
.colorsUsed
& BGR_COLOR
));
392 config
.colorsUsed
|= BGR_COLOR
;
397 XFreePixmap( DADisplay
, mainPixmap
);
398 XFreePixmap( DADisplay
, mainPixmap_mask
);
399 XFreePixmap( DADisplay
, symbolsPixmap
);
400 XFreePixmap( DADisplay
, charsPixmap
);
401 XFreePixmap( DADisplay
, numbersPixmap
);
402 XFreePixmap( DADisplay
, buttonPixmap
);
404 if( tickerGC
!= NULL
)
406 XFreeGC( DADisplay
, tickerGC
);
408 if( tickerFS
!= NULL
) {
409 XFreeFont( DADisplay
, tickerFS
);
415 DAMakePixmapFromData( main_xpm
, &mainPixmap
, &mainPixmap_mask
, &dummy
, &dummy
);
416 DAMakePixmapFromData( symbols_xpm
, &symbolsPixmap
, NULL
, &dummy
, &dummy
);
417 DAMakePixmapFromData( chars_xpm
, &charsPixmap
, NULL
, &dummy
, &dummy
);
418 DAMakePixmapFromData( numbers_xpm
, &numbersPixmap
, NULL
, &dummy
, &dummy
);
419 DAMakePixmapFromData( button_xpm
, &buttonPixmap
, NULL
, &dummy
, &dummy
);
421 if( config
.useX11Font
!= NULL
)
425 if( config
.fontColor
!= NULL
)
426 values
.foreground
= DAGetColor( config
.fontColor
);
428 values
.foreground
= DAGetColor( "#D3D3D3" );
430 tickerFS
= XLoadQueryFont( DADisplay
, config
.useX11Font
);
431 if( tickerFS
== NULL
)
432 ABORT( "Cannot load font \"%s\"", config
.useX11Font
);
434 values
.font
= tickerFS
->fid
;
435 tickerGC
= XCreateGC( DADisplay
, DAWindow
, GCForeground
| GCFont
,
440 clipRect
.height
= 23;
442 XSetClipRectangles( DADisplay
, tickerGC
, 0, 0, &clipRect
, 1, Unsorted
);
445 if( config
.noshape
) // non-shaped dockapp ?
448 DASetShape( mainPixmap_mask
);
451 void MarkName( unsigned long checksum
)
455 for( name
= names
; name
!= NULL
; name
= name
->next
) {
456 if( name
->checksum
== checksum
) {
457 name
->flag
|= FLAG_READ
;
458 if( config
.newMailsOnly
)
465 void DetermineState( void )
469 for( name
= names
; name
!= NULL
; name
= name
->next
)
470 if(!( name
->flag
& FLAG_READ
)) {
471 state
= STATE_NEWMAIL
;
473 if( config
.cmdOnMail
!= NULL
) {
474 int ret
= system( config
.cmdOnMail
);
476 if( ret
== 127 || ret
== -1 )
477 WARNING( "execution of command \"%s\" failed.\n", config
.cmdOnMail
);
484 void ReadChecksumFile( void )
486 FILE *f
= fopen( config
.checksumFileName
, "rb" );
487 if( f
!= NULL
) while( !feof( f
)) {
488 unsigned long checksum
;
489 if( fread( &checksum
, sizeof(long), 1, f
) != 1 )
492 MarkName( checksum
);
499 void WriteChecksumFile( bool writeAll
)
502 TRACE( "writing checksums:" );
504 if(( f
= fopen( config
.checksumFileName
, "wb" )) != NULL
) {
506 for( name
= names
; name
!= NULL
; name
= name
->next
) {
507 if( writeAll
|| (name
->flag
& FLAG_READ
)) {
508 fwrite( &name
->checksum
, sizeof(long), 1, f
);
509 TRACE( " %X", name
->checksum
);
520 void UpdateChecksum( unsigned long *checksum
, const char *buf
)
523 unsigned int i
, len
= strlen( buf
);
525 for( i
= 0; i
< len
; ++i
)
526 *checksum
+= buf
[i
] << (( i
% sizeof(long) ) * 8 );
530 void RemoveChecksumFile( void )
532 TRACE( "removing checksum-file\n" );
533 remove( config
.checksumFileName
);
536 static void ExitHandler( int sig
)
541 void TimedOut( void )
545 ResetConfigStrings();
546 exit( EXIT_SUCCESS
);
549 CheckTimeOut( true );
552 void CheckTimeOut( bool force
)
554 static int checkMail
= 0;
557 gettimeofday(&now
, NULL
);
559 unsigned long nowMs
= now
.tv_sec
* 1000L + now
.tv_usec
/ 1000L;
561 if (!force
&& nowMs
- lastTimeOut
< 1000L / config
.fps
)
566 if( readConfigFile
) {
567 readConfigFile
= false;
568 UpdateConfiguration();
575 TRACE( "checking for new mail...\n" );
583 UpdatePixmap( checkMail
% config
.fps
< config
.fps
/2 );
585 if( ++checkMail
>= config
.fps
* config
.checkInterval
)
589 void CheckMBox( void )
591 struct stat fileStat
;
593 // error retrieving file-stats -> no/zero-size file and no new/read mails
595 if( stat( config
.mailBox
, &fileStat
) == -1 || fileStat
.st_size
== 0 ) {
596 if( state
!= STATE_NOMAIL
) {
597 state
= STATE_NOMAIL
;
599 RemoveChecksumFile();
603 // file has changed -> new mails arrived or some mails removed
604 if( lastModifySeconds
!= fileStat
.st_mtime
|| forceRead
) {
606 ParseMBoxFile( &fileStat
);
607 stat( config
.mailBox
, &fileStat
);
609 // file has accessed (read) -> mark all mails as "read", because
610 // it cannot be decided which mails the user has read...
611 } else if( lastAccessSeconds
!= fileStat
.st_atime
) {
612 state
= STATE_READMAIL
;
613 WriteChecksumFile( true );
614 if( config
.newMailsOnly
) {
616 SetMailFlags( FLAG_READ
);
621 lastModifySeconds
= fileStat
.st_mtime
;
622 lastAccessSeconds
= fileStat
.st_atime
;
626 void CheckMaildir( void )
629 int lastState
= state
;
630 int lastMailCount
= numMails
;
635 TRACE( "all names cleared\n" );
638 state
= STATE_NOMAIL
;
640 if(( dir
= opendir( config
.mailBox
)) != NULL
)
642 struct dirent
*dirEnt
= NULL
;
643 //bool writeChecksums = false;
646 for( name
= names
; name
!= NULL
; name
= name
->next
)
647 name
->visited
= false;
649 while(( dirEnt
= readdir( dir
)) != NULL
)
651 char *fullName
= MakePathName( config
.mailBox
, dirEnt
->d_name
);
652 struct stat fileStat
;
654 if ( fullName
== NULL
)
656 WARNING( "Cannot allocate file/path\n" );
660 if( !stat( fullName
, &fileStat
) == 0 )
661 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
662 else if( S_ISDIR( fileStat
.st_mode
))
664 if( strcmp( dirEnt
->d_name
, "new" ) == 0 ) {
665 if( TraverseDirectory( fullName
, true ) > 0 )
666 state
= STATE_NEWMAIL
;
668 else if( strcmp( dirEnt
->d_name
, "cur" ) == 0 ) {
669 if( TraverseDirectory( fullName
, false ) > 0 )
670 if( state
!= STATE_NEWMAIL
)
671 state
= STATE_READMAIL
;
673 // directories ".", ".." and "tmp" discarded
680 WARNING( "can't open directory \"%s\"\n", config
.mailBox
);
682 if( lastState
!= state
|| lastMailCount
!= numMails
)
686 int TraverseDirectory( const char *name
, bool isNewMail
)
691 if(( dir
= opendir( name
)) != NULL
)
693 struct dirent
*dirEnt
= NULL
;
695 while(( dirEnt
= readdir( dir
)) != NULL
)
697 char *fullName
= MakePathName( name
, dirEnt
->d_name
);
698 struct stat fileStat
;
699 unsigned long checksum
= 0;
702 if ( fullName
== NULL
)
704 WARNING( "Cannot allocate file/path\n" );
708 if( !stat( fullName
, &fileStat
) == 0 )
709 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
710 else if( !S_ISDIR( fileStat
.st_mode
))
712 TRACE( "found email-file \"%s\"\n", fullName
);
713 UpdateChecksum( &checksum
, dirEnt
->d_name
);
715 if(( name
= GetMail( checksum
)) == NULL
)
717 TRACE( "-> new file - parsing it\n" );
718 ParseMaildirFile( fullName
, checksum
, &fileStat
, isNewMail
);
721 name
->flag
= isNewMail
? FLAG_INITIAL
: FLAG_READ
;
722 name
->visited
= true;
735 name_t
*GetMail( unsigned long checksum
)
739 for( name
= names
; name
!= NULL
; name
= name
->next
)
740 if( name
->checksum
== checksum
)
746 void UpdatePixmap( bool flashMailSymbol
)
750 if( !forceRedraw
&& !HasTickerWork() )
755 XCopyArea( DADisplay
, mainPixmap
, outPixmap
, DAGC
,
756 0, 0, 64, 64, 0, 0 );
760 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
761 50, 0, 5, 9, 6, 49 );
764 drawCount
= numMails
;
766 for( i
= 0; i
< 3; ++i
, drawCount
/= 10 )
768 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
769 (drawCount
%10)*5, 0, 5, 9, 24-i
*6, 49 );
776 XCopyArea( DADisplay
, buttonPixmap
, outPixmap
, DAGC
,
777 0, 0, 23, 11, 36, 48 );
782 if( flashMailSymbol
)
783 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
784 13, 0, 37, 12, 20, 7 );
786 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
787 0, 0, 13, 12, 7, 7 );
789 if( config
.useX11Font
== NULL
)
790 DrawTickerBuildinFont();
793 default: // make compiler happy
797 DASetPixmap( outPixmap
);
800 void ParseMBoxFile( struct stat
*fileStat
)
803 struct utimbuf timeStruct
;
805 FILE *f
= fopen( config
.mailBox
, "rt" );
806 unsigned long checksum
;
808 state
= STATE_READMAIL
;
814 WARNING( "can't open mbox \"%s\"\n", config
.mailBox
);
818 while( fgets( buf
, 1024, f
) != NULL
)
820 if( strncmp( buf
, "From ", 5 ) == 0 ) {
827 UpdateChecksum( &checksum
, buf
);
829 if( fromFound
&& strncasecmp( buf
, "from: ", 6 ) == 0 )
831 if( SkipSender( buf
+6 ))
835 if(( name
= ParseFromField( buf
+6 )) == NULL
)
837 WARNING( "Could not parse From field\n" );
840 InsertName( name
, checksum
, FLAG_INITIAL
);
845 } else if( config
.considerStatusField
&& strncasecmp( buf
, "status: ", 8 ) == 0 &&
846 strstr( buf
+8, config
.readStatus
) == NULL
)
860 timeStruct
.actime
= fileStat
->st_atime
;
861 timeStruct
.modtime
= fileStat
->st_mtime
;
862 utime( config
.mailBox
, &timeStruct
);
865 void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
866 struct stat
*fileStat
, bool isNewMail
)
869 struct utimbuf timeStruct
;
870 FILE *f
= fopen( fileName
, "rt" );
874 WARNING( "can't open maildir file \"%s\"\n", fileName
);
878 while( fgets( buf
, 1024, f
) != NULL
)
880 if( strncasecmp( buf
, "from: ", 6 ) == 0 )
882 if( SkipSender( buf
+6 ))
886 if(( name
= ParseFromField( buf
+6 )) == NULL
)
888 WARNING( "Could not parse From field\n" );
891 InsertName( name
, checksum
, isNewMail
? FLAG_INITIAL
: FLAG_READ
);
899 timeStruct
.actime
= fileStat
->st_atime
;
900 timeStruct
.modtime
= fileStat
->st_mtime
;
901 utime( fileName
, &timeStruct
);
904 char *ParseFromField( char *buf
)
906 parse_state_t state
= STATE_FULLNAME
;
907 int fullNameEncoded
= 0;
908 int saveAtCharPos
= -1;
913 int maxLen
= strlen( buf
) + 1;
916 if(( fullName
= calloc( maxLen
, sizeof *fullName
)) == NULL
)
918 if(( addressName
= calloc( maxLen
, sizeof *addressName
)) == NULL
)
923 if(( comment
= calloc( maxLen
, sizeof *comment
)) == NULL
)
930 // FIXME: Don't do that "encoded" dance. It's not intended by
931 // RFC2047, and it's better to just do it in the end.
934 for( c
= buf
; *c
!= '\0'; ++c
)
941 state
= STATE_QUOTED_FULLNAME
;
944 if( fullName
[0] != '\0' &&
945 fullName
[ strlen( fullName
) - 1 ] == ' ' )
946 fullName
[ strlen( fullName
) - 1 ] = '\0';
947 state
= STATE_ADDRESS
;
950 saveAtCharPos
= strlen( fullName
);
951 fullName
[ saveAtCharPos
] = *c
;
954 state
= STATE_COMMENT
;
957 if( *(c
+1) == '?' ) {
960 state
= STATE_ENCODED_FULLNAME
;
962 } // else do the default action
964 if( fullName
[0] != '\0' || !isspace ( *c
))
965 fullName
[ strlen( fullName
) ] = *c
;
969 case STATE_QUOTED_FULLNAME
:
973 fullName
[ strlen( fullName
) ] = *(++c
);
976 state
= STATE_FULLNAME
;
979 fullName
[ strlen( fullName
) ] = *c
;
983 case STATE_ENCODED_FULLNAME
:
987 if( *(c
+1) == '=' ) {
989 state
= STATE_FULLNAME
;
993 ; // do nothing... COMING SOON: decode at least latin1
1001 state
= STATE_QUOTED_ADDRESS
;
1004 // FIXME: Shouldn't it break here?
1005 // Since the address is finished?
1008 atChar
= &addressName
[ strlen( addressName
) ];
1012 addressName
[ strlen( addressName
) ] = *c
;
1016 case STATE_QUOTED_ADDRESS
:
1020 state
= STATE_ADDRESS
;
1023 addressName
[ strlen( addressName
) ] = *(++c
);
1026 addressName
[ strlen( addressName
) ] = *c
;
1032 state
= STATE_FULLNAME
;
1035 comment
[ strlen( comment
) ] = *c
;
1043 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1044 // Comment seen: if there's an address, append to
1045 // fullname. If no address, copy fullname to address
1046 // and comment to fullname.
1048 strcat(fullName
, "(");
1049 strcat(fullName
, comment
);
1050 strcat(fullName
, ")");
1052 strcpy(addressName
, fullName
);
1053 strcpy(fullName
, comment
);
1058 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1060 // what name should be tickered
1061 if( config
.tickerMode
== TICKER_FAMILYNAME
&& fullName
[0] != '\0' && !fullNameEncoded
) {
1062 free( addressName
);
1065 if( state
== STATE_FULLNAME
) {
1066 strcpy( addressName
, fullName
);
1067 if( saveAtCharPos
!= -1 )
1068 atChar
= &addressName
[saveAtCharPos
];
1070 if( config
.tickerMode
== TICKER_NICKNAME
) {
1071 if( atChar
!= NULL
)
1079 bool SkipSender( char *address
)
1082 int len
= strlen( address
);
1084 // remove trailing '\n' got from fgets
1085 if( address
[len
-1] == '\n' )
1086 address
[len
-1] = '\0';
1088 for( skipName
= config
.skipNames
;
1089 skipName
!= NULL
&& *skipName
!= NULL
; skipName
++ )
1091 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName
, address
);
1093 // call libc-fnmatch (wildcard-match :-) !
1094 if( !fnmatch( *skipName
, address
, 0 )) {
1095 TRACE( "skipping sender \"%s\"\n", *skipName
);
1103 void InsertName( char *name
, unsigned long checksum
, flag_t flag
)
1107 TRACE( "insertName: %X, \"%s\"\n", checksum
, name
);
1108 if (( item
= malloc( sizeof( name_t
))) == NULL
)
1114 item
->name
= name
; /*strdup( name );*/
1115 item
->checksum
= checksum
;
1117 item
->visited
= true;
1121 namesChanged
= true;
1124 void RemoveLastName( void )
1126 if( names
!= NULL
) {
1127 name_t
*name
= names
;
1128 names
= names
->next
;
1134 void ClearAllNames( void )
1136 name_t
*name
, *nextName
;
1138 for( name
= names
; name
!= NULL
; name
= nextName
) {
1139 nextName
= name
->next
;
1148 namesChanged
= true;
1151 void SetMailFlags( flag_t flag
)
1155 for( name
= names
; name
!= NULL
; name
= name
->next
)
1159 void DrawTickerX11Font( void )
1161 // 49x21+7+20 out-drawable size
1163 static int insertAt
;
1165 if( curTickerName
== NULL
|| namesChanged
)
1167 for( curTickerName
= names
;
1168 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1169 curTickerName
= curTickerName
->next
);
1171 if( curTickerName
== NULL
)
1174 namesChanged
= false;
1178 XDrawString( DADisplay
, outPixmap
, tickerGC
, insertAt
,
1179 41-tickerFS
->max_bounds
.descent
,
1180 curTickerName
->name
, strlen( curTickerName
->name
));
1184 if( insertAt
< -XTextWidth( tickerFS
, curTickerName
->name
,
1185 strlen( curTickerName
->name
)) + 6 )
1188 curTickerName
= curTickerName
->next
;
1189 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1191 if( curTickerName
!= NULL
) {
1197 void DrawTickerBuildinFont( void )
1199 // 49x21+7+20 out-drawable size
1200 // 14x21 font-character size
1202 static int insertAt
;
1203 static int takeItFrom
;
1207 unsigned char *currentChar
;
1212 if( curTickerName
== NULL
|| namesChanged
) {
1214 for( curTickerName
= names
;
1215 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1216 curTickerName
= curTickerName
->next
);
1218 if( curTickerName
== NULL
)
1223 namesChanged
= false;
1226 leftSpace
= takeItFrom
% 14;
1228 for( currentChar
= (unsigned char *)&curTickerName
->name
[takeItFrom
/14],
1229 drawTo
= insertAt
; *currentChar
!= '\0'; ++currentChar
)
1232 int outChar
= (*currentChar
< 32 || *currentChar
>= 128) ? '?' :
1234 int charWidth
= 57-drawTo
>= 14 ? 14 - leftSpace
: 57-drawTo
;
1236 XCopyArea( DADisplay
, charsPixmap
, outPixmap
, DAGC
,
1237 (outChar
-32)*14+leftSpace
, 0, charWidth
, 21, drawTo
, 20 );
1240 drawTo
+= charWidth
;
1246 if( --insertAt
< 7 ) {
1250 if( takeItFrom
/14 >= strlen( curTickerName
->name
)) {
1253 curTickerName
= curTickerName
->next
;
1254 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1256 if( curTickerName
!= NULL
) {
1264 void ButtonPressed( int button
, int state
, int x
, int y
)
1266 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1267 buttonPressed
= true;
1270 // reread the config file
1271 readConfigFile
= true;
1273 CheckTimeOut( false );
1276 void ButtonReleased( int button
, int state
, int x
, int y
)
1278 buttonPressed
= false;
1281 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1282 int ret
= system( config
.runCmd
);
1284 if( ret
== 127 || ret
== -1 )
1285 WARNING( "execution of command \"%s\" failed.\n", config
.runCmd
);
1288 CheckTimeOut( false );
1291 void GetHexColorString( const char *colorName
, char *xpmLine
)
1295 if( XParseColor( DADisplay
,
1296 DefaultColormap( DADisplay
, DefaultScreen( DADisplay
)),
1297 colorName
, &color
))
1299 sprintf( xpmLine
, "%02X%02X%02X", color
.red
>>8, color
.green
>>8,
1302 WARNING( "unknown colorname: \"%s\"\n", colorName
);
1305 char *XpmColorLine( const char *colorName
, char *colorLine
, bool disposeLine
)
1307 char *newLine
= strdup( colorLine
);
1308 char *from
= strrchr( newLine
, '#' );
1310 if( from
== NULL
&& !strcasecmp( &colorLine
[ strlen( colorLine
) - 4 ], "none" )) {
1311 // if no # found, it should be a None-color line
1313 newLine
= malloc( 12 );
1314 strcpy( newLine
, " \tc #" );
1322 GetHexColorString( colorName
, from
+1 );
1327 void UpdateConfiguration( void )
1329 struct stat fileStat
;
1331 TRACE( "reading configuration file...\n" );
1333 ReadConfigFile( true );
1335 // if no path/name to an mbox or maildir inbox directory was given,
1336 // use the environment
1337 if( config
.mailBox
== NULL
)
1338 config
.mailBox
= getenv( "MAIL" );
1340 // mbox or maildir ?
1341 if( config
.mailBox
!= NULL
&& stat( config
.mailBox
, &fileStat
) == 0 )
1342 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
1346 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
1348 PreparePixmaps( true );
1350 DASetTimeout (1000 / config
.fps
);
1353 void CleanupNames( void )
1355 name_t
*name
, *last
= NULL
, *nextName
;
1359 for( name
= names
; name
!= NULL
; name
= nextName
)
1361 nextName
= name
->next
;
1363 if( !name
->visited
) {
1367 last
->next
= name
->next
;
1374 if( !config
.newMailsOnly
|| (name
->flag
& FLAG_READ
) == 0 )
1380 bool HasTickerWork( void )
1382 name_t
*nextTickerName
;
1387 if( curTickerName
== NULL
|| namesChanged
) {
1389 for( nextTickerName
= names
;
1390 nextTickerName
!= NULL
&& ( config
.newMailsOnly
&& ( nextTickerName
->flag
& FLAG_READ
));
1391 nextTickerName
= nextTickerName
->next
);
1393 if( nextTickerName
== NULL
)