dockapps/wmnotify/src/wmnotify.c
2014-10-05 19:18:49 +01:00

469 lines
13 KiB
C

/*
* 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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/types.h>
#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 );
}