/////////////////////////////////////////////////////////////////////////////// // wmail.c // email indicator tool designed as docklet for Window Maker // main c source-file // // wmail version 2.0 // // Copyright 2000~2002, Sven Geisenhainer . // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions, and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions, and the following disclaimer in the // documentation and/or other materials provided with the distribution. // 3. The name of the author may not be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /////////////////////////////////////////////////////////////////////////////// // includes #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "config.h" // pixmaps #ifdef USE_DELT_XPMS #include "xpm_delt/main.xpm" #include "xpm_delt/symbols.xpm" #include "xpm_delt/numbers.xpm" #include "xpm_delt/button.xpm" #include "xpm_delt/chars.xpm" #else #include "xpm/main.xpm" #include "xpm/symbols.xpm" #include "xpm/numbers.xpm" #include "xpm/button.xpm" #include "xpm/chars.xpm" #endif /////////////////////////////////////////////////////////////////////////////// // typedefs typedef enum { FLAG_INITIAL = 0, FLAG_READ = 1 } flag_t; typedef struct _name_t { char *name; unsigned long checksum; flag_t flag; bool visited; struct _name_t *next; } name_t; typedef enum { STATE_NOMAIL, STATE_NEWMAIL, STATE_READMAIL } mail_state_t; typedef enum { STATE_ADDRESS, STATE_QUOTED_ADDRESS, STATE_FULLNAME, STATE_QUOTED_FULLNAME, STATE_ENCODED_FULLNAME, STATE_COMMENT } parse_state_t; /////////////////////////////////////////////////////////////////////////////// // data mail_state_t state = STATE_NOMAIL; int numMails = 0; bool namesChanged = false; bool buttonPressed = false; bool readConfigFile = false; bool isMaildir = false; bool forceRead = false; bool forceRedraw = true; time_t lastModifySeconds = 0; time_t lastAccessSeconds = 0; Pixmap mainPixmap; Pixmap mainPixmap_mask; Pixmap symbolsPixmap; Pixmap charsPixmap; Pixmap numbersPixmap; Pixmap buttonPixmap; Pixmap outPixmap; GC tickerGC; XFontStruct *tickerFS = NULL; name_t *names = NULL; name_t *curTickerName = NULL; static DAProgramOption options[] = { {"-display", NULL, "display to use", DOString, False, {&config.display}}, {"-c", "--command", "cmd to run on btn-click (\"xterm -e mail\" is default)", DOString, False, {&config.runCmd} }, {"-i", "--intervall", "number of secs between mail-status updates (1 is default)", DONatural, False, {&config.checkInterval} }, {"-f", "--familyname", "tickers the family-name if available", DONone, False, {NULL} }, {"-fps", "--frames", "ticker frames per second", DONatural, False, {&config.fps} }, {"-s", "--shortname", "tickers the nickname (all before the '@')", DONone, False, {NULL} }, {"-sc", "--symbolcolor", "symbol color-name", DOString, False, {&config.symbolColor} }, {"-fc", "--fontcolor", "ticker-font color-name", DOString, False, {&config.fontColor} }, {"-bc", "--backcolor", "backlight color-name", DOString, False, {&config.backColor} }, {"-oc", "--offcolor", "off-light color-name", DOString, False, {&config.offlightColor} }, {"-bg", "--background", "frame-background for non-shaped window", DOString, False, {&config.backgroundColor} }, {"-ns", "--noshape", "make the dockapp non-shaped (combine with -w)", DONone, False, {NULL} }, {"-n", "--new", "forces wmail to show new mail exclusively", DONone, False, {NULL} }, {"-mb", "--mailbox", "specify another mailbox ($MAIL is default)", DOString, False, {&config.mailBox} }, {"-e", "--execute", "command to execute when receiving a new mail", DOString, False, {&config.cmdOnMail} }, {"-sf", "--statusfield", "consider the status-field of the mail header to distinguish unread mails", DONone, False, {NULL} }, {"-rs", "--readstatus", "status field content that your client uses to mark read mails", DOString, False, {&config.readStatus} }, {"-fn", "--tickerfont", "use specified X11 font to draw the ticker", DOString, False, {&config.useX11Font} } }; /////////////////////////////////////////////////////////////////////////////// // prototypes void PreparePixmaps( bool freeThemFirst ); void TimerHandler( int dummy ); void CheckMBox(); void CheckMaildir(); int TraverseDirectory( const char *name, bool isNewMail ); name_t *GetMail( unsigned long checksum ); void UpdatePixmap( bool flashMailSymbol ); void ParseMBoxFile( struct stat *fileStat ); void ParseMaildirFile( const char *fileName, unsigned long checksum, struct stat *fileStat, bool isNewMail ); char *ParseFromField( char *buf ); bool SkipSender( char *address ); void InsertName( char *name, unsigned long checksum, flag_t flag ); void RemoveLastName(); void ClearAllNames(); void DrawTickerX11Font(); void DrawTickerBuildinFont(); void ButtonPressed( int button, int state, int x, int y ); void ButtonReleased( int button, int state, int x, int y ); char *XpmColorLine( const char *colorName, char *colorLine, bool disposeLine ); void ReadChecksumFile(); void WriteChecksumFile( bool writeAll ); void UpdateChecksum( unsigned long *checksum, const char *buf ); void RemoveChecksumFile(); void SetMailFlags( flag_t flag ); void MarkName( unsigned long checksum ); void DetermineState(); void UpdateConfiguration(); void CleanupNames(); char *FileNameConcat( const char *path, const char *fileName ); bool HasTickerWork(); /////////////////////////////////////////////////////////////////////////////// // implementation void SetTimer() { struct itimerval timerVal; timerVal.it_interval.tv_sec = 0; timerVal.it_interval.tv_usec = 1000000/config.fps; timerVal.it_value.tv_sec = 0; timerVal.it_value.tv_usec = 1000000/config.fps; setitimer( ITIMER_REAL, &timerVal, NULL ); } int main( int argc, char **argv ) { char *usersHome; struct sigaction sa; int ret; struct stat fileStat; XTextProperty windowName; char *name = argv[0]; DACallbacks callbacks = { NULL, &ButtonPressed, &ButtonReleased, NULL, NULL, NULL, NULL }; // read the config file and overide the default-settings ReadConfigFile( false ); if( config.checksumFileName == NULL ) { if(( usersHome = getenv( "HOME" )) == NULL ) { WARNING( "HOME environment-variable is not set, placing %s in current directory!\n", WMAIL_CHECKSUM_FILE ); config.checksumFileName = WMAIL_CHECKSUM_FILE; } else config.checksumFileName = MakePathName( usersHome, WMAIL_CHECKSUM_FILE ); } TRACE( "using checksum-file \"%s\"\n", config.checksumFileName ); // parse cmdline-args and overide defaults and cfg-file settings DAParseArguments( argc, argv, options, sizeof(options) / sizeof(DAProgramOption), WMAIL_NAME, WMAIL_VERSION ); if( options[0].used ) config.givenOptions |= CL_DISPLAY; if( options[1].used ) config.givenOptions |= CL_RUNCMD; if( options[2].used ) config.givenOptions |= CL_CHECKINTERVAL; if( options[3].used ) { config.givenOptions |= CL_TICKERMODE; config.tickerMode = TICKER_FAMILYNAME; } if( options[4].used ) config.givenOptions |= CL_FPS; if( options[5].used ) { config.givenOptions |= CL_TICKERMODE; config.tickerMode = TICKER_NICKNAME; } if( options[6].used ) config.givenOptions |= CL_SYMBOLCOLOR; if( options[7].used ) config.givenOptions |= CL_FONTCOLOR; if( options[8].used ) config.givenOptions |= CL_BACKCOLOR; if( options[9].used ) config.givenOptions |= CL_OFFLIGHTCOLOR; if( options[10].used ) config.givenOptions |= CL_BACKGROUNDCOLOR; if( options[11].used ) { config.givenOptions |= CL_NOSHAPE; config.noshape = true; } if( options[12].used ) { config.givenOptions |= CL_NEWMAILONLY; config.newMailsOnly = true; } if( options[13].used ) config.givenOptions |= CL_MAILBOX; if( options[14].used ) config.givenOptions |= CL_CMDONMAIL; if( options[15].used ) { config.givenOptions |= CL_CONSIDERSTATUSFIELD; config.considerStatusField = true; } if( options[16].used ) config.givenOptions |= CL_READSTATUS; if( options[17].used ) config.givenOptions |= CL_USEX11FONT; if( config.mailBox == NULL ) ABORT( "no mailbox specified - please define at least your $MAIL environment-variable!\n" ); else if( stat( config.mailBox, &fileStat ) == 0 ) isMaildir = S_ISDIR( fileStat.st_mode ) != 0; TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" ); // dockapp size hard wired - sorry... DAInitialize( config.display, "wmail", 64, 64, argc, argv ); outPixmap = DAMakePixmap(); PreparePixmaps( false ); DASetCallbacks( &callbacks ); DASetTimeout( -1 ); sa.sa_handler = TimerHandler; sigemptyset( &sa.sa_mask ); sa.sa_flags = SA_RESTART; ret = sigaction( SIGALRM, &sa, 0 ); if( ret ) { perror( "wmail error: sigaction" ); exit( 1 ); } XStringListToTextProperty( &name, 1, &windowName ); XSetWMName( DADisplay, DAWindow, &windowName ); UpdatePixmap( false ); DAShow(); SetTimer(); DAEventLoop(); return 0; } void PreparePixmaps( bool freeMem ) { // simple recoloring of the raw xpms befor creating Pixmaps of them // this works as long as you don't "touch" the images... unsigned dummy; XGCValues values; if( config.symbolColor != NULL ) { // symbol color ? symbols_xpm[2] = XpmColorLine( config.symbolColor, symbols_xpm[2], freeMem && ( config.colorsUsed & SYM_COLOR )); config.colorsUsed |= SYM_COLOR; } else { symbols_xpm[2] = XpmColorLine( "#20B2AA", symbols_xpm[2], freeMem && ( config.colorsUsed & SYM_COLOR )); config.colorsUsed |= SYM_COLOR; } if( config.fontColor != NULL ) { // font color ? chars_xpm[3] = XpmColorLine( config.fontColor, chars_xpm[3], freeMem && ( config.colorsUsed & FNT_COLOR )); numbers_xpm[3] = XpmColorLine( config.fontColor, numbers_xpm[3], freeMem && ( config.colorsUsed & FNT_COLOR )); config.colorsUsed |= FNT_COLOR; } else { chars_xpm[3] = XpmColorLine( "#D3D3D3", chars_xpm[3], freeMem && ( config.colorsUsed & FNT_COLOR )); numbers_xpm[3] = XpmColorLine( "#D3D3D3", numbers_xpm[3], freeMem && ( config.colorsUsed & FNT_COLOR )); config.colorsUsed |= FNT_COLOR; } if( config.backColor != NULL ) { // backlight color ? main_xpm[3] = XpmColorLine( config.backColor, main_xpm[3], freeMem && ( config.colorsUsed & BCK_COLOR )); symbols_xpm[3] = XpmColorLine( config.backColor, symbols_xpm[3], freeMem && ( config.colorsUsed & BCK_COLOR )); chars_xpm[2] = XpmColorLine( config.backColor, chars_xpm[2], freeMem && ( config.colorsUsed & BCK_COLOR )); numbers_xpm[2] = XpmColorLine( config.backColor, numbers_xpm[2], freeMem && ( config.colorsUsed & BCK_COLOR )); config.colorsUsed |= BCK_COLOR; } else { main_xpm[3] = XpmColorLine( "#282828", main_xpm[3], freeMem && ( config.colorsUsed & BCK_COLOR )); symbols_xpm[3] = XpmColorLine( "#282828", symbols_xpm[3], freeMem && ( config.colorsUsed & BCK_COLOR )); chars_xpm[2] = XpmColorLine( "#282828", chars_xpm[2], freeMem && ( config.colorsUsed & BCK_COLOR )); numbers_xpm[2] = XpmColorLine( "#282828", numbers_xpm[2], freeMem && ( config.colorsUsed & BCK_COLOR )); config.colorsUsed |= BCK_COLOR; } if( config.offlightColor != NULL ) { // off-light color ? main_xpm[2] = XpmColorLine( config.offlightColor, main_xpm[2], freeMem && ( config.colorsUsed & OFF_COLOR )); numbers_xpm[4] = XpmColorLine( config.offlightColor, numbers_xpm[4], freeMem && ( config.colorsUsed & OFF_COLOR )); config.colorsUsed |= OFF_COLOR; } else { main_xpm[2] = XpmColorLine( "#000000", main_xpm[2], freeMem && ( config.colorsUsed & OFF_COLOR )); numbers_xpm[4] = XpmColorLine( "#000000", numbers_xpm[4], freeMem && ( config.colorsUsed & OFF_COLOR )); config.colorsUsed |= OFF_COLOR; } if( config.backgroundColor != NULL ) { // window-frame background (only seen if nonshaped) ? main_xpm[1] = XpmColorLine( config.backgroundColor, main_xpm[1], freeMem && ( config.colorsUsed & BGR_COLOR )); config.colorsUsed |= BGR_COLOR; } if( freeMem ) { XFreePixmap( DADisplay, mainPixmap ); XFreePixmap( DADisplay, mainPixmap_mask ); XFreePixmap( DADisplay, symbolsPixmap ); XFreePixmap( DADisplay, charsPixmap ); XFreePixmap( DADisplay, numbersPixmap ); XFreePixmap( DADisplay, buttonPixmap ); if( tickerGC != NULL ) { XFreeGC( DADisplay, tickerGC ); tickerGC = NULL; if( tickerFS != NULL ) { XFreeFont( DADisplay, tickerFS ); tickerFS = NULL; } } } DAMakePixmapFromData( main_xpm, &mainPixmap, &mainPixmap_mask, &dummy, &dummy ); DAMakePixmapFromData( symbols_xpm, &symbolsPixmap, NULL, &dummy, &dummy ); DAMakePixmapFromData( chars_xpm, &charsPixmap, NULL, &dummy, &dummy ); DAMakePixmapFromData( numbers_xpm, &numbersPixmap, NULL, &dummy, &dummy ); DAMakePixmapFromData( button_xpm, &buttonPixmap, NULL, &dummy, &dummy ); if( config.useX11Font != NULL ) { XRectangle clipRect; if( config.fontColor != NULL ) values.foreground = DAGetColor( config.fontColor ); else values.foreground = DAGetColor( "#D3D3D3" ); tickerFS = XLoadQueryFont( DADisplay, config.useX11Font ); if( tickerFS == NULL ) ABORT( "Cannot load font \"%s\"", config.useX11Font ); values.font = tickerFS->fid; tickerGC = XCreateGC( DADisplay, DAWindow, GCForeground | GCFont, &values ); clipRect.x = 6; clipRect.y = 19; clipRect.width = 52; clipRect.height = 23; XSetClipRectangles( DADisplay, tickerGC, 0, 0, &clipRect, 1, Unsorted ); } if( config.noshape ) // non-shaped dockapp ? DASetShape( None ); else DASetShape( mainPixmap_mask ); } void MarkName( unsigned long checksum ) { name_t *name; for( name = names; name != NULL; name = name->next ) { if( name->checksum == checksum ) { name->flag |= FLAG_READ; if( config.newMailsOnly ) numMails--; break; } } } void DetermineState() { name_t *name; for( name = names; name != NULL; name = name->next ) if(!( name->flag & FLAG_READ )) { state = STATE_NEWMAIL; if( config.cmdOnMail != NULL ) { int ret = system( config.cmdOnMail ); if( ret == 127 || ret == -1 ) WARNING( "execution of command \"%s\" failed.\n", config.cmdOnMail ); } break; } } void ReadChecksumFile() { FILE *f = fopen( config.checksumFileName, "rb" ); if( f != NULL ) while( !feof( f )) { unsigned long checksum; if( fread( &checksum, sizeof(long), 1, f ) != 1 ) continue; MarkName( checksum ); } else return; fclose( f ); } void WriteChecksumFile( bool writeAll ) { FILE *f; TRACE( "writing checksums:" ); if(( f = fopen( config.checksumFileName, "wb" )) != NULL ) { name_t *name; for( name = names; name != NULL; name = name->next ) { if( writeAll || (name->flag & FLAG_READ)) { fwrite( &name->checksum, sizeof(long), 1, f ); TRACE( " %X", name->checksum ); } } } else return; TRACE( "\n" ); fclose( f ); } void UpdateChecksum( unsigned long *checksum, const char *buf ) { if( buf != NULL ) { unsigned int i, len = strlen( buf ); for( i = 0; i < len; ++i ) *checksum += buf[i] << (( i % sizeof(long) ) * 8 ); } } void RemoveChecksumFile() { TRACE( "removing checksum-file\n" ); remove( config.checksumFileName ); } // dummy needed because this func is a signal-handler void TimerHandler( int dummy ) { static int checkMail = 0; if( readConfigFile ) { readConfigFile = false; UpdateConfiguration(); checkMail = 0; forceRead = true; } if( checkMail == 0 ) { TRACE( "checking for new mail...\n" ); if( isMaildir ) CheckMaildir(); else CheckMBox(); } UpdatePixmap( checkMail % config.fps < config.fps/2 ); if( ++checkMail >= config.fps * config.checkInterval ) checkMail = 0; } void CheckMBox() { struct stat fileStat; // error retrieving file-stats -> no/zero-size file and no new/read mails // available if( stat( config.mailBox, &fileStat ) == -1 || fileStat.st_size == 0 ) { if( state != STATE_NOMAIL ) { state = STATE_NOMAIL; ClearAllNames(); RemoveChecksumFile(); forceRedraw = true; } } else { // file has changed -> new mails arrived or some mails removed if( lastModifySeconds != fileStat.st_mtime || forceRead ) { forceRead = false; ParseMBoxFile( &fileStat ); stat( config.mailBox, &fileStat ); forceRedraw = true; // file has accessed (read) -> mark all mails as "read", because // it cannot be decided which mails the user has read... } else if( lastAccessSeconds != fileStat.st_atime ) { state = STATE_READMAIL; WriteChecksumFile( true ); if( config.newMailsOnly ) { numMails = 0; SetMailFlags( FLAG_READ ); } forceRedraw = true; } lastModifySeconds = fileStat.st_mtime; lastAccessSeconds = fileStat.st_atime; } } void CheckMaildir() { DIR *dir = NULL; int lastState = state; int lastMailCount = numMails; if( forceRead ) { forceRead = false; ClearAllNames(); TRACE( "all names cleared\n" ); } state = STATE_NOMAIL; if(( dir = opendir( config.mailBox )) != NULL ) { struct dirent *dirEnt = NULL; //bool writeChecksums = false; name_t *name; for( name = names; name != NULL; name = name->next ) name->visited = false; while(( dirEnt = readdir( dir )) != NULL ) { char *fullName = FileNameConcat( config.mailBox, dirEnt->d_name ); struct stat fileStat; if( !stat( fullName, &fileStat ) == 0 ) { WARNING( "Can't stat file/path \"%s\"\n", fullName ); free( fullName ); continue; } if( S_ISDIR( fileStat.st_mode )) { if( strcmp( dirEnt->d_name, "new" ) == 0 ) { if( TraverseDirectory( fullName, true ) > 0 ) state = STATE_NEWMAIL; } else if( strcmp( dirEnt->d_name, "cur" ) == 0 ) { if( TraverseDirectory( fullName, false ) > 0 ) if( state != STATE_NEWMAIL ) state = STATE_READMAIL; } // directories ".", ".." and "tmp" discarded } } closedir( dir ); CleanupNames(); } else WARNING( "can't open directory \"%s\"\n", config.mailBox ); if( lastState != state || lastMailCount != numMails ) forceRedraw = true; } int TraverseDirectory( const char *name, bool isNewMail ) { DIR *dir = NULL; int mails = 0; if(( dir = opendir( name )) != NULL ) { struct dirent *dirEnt = NULL; while(( dirEnt = readdir( dir )) != NULL ) { char *fullName = FileNameConcat( name, dirEnt->d_name ); struct stat fileStat; unsigned long checksum = 0; name_t *name; if( !stat( fullName, &fileStat ) == 0 ) { WARNING( "Can't stat file/path \"%s\"\n", fullName ); free( fullName ); continue; } if( !S_ISDIR( fileStat.st_mode )) { TRACE( "found email-file \"%s\"\n", fullName ); UpdateChecksum( &checksum, dirEnt->d_name ); if(( name = GetMail( checksum )) == NULL ) { TRACE( "-> new file - parsing it\n" ); ParseMaildirFile( fullName, checksum, &fileStat, isNewMail ); } else { name->flag = isNewMail ? FLAG_INITIAL : FLAG_READ; name->visited = true; } ++mails; } } } closedir( dir ); return mails; } char *FileNameConcat( const char *path, const char *fileName ) { int len1 = strlen( path ); int len2 = strlen( fileName ); char *buf; if( path[len1-1] == '/' ) --len1; buf = (char *)malloc( len1 + len2 + 2 ); memcpy( buf, path, len1 ); buf[len1] = '/'; memcpy( &buf[len1+1], fileName, len2 ); buf[len1+len2+1] = '\0'; return buf; } name_t *GetMail( unsigned long checksum ) { name_t *name; for( name = names; name != NULL; name = name->next ) if( name->checksum == checksum ) return name; return NULL; } void UpdatePixmap( bool flashMailSymbol ) { int drawCount, i; if( !forceRedraw && !HasTickerWork() ) return; forceRedraw = false; XCopyArea( DADisplay, mainPixmap, outPixmap, DAGC, 0, 0, 64, 64, 0, 0 ); if( numMails > 999 ) { XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC, 50, 0, 5, 9, 6, 49 ); drawCount = 999; } else drawCount = numMails; for( i = 0; i < 3; ++i, drawCount /= 10 ) { XCopyArea( DADisplay, numbersPixmap, outPixmap, DAGC, (drawCount%10)*5, 0, 5, 9, 24-i*6, 49 ); if( drawCount <= 9 ) break; } if( buttonPressed ) { XCopyArea( DADisplay, buttonPixmap, outPixmap, DAGC, 0, 0, 23, 11, 36, 48 ); } switch( state ) { case STATE_NEWMAIL: if( flashMailSymbol ) XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC, 13, 0, 37, 12, 20, 7 ); case STATE_READMAIL: XCopyArea( DADisplay, symbolsPixmap, outPixmap, DAGC, 0, 0, 13, 12, 7, 7 ); if( config.useX11Font == NULL ) DrawTickerBuildinFont(); else DrawTickerX11Font(); default: // make compiler happy ; } DASetPixmap( outPixmap ); } void ParseMBoxFile( struct stat *fileStat ) { char buf[1024]; struct utimbuf timeStruct; int fromFound = 0; FILE *f = fopen( config.mailBox, "rt" ); unsigned long checksum; state = STATE_READMAIL; ClearAllNames(); numMails = 0; if( f == NULL ) { WARNING( "can't open mbox \"%s\"\n", config.mailBox ); return; } while( fgets( buf, 1024, f ) != NULL ) { if( strncmp( buf, "From ", 5 ) == 0 ) { fromFound = 1; checksum = 0; continue; } if( fromFound ) UpdateChecksum( &checksum, buf ); if( fromFound && strncasecmp( buf, "from: ", 6 ) == 0 ) { if( SkipSender( buf+6 )) goto NEXTMAIL; InsertName( ParseFromField( buf+6 ), checksum, FLAG_INITIAL ); ++numMails; fromFound = 0; checksum = 0; } else if( config.considerStatusField && strncasecmp( buf, "status: ", 8 ) == 0 && strstr( buf+8, config.readStatus ) == NULL ) { RemoveLastName(); --numMails; } NEXTMAIL: ; } fclose( f ); ReadChecksumFile(); DetermineState(); timeStruct.actime = fileStat->st_atime; timeStruct.modtime = fileStat->st_mtime; utime( config.mailBox, &timeStruct ); } void ParseMaildirFile( const char *fileName, unsigned long checksum, struct stat *fileStat, bool isNewMail ) { char buf[1024]; struct utimbuf timeStruct; FILE *f = fopen( fileName, "rt" ); if( f == NULL ) { WARNING( "can't open maildir file \"%s\"\n", fileName ); return; } while( fgets( buf, 1024, f ) != NULL ) { if( strncasecmp( buf, "from: ", 6 ) == 0 ) { if( SkipSender( buf+6 )) break; InsertName( ParseFromField( buf+6 ), checksum, isNewMail ? FLAG_INITIAL : FLAG_READ ); //++numMails; } } fclose( f ); timeStruct.actime = fileStat->st_atime; timeStruct.modtime = fileStat->st_mtime; utime( fileName, &timeStruct ); } char *ParseFromField( char *buf ) { parse_state_t state = STATE_FULLNAME; int fullNameEncoded = 0; int saveAtCharPos = -1; char *fullName; char *addressName; char *atChar = NULL; char *c; int maxLen = strlen( buf ) + 1; char *comment; // FIXME: Uhm, those mallocs might fail... fullName = malloc( maxLen ); addressName = malloc( maxLen ); comment = malloc( maxLen ); memset( fullName, '\0', maxLen ); memset( addressName, '\0', maxLen ); memset( comment, '\0', maxLen ); // FIXME: Don't do that "encoded" dance. It's not intended by // RFC2047, and it's better to just do it in the end. // Cleaner. for( c = buf; *c != '\0'; ++c ) { switch( state ) { case STATE_FULLNAME: switch( *c ) { case '"': state = STATE_QUOTED_FULLNAME; continue; case '<': if( fullName[0] != '\0' && fullName[ strlen( fullName ) - 1 ] == ' ' ) fullName[ strlen( fullName ) - 1 ] = '\0'; state = STATE_ADDRESS; continue; case '@': saveAtCharPos = strlen( fullName ); fullName[ saveAtCharPos ] = *c; continue; case '(': state = STATE_COMMENT; continue; case '=': if( *(c+1) == '?' ) { ++c; fullNameEncoded = 1; state = STATE_ENCODED_FULLNAME; continue; } // else do the default action default: fullName[ strlen( fullName ) ] = *c; } continue; case STATE_QUOTED_FULLNAME: switch( *c ) { case '\\': fullName[ strlen( fullName ) ] = *(++c); continue; case '"': state = STATE_FULLNAME; continue; default: fullName[ strlen( fullName ) ] = *c; } continue; case STATE_ENCODED_FULLNAME: switch( *c ) { case '?': if( *(c+1) == '=' ) { ++c; state = STATE_FULLNAME; continue; } default: ; // do nothing... COMING SOON: decode at least latin1 } continue; case STATE_ADDRESS: switch( *c ) { case '"': state = STATE_QUOTED_ADDRESS; case '>': case '\n': // FIXME: Shouldn't it break here? // Since the address is finished? continue; case '@': atChar = &addressName[ strlen( addressName ) ]; *atChar = *c; continue; default: addressName[ strlen( addressName ) ] = *c; } continue; case STATE_QUOTED_ADDRESS: switch( *c ) { case '"': state = STATE_ADDRESS; continue; case '\\': addressName[ strlen( addressName ) ] = *(++c); continue; default: addressName[ strlen( addressName ) ] = *c; } continue; case STATE_COMMENT: switch( *c ) { case ')': state = STATE_FULLNAME; continue; default: comment[ strlen( comment ) ] = *c; continue; } continue; } } if (*comment) { //WARNING("Comment seen: %s\nIn: %s\nFullname: %s\nAddress: %s\n", comment, buf, fullName, addressName); // Comment seen: if there's an address, append to // fullname. If no address, copy fullname to address // and comment to fullname. if (*addressName) { strcat(fullName, "("); strcat(fullName, comment); strcat(fullName, ")"); } else { strcpy(addressName, fullName); strcpy(fullName, comment); } } //WARNING("Fullname: %s\nAddress: %s\n", fullName, addressName); // what name should be tickered if( config.tickerMode == TICKER_FAMILYNAME && fullName[0] != '\0' && !fullNameEncoded ) { free( addressName ); return fullName; } else { if( state == STATE_FULLNAME ) { strcpy( addressName, fullName ); if( saveAtCharPos != -1 ) atChar = &addressName[saveAtCharPos]; } if( config.tickerMode == TICKER_NICKNAME ) { if( atChar != NULL ) *atChar = '\0'; } free( fullName ); return addressName; } } bool SkipSender( char *address ) { char **skipName; int len = strlen( address ); // remove trailing '\n' got from fgets if( address[len-1] == '\n' ) address[len-1] = '\0'; for( skipName = config.skipNames; skipName != NULL && *skipName != NULL; skipName++ ) { TRACE( "comparing \"%s\" and \"%s\"\n", *skipName, address ); // call libc-fnmatch (wildcard-match :-) ! if( !fnmatch( *skipName, address, 0 )) { TRACE( "skipping sender \"%s\"\n", *skipName ); return true; } } return false; } void InsertName( char *name, unsigned long checksum, flag_t flag ) { name_t *item; TRACE( "insertName: %X, \"%s\"\n", checksum, name ); item = (name_t *)malloc( sizeof( name_t )); item->name = name; /*strdup( name );*/ item->checksum = checksum; item->flag = flag; item->visited = true; item->next = names; names = item; namesChanged = true; } void RemoveLastName() { if( names != NULL ) { name_t *name = names; names = names->next; free( name ); } } void ClearAllNames() { name_t *name, *nextName; for( name = names; name != NULL; name = nextName ) { nextName = name->next; free( name->name ); free( name ); } names = NULL; numMails = 0; namesChanged = true; } void SetMailFlags( flag_t flag ) { name_t *name; for( name = names; name != NULL; name = name->next ) name->flag |= flag; } void DrawTickerX11Font() { // 49x21+7+20 out-drawable size static int insertAt; if( curTickerName == NULL || namesChanged ) { for( curTickerName = names; curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ); curTickerName = curTickerName->next ); if( curTickerName == NULL ) return; namesChanged = false; insertAt = 54; } XDrawString( DADisplay, outPixmap, tickerGC, insertAt, 41-tickerFS->max_bounds.descent, curTickerName->name, strlen( curTickerName->name )); --insertAt; if( insertAt < -XTextWidth( tickerFS, curTickerName->name, strlen( curTickerName->name )) + 6 ) { do { curTickerName = curTickerName->next; } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ )); if( curTickerName != NULL ) { insertAt = 54; } } } void DrawTickerBuildinFont() { // 49x21+7+20 out-drawable size // 14x21 font-character size static int insertAt; static int takeItFrom; int leftSpace; int drawTo; unsigned char *currentChar; if( names == NULL ) return; if( curTickerName == NULL || namesChanged ) { for( curTickerName = names; curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ ); curTickerName = curTickerName->next ); if( curTickerName == NULL ) return; insertAt = 57; takeItFrom = 0; namesChanged = false; } leftSpace = takeItFrom % 14; for( currentChar = (unsigned char *)&curTickerName->name[takeItFrom/14], drawTo = insertAt; *currentChar != '\0'; ++currentChar ) { int outChar = (*currentChar < 32 || *currentChar >= 128) ? '?' : *currentChar; int charWidth = 57-drawTo >= 14 ? 14 - leftSpace : 57-drawTo; XCopyArea( DADisplay, charsPixmap, outPixmap, DAGC, (outChar-32)*14+leftSpace, 0, charWidth, 21, drawTo, 20 ); leftSpace = 0; drawTo += charWidth; if( drawTo > 57 ) break; } if( --insertAt < 7 ) { insertAt = 7; takeItFrom++; if( takeItFrom/14 >= strlen( curTickerName->name )) { do { curTickerName = curTickerName->next; } while( curTickerName != NULL && config.newMailsOnly && ( curTickerName->flag & FLAG_READ )); if( curTickerName != NULL ) { takeItFrom = 0; insertAt = 57; } } } } void ButtonPressed( int button, int state, int x, int y ) { if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) { buttonPressed = true; forceRedraw = true; } else // reread the config file readConfigFile = true; } void ButtonReleased( int button, int state, int x, int y ) { buttonPressed = false; forceRedraw = true; if( x >= 35 && x <= 59 && y >= 47 && y <= 59 ) { int ret = system( config.runCmd ); if( ret == 127 || ret == -1 ) WARNING( "execution of command \"%s\" failed.\n", config.runCmd ); } } void GetHexColorString( const char *colorName, char *xpmLine ) { XColor color; if( XParseColor( DADisplay, DefaultColormap( DADisplay, DefaultScreen( DADisplay )), colorName, &color )) { sprintf( xpmLine, "%02X%02X%02X", color.red>>8, color.green>>8, color.blue>>8 ); } else WARNING( "unknown colorname: \"%s\"\n", colorName ); } char *XpmColorLine( const char *colorName, char *colorLine, bool disposeLine ) { char *newLine = strdup( colorLine ); char *from = strrchr( newLine, '#' ); if( from == NULL && !strcasecmp( &colorLine[ strlen( colorLine ) - 4 ], "none" )) { // if no # found, it should be a None-color line free( newLine ); newLine = malloc( 12 ); strcpy( newLine, " \tc #" ); newLine[11] = '\0'; from = newLine + 4; } if( disposeLine ) free( colorLine ); GetHexColorString( colorName, from+1 ); return newLine; } void UpdateConfiguration() { struct stat fileStat; TRACE( "reading configuration file...\n" ); ReadConfigFile( true ); // if no path/name to an mbox or maildir inbox directory was given, // use the environment if( config.mailBox == NULL ) config.mailBox = getenv( "MAIL" ); // mbox or maildir ? if( config.mailBox != NULL && stat( config.mailBox, &fileStat ) == 0 ) isMaildir = S_ISDIR( fileStat.st_mode ) != 0; else isMaildir = false; TRACE( "mailbox is of type %s\n", isMaildir ? "maildir" : "mbox" ); PreparePixmaps( true ); SetTimer(); } void CleanupNames() { name_t *name, *last = NULL, *nextName; numMails = 0; for( name = names; name != NULL; name = nextName ) { nextName = name->next; if( !name->visited ) { if( last == NULL ) names = name->next; else last->next = name->next; free( name ); } else { last = name; if( !config.newMailsOnly || (name->flag & FLAG_READ) == 0 ) ++numMails; } } } bool HasTickerWork() { name_t *nextTickerName; if( names == NULL ) return false; if( curTickerName == NULL || namesChanged ) { for( nextTickerName = names; nextTickerName != NULL && ( config.newMailsOnly && ( nextTickerName->flag & FLAG_READ )); nextTickerName = nextTickerName->next ); if( nextTickerName == NULL ) return false; } return true; }