/* * wmnotify.c -- POP3 E-mail notification program * * Copyright (C) 2003 Hugo Villeneuve (hugo@hugovil.com) * based on WMPop3 by Scott Holden (scotth@thezone.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Define filename_M */ #define WMNOTIFY_M 1 #if HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "dockapp.h" #include "pop3.h" #include "imap.h" #include "network.h" #include "xevents.h" #include "options.h" #include "configfile.h" #if defined(HAVE_SNDFILE) # include "sound.h" #endif #include "wmnotify.xpm" #include "wmnotify.h" /* Set in DoubleClick() to stop the new mail animation when the mail client is opened. */ static bool animation_stop = false; static int animation_image = MAILBOX_FULL; /* Set in response to signal sent by SingleClick() to force mail check. Also set to true at * startup to force initial check. */ static bool manual_check = true; /* Used to signal TimerThread to quit. Inactive for now. */ static bool quit = false; static int double_click_notif = false; /* TimerThread ID */ static pthread_t timer_thread; inline void ErrorLocation( const char *file, int line ) { fprintf( stderr, " Error in file \"%s\" at line #%d\n", file, line ); } void * xmalloc( size_t size, const char *filename, int line_number ) { void *value; value = malloc( size ); if( value == NULL ) { perror( PACKAGE ); ErrorLocation( filename, line_number ); exit( EXIT_FAILURE ); } return value; } static void DisplayOpenedEmptyMailbox( void ) { /* Opened and empty mailbox image */ copyXPMArea( MAILBOX_OPENED_EMPTY_SRC_X, MAILBOX_OPENED_EMPTY_SRC_Y, MAILBOX_SIZE_X, MAILBOX_SIZE_Y, MAILBOX_DEST_X, MAILBOX_DEST_Y ); RedrawWindow(); } static void DisplayOpenedFullMailbox( void ) { /* Full mailbox image */ copyXPMArea( MAILBOX_OPENED_FULL_SRC_X, MAILBOX_OPENED_FULL_SRC_Y, MAILBOX_SIZE_X, MAILBOX_SIZE_Y, MAILBOX_DEST_X, MAILBOX_DEST_Y ); RedrawWindow(); } static void DisplayClosedMailbox( void ) { /* Opened mailbox image */ copyXPMArea( MAILBOX_CLOSED_SRC_X, MAILBOX_CLOSED_SRC_Y, MAILBOX_SIZE_X, MAILBOX_SIZE_Y, MAILBOX_DEST_X, MAILBOX_DEST_Y ); RedrawWindow(); } static void DisplayExecuteCommandNotification( void ) { /* Visual notification that the double-click was catched. */ copyXPMArea( EXEC_CMD_IMG_SRC_X, EXEC_CMD_IMG_SRC_Y, MAILBOX_SIZE_X, MAILBOX_SIZE_Y, MAILBOX_DEST_X, MAILBOX_DEST_Y ); RedrawWindow(); } static void ExecuteCommand( char *argv[] ) { pid_t pid; char *msg; /* No command defined, this is not an error. */ if( argv[0] == NULL ) { return; } pid = fork(); /* fork a child process. */ if( pid < 0) { perror( PACKAGE ); ErrorLocation( __FILE__, __LINE__ ); exit( EXIT_FAILURE ); } else if( pid == 0 ) { /* Child process */ /* When execvp() is successful, it doesn't return; otherwise, it returns -1 and sets errno. */ (void) execvp( argv[0], argv ); msg = strerror( errno ); fprintf( stderr, "%s: The external mail program couldn't be started.\n", PACKAGE); fprintf( stderr, "Check your path or your configuration file for errors.\n" ); fprintf( stderr, "%s: \"%s\"\n", msg, argv[0] ); exit( EXIT_FAILURE ); } } /* single-click --> Checking mail */ static void SingleClick( void ) { int status; if( wmnotify_infos.debug ) { printf( "%s: SingleClick() Entry\n", PACKAGE ); } /* Sending a signal to awake the TimerThread() thread. */ status = pthread_kill( timer_thread, SIGUSR1 ); if( status != EXIT_SUCCESS ) { fprintf( stderr, "%s: pthread_kill() error (%d)\n", PACKAGE, status ); ErrorLocation( __FILE__, __LINE__ ); exit( EXIT_FAILURE ); } if( wmnotify_infos.debug ) { printf( "%s: SingleClick() Exit\n", PACKAGE ); } } /* Double-click --> Starting external mail client. */ static void DoubleClick( void ) { int status; if( wmnotify_infos.mail_client_argv[0] != NULL ) { /* Starting external mail client. */ ExecuteCommand( wmnotify_infos.mail_client_argv ); double_click_notif = true; /* Sending a signal to awake the TimerThread() thread. This was previously done with a mutex variable (animation_stop), but this caused a bug when the following sequence was encountered: -The user double-click to start the external mail client -A new E-mail is received shortly after that -The user exit the external mail client -The user manually check for new E-mail -The audio notification sound is played, but no animation image is displayed. This was because setting the mutex variable 'animation_stop' didn't awakened the TimerThread(), but single-clicking awakened it. Since the 'animation_stop' variable was still set to true, no animation occured. */ status = pthread_kill( timer_thread, SIGUSR2 ); if( status != EXIT_SUCCESS ) { fprintf( stderr, "%s: pthread_kill() error (%d)\n", PACKAGE, status ); ErrorLocation( __FILE__, __LINE__ ); exit( EXIT_FAILURE ); } DisplayExecuteCommandNotification(); sleep(1); DisplayClosedMailbox(); double_click_notif = false; } else { fprintf( stderr, "%s: Warning: No email-client defined.\n", PACKAGE ); } } static void CatchChildTerminationSignal( int signal ) { switch( signal ) { case SIGCHLD: /* Wait for Mail Client child process termination. Child enters zombie state: process is dead and most resources are released, but process descriptor remains until parent reaps exit status via wait. */ /* The WNOHANG option prevents the call to waitpid from suspending execution of the caller. */ (void) waitpid( 0, NULL, WNOHANG ); break; default: fprintf( stderr, "%s: Unregistered signal received, exiting.\n", PACKAGE ); exit( EXIT_FAILURE ); } } static void CatchTimerSignal( int signal ) { switch( signal ) { case SIGUSR1: /* Catching the signal sent by the SingleClick() function. */ manual_check = true; break; case SIGUSR2: /* Catching the signal sent by the DoubleClick() function. */ animation_stop = true; break; default: fprintf( stderr, "%s: CatchTimerSignal(): unknown signal (%d)\n", PACKAGE, signal ); ErrorLocation( __FILE__, __LINE__ ); exit( EXIT_FAILURE ); } } static void NewMailAnimation( void ) { if( animation_image == MAILBOX_FULL ) { DisplayOpenedFullMailbox(); animation_image = MAILBOX_CLOSED; if( wmnotify_infos.debug ) { printf( "%s: NewMailAnimation() MAILBOX_FULL.\n", PACKAGE ); } } else { DisplayClosedMailbox(); animation_image = MAILBOX_FULL; if( wmnotify_infos.debug ) { printf( "%s: NewMailAnimation() MAILBOX_CLOSED.\n", PACKAGE ); } } } /* We display the opened mailbox image only when doing a manual check. */ static int CheckForNewMail( bool manual_check ) { int new_messages; if( manual_check == true ) { DisplayOpenedEmptyMailbox(); } if( wmnotify_infos.protocol == POP3_PROTOCOL ) { new_messages = POP3_CheckForNewMail(); } else if( wmnotify_infos.protocol == IMAP4_PROTOCOL ) { new_messages = IMAP4_CheckForNewMail(); } else { ErrorLocation( __FILE__, __LINE__ ); exit( EXIT_FAILURE ); } if( ( manual_check == true ) && ( new_messages > 0 ) ) { animation_image = MAILBOX_FULL; } return new_messages; } static void * TimerThread( /*@unused@*/ void *arg ) { int new_messages = 0; int counter = -1; bool animation_running = false; /* For catching the signal SIGUSR1. This signal is sent by the main program thread when the * user is issuing a single-click to manually check for new mails. */ (void) signal( SIGUSR1, CatchTimerSignal ); /* For catching the signal SIGUSR2. This signal is sent by the main program thread when the * user is issuing a double-click to start ther external mail client. */ (void) signal( SIGUSR2, CatchTimerSignal ); while( quit == false ) { if( wmnotify_infos.debug ) { printf( "%s: Timer thread iteration.\n", PACKAGE ); } if( ( manual_check == true ) || ( counter == 0 ) ) { new_messages = CheckForNewMail( manual_check ); manual_check = false; if( wmnotify_infos.debug ) { printf( "%s: new messages = %d.\n", PACKAGE, new_messages ); } if( new_messages > 0 ) { /* Checking if audio notification was already produced. */ if( animation_running == false ) { /* Audible notification, if requested in configuration file. */ if( wmnotify_infos.audible_notification != false ) { if( strlen( wmnotify_infos.audiofile ) != 0 ) { #if defined(HAVE_SNDFILE) PlayAudioFile( wmnotify_infos.audiofile, wmnotify_infos.volume ); #endif } else { AudibleBeep(); } } animation_running = true; } /* Number of times to execute timer loop before checking again for new mails when the * animation is running (when the animation is running, we sleep for * NEW_MAIL_ANIMATION_DURATION instead of wmnotify_infos.mail_check_interval). We set * the check interval to 30 seconds because we want the new mail condition to be * removed as soon as possible when the new messages are checked. */ counter = 30 * 1000000 / NEW_MAIL_ANIMATION_DURATION; } } if( ( animation_stop == true ) || ( new_messages <= 0 ) ) { if( wmnotify_infos.debug ) { if( animation_stop != false ) { printf( "%s: animation_stop is true\n", PACKAGE ); } } animation_running = false; animation_stop = false; if( double_click_notif == false ) { /* Before exiting, be sure to put NO MAIL image back in place... */ DisplayClosedMailbox(); } } /* If sleep() returns because the requested time has elapsed, the value returned will be * 0. If sleep() returns because of premature arousal due to delivery of a signal, the * return value will be the "unslept" amount (the requested time minus the time actually * slept) in seconds. */ if( animation_running == false ) { (void) sleep( wmnotify_infos.mail_check_interval ); counter = 0; } else { NewMailAnimation(); (void) usleep( NEW_MAIL_ANIMATION_DURATION ); counter--; } if( wmnotify_infos.debug ) { printf( "%s: counter = %d\n", PACKAGE, counter ); } } /* end while */ if( wmnotify_infos.debug ) { printf( "%s: Error, TimerThread() exited abnormally\n", PACKAGE ); } /* This code is never reached for now, because quit is always false. */ pthread_exit( NULL ); } /******************************************************************************* * Main function ******************************************************************************/ int main( int argc, char *argv[] ) { int status; /* Initialization */ ParseCommandLineOptions( argc, argv ); /* Reading configuration options from configuration file. */ ConfigurationFileInit(); /* For catching the termination signal SIGCHLD when the external mail client program is terminated, thus permitting removing zombi processes... */ (void) signal( SIGCHLD, CatchChildTerminationSignal ); /* Initialize callback function pointers. */ ProcessXlibEventsInit( SingleClick, DoubleClick ); /* Initializing and creating a DockApp window. */ InitDockAppWindow( argc, argv, wmnotify_xpm, wmnotify_infos.display_arg, wmnotify_infos.geometry_arg ); /* Starting thread for periodically checking for new mail. */ status = pthread_create( &timer_thread, NULL, TimerThread, NULL ); if( status != 0 ) { fprintf( stderr, "%s: Thread creation failed (%d)\n", PACKAGE, status ); ErrorLocation( __FILE__, __LINE__ ); exit( EXIT_FAILURE ); } /* Main loop, processing X Events */ ProcessXlibEvents(); /* This code is never reached for now. */ fprintf( stderr, "%s: Program exit\n", PACKAGE ); exit( EXIT_SUCCESS ); }