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 )