2 * wmnotify.c -- POP3 E-mail notification program
4 * Copyright (C) 2003 Hugo Villeneuve (hugo@hugovil.com)
5 * based on WMPop3 by Scott Holden (scotth@thezone.net)
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
22 /* Define filename_M */
39 #include <sys/types.h>
48 #include "configfile.h"
49 #if defined(HAVE_SNDFILE)
52 #include "wmnotify.xpm"
56 /* Set in DoubleClick() to stop the new mail animation when the mail client is
58 static bool animation_stop
= false;
60 static int animation_image
= MAILBOX_FULL
;
62 /* Set in response to signal sent by SingleClick() to force mail check. Also set to true at
63 * startup to force initial check. */
64 static bool manual_check
= true;
66 /* Used to signal TimerThread to quit. Inactive for now. */
67 static bool quit
= false;
69 static int double_click_notif
= false;
72 static pthread_t timer_thread
;
76 ErrorLocation( const char *file
, int line
)
78 fprintf( stderr
, " Error in file \"%s\" at line #%d\n", file
, line
);
83 xmalloc( size_t size
, const char *filename
, int line_number
)
87 value
= malloc( size
);
91 ErrorLocation( filename
, line_number
);
100 DisplayOpenedEmptyMailbox( void )
102 /* Opened and empty mailbox image */
103 copyXPMArea( MAILBOX_OPENED_EMPTY_SRC_X
, MAILBOX_OPENED_EMPTY_SRC_Y
,
104 MAILBOX_SIZE_X
, MAILBOX_SIZE_Y
, MAILBOX_DEST_X
, MAILBOX_DEST_Y
);
110 DisplayOpenedFullMailbox( void )
112 /* Full mailbox image */
113 copyXPMArea( MAILBOX_OPENED_FULL_SRC_X
, MAILBOX_OPENED_FULL_SRC_Y
,
114 MAILBOX_SIZE_X
, MAILBOX_SIZE_Y
,
115 MAILBOX_DEST_X
, MAILBOX_DEST_Y
);
121 DisplayClosedMailbox( void )
123 /* Opened mailbox image */
124 copyXPMArea( MAILBOX_CLOSED_SRC_X
, MAILBOX_CLOSED_SRC_Y
,
125 MAILBOX_SIZE_X
, MAILBOX_SIZE_Y
,
126 MAILBOX_DEST_X
, MAILBOX_DEST_Y
);
132 DisplayExecuteCommandNotification( void )
134 /* Visual notification that the double-click was catched. */
135 copyXPMArea( EXEC_CMD_IMG_SRC_X
, EXEC_CMD_IMG_SRC_Y
,
136 MAILBOX_SIZE_X
, MAILBOX_SIZE_Y
, MAILBOX_DEST_X
, MAILBOX_DEST_Y
);
142 ExecuteCommand( char *argv
[] )
147 /* No command defined, this is not an error. */
148 if( argv
[0] == NULL
) {
152 pid
= fork(); /* fork a child process. */
156 ErrorLocation( __FILE__
, __LINE__
);
157 exit( EXIT_FAILURE
);
159 else if( pid
== 0 ) { /* Child process */
160 /* When execvp() is successful, it doesn't return; otherwise, it returns
161 -1 and sets errno. */
162 (void) execvp( argv
[0], argv
);
164 msg
= strerror( errno
);
165 fprintf( stderr
, "%s: The external mail program couldn't be started.\n",
167 fprintf( stderr
, "Check your path or your configuration file for errors.\n"
169 fprintf( stderr
, "%s: \"%s\"\n", msg
, argv
[0] );
170 exit( EXIT_FAILURE
);
175 /* single-click --> Checking mail */
181 if( wmnotify_infos
.debug
) {
182 printf( "%s: SingleClick() Entry\n", PACKAGE
);
185 /* Sending a signal to awake the TimerThread() thread. */
186 status
= pthread_kill( timer_thread
, SIGUSR1
);
187 if( status
!= EXIT_SUCCESS
) {
188 fprintf( stderr
, "%s: pthread_kill() error (%d)\n", PACKAGE
, status
);
189 ErrorLocation( __FILE__
, __LINE__
);
190 exit( EXIT_FAILURE
);
193 if( wmnotify_infos
.debug
) {
194 printf( "%s: SingleClick() Exit\n", PACKAGE
);
199 /* Double-click --> Starting external mail client. */
205 if( wmnotify_infos
.mail_client_argv
[0] != NULL
) {
206 /* Starting external mail client. */
207 ExecuteCommand( wmnotify_infos
.mail_client_argv
);
209 double_click_notif
= true;
211 /* Sending a signal to awake the TimerThread() thread. This was previously
212 done with a mutex variable (animation_stop), but this caused a bug when the
213 following sequence was encountered:
214 -The user double-click to start the external mail client
215 -A new E-mail is received shortly after that
216 -The user exit the external mail client
217 -The user manually check for new E-mail
218 -The audio notification sound is played, but no animation image is
220 This was because setting the mutex variable 'animation_stop' didn't
221 awakened the TimerThread(), but single-clicking awakened it. Since the
222 'animation_stop' variable was still set to true, no animation occured. */
223 status
= pthread_kill( timer_thread
, SIGUSR2
);
224 if( status
!= EXIT_SUCCESS
) {
225 fprintf( stderr
, "%s: pthread_kill() error (%d)\n", PACKAGE
, status
);
226 ErrorLocation( __FILE__
, __LINE__
);
227 exit( EXIT_FAILURE
);
230 DisplayExecuteCommandNotification();
232 DisplayClosedMailbox();
234 double_click_notif
= false;
237 fprintf( stderr
, "%s: Warning: No email-client defined.\n", PACKAGE
);
243 CatchChildTerminationSignal( int signal
)
247 /* Wait for Mail Client child process termination. Child enters zombie
248 state: process is dead and most resources are released, but process
249 descriptor remains until parent reaps exit status via wait. */
251 /* The WNOHANG option prevents the call to waitpid from suspending execution
253 (void) waitpid( 0, NULL
, WNOHANG
);
256 fprintf( stderr
, "%s: Unregistered signal received, exiting.\n", PACKAGE
);
257 exit( EXIT_FAILURE
);
263 CatchTimerSignal( int signal
)
267 /* Catching the signal sent by the SingleClick() function. */
271 /* Catching the signal sent by the DoubleClick() function. */
272 animation_stop
= true;
275 fprintf( stderr
, "%s: CatchTimerSignal(): unknown signal (%d)\n", PACKAGE
,
277 ErrorLocation( __FILE__
, __LINE__
);
278 exit( EXIT_FAILURE
);
284 NewMailAnimation( void )
286 if( animation_image
== MAILBOX_FULL
) {
287 DisplayOpenedFullMailbox();
288 animation_image
= MAILBOX_CLOSED
;
289 if( wmnotify_infos
.debug
) {
290 printf( "%s: NewMailAnimation() MAILBOX_FULL.\n", PACKAGE
);
294 DisplayClosedMailbox();
295 animation_image
= MAILBOX_FULL
;
296 if( wmnotify_infos
.debug
) {
297 printf( "%s: NewMailAnimation() MAILBOX_CLOSED.\n", PACKAGE
);
303 /* We display the opened mailbox image only when doing a manual check. */
305 CheckForNewMail( bool manual_check
)
309 if( manual_check
== true ) {
310 DisplayOpenedEmptyMailbox();
313 if( wmnotify_infos
.protocol
== POP3_PROTOCOL
) {
314 new_messages
= POP3_CheckForNewMail();
316 else if( wmnotify_infos
.protocol
== IMAP4_PROTOCOL
) {
317 new_messages
= IMAP4_CheckForNewMail();
320 ErrorLocation( __FILE__
, __LINE__
);
321 exit( EXIT_FAILURE
);
324 if( ( manual_check
== true ) && ( new_messages
> 0 ) ) {
325 animation_image
= MAILBOX_FULL
;
333 TimerThread( /*@unused@*/ void *arg
)
335 int new_messages
= 0;
337 bool animation_running
= false;
339 /* For catching the signal SIGUSR1. This signal is sent by the main program thread when the
340 * user is issuing a single-click to manually check for new mails. */
341 (void) signal( SIGUSR1
, CatchTimerSignal
);
343 /* For catching the signal SIGUSR2. This signal is sent by the main program thread when the
344 * user is issuing a double-click to start ther external mail client. */
345 (void) signal( SIGUSR2
, CatchTimerSignal
);
347 while( quit
== false ) {
348 if( wmnotify_infos
.debug
) {
349 printf( "%s: Timer thread iteration.\n", PACKAGE
);
351 if( ( manual_check
== true ) || ( counter
== 0 ) ) {
352 new_messages
= CheckForNewMail( manual_check
);
353 manual_check
= false;
355 if( wmnotify_infos
.debug
) {
356 printf( "%s: new messages = %d.\n", PACKAGE
, new_messages
);
359 if( new_messages
> 0 ) {
360 /* Checking if audio notification was already produced. */
361 if( animation_running
== false ) {
362 /* Audible notification, if requested in configuration file. */
363 if( wmnotify_infos
.audible_notification
!= false ) {
364 if( strlen( wmnotify_infos
.audiofile
) != 0 ) {
365 #if defined(HAVE_SNDFILE)
366 PlayAudioFile( wmnotify_infos
.audiofile
, wmnotify_infos
.volume
);
374 animation_running
= true;
376 /* Number of times to execute timer loop before checking again for new mails when the
377 * animation is running (when the animation is running, we sleep for
378 * NEW_MAIL_ANIMATION_DURATION instead of wmnotify_infos.mail_check_interval). We set
379 * the check interval to 30 seconds because we want the new mail condition to be
380 * removed as soon as possible when the new messages are checked. */
381 counter
= 30 * 1000000 / NEW_MAIL_ANIMATION_DURATION
;
385 if( ( animation_stop
== true ) || ( new_messages
<= 0 ) ) {
386 if( wmnotify_infos
.debug
) {
387 if( animation_stop
!= false ) {
388 printf( "%s: animation_stop is true\n", PACKAGE
);
391 animation_running
= false;
392 animation_stop
= false;
393 if( double_click_notif
== false ) {
394 /* Before exiting, be sure to put NO MAIL image back in place... */
395 DisplayClosedMailbox();
399 /* If sleep() returns because the requested time has elapsed, the value returned will be
400 * 0. If sleep() returns because of premature arousal due to delivery of a signal, the
401 * return value will be the "unslept" amount (the requested time minus the time actually
402 * slept) in seconds. */
404 if( animation_running
== false ) {
405 (void) sleep( wmnotify_infos
.mail_check_interval
);
410 (void) usleep( NEW_MAIL_ANIMATION_DURATION
);
414 if( wmnotify_infos
.debug
) {
415 printf( "%s: counter = %d\n", PACKAGE
, counter
);
419 if( wmnotify_infos
.debug
) {
420 printf( "%s: Error, TimerThread() exited abnormally\n", PACKAGE
);
423 /* This code is never reached for now, because quit is always false. */
424 pthread_exit( NULL
);
428 /*******************************************************************************
430 ******************************************************************************/
432 main( int argc
, char *argv
[] )
437 ParseCommandLineOptions( argc
, argv
);
439 /* Reading configuration options from configuration file. */
440 ConfigurationFileInit();
442 /* For catching the termination signal SIGCHLD when the external mail client
443 program is terminated, thus permitting removing zombi processes... */
444 (void) signal( SIGCHLD
, CatchChildTerminationSignal
);
446 /* Initialize callback function pointers. */
447 ProcessXlibEventsInit( SingleClick
, DoubleClick
);
449 /* Initializing and creating a DockApp window. */
450 InitDockAppWindow( argc
, argv
, wmnotify_xpm
, wmnotify_infos
.display_arg
,
451 wmnotify_infos
.geometry_arg
);
453 /* Starting thread for periodically checking for new mail. */
454 status
= pthread_create( &timer_thread
, NULL
, TimerThread
, NULL
);
456 fprintf( stderr
, "%s: Thread creation failed (%d)\n", PACKAGE
, status
);
457 ErrorLocation( __FILE__
, __LINE__
);
458 exit( EXIT_FAILURE
);
461 /* Main loop, processing X Events */
464 /* This code is never reached for now. */
465 fprintf( stderr
, "%s: Program exit\n", PACKAGE
);
467 exit( EXIT_SUCCESS
);