/* * mailnotify.c * * xmms plugin that checks for new mail and will soon be able * to play selected music relating to the sender of the new mail on arrival. * * get the latest version from http://iso.kapsobor.de/xmms/ * * iso:crash * * ----------------------------- */ #define MAX_ERRORS 5 #define MAX_ORIGINS 128 #define DEFAULT_INTERVAL 10 #define MAX_FOLDERS 64 #define DEFAULT_ALERT_FILE "/usr/X11R6/lib/xmms/General/alert.wav" #define DEFAULT_POPUP_COMMAND "xterm -e mutt -f %MB" #define CONTINUE_WHERE_INTERRUPTED 1 #define SIZE_TOLERANCE 100 /* #define DEBUG */ /* ---------------------------- */ #define VERSION "0.2.0" #include #include #include "xmmsctrl.h" #include "util.h" #include #include "configfile.h" #include #include #include #include #include #include #include #include #include #ifdef PWDINSYS # include #else # include #endif #ifndef ANSI_C extern struct passwd *getpwnam(), *getpwuid(); extern char *genenv(); #endif #ifdef I_TIME # include #endif #ifdef I_SYSTIME # include #endif #ifdef BSD # include #endif #ifdef I_UTIME # include #endif #ifdef I_SYSUTIME # include #else # include #endif gint all_folders = 0; long bytes(); gint isdir(); struct folder { char foldername[256]; char prefix[48]; long filesize; int xs_errors; gchar sender[256]; gchar command[256]; gboolean runcmd; gchar alertfile[256]; } folders[MAX_FOLDERS] = {{{0}}}; struct origin { gchar senderaddr[256]; gboolean runcmd; gchar command[256]; gchar alertfile[256]; } origins[MAX_ORIGINS] = {{{0}}}; /* #if !defined(UTIMBUF) struct utimbuf { time_t actime; time_t modtime; }; #endif */ FILE *fd = NULL; #if defined (BSD) && !defined(UTIMBUF) time_t utime_buffer[2]; #else struct utimbuf utime_buffer; #endif extern int errno; static GtkWidget *conf_dialog, *about_win; static pthread_t tid; static GtkWidget *mailboxname_entry, *alertfile_entry, *command_entry, *seconds_entry, *popup_check; void *mailnotify_thread(void *args); void mailnotify_init(void); void mailnotify_about(void); void mailnotify_config(void); void mailnotify_config_save(GtkWidget *wid, gpointer data); void mailnotify_cleanup(void); void get_default_folder(void); void new_mail_notification(); int read_sender(); void register_folders(void); void check_config(void); /* the configurable options */ static gchar *mailboxname = NULL; static gchar *alertfile = NULL; static gchar *popup_command = NULL; static gboolean popup_active; static gint check_seconds; static gboolean xfade_active; static gboolean origin_override = TRUE; static gint saved_position, saved_offset; gchar *about_text = "MailNofity "VERSION" checks for new mail and plays a selected file on arrival.\nIt will also run a configurable command if desired, e.g. launch your favorite mail client, i.e. mutt.\n\nmailnotify@kapsobor.de\nhttp://iso.kapsobor.de/xmms/"; /* ------------ */ GeneralPlugin mailnotify = { NULL, NULL, -1, "Mail Notify "VERSION, mailnotify_init, mailnotify_about, mailnotify_config, mailnotify_cleanup, }; void mailnotify_cleanup(void) { pthread_cancel (tid); } GeneralPlugin *get_gplugin_info(void) { return (&mailnotify); } void read_config (GtkWidget *wid, gpointer data) { gchar *oppi; ConfigFile *config; #ifdef DEBUG printf("reading configuration"); #endif if ((config = xmms_cfg_open_default_file()) != NULL) { xmms_cfg_read_string (config, "mailnotify", "mailboxname", &mailboxname); xmms_cfg_read_string (config, "mailnotify", "alertfile", &alertfile); xmms_cfg_read_boolean (config, "mailnotify", "popup_active", &popup_active); xmms_cfg_read_string (config, "mailnotify", "popup_command", &popup_command); xmms_cfg_read_int (config, "mailnotify", "check_seconds", &check_seconds); #ifdef DEBUG printf("looking for xfader plugin\n"); #endif /* with xfader plugin activated we have to do some workaround */ xmms_cfg_read_string (config, "xmms", "output_plugin", &oppi); } if(strstr(oppi, "libcrossfade.so") != NULL) xfade_active = TRUE; else xfade_active = FALSE; xmms_cfg_free(config); check_config(); } void mailnotify_init(void) { pthread_attr_t attr; #ifdef DEBUG printf("initialisation phase\n"); #endif read_config(NULL, NULL); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedpolicy(&attr, SCHED_OTHER); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&tid, &attr, mailnotify_thread, NULL); return; } void *mailnotify_thread(void *args) { gint i, finalsize; long lastsize, newsize; register struct folder *current_folder; register_folders(); /* warning: more folders option still missing here!! */ while(1) { /** mail check loop *****/ #ifdef DEBUG printf("Checking %d Folders, starting ..\n", all_folders); #endif for(i = 0; i < all_folders; i++) { current_folder = &folders[i]; #ifdef DEBUG printf("Checking Folder #%d ( %s )\n", i + 1, current_folder->foldername); #endif if((newsize = bytes(current_folder->foldername)) == current_folder->filesize) { #ifdef DEBUG printf("Everything is okay. No new Mail has arrived. Size remains %ld bytes.\n", current_folder->filesize); #endif continue; } if ((fd = fopen(current_folder->foldername,"r")) == NULL) { if(errno == EACCES) { current_folder->xs_errors++; if(current_folder->xs_errors > MAX_ERRORS) { printf("Fatal: %d errors checking %s: Cannot access file.",MAX_ERRORS,current_folder->foldername); } } continue; } if ((newsize = bytes(current_folder->foldername)) > current_folder->filesize) { /* new mail has arrived, SIZE_TOLERANCE is required as little changes (f.e. flag toggles) could probably change the filesize */ if( newsize > current_folder->filesize + SIZE_TOLERANCE) { #ifdef DEBUG printf("PANIC! New mail has arrived.\n"); #endif if (fseek(fd, current_folder->filesize, 0) != 0) { printf("Fatal: Could not fseek to %ld in %s", current_folder->filesize, current_folder->foldername); } else { /* check for sender of new mail */ read_sender(fd, current_folder); /* notify user by selected methods */ new_mail_notification(current_folder); } } current_folder->filesize=newsize; #ifdef DEBUG printf("set new filesize %ld", current_folder->filesize); #endif #if defined(BSD) && !defined(UTIMBUG) utime(current_folder->foldername, utime_buffer); #else utime(current_folder->foldername, &utime_buffer); #endif } else { current_folder->filesize = bytes(current_folder->foldername); lastsize = current_folder->filesize; finalsize = 0; while(! finalsize) { sleep(1); newsize = bytes(current_folder->foldername); if (newsize != lastsize) lastsize = newsize; else finalsize++; } current_folder->filesize=newsize; } fclose(fd); } sleep(DEFAULT_INTERVAL); /* end of loop *****/ } } /* the about window */ void mailnotify_about(void) { GtkWidget *button, *label, *bigbox, *buttonbox, *textframe; if(about_win) return; about_win = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_title(GTK_WINDOW(about_win), ("About")); gtk_container_set_border_width(GTK_CONTAINER(about_win), 15); gtk_window_set_policy(GTK_WINDOW(about_win), FALSE, FALSE, FALSE); gtk_window_set_position(GTK_WINDOW(about_win), GTK_WIN_POS_MOUSE); textframe = gtk_frame_new ("XMMS Mail Notify Plugin:"); bigbox = gtk_vbox_new(FALSE, 15); gtk_container_add(GTK_CONTAINER(about_win), bigbox); gtk_container_add(GTK_CONTAINER(bigbox), textframe); label = gtk_label_new(about_text); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); gtk_container_add(GTK_CONTAINER(textframe), label); buttonbox = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(buttonbox), GTK_BUTTONBOX_DEFAULT_STYLE); gtk_button_box_set_spacing(GTK_BUTTON_BOX(buttonbox), 5); gtk_box_pack_start(GTK_BOX(bigbox), buttonbox, FALSE, FALSE, 0); gtk_signal_connect(GTK_OBJECT(about_win), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &about_win); button = gtk_button_new_with_label("Check."); GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer) about_win); gtk_box_pack_start(GTK_BOX ((buttonbox)), button, FALSE, TRUE, 5); gtk_widget_show_all(about_win); } /* save config file */ void mailnotify_config_save(GtkWidget *wid, gpointer data) { ConfigFile *config; #ifdef DEBUG printf("saving configuration\n"); #endif if ((config = xmms_cfg_open_default_file()) == NULL) config = xmms_cfg_new(); mailboxname = g_strdup(gtk_entry_get_text(GTK_ENTRY(mailboxname_entry))); alertfile = g_strdup(gtk_entry_get_text(GTK_ENTRY(alertfile_entry))); popup_command = g_strdup(gtk_entry_get_text(GTK_ENTRY(command_entry))); popup_active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(popup_check)); check_seconds = atoi(gtk_entry_get_text(GTK_ENTRY(seconds_entry))); xmms_cfg_write_string (config, "mailnotify", "mailboxname", mailboxname); xmms_cfg_write_string (config, "mailnotify", "alertfile", alertfile); xmms_cfg_write_string (config, "mailnotify", "popup_command", popup_command); xmms_cfg_write_boolean(config, "mailnotify", "popup_active", popup_active); xmms_cfg_write_int (config, "mailnotify", "check_seconds", check_seconds); xmms_cfg_write_default_file (config); xmms_cfg_free(config); return; } /* the config-ok button action */ void mailnotify_config_ok(GtkWidget *wid, gpointer data) { mailnotify_config_save(NULL, NULL); gtk_widget_destroy(conf_dialog); conf_dialog = NULL; return; } /* the config window */ void mailnotify_config(void) { GtkWidget *ok_button, *apply_button, *cancel_button, *buttonbox, *atable, *seconds_label_2, *bigbox, *checkframe, *actionframe, *mailboxname_label, *ctable, *alertfile_label, *seconds_label, *command_label; gchar *csbuf; if (conf_dialog) return; conf_dialog = gtk_window_new(GTK_WINDOW_DIALOG); #ifdef DEBUG printf("configuration window coming up\n"); #endif /* configure window might popup before plugin is activated (and therefore before _init() is called */ read_config(NULL, NULL); gtk_window_set_title(GTK_WINDOW(conf_dialog), ("XMMS MailNotify Configuration")); gtk_window_set_policy(GTK_WINDOW(conf_dialog), FALSE, FALSE, FALSE); gtk_window_set_position(GTK_WINDOW(conf_dialog), GTK_WIN_POS_MOUSE); gtk_container_set_border_width(GTK_CONTAINER(conf_dialog), 15); gtk_signal_connect(GTK_OBJECT(conf_dialog), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), &conf_dialog); bigbox = gtk_vbox_new(FALSE, 5); gtk_container_add(GTK_CONTAINER (GTK_WINDOW(conf_dialog)), bigbox); checkframe = gtk_frame_new ("Check"); gtk_container_add(GTK_CONTAINER(bigbox), checkframe); ctable = gtk_table_new(2, 4, FALSE); gtk_container_add(GTK_CONTAINER(checkframe), ctable); actionframe = gtk_frame_new ("Action"); gtk_container_add(GTK_CONTAINER(bigbox), actionframe); atable = gtk_table_new(2, 3, FALSE); gtk_container_add(GTK_CONTAINER(actionframe), atable); mailboxname_label = gtk_label_new("Mailboxfile:"); gtk_label_set_justify(GTK_LABEL(mailboxname_label), GTK_JUSTIFY_RIGHT); gtk_table_attach_defaults(GTK_TABLE(ctable), mailboxname_label, 0, 1, 0, 1 ); alertfile_label = gtk_label_new("Alertfile:"); gtk_table_attach_defaults(GTK_TABLE(atable), alertfile_label , 0, 1, 0, 1 ); mailboxname_entry = gtk_entry_new(); alertfile_entry = gtk_entry_new(); if(mailboxname != NULL) gtk_entry_set_text(GTK_ENTRY(mailboxname_entry), mailboxname); if(alertfile != NULL) gtk_entry_set_text(GTK_ENTRY(alertfile_entry), alertfile); gtk_table_attach_defaults(GTK_TABLE(ctable), mailboxname_entry, 1, 4, 0, 1); gtk_table_attach_defaults(GTK_TABLE(atable), alertfile_entry, 1, 3, 0,1); seconds_label = gtk_label_new("Check every"); gtk_table_attach_defaults(GTK_TABLE(ctable), seconds_label, 0, 1, 1, 2); seconds_label_2 = gtk_label_new("seconds."); gtk_label_set_justify(GTK_LABEL(seconds_label_2), GTK_JUSTIFY_LEFT); gtk_table_attach_defaults(GTK_TABLE(ctable), seconds_label_2, 2, 4, 1, 2); popup_check = gtk_check_button_new(); if(popup_active) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(popup_check), TRUE); else gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(popup_check), FALSE); gtk_table_attach_defaults(GTK_TABLE(atable), popup_check, 0,1,1,2); command_label = gtk_label_new("run command:"); gtk_table_attach_defaults(GTK_TABLE(atable), command_label, 1, 2, 1, 2); seconds_entry = gtk_entry_new(); command_entry = gtk_entry_new(); gtk_widget_set_usize(seconds_entry, 10, -2); if(popup_command != NULL) gtk_entry_set_text(GTK_ENTRY(command_entry), popup_command); if(check_seconds) { csbuf = (char*) malloc(sizeof(gint) + 1); sprintf(csbuf, "%d", check_seconds); gtk_entry_set_text(GTK_ENTRY(seconds_entry), csbuf); } gtk_table_attach_defaults(GTK_TABLE(ctable), seconds_entry, 1, 2, 1, 2); gtk_table_attach_defaults(GTK_TABLE(atable), command_entry, 2, 3, 1, 2); buttonbox = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(buttonbox), GTK_BUTTONBOX_END); gtk_button_box_set_spacing(GTK_BUTTON_BOX(buttonbox), 5); gtk_box_pack_start(GTK_BOX(bigbox), buttonbox, FALSE, FALSE, 0); ok_button = gtk_button_new_with_label("Ok"); apply_button = gtk_button_new_with_label("Apply"); cancel_button = gtk_button_new_with_label("Cancel"); gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), (gpointer) conf_dialog); gtk_signal_connect_object(GTK_OBJECT(apply_button), "clicked", GTK_SIGNAL_FUNC (mailnotify_config_save), NULL); gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked", GTK_SIGNAL_FUNC (mailnotify_config_ok), NULL); GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(cancel_button, GTK_CAN_DEFAULT); GTK_WIDGET_SET_FLAGS(apply_button, GTK_CAN_DEFAULT); gtk_box_pack_start(GTK_BOX(buttonbox), ok_button, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(buttonbox), apply_button, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(buttonbox), cancel_button, TRUE, TRUE, 0); gtk_widget_show_all(conf_dialog); } /* look for the default mailfolder */ void get_default_folder(void) { gchar *incoming_folder, *user_name = ""; gchar buf[256]; struct passwd *pw; incoming_folder = (gchar *)getenv("MAIL"); if (incoming_folder == NULL || strlen(incoming_folder) < 1) { if((pw = getpwuid(geteuid())) == NULL) { #ifdef DEBUG printf("You are missing a passwd entry. Bailing out.\n"); #endif } else { user_name = pw->pw_name; } if (isdir("/var/mail", NULL)) { snprintf(buf, sizeof(buf), "/var/mail/%s", user_name); } else { snprintf(buf, sizeof(buf), "/var/spool/mail/%s", user_name); } mailboxname = g_strdup_printf("%s",buf); } else { mailboxname = incoming_folder; } } /* fsize */ long bytes(name) char *name; { int check = 1; extern int errno; struct stat buffer; if (stat(name, &buffer) != 0) { if (errno != 2) { printf("Error %d while fstat on %s", errno, name); } else check = 0; } #if defined(BSD) && !defined(UTIMBUF) utime_buffer[0] = buffer.st_atime; utime_buffer[1] = buffer.st_mtime; #else utime_buffer.actime = buffer.st_atime; utime_buffer.modtime = buffer.st_mtime; #endif return (check ? buffer.st_size : 0); } /* check dir */ int isdir(char *dir) { struct stat st; int result = FALSE; if (stat(dir, &st) == 0 && S_ISDIR(st.st_mode)) result = TRUE; return result; } /* * * * * * * * * * * execute the popup_command if desired and play the file, i.e. * add the file to the playlist, play the last position and * try to figure out when it might be over, afterwards skip * back to the previously played file and continue playing. */ void new_mail_notification(newmail_folder) register struct folder *newmail_folder; { gint curpos, afpos; static gboolean toggleshuffle = FALSE, wasplaying = TRUE; static gchar **list, *pcmd, *p, *argv[4] = {"/bin/sh", "-c", NULL, NULL}; pid_t pid; #ifdef DEBUG gchar *dummy; printf("Incoming mail from %s\n", newmail_folder->sender); #endif /* beschraenkter mist: jetzt geht das aber erstmal nur, wenn %mb am ende steht. autsch! */ /* list auch entsprechend nach newmail_folder->sender aussuchen!! */ list = g_malloc0((strlen(alertfile) + 4) * sizeof(gchar *)); pcmd = g_malloc0((strlen(mailboxname) + strlen(popup_command)) * sizeof(gchar *)); /* play file responding to newmail_folder->sender (missing!) */ list[0] = alertfile; /* popup if desired */ if((popup_active) && (popup_command != NULL)) { /* replace %MB in cmdstring w/ mailbox name */ strcpy(pcmd, popup_command); if((p = strstr(pcmd, "%MB")) != NULL) { strncpy(p, newmail_folder->foldername, strlen(newmail_folder->foldername)); } pcmd = g_strdup_printf("%s &", pcmd); #ifdef DEBUG printf("execute popup command '%s'\n",pcmd); #endif argv[2] = pcmd; pid = vfork(); if(pid == (pid_t) 0) { for(curpos = 3; curpos < 255; curpos++) close(curpos); execv("/bin/sh", argv); } #ifdef DEBUG else { printf("master control program still active, all done.\n"); } #endif } /* play alertfile if selected */ if(alertfile != NULL) { #ifdef DEBUG printf("play alert file '%s'\n", alertfile); #endif /* warning: crazy rumgetuedel ahead! */ if(xmms_remote_is_shuffle(mailnotify.xmms_session)) { xmms_remote_toggle_shuffle(mailnotify.xmms_session); toggleshuffle = TRUE; } saved_position = xmms_remote_get_playlist_pos(mailnotify.xmms_session); saved_offset = xmms_remote_get_output_time(mailnotify.xmms_session); afpos = xmms_remote_get_playlist_length(mailnotify.xmms_session); xmms_remote_playlist(mailnotify.xmms_session, list, 1, TRUE); if(xmms_remote_get_playlist_length(mailnotify.xmms_session) == (afpos + 1)) { #ifdef DEBUG printf("playing file %d", afpos); dummy = xmms_remote_get_playlist_file(mailnotify.xmms_session, afpos); printf("which is %s\n", dummy); #endif xmms_remote_set_playlist_pos(mailnotify.xmms_session, afpos); if(!xmms_remote_is_playing(mailnotify.xmms_session)) { wasplaying = FALSE; xmms_remote_play(mailnotify.xmms_session); } curpos = xmms_remote_get_playlist_pos(mailnotify.xmms_session); xmms_usleep(100000); /* if crossfade output plugin is activated we need to do some tricks here */ if(xfade_active) { printf("Warning! Xfade workaround not yet implemented!\nTurn off crossfade plugin or mailnotify wont work!\n"); /* implement xfade workaround here.. */ } do { xmms_usleep(10000); curpos = xmms_remote_get_playlist_pos(mailnotify.xmms_session); } while(curpos == afpos); #ifdef DEBUG printf("playing finished, deleting file from playlist\n"); printf("and resetting to %d", saved_position); dummy = xmms_remote_get_playlist_file(mailnotify.xmms_session, saved_position); printf(" which is %s\n", dummy); #endif xmms_remote_stop(mailnotify.xmms_session); xmms_remote_set_playlist_pos(mailnotify.xmms_session, saved_position); xmms_remote_playlist_delete(mailnotify.xmms_session, afpos); if(wasplaying) { xmms_remote_play(mailnotify.xmms_session); #ifdef CONTINUE_WHERE_INTERRUPTED xmms_remote_jump_to_time(mailnotify.xmms_session, saved_offset); #endif } } else { printf("Enqueuing %s failed.\n", list[0]); } if(toggleshuffle) xmms_remote_toggle_shuffle(mailnotify.xmms_session); } g_free(pcmd); g_free(list); } void register_folders(void) { /* currently there is only one mailbox supported.. */ strcpy(folders[0].foldername, mailboxname); fd = fopen(folders[0].foldername, "r"); folders[0].filesize = bytes(folders[0].foldername); #ifdef DEBUG printf("Folder %s is %ld bytes in size.\n", folders[0].foldername, folders[0].filesize); #endif all_folders = 1; if (fd != NULL) fclose(fd); } void check_config(void) { if(mailboxname == NULL) get_default_folder(); if(alertfile == NULL) alertfile = DEFAULT_ALERT_FILE; if(popup_command == NULL) popup_command = DEFAULT_POPUP_COMMAND; if(check_seconds <= 1) check_seconds = DEFAULT_INTERVAL; #ifdef DEBUG printf("Check Config:\n\tmailboxname = %s\n\talertfile = %s\n\tpopup_cmd = %s\n\tinterval = %d\n\txfade = %d\n", mailboxname, alertfile, popup_command, check_seconds, xfade_active); #endif } int read_sender(mailfile, nmfolder) FILE *mailfile; register struct folder *nmfolder; { register gchar *buffer; gchar *from_match = "From"; regmatch_t pmatch[5]; regex_t rbuf; buffer = g_malloc0(255 * sizeof(gchar *)); while(!feof(mailfile) && (strlen(nmfolder->sender) < 3)) { fgets(buffer, 255, mailfile); if(!strncmp(buffer, from_match, sizeof(from_match) )) { /** i like perl that much.. **/ if(regcomp(&rbuf, "[[:blank:]<]\\(.*@[[:alnum:][:punct:]]*\\)[[:blank:]>]",0)) { printf("Fatal: error compiling regular expression.\n"); } if(!regexec(&rbuf, buffer, 2,pmatch,0 )) { memcpy(nmfolder->sender, buffer + pmatch[1].rm_so, pmatch[1].rm_eo - pmatch[1].rm_so); break; } break; } } g_free(buffer); if(nmfolder->sender) return 1; else return 0; } /* end of file */