1363 lines
34 KiB
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;
|
||
|
}
|