grim/guifications2

flow: Merged 'autotools_mingw_cleanup' to ('develop').
/*
* Guifications - The end all, be all, toaster popup plugin
* Copyright (C) 2003-2008 Gary Kramlich
*
* 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 Street, Fifth Floor, Boston, MA 02111-1301, USA.
*/
#include <glib.h>
#include <string.h>
#ifdef HAVE_CONFIG_H
# include "../gf_config.h"
#endif
#include "gf_internal.h"
#include <debug.h>
#include <account.h>
#include <blist.h>
#include <connection.h>
#include <conversation.h>
#include <notify.h>
#include <plugin.h>
#include <prefs.h>
#include <status.h>
#include <util.h>
#include <version.h>
#include "gf_blist.h"
#include "gf_display.h"
#include "gf_event.h"
#include "gf_notification.h"
#include "gf_preferences.h"
struct _GfEvent {
gchar *n_type;
gchar *name;
gchar *description;
GfEventPriority priority;
gchar *tokens;
gboolean show;
};
static GList *events = NULL;
void *(*real_notify_email)(PurpleConnection *gc, const char *subject, const char *from,
const char *to, const char *url);
void *(*real_notify_emails)(PurpleConnection *gc, size_t count, gboolean detailed,
const char **subject, const char **from,
const char **to, const char **url);
/*******************************************************************************
* API
******************************************************************************/
GfEvent *
gf_event_new(const gchar *notification_type, const gchar *tokens,
const gchar *name, const gchar *description,
GfEventPriority priority)
{
GfEvent *event;
g_return_val_if_fail(notification_type, NULL);
g_return_val_if_fail(name, NULL);
g_return_val_if_fail(description, NULL);
event = g_new0(GfEvent, 1);
event->priority = priority;
event->n_type = g_strdup(notification_type);
if(tokens)
event->tokens = g_strdup(tokens);
else
event->tokens = g_strdup(TOKENS_DEFAULT);
event->name = g_strdup(name);
event->description = g_strdup(description);
if(!g_list_find(events, event))
events = g_list_append(events, event);
else {
purple_debug_info("Guifications", "Event already exists\n");
gf_event_destroy(event);
}
return event;
}
GfEvent *
gf_event_find_for_notification(const gchar *type) {
GfEvent *event;
GList *l;
for(l = events; l; l = l->next) {
event = GF_EVENT(l->data);
if(!g_ascii_strcasecmp(event->n_type, type))
return event;
}
return NULL;
}
void
gf_event_destroy(GfEvent *event) {
g_return_if_fail(event);
events = g_list_remove(events, event);
g_free(event->n_type);
g_free(event->name);
g_free(event->description);
g_free(event->tokens);
g_free(event);
}
const gchar *
gf_event_get_notification_type(GfEvent *event) {
g_return_val_if_fail(event, NULL);
return event->n_type;
}
const gchar *
gf_event_get_tokens(GfEvent *event) {
g_return_val_if_fail(event, NULL);
return event->tokens;
}
const gchar *
gf_event_get_name(GfEvent *event) {
g_return_val_if_fail(event, NULL);
return event->name;
}
const gchar *
gf_event_get_description(GfEvent *event) {
g_return_val_if_fail(event, NULL);
return event->description;
}
GfEventPriority
gf_event_get_priority(GfEvent *event) {
g_return_val_if_fail(event, GF_EVENT_PRIORITY_NORMAL);
return event->priority;
}
void
gf_event_set_show(GfEvent *event, gboolean show) {
g_return_if_fail(event);
event->show = show;
}
gboolean
gf_event_get_show(GfEvent *event) {
g_return_val_if_fail(event, FALSE);
return event->show;
}
gboolean
gf_event_show_notification(const gchar *n_type) {
GfEvent *event;
g_return_val_if_fail(n_type, FALSE);
event = gf_event_find_for_notification(n_type);
if(event)
return event->show;
return FALSE;
}
const GList *gf_events_get() {
return events;
}
void
gf_events_save() {
GfEvent *event;
GList *l = NULL, *e;
for(e = events; e; e = e->next) {
event = GF_EVENT(e->data);
if(event->show)
l = g_list_append(l, event->n_type);
}
purple_prefs_set_string_list(GF_PREF_BEHAVIOR_NOTIFICATIONS, l);
g_list_free(l);
}
gint
gf_events_count() {
return g_list_length(events);
}
const gchar *
gf_events_get_nth_name(gint nth) {
GfEvent *event = g_list_nth_data(events, nth);
return event->name;
}
const gchar *
gf_events_get_nth_notification(gint nth) {
GfEvent *event = g_list_nth_data(events, nth);
return event->n_type;
}
/*******************************************************************************
* Signon flood be gone!
******************************************************************************/
static GList *accounts = NULL;
static gboolean
gf_event_connection_throttle_cb(gpointer data) {
PurpleAccount *account = (PurpleAccount *)data;
if(!account)
return FALSE;
if(!purple_account_get_connection(account)) {
accounts = g_list_remove(accounts, account);
return FALSE;
}
if(!purple_account_is_connected(account))
return TRUE;
accounts = g_list_remove(accounts, account);
return FALSE;
}
static void
gf_event_connection_throttle(PurpleConnection *gc, gpointer data) {
PurpleAccount *account;
if(!gc)
return;
account = purple_connection_get_account(gc);
if(!account)
return;
accounts = g_list_append(accounts, account);
g_timeout_add(10000, gf_event_connection_throttle_cb, (gpointer)account);
}
/*******************************************************************************
* Chat Join Flooding
******************************************************************************/
static GList *chats = NULL;
static gboolean
gf_event_conversation_throttle_cb(gpointer data) {
PurpleConversation *conv = (PurpleConversation *)data;
if(conv)
chats = g_list_remove(chats, conv);
return FALSE;
}
static void
gf_event_conversation_throttle(PurpleConversation *conv, gpointer data) {
if(!conv)
return;
if(purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT)
return;
chats = g_list_append(chats, conv);
g_timeout_add(5000, gf_event_conversation_throttle_cb, (gpointer)conv);
}
/*******************************************************************************
* Helpers
******************************************************************************/
/* first pass to see if a notification should be shown.. */
static gboolean
gf_event_should_show(const gchar *notification, PurpleAccount *account) {
if(gf_display_screen_saver_is_running())
return FALSE;
if(!purple_account_is_connected(account))
return FALSE;
if(g_list_find(accounts, account))
return FALSE;
if(!gf_event_show_notification(notification))
return FALSE;
if(!purple_prefs_get_bool(GF_PREF_BEHAVIOR_SHOW_WHILE_AWAY))
if(!purple_presence_is_available(account->presence))
return FALSE;
return TRUE;
}
/* second pass to see if a conversation notification should be shown */
static gboolean
gf_event_conversation_show(PurpleConversation *conv, const gchar *sender) {
if(!conv)
return FALSE;
if(purple_conversation_has_focus(conv))
return FALSE;
if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
if(g_list_find(chats, conv))
return FALSE;
if(sender) {
const gchar *nick;
nick = purple_conv_chat_get_nick(PURPLE_CONV_CHAT(conv));
if(!strcmp(nick, sender))
return FALSE;
}
}
return TRUE;
}
static void
gf_event_common(const gchar *n_type, PurpleAccount *account, PurpleBuddy *buddy,
PurpleConversation *conv, const gchar *target,
const gchar *message, PurpleConvChatBuddyFlags flags,
const GHashTable *components, const gchar *extra)
{
GfNotification *notification = NULL;
GfEventInfo *info = NULL;
g_return_if_fail(n_type);
g_return_if_fail(account);
if(!gf_event_should_show(n_type, account))
return;
if(conv && target) {
if(!gf_event_conversation_show(conv, target))
return;
}
if(buddy) {
notification = gf_blist_get_notification_for_buddy(buddy, n_type);
} else {
notification = gf_notification_find_for_event(n_type);
}
if(!notification)
return;
info = gf_event_info_new(n_type);
gf_event_info_set_account(info, account);
if(buddy)
gf_event_info_set_buddy(info, buddy);
if(conv)
gf_event_info_set_conversation(info, conv);
if(target)
gf_event_info_set_target(info, target);
if(message)
gf_event_info_set_message(info, message);
gf_event_info_set_conv_chat_buddy_flags(info, flags);
if(components)
gf_event_info_set_components(info, components);
if(extra)
gf_event_info_set_extra(info, extra);
gf_display_show_event(info, notification);
}
/*******************************************************************************
* Buddy callbacks
******************************************************************************/
static void
gf_event_buddy(PurpleBuddy *buddy, gpointer data) {
const gchar *notification = (const gchar *)data;
if(!gf_event_should_show(notification, buddy->account))
return;
gf_event_common(notification, buddy->account, buddy, NULL, buddy->name,
NULL, PURPLE_CBFLAGS_NONE, NULL, NULL);
}
static void
gf_event_buddy_status(PurpleBuddy *buddy, PurpleStatus *oldstatus,
PurpleStatus *newstatus, gpointer data)
{
const gchar *notification;
if (purple_status_is_available(oldstatus) &&
!purple_status_is_available(newstatus)) {
notification = "away";
} else if (!purple_status_is_available(oldstatus) &&
purple_status_is_available(newstatus)) {
notification = "back";
} else
return;
gf_event_common(notification, buddy->account, buddy, NULL, buddy->name,
NULL, PURPLE_CBFLAGS_NONE, NULL, NULL);
}
static void
gf_event_buddy_idle(PurpleBuddy *buddy, gboolean oldidle, gboolean newidle,
gpointer data)
{
const gchar *notification;
notification = (newidle) ? "idle" : "unidle";
gf_event_common(notification, buddy->account, buddy, NULL, buddy->name,
NULL, PURPLE_CBFLAGS_NONE, NULL, NULL);
}
/*******************************************************************************
* Conversation callbacks
******************************************************************************/
static void
gf_event_im_message(PurpleAccount *account, const gchar *sender,
const gchar *message, PurpleConversation *conv, gint flags,
gpointer data)
{
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
gchar *plain_message = NULL;
buddy = purple_find_buddy(account, sender);
plain_message = purple_markup_strip_html(message);
gf_event_common(notification, account, buddy, conv, sender, plain_message,
PURPLE_CBFLAGS_NONE, NULL, NULL);
g_free(plain_message);
}
static void
gf_event_chat_message(PurpleAccount *account, const gchar *sender,
const gchar *message, PurpleConversation *conv, gint flags,
gpointer data)
{
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
gchar *plain_message = NULL;
plain_message = purple_markup_strip_html(message);
buddy = purple_find_buddy(account, sender);
gf_event_common(notification, account, buddy, conv, sender, plain_message,
PURPLE_CBFLAGS_NONE, NULL, NULL);
g_free(plain_message);
}
static void
gf_event_chat_nick(PurpleAccount *account, const gchar *sender,
const gchar *message, PurpleConversation *conv, PurpleMessageFlags flags,
gpointer data)
{
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
gchar *plain_message = NULL;
const gchar *nick = NULL;
nick = purple_conv_chat_get_nick(PURPLE_CONV_CHAT(conv));
if (nick && !strcmp(sender, nick))
return;
if(!purple_utf8_has_word(message, nick))
return;
plain_message = purple_markup_strip_html(message);
buddy = purple_find_buddy(account, sender);
gf_event_common(notification, account, buddy, conv, sender, plain_message,
PURPLE_CBFLAGS_NONE, NULL, NULL);
g_free(plain_message);
}
static void
gf_event_typing(PurpleAccount *account, const char *name, gpointer data) {
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
PurpleConversation *conv;
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, account);
buddy = purple_find_buddy(account, name);
gf_event_common(notification, account, buddy, conv, name, NULL,
PURPLE_CBFLAGS_NONE, NULL, NULL);
}
static void
gf_event_chat_join(PurpleConversation *conv, const gchar *name,
PurpleConvChatBuddyFlags flags, gboolean *new_arrival,
gpointer data) {
PurpleAccount *account = NULL;
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
account = purple_conversation_get_account(conv);
buddy = purple_find_buddy(account, name);
gf_event_common(notification, account, buddy, conv, name, NULL, flags,
NULL, NULL);
}
static void
gf_event_chat_part(PurpleConversation *conv, const gchar *name,
const gchar *reason, gpointer data)
{
PurpleAccount *account = NULL;
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
gchar *plain_message = NULL;
account = purple_conversation_get_account(conv);
plain_message = (reason) ? purple_markup_strip_html(reason) : g_strdup("");
buddy = purple_find_buddy(account, name);
gf_event_common(notification, account, buddy, conv, name, plain_message,
PURPLE_CBFLAGS_NONE, NULL, NULL);
g_free(plain_message);
}
static gint
gf_event_chat_invite(PurpleAccount *account, const gchar *inviter,
const gchar *chat, const gchar *invite_message,
const GHashTable *components, gpointer data)
{
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
gchar *plain_message = NULL;
plain_message = (invite_message) ? purple_markup_strip_html(invite_message) :
g_strdup("");
buddy = purple_find_buddy(account, inviter);
gf_event_common(notification, account, buddy, NULL, inviter, plain_message,
PURPLE_CBFLAGS_NONE, components, chat);
g_free(plain_message);
return 0;
}
static void
gf_event_topic_changed(PurpleConversation *conv, const gchar *who,
const gchar *topic, gpointer data)
{
PurpleAccount *account = NULL;
PurpleBuddy *buddy = NULL;
const gchar *notification = (const gchar *)data;
gchar *plain_message = NULL;
account = purple_conversation_get_account(conv);
plain_message = (topic) ? purple_markup_strip_html(topic) : g_strdup("");
if(who)
buddy = purple_find_buddy(account, who);
gf_event_common(notification, account, buddy, conv, who, plain_message,
PURPLE_CBFLAGS_NONE, NULL, NULL);
g_free(plain_message);
}
/*******************************************************************************
* Mail Stuff
******************************************************************************/
static void *
gf_event_email(PurpleConnection *gc, const char *subject, const char *from,
const char *to, const char *url)
{
gf_event_common("new-email", gc->account,
purple_find_buddy(gc->account, from),
NULL, from, NULL, PURPLE_CBFLAGS_NONE, NULL, subject);
return real_notify_email(gc, subject, from, to, url);
}
static void *
gf_event_emails(PurpleConnection *gc, size_t count, gboolean detailed,
const char **subject, const char **from,
const char **to, const char **url)
{
if(count == 0) {
return NULL;
} else if(count == 1) {
if(subject && *subject) {
return gf_event_email(gc, *subject, *from, *to, *url);
}
} else {
gchar *c = g_strdup_printf("%d", (gint)count);
gf_event_common("new-emails", gc->account, NULL, NULL, *to, NULL,
PURPLE_CBFLAGS_NONE, NULL, c);
g_free(c);
}
return real_notify_emails(gc, count, detailed, subject, from, to, url);
}
static void
gf_event_email_init() {
g_return_if_fail(!real_notify_email);
PurpleNotifyUiOps *notify_ops_orig = purple_notify_get_ui_ops();
real_notify_email = notify_ops_orig->notify_email;
notify_ops_orig->notify_email = gf_event_email;
real_notify_emails = notify_ops_orig->notify_emails;
notify_ops_orig->notify_emails = gf_event_emails;
}
static void gf_event_email_uninit() {
g_return_if_fail(real_notify_email);
PurpleNotifyUiOps *notify_ops_orig = purple_notify_get_ui_ops();
notify_ops_orig->notify_email = real_notify_email;
notify_ops_orig->notify_emails = real_notify_emails;
}
/*******************************************************************************
* File transfer stuff
******************************************************************************/
static void
gf_event_file_recv_cancel(PurpleXfer *xfer, gpointer data)
{
const gchar *notification = data;
gf_event_common(notification, xfer->account, NULL, NULL, xfer->who,
NULL, PURPLE_CBFLAGS_NONE, NULL, xfer->local_filename);
}
/*******************************************************************************
* Subsystem
******************************************************************************/
void
gf_events_init(PurplePlugin *plugin) {
GList *l, *ll = NULL;
void *blist_handle = NULL, *conv_handle = NULL, *xfers_handle = NULL;
g_return_if_fail(plugin);
/* create all of our default events */
gf_event_new("sign-on", TOKENS_DEFAULT "n", _("Sign on"),
_("Displayed when a buddy comes online."),
GF_EVENT_PRIORITY_HIGHER);
gf_event_new("sign-off", TOKENS_DEFAULT "n", _("Sign off"),
_("Displayed when a buddy goes offline."),
GF_EVENT_PRIORITY_HIGHER);
gf_event_new("away", TOKENS_DEFAULT "n", _("Away"),
_("Displayed when a buddy goes away."),
GF_EVENT_PRIORITY_HIGH);
gf_event_new("back", TOKENS_DEFAULT "n", _("Back"),
_("Displayed when a buddy returns from away."),
GF_EVENT_PRIORITY_HIGH);
gf_event_new("idle", TOKENS_DEFAULT "n", _("Idle"),
_("Displayed when a buddy goes idle."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new("unidle", TOKENS_DEFAULT "n", _("Unidle"),
_("Displayed when a buddy returns from idle."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new("im-message", TOKENS_DEFAULT "Ccnr", _("IM message"),
_("Displayed when someone sends you a message."),
GF_EVENT_PRIORITY_HIGHEST);
gf_event_new("typing", TOKENS_DEFAULT "Ccnr", _("Typing"),
_("Displayed when someone is typing a message to you."),
GF_EVENT_PRIORITY_HIGHER);
gf_event_new("typing-stopped", TOKENS_DEFAULT "Ccnr", _("Stopped typing"),
_("Displayed when someone has stopped typing a message to you."),
GF_EVENT_PRIORITY_HIGHER);
gf_event_new("chat-message", TOKENS_DEFAULT "Ccnr", _("Chat message"),
_("Displayed when someone talks in a chat."),
GF_EVENT_PRIORITY_HIGHER);
gf_event_new("nick-highlight", TOKENS_DEFAULT "Ccnr", _("Name spoken"),
_("Displayed when someone says your nick in a chat"),
GF_EVENT_PRIORITY_HIGHEST);
gf_event_new("chat-join", TOKENS_DEFAULT "Ccnr", _("Join"),
_("Displayed when someone joins a chat."),
GF_EVENT_PRIORITY_LOW);
gf_event_new("chat-part", TOKENS_DEFAULT "Ccnr", _("Leave"),
_("Displayed when someone leaves a chat."),
GF_EVENT_PRIORITY_LOW);
gf_event_new("chat-invite", TOKENS_DEFAULT "Ccnr", _("Invited"),
_("Displayed when someone invites you to a chat."),
GF_EVENT_PRIORITY_HIGHEST);
gf_event_new("topic-changed", TOKENS_DEFAULT "Ccnr", _("Topic changed"),
_("Displayed when a topic is changed in a chat."),
GF_EVENT_PRIORITY_LOW);
gf_event_new("new-email", TOKENS_DEFAULT "c", _("Email"),
_("Displayed when you receive new email."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new("new-emails", TOKENS_DEFAULT "c", _("Emails"),
_("Displaed when you receive multiple new emails."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new(GF_NOTIFICATION_MASTER, TOKENS_DEFAULT "Ccnr",
_("Master"), _("Master notification for the theme editor."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new("file-remote-cancel", TOKENS_DEFAULT "nX", _("File receive cancelled"),
_("Displayed when the buddy cancels the file transfer."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new("file-recv-complete", TOKENS_DEFAULT "nX", _("File receive completed"),
_("Displayed when file transfer completes for a file you are receiving."),
GF_EVENT_PRIORITY_NORMAL);
gf_event_new("file-send-complete", TOKENS_DEFAULT "nX", _("File send completed"),
_("Displayed when file transfer completes for a file you are sending."),
GF_EVENT_PRIORITY_NORMAL);
/* add our string list pref */
for(l = events; l; l = l->next) {
GfEvent *event = GF_EVENT(l->data);
ll = g_list_append(ll, event->n_type);
}
purple_prefs_add_string_list(GF_PREF_BEHAVIOR_NOTIFICATIONS, ll);
g_list_free(ll);
/* now that the pref is created, set the events that are supposed to be displayed
* to allow displaying
*/
l = purple_prefs_get_string_list(GF_PREF_BEHAVIOR_NOTIFICATIONS);
for(ll = l; ll; ll = ll->next) {
gchar *event_name = (gchar *)ll->data;
if(event_name) {
GfEvent *event = gf_event_find_for_notification(event_name);
g_free(ll->data);
if(event)
event->show = TRUE;
}
}
g_list_free(l);
/* connect all of our signals */
blist_handle = purple_blist_get_handle();
conv_handle = purple_conversations_get_handle();
xfers_handle = purple_xfers_get_handle();
/* blist signals */
purple_signal_connect(blist_handle, "buddy-signed-on", plugin,
PURPLE_CALLBACK(gf_event_buddy), "sign-on");
purple_signal_connect(blist_handle, "buddy-signed-off", plugin,
PURPLE_CALLBACK(gf_event_buddy), "sign-off");
purple_signal_connect(blist_handle, "buddy-status-changed", plugin,
PURPLE_CALLBACK(gf_event_buddy_status), NULL);
purple_signal_connect(blist_handle, "buddy-idle-changed", plugin,
PURPLE_CALLBACK(gf_event_buddy_idle), NULL);
/* conversation signals */
purple_signal_connect(conv_handle, "received-im-msg", plugin,
PURPLE_CALLBACK(gf_event_im_message), "im-message");
purple_signal_connect(conv_handle, "received-chat-msg", plugin,
PURPLE_CALLBACK(gf_event_chat_message), "chat-message");
purple_signal_connect(conv_handle, "received-chat-msg", plugin,
PURPLE_CALLBACK(gf_event_chat_nick), "nick-highlight");
purple_signal_connect(conv_handle, "chat-buddy-joined", plugin,
PURPLE_CALLBACK(gf_event_chat_join), "chat-join");
purple_signal_connect(conv_handle, "chat-buddy-left", plugin,
PURPLE_CALLBACK(gf_event_chat_part), "chat-part");
purple_signal_connect(conv_handle, "chat-invited", plugin,
PURPLE_CALLBACK(gf_event_chat_invite), "chat-invite");
purple_signal_connect(conv_handle, "buddy-typing", plugin,
PURPLE_CALLBACK(gf_event_typing), "typing");
purple_signal_connect(conv_handle, "buddy-typing-stopped", plugin,
PURPLE_CALLBACK(gf_event_typing), "typed");
purple_signal_connect(conv_handle, "chat-topic-changed", plugin,
PURPLE_CALLBACK(gf_event_topic_changed), "topic-changed");
/* both of these are used just to not display the flood of guifications
* we'd get if we weren't throttling them. Makes it look much prettier
* im my opinion :)
*/
purple_signal_connect(purple_connections_get_handle(), "signed-on", plugin,
PURPLE_CALLBACK(gf_event_connection_throttle), NULL);
purple_signal_connect(conv_handle, "chat-joined", plugin,
PURPLE_CALLBACK(gf_event_conversation_throttle),
NULL);
/* mail stuff */
gf_event_email_init();
purple_signal_connect(xfers_handle, "file-recv-cancel", plugin,
PURPLE_CALLBACK(gf_event_file_recv_cancel),
"file-remote-cancel");
purple_signal_connect(xfers_handle, "file-recv-complete", plugin,
PURPLE_CALLBACK(gf_event_file_recv_cancel),
"file-recv-complete");
purple_signal_connect(xfers_handle, "file-send-complete", plugin,
PURPLE_CALLBACK(gf_event_file_recv_cancel),
"file-send-complete");
}
void
gf_events_uninit() {
GList *l = NULL;
gf_event_email_uninit();
for(l = events; l; l = l->next)
gf_event_destroy(GF_EVENT(l->data));
g_list_free(events);
events = NULL;
}