dockapps/wmail/src/wmail.c

1363 lines
34 KiB
C

///////////////////////////////////////////////////////////////////////////////
// wmail.c
// email indicator tool designed as docklet for Window Maker
// main c source-file
//
// wmail version 2.0
//
// Copyright 2000~2002, Sven Geisenhainer <sveng@informatik.uni-jena.de>.
// 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 <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <utime.h>
#include <fnmatch.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <X11/Xlib.h>
#include <dockapp.h>
#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;
}