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 ///////////////////////////////////////////////////////////////////////////////
54 #include "xpm_delt/main.xpm"
55 #include "xpm_delt/symbols.xpm"
56 #include "xpm_delt/numbers.xpm"
57 #include "xpm_delt/button.xpm"
58 #include "xpm_delt/chars.xpm"
60 #include "xpm/main.xpm"
61 #include "xpm/symbols.xpm"
62 #include "xpm/numbers.xpm"
63 #include "xpm/button.xpm"
64 #include "xpm/chars.xpm"
68 ///////////////////////////////////////////////////////////////////////////////
76 typedef struct _name_t
{
78 unsigned long checksum
;
94 STATE_QUOTED_FULLNAME
,
95 STATE_ENCODED_FULLNAME
,
100 ///////////////////////////////////////////////////////////////////////////////
103 mail_state_t state
= STATE_NOMAIL
;
105 bool namesChanged
= false;
106 bool buttonPressed
= false;
107 bool readConfigFile
= false;
108 bool isMaildir
= false;
109 bool forceRead
= false;
110 bool forceRedraw
= true;
111 time_t lastModifySeconds
= 0;
112 time_t lastAccessSeconds
= 0;
114 Pixmap mainPixmap_mask
;
115 Pixmap symbolsPixmap
;
117 Pixmap numbersPixmap
;
121 XFontStruct
*tickerFS
= NULL
;
122 name_t
*names
= NULL
;
123 name_t
*curTickerName
= NULL
;
125 static DAProgramOption options
[] = {
126 {"-display", NULL
, "display to use", DOString
, False
, {&config
.display
}},
127 {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)",
128 DOString
, False
, {&config
.runCmd
} },
129 {"-i", "--intervall",
130 "number of secs between mail-status updates (1 is default)", DONatural
,
131 False
, {&config
.checkInterval
} },
132 {"-f", "--familyname", "tickers the family-name if available", DONone
,
134 {"-fps", "--frames", "ticker frames per second", DONatural
,
135 False
, {&config
.fps
} },
136 {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone
,
138 {"-sc", "--symbolcolor", "symbol color-name",
139 DOString
, False
, {&config
.symbolColor
} },
140 {"-fc", "--fontcolor", "ticker-font color-name",
141 DOString
, False
, {&config
.fontColor
} },
142 {"-bc", "--backcolor", "backlight color-name",
143 DOString
, False
, {&config
.backColor
} },
144 {"-oc", "--offcolor", "off-light color-name",
145 DOString
, False
, {&config
.offlightColor
} },
146 {"-bg", "--background", "frame-background for non-shaped window",
147 DOString
, False
, {&config
.backgroundColor
} },
148 {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)",
149 DONone
, False
, {NULL
} },
150 {"-n", "--new", "forces wmail to show new mail exclusively", DONone
, False
, {NULL
} },
151 {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString
, False
, {&config
.mailBox
} },
152 {"-e", "--execute", "command to execute when receiving a new mail", DOString
, False
, {&config
.cmdOnMail
} },
153 {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone
, False
, {NULL
} },
154 {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString
, False
, {&config
.readStatus
} },
155 {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString
, False
, {&config
.useX11Font
} }
159 ///////////////////////////////////////////////////////////////////////////////
162 void PreparePixmaps( bool freeThemFirst
);
163 void TimerHandler( int dummy
);
166 int TraverseDirectory( const char *name
, bool isNewMail
);
167 name_t
*GetMail( unsigned long checksum
);
168 void UpdatePixmap( bool flashMailSymbol
);
169 void ParseMBoxFile( struct stat
*fileStat
);
170 void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
171 struct stat
*fileStat
, bool isNewMail
);
172 char *ParseFromField( char *buf
);
173 bool SkipSender( char *address
);
174 void InsertName( char *name
, unsigned long checksum
, flag_t flag
);
175 void RemoveLastName();
176 void ClearAllNames();
177 void DrawTickerX11Font();
178 void DrawTickerBuildinFont();
179 void ButtonPressed( int button
, int state
, int x
, int y
);
180 void ButtonReleased( int button
, int state
, int x
, int y
);
181 char *XpmColorLine( const char *colorName
, char *colorLine
, bool disposeLine
);
182 void ReadChecksumFile();
183 void WriteChecksumFile( bool writeAll
);
184 void UpdateChecksum( unsigned long *checksum
, const char *buf
);
185 void RemoveChecksumFile();
186 void SetMailFlags( flag_t flag
);
187 void MarkName( unsigned long checksum
);
188 void DetermineState();
189 void UpdateConfiguration();
191 char *FileNameConcat( const char *path
, const char *fileName
);
192 bool HasTickerWork();
195 ///////////////////////////////////////////////////////////////////////////////
201 struct itimerval timerVal
;
203 timerVal
.it_interval
.tv_sec
= 0;
204 timerVal
.it_interval
.tv_usec
= 1000000/config
.fps
;
205 timerVal
.it_value
.tv_sec
= 0;
206 timerVal
.it_value
.tv_usec
= 1000000/config
.fps
;
208 setitimer( ITIMER_REAL
, &timerVal
, NULL
);
211 int main( int argc
, char **argv
)
216 struct stat fileStat
;
217 XTextProperty windowName
;
218 char *name
= argv
[0];
219 DACallbacks callbacks
= { NULL
, &ButtonPressed
, &ButtonReleased
,
220 NULL
, NULL
, NULL
, NULL
};
222 // read the config file and overide the default-settings
223 ReadConfigFile( false );
225 if( config
.checksumFileName
== NULL
) {
226 if(( usersHome
= getenv( "HOME" )) == NULL
) {
227 WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE
);
228 config
.checksumFileName
= WMAIL_CHECKSUM_FILE
;
230 config
.checksumFileName
= MakePathName( usersHome
, WMAIL_CHECKSUM_FILE
);
233 TRACE( "using checksum-file \"%s\"\n", config
.checksumFileName
);
235 // parse cmdline-args and overide defaults and cfg-file settings
236 DAParseArguments( argc
, argv
, options
,
237 sizeof(options
) / sizeof(DAProgramOption
),
238 WMAIL_NAME
, WMAIL_VERSION
);
240 if( options
[0].used
)
241 config
.givenOptions
|= CL_DISPLAY
;
242 if( options
[1].used
)
243 config
.givenOptions
|= CL_RUNCMD
;
244 if( options
[2].used
)
245 config
.givenOptions
|= CL_CHECKINTERVAL
;
246 if( options
[3].used
) {
247 config
.givenOptions
|= CL_TICKERMODE
;
248 config
.tickerMode
= TICKER_FAMILYNAME
;
250 if( options
[4].used
)
251 config
.givenOptions
|= CL_FPS
;
252 if( options
[5].used
) {
253 config
.givenOptions
|= CL_TICKERMODE
;
254 config
.tickerMode
= TICKER_NICKNAME
;
256 if( options
[6].used
)
257 config
.givenOptions
|= CL_SYMBOLCOLOR
;
258 if( options
[7].used
)
259 config
.givenOptions
|= CL_FONTCOLOR
;
260 if( options
[8].used
)
261 config
.givenOptions
|= CL_BACKCOLOR
;
262 if( options
[9].used
)
263 config
.givenOptions
|= CL_OFFLIGHTCOLOR
;
264 if( options
[10].used
)
265 config
.givenOptions
|= CL_BACKGROUNDCOLOR
;
266 if( options
[11].used
) {
267 config
.givenOptions
|= CL_NOSHAPE
;
268 config
.noshape
= true;
270 if( options
[12].used
) {
271 config
.givenOptions
|= CL_NEWMAILONLY
;
272 config
.newMailsOnly
= true;
274 if( options
[13].used
)
275 config
.givenOptions
|= CL_MAILBOX
;
276 if( options
[14].used
)
277 config
.givenOptions
|= CL_CMDONMAIL
;
278 if( options
[15].used
) {
279 config
.givenOptions
|= CL_CONSIDERSTATUSFIELD
;
280 config
.considerStatusField
= true;
282 if( options
[16].used
)
283 config
.givenOptions
|= CL_READSTATUS
;
284 if( options
[17].used
)
285 config
.givenOptions
|= CL_USEX11FONT
;
287 if( config
.mailBox
== NULL
)
288 ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" );
289 else if( stat( config
.mailBox
, &fileStat
) == 0 )
290 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
292 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
294 // dockapp size hard wired - sorry...
295 DAInitialize( config
.display
, "wmail", 64, 64, argc
, argv
);
297 outPixmap
= DAMakePixmap();
298 PreparePixmaps( false );
300 DASetCallbacks( &callbacks
);
303 sa
.sa_handler
= TimerHandler
;
304 sigemptyset( &sa
.sa_mask
);
305 sa
.sa_flags
= SA_RESTART
;
306 ret
= sigaction( SIGALRM
, &sa
, 0 );
309 perror( "wmail error: sigaction" );
313 XStringListToTextProperty( &name
, 1, &windowName
);
314 XSetWMName( DADisplay
, DAWindow
, &windowName
);
316 UpdatePixmap( false );
325 void PreparePixmaps( bool freeMem
)
327 // simple recoloring of the raw xpms befor creating Pixmaps of them
328 // this works as long as you don't "touch" the images...
333 if( config
.symbolColor
!= NULL
) { // symbol color ?
334 symbols_xpm
[2] = XpmColorLine( config
.symbolColor
, symbols_xpm
[2],
335 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
336 config
.colorsUsed
|= SYM_COLOR
;
338 symbols_xpm
[2] = XpmColorLine( "#20B2AA", symbols_xpm
[2],
339 freeMem
&& ( config
.colorsUsed
& SYM_COLOR
));
340 config
.colorsUsed
|= SYM_COLOR
;
343 if( config
.fontColor
!= NULL
) { // font color ?
344 chars_xpm
[3] = XpmColorLine( config
.fontColor
, chars_xpm
[3],
345 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
346 numbers_xpm
[3] = XpmColorLine( config
.fontColor
, numbers_xpm
[3],
347 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
348 config
.colorsUsed
|= FNT_COLOR
;
350 chars_xpm
[3] = XpmColorLine( "#D3D3D3", chars_xpm
[3],
351 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
352 numbers_xpm
[3] = XpmColorLine( "#D3D3D3", numbers_xpm
[3],
353 freeMem
&& ( config
.colorsUsed
& FNT_COLOR
));
354 config
.colorsUsed
|= FNT_COLOR
;
357 if( config
.backColor
!= NULL
) { // backlight color ?
358 main_xpm
[3] = XpmColorLine( config
.backColor
, main_xpm
[3],
359 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
360 symbols_xpm
[3] = XpmColorLine( config
.backColor
, symbols_xpm
[3],
361 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
362 chars_xpm
[2] = XpmColorLine( config
.backColor
, chars_xpm
[2],
363 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
364 numbers_xpm
[2] = XpmColorLine( config
.backColor
, numbers_xpm
[2],
365 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
366 config
.colorsUsed
|= BCK_COLOR
;
368 main_xpm
[3] = XpmColorLine( "#282828", main_xpm
[3],
369 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
370 symbols_xpm
[3] = XpmColorLine( "#282828", symbols_xpm
[3],
371 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
372 chars_xpm
[2] = XpmColorLine( "#282828", chars_xpm
[2],
373 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
374 numbers_xpm
[2] = XpmColorLine( "#282828", numbers_xpm
[2],
375 freeMem
&& ( config
.colorsUsed
& BCK_COLOR
));
376 config
.colorsUsed
|= BCK_COLOR
;
379 if( config
.offlightColor
!= NULL
) { // off-light color ?
380 main_xpm
[2] = XpmColorLine( config
.offlightColor
, main_xpm
[2],
381 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
382 numbers_xpm
[4] = XpmColorLine( config
.offlightColor
, numbers_xpm
[4],
383 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
384 config
.colorsUsed
|= OFF_COLOR
;
386 main_xpm
[2] = XpmColorLine( "#000000", main_xpm
[2],
387 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
388 numbers_xpm
[4] = XpmColorLine( "#000000", numbers_xpm
[4],
389 freeMem
&& ( config
.colorsUsed
& OFF_COLOR
));
390 config
.colorsUsed
|= OFF_COLOR
;
393 if( config
.backgroundColor
!= NULL
) { // window-frame background (only seen if nonshaped) ?
394 main_xpm
[1] = XpmColorLine( config
.backgroundColor
, main_xpm
[1],
395 freeMem
&& ( config
.colorsUsed
& BGR_COLOR
));
396 config
.colorsUsed
|= BGR_COLOR
;
401 XFreePixmap( DADisplay
, mainPixmap
);
402 XFreePixmap( DADisplay
, mainPixmap_mask
);
403 XFreePixmap( DADisplay
, symbolsPixmap
);
404 XFreePixmap( DADisplay
, charsPixmap
);
405 XFreePixmap( DADisplay
, numbersPixmap
);
406 XFreePixmap( DADisplay
, buttonPixmap
);
408 if( tickerGC
!= NULL
)
410 XFreeGC( DADisplay
, tickerGC
);
412 if( tickerFS
!= NULL
) {
413 XFreeFont( DADisplay
, tickerFS
);
419 DAMakePixmapFromData( main_xpm
, &mainPixmap
, &mainPixmap_mask
, &dummy
, &dummy
);
420 DAMakePixmapFromData( symbols_xpm
, &symbolsPixmap
, NULL
, &dummy
, &dummy
);
421 DAMakePixmapFromData( chars_xpm
, &charsPixmap
, NULL
, &dummy
, &dummy
);
422 DAMakePixmapFromData( numbers_xpm
, &numbersPixmap
, NULL
, &dummy
, &dummy
);
423 DAMakePixmapFromData( button_xpm
, &buttonPixmap
, NULL
, &dummy
, &dummy
);
425 if( config
.useX11Font
!= NULL
)
429 if( config
.fontColor
!= NULL
)
430 values
.foreground
= DAGetColor( config
.fontColor
);
432 values
.foreground
= DAGetColor( "#D3D3D3" );
434 tickerFS
= XLoadQueryFont( DADisplay
, config
.useX11Font
);
435 if( tickerFS
== NULL
)
436 ABORT( "Cannot load font \"%s\"", config
.useX11Font
);
438 values
.font
= tickerFS
->fid
;
439 tickerGC
= XCreateGC( DADisplay
, DAWindow
, GCForeground
| GCFont
,
444 clipRect
.height
= 23;
446 XSetClipRectangles( DADisplay
, tickerGC
, 0, 0, &clipRect
, 1, Unsorted
);
449 if( config
.noshape
) // non-shaped dockapp ?
452 DASetShape( mainPixmap_mask
);
455 void MarkName( unsigned long checksum
)
459 for( name
= names
; name
!= NULL
; name
= name
->next
) {
460 if( name
->checksum
== checksum
) {
461 name
->flag
|= FLAG_READ
;
462 if( config
.newMailsOnly
)
469 void DetermineState()
473 for( name
= names
; name
!= NULL
; name
= name
->next
)
474 if(!( name
->flag
& FLAG_READ
)) {
475 state
= STATE_NEWMAIL
;
477 if( config
.cmdOnMail
!= NULL
) {
478 int ret
= system( config
.cmdOnMail
);
480 if( ret
== 127 || ret
== -1 )
481 WARNING( "execution of command \"%s\" failed.\n", config
.cmdOnMail
);
488 void ReadChecksumFile()
490 FILE *f
= fopen( config
.checksumFileName
, "rb" );
491 if( f
!= NULL
) while( !feof( f
)) {
492 unsigned long checksum
;
493 if( fread( &checksum
, sizeof(long), 1, f
) != 1 )
496 MarkName( checksum
);
503 void WriteChecksumFile( bool writeAll
)
506 TRACE( "writing checksums:" );
508 if(( f
= fopen( config
.checksumFileName
, "wb" )) != NULL
) {
510 for( name
= names
; name
!= NULL
; name
= name
->next
) {
511 if( writeAll
|| (name
->flag
& FLAG_READ
)) {
512 fwrite( &name
->checksum
, sizeof(long), 1, f
);
513 TRACE( " %X", name
->checksum
);
524 void UpdateChecksum( unsigned long *checksum
, const char *buf
)
527 unsigned int i
, len
= strlen( buf
);
529 for( i
= 0; i
< len
; ++i
)
530 *checksum
+= buf
[i
] << (( i
% sizeof(long) ) * 8 );
534 void RemoveChecksumFile()
536 TRACE( "removing checksum-file\n" );
537 remove( config
.checksumFileName
);
540 // dummy needed because this func is a signal-handler
541 void TimerHandler( int dummy
)
543 static int checkMail
= 0;
545 if( readConfigFile
) {
546 readConfigFile
= false;
547 UpdateConfiguration();
554 TRACE( "checking for new mail...\n" );
562 UpdatePixmap( checkMail
% config
.fps
< config
.fps
/2 );
564 if( ++checkMail
>= config
.fps
* config
.checkInterval
)
570 struct stat fileStat
;
572 // error retrieving file-stats -> no/zero-size file and no new/read mails
574 if( stat( config
.mailBox
, &fileStat
) == -1 || fileStat
.st_size
== 0 ) {
575 if( state
!= STATE_NOMAIL
) {
576 state
= STATE_NOMAIL
;
578 RemoveChecksumFile();
582 // file has changed -> new mails arrived or some mails removed
583 if( lastModifySeconds
!= fileStat
.st_mtime
|| forceRead
) {
585 ParseMBoxFile( &fileStat
);
586 stat( config
.mailBox
, &fileStat
);
588 // file has accessed (read) -> mark all mails as "read", because
589 // it cannot be decided which mails the user has read...
590 } else if( lastAccessSeconds
!= fileStat
.st_atime
) {
591 state
= STATE_READMAIL
;
592 WriteChecksumFile( true );
593 if( config
.newMailsOnly
) {
595 SetMailFlags( FLAG_READ
);
600 lastModifySeconds
= fileStat
.st_mtime
;
601 lastAccessSeconds
= fileStat
.st_atime
;
608 int lastState
= state
;
609 int lastMailCount
= numMails
;
614 TRACE( "all names cleared\n" );
617 state
= STATE_NOMAIL
;
619 if(( dir
= opendir( config
.mailBox
)) != NULL
)
621 struct dirent
*dirEnt
= NULL
;
622 //bool writeChecksums = false;
625 for( name
= names
; name
!= NULL
; name
= name
->next
)
626 name
->visited
= false;
628 while(( dirEnt
= readdir( dir
)) != NULL
)
630 char *fullName
= FileNameConcat( config
.mailBox
, dirEnt
->d_name
);
631 struct stat fileStat
;
633 if( !stat( fullName
, &fileStat
) == 0 ) {
634 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
639 if( S_ISDIR( fileStat
.st_mode
))
641 if( strcmp( dirEnt
->d_name
, "new" ) == 0 ) {
642 if( TraverseDirectory( fullName
, true ) > 0 )
643 state
= STATE_NEWMAIL
;
645 else if( strcmp( dirEnt
->d_name
, "cur" ) == 0 ) {
646 if( TraverseDirectory( fullName
, false ) > 0 )
647 if( state
!= STATE_NEWMAIL
)
648 state
= STATE_READMAIL
;
650 // directories ".", ".." and "tmp" discarded
656 WARNING( "can't open directory \"%s\"\n", config
.mailBox
);
658 if( lastState
!= state
|| lastMailCount
!= numMails
)
662 int TraverseDirectory( const char *name
, bool isNewMail
)
667 if(( dir
= opendir( name
)) != NULL
)
669 struct dirent
*dirEnt
= NULL
;
671 while(( dirEnt
= readdir( dir
)) != NULL
)
673 char *fullName
= FileNameConcat( name
, dirEnt
->d_name
);
674 struct stat fileStat
;
675 unsigned long checksum
= 0;
678 if( !stat( fullName
, &fileStat
) == 0 ) {
679 WARNING( "Can't stat file/path \"%s\"\n", fullName
);
684 if( !S_ISDIR( fileStat
.st_mode
))
686 TRACE( "found email-file \"%s\"\n", fullName
);
687 UpdateChecksum( &checksum
, dirEnt
->d_name
);
689 if(( name
= GetMail( checksum
)) == NULL
)
691 TRACE( "-> new file - parsing it\n" );
692 ParseMaildirFile( fullName
, checksum
, &fileStat
, isNewMail
);
695 name
->flag
= isNewMail
? FLAG_INITIAL
: FLAG_READ
;
696 name
->visited
= true;
708 char *FileNameConcat( const char *path
, const char *fileName
)
710 int len1
= strlen( path
);
711 int len2
= strlen( fileName
);
714 if( path
[len1
-1] == '/' )
717 buf
= (char *)malloc( len1
+ len2
+ 2 );
719 memcpy( buf
, path
, len1
);
721 memcpy( &buf
[len1
+1], fileName
, len2
);
722 buf
[len1
+len2
+1] = '\0';
727 name_t
*GetMail( unsigned long checksum
)
731 for( name
= names
; name
!= NULL
; name
= name
->next
)
732 if( name
->checksum
== checksum
)
738 void UpdatePixmap( bool flashMailSymbol
)
742 if( !forceRedraw
&& !HasTickerWork() )
747 XCopyArea( DADisplay
, mainPixmap
, outPixmap
, DAGC
,
748 0, 0, 64, 64, 0, 0 );
752 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
753 50, 0, 5, 9, 6, 49 );
756 drawCount
= numMails
;
758 for( i
= 0; i
< 3; ++i
, drawCount
/= 10 )
760 XCopyArea( DADisplay
, numbersPixmap
, outPixmap
, DAGC
,
761 (drawCount
%10)*5, 0, 5, 9, 24-i
*6, 49 );
768 XCopyArea( DADisplay
, buttonPixmap
, outPixmap
, DAGC
,
769 0, 0, 23, 11, 36, 48 );
774 if( flashMailSymbol
)
775 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
776 13, 0, 37, 12, 20, 7 );
778 XCopyArea( DADisplay
, symbolsPixmap
, outPixmap
, DAGC
,
779 0, 0, 13, 12, 7, 7 );
781 if( config
.useX11Font
== NULL
)
782 DrawTickerBuildinFont();
785 default: // make compiler happy
789 DASetPixmap( outPixmap
);
792 void ParseMBoxFile( struct stat
*fileStat
)
795 struct utimbuf timeStruct
;
797 FILE *f
= fopen( config
.mailBox
, "rt" );
798 unsigned long checksum
;
800 state
= STATE_READMAIL
;
806 WARNING( "can't open mbox \"%s\"\n", config
.mailBox
);
810 while( fgets( buf
, 1024, f
) != NULL
)
812 if( strncmp( buf
, "From ", 5 ) == 0 ) {
819 UpdateChecksum( &checksum
, buf
);
821 if( fromFound
&& strncasecmp( buf
, "from: ", 6 ) == 0 )
823 if( SkipSender( buf
+6 ))
826 InsertName( ParseFromField( buf
+6 ), checksum
, FLAG_INITIAL
);
831 } else if( config
.considerStatusField
&& strncasecmp( buf
, "status: ", 8 ) == 0 &&
832 strstr( buf
+8, config
.readStatus
) == NULL
)
846 timeStruct
.actime
= fileStat
->st_atime
;
847 timeStruct
.modtime
= fileStat
->st_mtime
;
848 utime( config
.mailBox
, &timeStruct
);
851 void ParseMaildirFile( const char *fileName
, unsigned long checksum
,
852 struct stat
*fileStat
, bool isNewMail
)
855 struct utimbuf timeStruct
;
856 FILE *f
= fopen( fileName
, "rt" );
860 WARNING( "can't open maildir file \"%s\"\n", fileName
);
864 while( fgets( buf
, 1024, f
) != NULL
)
866 if( strncasecmp( buf
, "from: ", 6 ) == 0 )
868 if( SkipSender( buf
+6 ))
871 InsertName( ParseFromField( buf
+6 ), checksum
,
872 isNewMail
? FLAG_INITIAL
: FLAG_READ
);
880 timeStruct
.actime
= fileStat
->st_atime
;
881 timeStruct
.modtime
= fileStat
->st_mtime
;
882 utime( fileName
, &timeStruct
);
885 char *ParseFromField( char *buf
)
887 parse_state_t state
= STATE_FULLNAME
;
888 int fullNameEncoded
= 0;
889 int saveAtCharPos
= -1;
894 int maxLen
= strlen( buf
) + 1;
897 // FIXME: Uhm, those mallocs might fail...
899 fullName
= malloc( maxLen
);
900 addressName
= malloc( maxLen
);
901 comment
= malloc( maxLen
);
903 memset( fullName
, '\0', maxLen
);
904 memset( addressName
, '\0', maxLen
);
905 memset( comment
, '\0', maxLen
);
907 // FIXME: Don't do that "encoded" dance. It's not intended by
908 // RFC2047, and it's better to just do it in the end.
911 for( c
= buf
; *c
!= '\0'; ++c
)
918 state
= STATE_QUOTED_FULLNAME
;
921 if( fullName
[0] != '\0' &&
922 fullName
[ strlen( fullName
) - 1 ] == ' ' )
923 fullName
[ strlen( fullName
) - 1 ] = '\0';
924 state
= STATE_ADDRESS
;
927 saveAtCharPos
= strlen( fullName
);
928 fullName
[ saveAtCharPos
] = *c
;
931 state
= STATE_COMMENT
;
934 if( *(c
+1) == '?' ) {
937 state
= STATE_ENCODED_FULLNAME
;
939 } // else do the default action
941 fullName
[ strlen( fullName
) ] = *c
;
945 case STATE_QUOTED_FULLNAME
:
949 fullName
[ strlen( fullName
) ] = *(++c
);
952 state
= STATE_FULLNAME
;
955 fullName
[ strlen( fullName
) ] = *c
;
959 case STATE_ENCODED_FULLNAME
:
963 if( *(c
+1) == '=' ) {
965 state
= STATE_FULLNAME
;
969 ; // do nothing... COMING SOON: decode at least latin1
977 state
= STATE_QUOTED_ADDRESS
;
980 // FIXME: Shouldn't it break here?
981 // Since the address is finished?
984 atChar
= &addressName
[ strlen( addressName
) ];
988 addressName
[ strlen( addressName
) ] = *c
;
992 case STATE_QUOTED_ADDRESS
:
996 state
= STATE_ADDRESS
;
999 addressName
[ strlen( addressName
) ] = *(++c
);
1002 addressName
[ strlen( addressName
) ] = *c
;
1008 state
= STATE_FULLNAME
;
1011 comment
[ strlen( comment
) ] = *c
;
1019 //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName);
1020 // Comment seen: if there's an address, append to
1021 // fullname. If no address, copy fullname to address
1022 // and comment to fullname.
1024 strcat(fullName
, "(");
1025 strcat(fullName
, comment
);
1026 strcat(fullName
, ")");
1028 strcpy(addressName
, fullName
);
1029 strcpy(fullName
, comment
);
1033 //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName);
1035 // what name should be tickered
1036 if( config
.tickerMode
== TICKER_FAMILYNAME
&& fullName
[0] != '\0' && !fullNameEncoded
) {
1037 free( addressName
);
1040 if( state
== STATE_FULLNAME
) {
1041 strcpy( addressName
, fullName
);
1042 if( saveAtCharPos
!= -1 )
1043 atChar
= &addressName
[saveAtCharPos
];
1045 if( config
.tickerMode
== TICKER_NICKNAME
) {
1046 if( atChar
!= NULL
)
1054 bool SkipSender( char *address
)
1057 int len
= strlen( address
);
1059 // remove trailing '\n' got from fgets
1060 if( address
[len
-1] == '\n' )
1061 address
[len
-1] = '\0';
1063 for( skipName
= config
.skipNames
;
1064 skipName
!= NULL
&& *skipName
!= NULL
; skipName
++ )
1066 TRACE( "comparing \"%s\" and \"%s\"\n", *skipName
, address
);
1068 // call libc-fnmatch (wildcard-match :-) !
1069 if( !fnmatch( *skipName
, address
, 0 )) {
1070 TRACE( "skipping sender \"%s\"\n", *skipName
);
1078 void InsertName( char *name
, unsigned long checksum
, flag_t flag
)
1082 TRACE( "insertName: %X, \"%s\"\n", checksum
, name
);
1083 item
= (name_t
*)malloc( sizeof( name_t
));
1084 item
->name
= name
; /*strdup( name );*/
1085 item
->checksum
= checksum
;
1087 item
->visited
= true;
1091 namesChanged
= true;
1094 void RemoveLastName()
1096 if( names
!= NULL
) {
1097 name_t
*name
= names
;
1098 names
= names
->next
;
1103 void ClearAllNames()
1105 name_t
*name
, *nextName
;
1107 for( name
= names
; name
!= NULL
; name
= nextName
) {
1108 nextName
= name
->next
;
1117 namesChanged
= true;
1120 void SetMailFlags( flag_t flag
)
1124 for( name
= names
; name
!= NULL
; name
= name
->next
)
1128 void DrawTickerX11Font()
1130 // 49x21+7+20 out-drawable size
1132 static int insertAt
;
1134 if( curTickerName
== NULL
|| namesChanged
)
1136 for( curTickerName
= names
;
1137 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1138 curTickerName
= curTickerName
->next
);
1140 if( curTickerName
== NULL
)
1143 namesChanged
= false;
1147 XDrawString( DADisplay
, outPixmap
, tickerGC
, insertAt
,
1148 41-tickerFS
->max_bounds
.descent
,
1149 curTickerName
->name
, strlen( curTickerName
->name
));
1153 if( insertAt
< -XTextWidth( tickerFS
, curTickerName
->name
,
1154 strlen( curTickerName
->name
)) + 6 )
1157 curTickerName
= curTickerName
->next
;
1158 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1160 if( curTickerName
!= NULL
) {
1166 void DrawTickerBuildinFont()
1168 // 49x21+7+20 out-drawable size
1169 // 14x21 font-character size
1171 static int insertAt
;
1172 static int takeItFrom
;
1176 unsigned char *currentChar
;
1181 if( curTickerName
== NULL
|| namesChanged
) {
1183 for( curTickerName
= names
;
1184 curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
);
1185 curTickerName
= curTickerName
->next
);
1187 if( curTickerName
== NULL
)
1192 namesChanged
= false;
1195 leftSpace
= takeItFrom
% 14;
1197 for( currentChar
= (unsigned char *)&curTickerName
->name
[takeItFrom
/14],
1198 drawTo
= insertAt
; *currentChar
!= '\0'; ++currentChar
)
1201 int outChar
= (*currentChar
< 32 || *currentChar
>= 128) ? '?' :
1203 int charWidth
= 57-drawTo
>= 14 ? 14 - leftSpace
: 57-drawTo
;
1205 XCopyArea( DADisplay
, charsPixmap
, outPixmap
, DAGC
,
1206 (outChar
-32)*14+leftSpace
, 0, charWidth
, 21, drawTo
, 20 );
1209 drawTo
+= charWidth
;
1215 if( --insertAt
< 7 ) {
1219 if( takeItFrom
/14 >= strlen( curTickerName
->name
)) {
1222 curTickerName
= curTickerName
->next
;
1223 } while( curTickerName
!= NULL
&& config
.newMailsOnly
&& ( curTickerName
->flag
& FLAG_READ
));
1225 if( curTickerName
!= NULL
) {
1233 void ButtonPressed( int button
, int state
, int x
, int y
)
1235 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1236 buttonPressed
= true;
1239 // reread the config file
1240 readConfigFile
= true;
1243 void ButtonReleased( int button
, int state
, int x
, int y
)
1245 buttonPressed
= false;
1248 if( x
>= 35 && x
<= 59 && y
>= 47 && y
<= 59 ) {
1249 int ret
= system( config
.runCmd
);
1251 if( ret
== 127 || ret
== -1 )
1252 WARNING( "execution of command \"%s\" failed.\n", config
.runCmd
);
1256 void GetHexColorString( const char *colorName
, char *xpmLine
)
1260 if( XParseColor( DADisplay
,
1261 DefaultColormap( DADisplay
, DefaultScreen( DADisplay
)),
1262 colorName
, &color
))
1264 sprintf( xpmLine
, "%02X%02X%02X", color
.red
>>8, color
.green
>>8,
1267 WARNING( "unknown colorname: \"%s\"\n", colorName
);
1270 char *XpmColorLine( const char *colorName
, char *colorLine
, bool disposeLine
)
1272 char *newLine
= strdup( colorLine
);
1273 char *from
= strrchr( newLine
, '#' );
1275 if( from
== NULL
&& !strcasecmp( &colorLine
[ strlen( colorLine
) - 4 ], "none" )) {
1276 // if no # found, it should be a None-color line
1278 newLine
= malloc( 12 );
1279 strcpy( newLine
, " \tc #" );
1287 GetHexColorString( colorName
, from
+1 );
1292 void UpdateConfiguration()
1294 struct stat fileStat
;
1296 TRACE( "reading configuration file...\n" );
1298 ReadConfigFile( true );
1300 // if no path/name to an mbox or maildir inbox directory was given,
1301 // use the environment
1302 if( config
.mailBox
== NULL
)
1303 config
.mailBox
= getenv( "MAIL" );
1305 // mbox or maildir ?
1306 if( config
.mailBox
!= NULL
&& stat( config
.mailBox
, &fileStat
) == 0 )
1307 isMaildir
= S_ISDIR( fileStat
.st_mode
) != 0;
1311 TRACE( "mailbox is of type %s\n", isMaildir
? "maildir" : "mbox" );
1313 PreparePixmaps( true );
1320 name_t
*name
, *last
= NULL
, *nextName
;
1324 for( name
= names
; name
!= NULL
; name
= nextName
)
1326 nextName
= name
->next
;
1328 if( !name
->visited
) {
1332 last
->next
= name
->next
;
1338 if( !config
.newMailsOnly
|| (name
->flag
& FLAG_READ
) == 0 )
1344 bool HasTickerWork()
1346 name_t
*nextTickerName
;
1351 if( curTickerName
== NULL
|| namesChanged
) {
1353 for( nextTickerName
= names
;
1354 nextTickerName
!= NULL
&& ( config
.newMailsOnly
&& ( nextTickerName
->flag
& FLAG_READ
));
1355 nextTickerName
= nextTickerName
->next
);
1357 if( nextTickerName
== NULL
)