Mercurial > pidgin.yaz
diff libgaim/pounce.c @ 14192:60b1bc8dbf37
[gaim-migrate @ 16863]
Renamed 'core' to 'libgaim'
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Sat, 19 Aug 2006 01:50:10 +0000 |
parents | |
children | a6aad36ca735 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgaim/pounce.c Sat Aug 19 01:50:10 2006 +0000 @@ -0,0 +1,1136 @@ +/** + * @file pounce.c Buddy Pounce API + * @ingroup core + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "internal.h" +#include "conversation.h" +#include "debug.h" +#include "pounce.h" + +#include "debug.h" +#include "pounce.h" +#include "util.h" + +typedef struct +{ + GString *buffer; + + GaimPounce *pounce; + GaimPounceEvent events; + GaimPounceOption options; + + char *ui_name; + char *pouncee; + char *protocol_id; + char *event_type; + char *option_type; + char *action_name; + char *param_name; + char *account_name; + +} PounceParserData; + +typedef struct +{ + char *name; + + gboolean enabled; + + GHashTable *atts; + +} GaimPounceActionData; + +typedef struct +{ + char *ui; + GaimPounceCb cb; + void (*new_pounce)(GaimPounce *); + void (*free_pounce)(GaimPounce *); + +} GaimPounceHandler; + + +static GHashTable *pounce_handlers = NULL; +static GList *pounces = NULL; +static guint save_timer = 0; +static gboolean pounces_loaded = FALSE; + + +/********************************************************************* + * Private utility functions * + *********************************************************************/ + +static GaimPounceActionData * +find_action_data(const GaimPounce *pounce, const char *name) +{ + GaimPounceActionData *action; + + g_return_val_if_fail(pounce != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + action = g_hash_table_lookup(pounce->actions, name); + + return action; +} + +static void +free_action_data(gpointer data) +{ + GaimPounceActionData *action_data = data; + + g_free(action_data->name); + + g_hash_table_destroy(action_data->atts); + + g_free(action_data); +} + + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static void +action_parameter_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *name, *param_value; + xmlnode *node, *child; + + name = (const char *)key; + param_value = (const char *)value; + node = (xmlnode *)user_data; + + child = xmlnode_new_child(node, "param"); + xmlnode_set_attrib(child, "name", name); + xmlnode_insert_data(child, param_value, -1); +} + +static void +action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data) +{ + const char *action; + GaimPounceActionData *action_data; + xmlnode *node, *child; + + action = (const char *)key; + action_data = (GaimPounceActionData *)value; + node = (xmlnode *)user_data; + + if (!action_data->enabled) + return; + + child = xmlnode_new_child(node, "action"); + xmlnode_set_attrib(child, "type", action); + + g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child); +} + +static void +add_event_to_xmlnode(xmlnode *node, const char *type) +{ + xmlnode *child; + + child = xmlnode_new_child(node, "event"); + xmlnode_set_attrib(child, "type", type); +} + +static void +add_option_to_xmlnode(xmlnode *node, const char *type) +{ + xmlnode *child; + + child = xmlnode_new_child(node, "option"); + xmlnode_set_attrib(child, "type", type); +} + +static xmlnode * +pounce_to_xmlnode(GaimPounce *pounce) +{ + xmlnode *node, *child; + GaimAccount *pouncer; + GaimPounceEvent events; + GaimPounceOption options; + + pouncer = gaim_pounce_get_pouncer(pounce); + events = gaim_pounce_get_events(pounce); + options = gaim_pounce_get_options(pounce); + + node = xmlnode_new("pounce"); + xmlnode_set_attrib(node, "ui", pounce->ui_type); + + child = xmlnode_new_child(node, "account"); + xmlnode_set_attrib(child, "protocol", pouncer->protocol_id); + xmlnode_insert_data(child, gaim_account_get_username(pouncer), -1); + + child = xmlnode_new_child(node, "pouncee"); + xmlnode_insert_data(child, gaim_pounce_get_pouncee(pounce), -1); + + /* Write pounce options */ + child = xmlnode_new_child(node, "options"); + if (options & GAIM_POUNCE_OPTION_AWAY) + add_option_to_xmlnode(child, "on-away"); + + /* Write pounce events */ + child = xmlnode_new_child(node, "events"); + if (events & GAIM_POUNCE_SIGNON) + add_event_to_xmlnode(child, "sign-on"); + if (events & GAIM_POUNCE_SIGNOFF) + add_event_to_xmlnode(child, "sign-off"); + if (events & GAIM_POUNCE_AWAY) + add_event_to_xmlnode(child, "away"); + if (events & GAIM_POUNCE_AWAY_RETURN) + add_event_to_xmlnode(child, "return-from-away"); + if (events & GAIM_POUNCE_IDLE) + add_event_to_xmlnode(child, "idle"); + if (events & GAIM_POUNCE_IDLE_RETURN) + add_event_to_xmlnode(child, "return-from-idle"); + if (events & GAIM_POUNCE_TYPING) + add_event_to_xmlnode(child, "start-typing"); + if (events & GAIM_POUNCE_TYPED) + add_event_to_xmlnode(child, "typed"); + if (events & GAIM_POUNCE_TYPING_STOPPED) + add_event_to_xmlnode(child, "stop-typing"); + if (events & GAIM_POUNCE_MESSAGE_RECEIVED) + add_event_to_xmlnode(child, "message-received"); + + /* Write pounce actions */ + child = xmlnode_new_child(node, "actions"); + g_hash_table_foreach(pounce->actions, action_parameter_list_to_xmlnode, child); + + if (gaim_pounce_get_save(pounce)) + child = xmlnode_new_child(node, "save"); + + return node; +} + +static xmlnode * +pounces_to_xmlnode(void) +{ + xmlnode *node, *child; + GList *cur; + + node = xmlnode_new("pounces"); + xmlnode_set_attrib(node, "version", "1.0"); + + for (cur = gaim_pounces_get_all(); cur != NULL; cur = cur->next) + { + child = pounce_to_xmlnode(cur->data); + xmlnode_insert_child(node, child); + } + + return node; +} + +static void +sync_pounces(void) +{ + xmlnode *node; + char *data; + + if (!pounces_loaded) + { + gaim_debug_error("pounce", "Attempted to save buddy pounces before " + "they were read!\n"); + return; + } + + node = pounces_to_xmlnode(); + data = xmlnode_to_formatted_str(node, NULL); + gaim_util_write_data_to_file("pounces.xml", data, -1); + g_free(data); + xmlnode_free(node); +} + +static gboolean +save_cb(gpointer data) +{ + sync_pounces(); + save_timer = 0; + return FALSE; +} + +static void +schedule_pounces_save(void) +{ + if (save_timer == 0) + save_timer = gaim_timeout_add(5000, save_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static void +free_parser_data(gpointer user_data) +{ + PounceParserData *data = user_data; + + if (data->buffer != NULL) + g_string_free(data->buffer, TRUE); + + g_free(data->ui_name); + g_free(data->pouncee); + g_free(data->protocol_id); + g_free(data->event_type); + g_free(data->option_type); + g_free(data->action_name); + g_free(data->param_name); + g_free(data->account_name); + + g_free(data); +} + +static void +start_element_handler(GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, GError **error) +{ + PounceParserData *data = user_data; + GHashTable *atts; + int i; + + atts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + for (i = 0; attribute_names[i] != NULL; i++) { + g_hash_table_insert(atts, g_strdup(attribute_names[i]), + g_strdup(attribute_values[i])); + } + + if (data->buffer != NULL) { + g_string_free(data->buffer, TRUE); + data->buffer = NULL; + } + + if (!strcmp(element_name, "pounce")) { + const char *ui = g_hash_table_lookup(atts, "ui"); + + if (ui == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'ui' parameter for pounce!\n"); + } + else + data->ui_name = g_strdup(ui); + + data->events = 0; + } + else if (!strcmp(element_name, "account")) { + const char *protocol_id = g_hash_table_lookup(atts, "protocol"); + + if (protocol_id == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'protocol' parameter for account!\n"); + } + else + data->protocol_id = g_strdup(protocol_id); + } + else if (!strcmp(element_name, "option")) { + const char *type = g_hash_table_lookup(atts, "type"); + + if (type == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'type' parameter for option!\n"); + } + else + data->option_type = g_strdup(type); + } + else if (!strcmp(element_name, "event")) { + const char *type = g_hash_table_lookup(atts, "type"); + + if (type == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'type' parameter for event!\n"); + } + else + data->event_type = g_strdup(type); + } + else if (!strcmp(element_name, "action")) { + const char *type = g_hash_table_lookup(atts, "type"); + + if (type == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'type' parameter for action!\n"); + } + else + data->action_name = g_strdup(type); + } + else if (!strcmp(element_name, "param")) { + const char *param_name = g_hash_table_lookup(atts, "name"); + + if (param_name == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Unset 'name' parameter for param!\n"); + } + else + data->param_name = g_strdup(param_name); + } + + g_hash_table_destroy(atts); +} + +static void +end_element_handler(GMarkupParseContext *context, const gchar *element_name, + gpointer user_data, GError **error) +{ + PounceParserData *data = user_data; + gchar *buffer = NULL; + + if (data->buffer != NULL) { + buffer = g_string_free(data->buffer, FALSE); + data->buffer = NULL; + } + + if (!strcmp(element_name, "account")) { + g_free(data->account_name); + data->account_name = g_strdup(buffer); + } + else if (!strcmp(element_name, "pouncee")) { + g_free(data->pouncee); + data->pouncee = g_strdup(buffer); + } + else if (!strcmp(element_name, "option")) { + if (!strcmp(data->option_type, "on-away")) + data->options |= GAIM_POUNCE_OPTION_AWAY; + + g_free(data->option_type); + data->option_type = NULL; + } + else if (!strcmp(element_name, "event")) { + if (!strcmp(data->event_type, "sign-on")) + data->events |= GAIM_POUNCE_SIGNON; + else if (!strcmp(data->event_type, "sign-off")) + data->events |= GAIM_POUNCE_SIGNOFF; + else if (!strcmp(data->event_type, "away")) + data->events |= GAIM_POUNCE_AWAY; + else if (!strcmp(data->event_type, "return-from-away")) + data->events |= GAIM_POUNCE_AWAY_RETURN; + else if (!strcmp(data->event_type, "idle")) + data->events |= GAIM_POUNCE_IDLE; + else if (!strcmp(data->event_type, "return-from-idle")) + data->events |= GAIM_POUNCE_IDLE_RETURN; + else if (!strcmp(data->event_type, "start-typing")) + data->events |= GAIM_POUNCE_TYPING; + else if (!strcmp(data->event_type, "typed")) + data->events |= GAIM_POUNCE_TYPED; + else if (!strcmp(data->event_type, "stop-typing")) + data->events |= GAIM_POUNCE_TYPING_STOPPED; + else if (!strcmp(data->event_type, "message-received")) + data->events |= GAIM_POUNCE_MESSAGE_RECEIVED; + + g_free(data->event_type); + data->event_type = NULL; + } + else if (!strcmp(element_name, "action")) { + if (data->pounce != NULL) { + gaim_pounce_action_register(data->pounce, data->action_name); + gaim_pounce_action_set_enabled(data->pounce, data->action_name, TRUE); + } + + g_free(data->action_name); + data->action_name = NULL; + } + else if (!strcmp(element_name, "param")) { + if (data->pounce != NULL) { + gaim_pounce_action_set_attribute(data->pounce, data->action_name, + data->param_name, buffer); + } + + g_free(data->param_name); + data->param_name = NULL; + } + else if (!strcmp(element_name, "events")) { + GaimAccount *account; + + account = gaim_accounts_find(data->account_name, data->protocol_id); + + g_free(data->account_name); + g_free(data->protocol_id); + + data->account_name = NULL; + data->protocol_id = NULL; + + if (account == NULL) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Account for pounce not found!\n"); + /* + * This pounce has effectively been removed, so make + * sure that we save the changes to pounces.xml + */ + schedule_pounces_save(); + } + else { + gaim_debug(GAIM_DEBUG_INFO, "pounce", + "Creating pounce: %s, %s\n", data->ui_name, + data->pouncee); + + data->pounce = gaim_pounce_new(data->ui_name, account, + data->pouncee, data->events, + data->options); + } + + g_free(data->pouncee); + data->pouncee = NULL; + } + else if (!strcmp(element_name, "save")) { + if (data->pounce != NULL) + gaim_pounce_set_save(data->pounce, TRUE); + } + else if (!strcmp(element_name, "pounce")) { + data->pounce = NULL; + data->events = 0; + data->options = 0; + + g_free(data->ui_name); + g_free(data->pouncee); + g_free(data->protocol_id); + g_free(data->event_type); + g_free(data->option_type); + g_free(data->action_name); + g_free(data->param_name); + g_free(data->account_name); + + data->ui_name = NULL; + data->pounce = NULL; + data->protocol_id = NULL; + data->event_type = NULL; + data->option_type = NULL; + data->action_name = NULL; + data->param_name = NULL; + data->account_name = NULL; + } + + g_free(buffer); +} + +static void +text_handler(GMarkupParseContext *context, const gchar *text, + gsize text_len, gpointer user_data, GError **error) +{ + PounceParserData *data = user_data; + + if (data->buffer == NULL) + data->buffer = g_string_new_len(text, text_len); + else + g_string_append_len(data->buffer, text, text_len); +} + +static GMarkupParser pounces_parser = +{ + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +gboolean +gaim_pounces_load(void) +{ + gchar *filename = g_build_filename(gaim_user_dir(), "pounces.xml", NULL); + gchar *contents = NULL; + gsize length; + GMarkupParseContext *context; + GError *error = NULL; + PounceParserData *parser_data; + + if (filename == NULL) { + pounces_loaded = TRUE; + return FALSE; + } + + if (!g_file_get_contents(filename, &contents, &length, &error)) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", + "Error reading pounces: %s\n", error->message); + + g_free(filename); + g_error_free(error); + + pounces_loaded = TRUE; + return FALSE; + } + + parser_data = g_new0(PounceParserData, 1); + + context = g_markup_parse_context_new(&pounces_parser, 0, + parser_data, free_parser_data); + + if (!g_markup_parse_context_parse(context, contents, length, NULL)) { + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + + pounces_loaded = TRUE; + + return FALSE; + } + + if (!g_markup_parse_context_end_parse(context, NULL)) { + gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Error parsing %s\n", + filename); + + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + pounces_loaded = TRUE; + + return FALSE; + } + + g_markup_parse_context_free(context); + g_free(contents); + g_free(filename); + + pounces_loaded = TRUE; + + return TRUE; +} + + +GaimPounce * +gaim_pounce_new(const char *ui_type, GaimAccount *pouncer, + const char *pouncee, GaimPounceEvent event, + GaimPounceOption option) +{ + GaimPounce *pounce; + GaimPounceHandler *handler; + + g_return_val_if_fail(ui_type != NULL, NULL); + g_return_val_if_fail(pouncer != NULL, NULL); + g_return_val_if_fail(pouncee != NULL, NULL); + g_return_val_if_fail(event != 0, NULL); + + pounce = g_new0(GaimPounce, 1); + + pounce->ui_type = g_strdup(ui_type); + pounce->pouncer = pouncer; + pounce->pouncee = g_strdup(pouncee); + pounce->events = event; + pounce->options = option; + + pounce->actions = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, free_action_data); + + handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type); + + if (handler != NULL && handler->new_pounce != NULL) + handler->new_pounce(pounce); + + pounces = g_list_append(pounces, pounce); + + schedule_pounces_save(); + + return pounce; +} + +void +gaim_pounce_destroy(GaimPounce *pounce) +{ + GaimPounceHandler *handler; + + g_return_if_fail(pounce != NULL); + + handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type); + + pounces = g_list_remove(pounces, pounce); + + g_free(pounce->ui_type); + g_free(pounce->pouncee); + + g_hash_table_destroy(pounce->actions); + + if (handler != NULL && handler->free_pounce != NULL) + handler->free_pounce(pounce); + + g_free(pounce); + + schedule_pounces_save(); +} + +void +gaim_pounce_destroy_all_by_account(GaimAccount *account) +{ + GaimAccount *pouncer; + GaimPounce *pounce; + GList *l, *l_next; + + g_return_if_fail(account != NULL); + + for (l = gaim_pounces_get_all(); l != NULL; l = l_next) + { + pounce = (GaimPounce *)l->data; + l_next = l->next; + + pouncer = gaim_pounce_get_pouncer(pounce); + if (pouncer == account) + gaim_pounce_destroy(pounce); + } +} + +void +gaim_pounce_set_events(GaimPounce *pounce, GaimPounceEvent events) +{ + g_return_if_fail(pounce != NULL); + g_return_if_fail(events != GAIM_POUNCE_NONE); + + pounce->events = events; + + schedule_pounces_save(); +} + +void +gaim_pounce_set_options(GaimPounce *pounce, GaimPounceOption options) +{ + g_return_if_fail(pounce != NULL); + + pounce->options = options; + + schedule_pounces_save(); +} + +void +gaim_pounce_set_pouncer(GaimPounce *pounce, GaimAccount *pouncer) +{ + g_return_if_fail(pounce != NULL); + g_return_if_fail(pouncer != NULL); + + pounce->pouncer = pouncer; + + schedule_pounces_save(); +} + +void +gaim_pounce_set_pouncee(GaimPounce *pounce, const char *pouncee) +{ + g_return_if_fail(pounce != NULL); + g_return_if_fail(pouncee != NULL); + + g_free(pounce->pouncee); + pounce->pouncee = g_strdup(pouncee); + + schedule_pounces_save(); +} + +void +gaim_pounce_set_save(GaimPounce *pounce, gboolean save) +{ + g_return_if_fail(pounce != NULL); + + pounce->save = save; + + schedule_pounces_save(); +} + +void +gaim_pounce_action_register(GaimPounce *pounce, const char *name) +{ + GaimPounceActionData *action_data; + + g_return_if_fail(pounce != NULL); + g_return_if_fail(name != NULL); + + if (g_hash_table_lookup(pounce->actions, name) != NULL) + return; + + action_data = g_new0(GaimPounceActionData, 1); + + action_data->name = g_strdup(name); + action_data->enabled = FALSE; + action_data->atts = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_insert(pounce->actions, g_strdup(name), action_data); + + schedule_pounces_save(); +} + +void +gaim_pounce_action_set_enabled(GaimPounce *pounce, const char *action, + gboolean enabled) +{ + GaimPounceActionData *action_data; + + g_return_if_fail(pounce != NULL); + g_return_if_fail(action != NULL); + + action_data = find_action_data(pounce, action); + + g_return_if_fail(action_data != NULL); + + action_data->enabled = enabled; + + schedule_pounces_save(); +} + +void +gaim_pounce_action_set_attribute(GaimPounce *pounce, const char *action, + const char *attr, const char *value) +{ + GaimPounceActionData *action_data; + + g_return_if_fail(pounce != NULL); + g_return_if_fail(action != NULL); + g_return_if_fail(attr != NULL); + + action_data = find_action_data(pounce, action); + + g_return_if_fail(action_data != NULL); + + if (value == NULL) + g_hash_table_remove(action_data->atts, attr); + else + g_hash_table_insert(action_data->atts, g_strdup(attr), + g_strdup(value)); + + schedule_pounces_save(); +} + +void +gaim_pounce_set_data(GaimPounce *pounce, void *data) +{ + g_return_if_fail(pounce != NULL); + + pounce->data = data; + + schedule_pounces_save(); +} + +GaimPounceEvent +gaim_pounce_get_events(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, GAIM_POUNCE_NONE); + + return pounce->events; +} + +GaimPounceOption +gaim_pounce_get_options(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, GAIM_POUNCE_OPTION_NONE); + + return pounce->options; +} + +GaimAccount * +gaim_pounce_get_pouncer(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, NULL); + + return pounce->pouncer; +} + +const char * +gaim_pounce_get_pouncee(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, NULL); + + return pounce->pouncee; +} + +gboolean +gaim_pounce_get_save(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, FALSE); + + return pounce->save; +} + +gboolean +gaim_pounce_action_is_enabled(const GaimPounce *pounce, const char *action) +{ + GaimPounceActionData *action_data; + + g_return_val_if_fail(pounce != NULL, FALSE); + g_return_val_if_fail(action != NULL, FALSE); + + action_data = find_action_data(pounce, action); + + g_return_val_if_fail(action_data != NULL, FALSE); + + return action_data->enabled; +} + +const char * +gaim_pounce_action_get_attribute(const GaimPounce *pounce, + const char *action, const char *attr) +{ + GaimPounceActionData *action_data; + + g_return_val_if_fail(pounce != NULL, NULL); + g_return_val_if_fail(action != NULL, NULL); + g_return_val_if_fail(attr != NULL, NULL); + + action_data = find_action_data(pounce, action); + + g_return_val_if_fail(action_data != NULL, NULL); + + return g_hash_table_lookup(action_data->atts, attr); +} + +void * +gaim_pounce_get_data(const GaimPounce *pounce) +{ + g_return_val_if_fail(pounce != NULL, NULL); + + return pounce->data; +} + +void +gaim_pounce_execute(const GaimAccount *pouncer, const char *pouncee, + GaimPounceEvent events) +{ + GaimPounce *pounce; + GaimPounceHandler *handler; + GaimPresence *presence; + GList *l, *l_next; + char *norm_pouncee; + + g_return_if_fail(pouncer != NULL); + g_return_if_fail(pouncee != NULL); + g_return_if_fail(events != GAIM_POUNCE_NONE); + + norm_pouncee = g_strdup(gaim_normalize(pouncer, pouncee)); + + for (l = gaim_pounces_get_all(); l != NULL; l = l_next) + { + pounce = (GaimPounce *)l->data; + l_next = l->next; + + presence = gaim_account_get_presence(pouncer); + + if ((gaim_pounce_get_events(pounce) & events) && + (gaim_pounce_get_pouncer(pounce) == pouncer) && + !gaim_utf8_strcasecmp(gaim_normalize(pouncer, gaim_pounce_get_pouncee(pounce)), + norm_pouncee) && + (pounce->options == GAIM_POUNCE_OPTION_NONE || + (pounce->options & GAIM_POUNCE_OPTION_AWAY && + !gaim_presence_is_available(presence)))) + { + handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type); + + if (handler != NULL && handler->cb != NULL) + { + handler->cb(pounce, events, gaim_pounce_get_data(pounce)); + + if (!gaim_pounce_get_save(pounce)) + gaim_pounce_destroy(pounce); + } + } + } + + g_free(norm_pouncee); +} + +GaimPounce * +gaim_find_pounce(const GaimAccount *pouncer, const char *pouncee, + GaimPounceEvent events) +{ + GaimPounce *pounce = NULL; + GList *l; + char *norm_pouncee; + + g_return_val_if_fail(pouncer != NULL, NULL); + g_return_val_if_fail(pouncee != NULL, NULL); + g_return_val_if_fail(events != GAIM_POUNCE_NONE, NULL); + + norm_pouncee = g_strdup(gaim_normalize(pouncer, pouncee)); + + for (l = gaim_pounces_get_all(); l != NULL; l = l->next) + { + pounce = (GaimPounce *)l->data; + + if ((gaim_pounce_get_events(pounce) & events) && + (gaim_pounce_get_pouncer(pounce) == pouncer) && + !gaim_utf8_strcasecmp(gaim_normalize(pouncer, gaim_pounce_get_pouncee(pounce)), + norm_pouncee)) + { + break; + } + + pounce = NULL; + } + + g_free(norm_pouncee); + + return pounce; +} + +void +gaim_pounces_register_handler(const char *ui, GaimPounceCb cb, + void (*new_pounce)(GaimPounce *pounce), + void (*free_pounce)(GaimPounce *pounce)) +{ + GaimPounceHandler *handler; + + g_return_if_fail(ui != NULL); + g_return_if_fail(cb != NULL); + + handler = g_new0(GaimPounceHandler, 1); + + handler->ui = g_strdup(ui); + handler->cb = cb; + handler->new_pounce = new_pounce; + handler->free_pounce = free_pounce; + + g_hash_table_insert(pounce_handlers, g_strdup(ui), handler); +} + +void +gaim_pounces_unregister_handler(const char *ui) +{ + g_return_if_fail(ui != NULL); + + g_hash_table_remove(pounce_handlers, ui); +} + +GList * +gaim_pounces_get_all(void) +{ + return pounces; +} + +static void +free_pounce_handler(gpointer user_data) +{ + GaimPounceHandler *handler = (GaimPounceHandler *)user_data; + + g_free(handler->ui); + g_free(handler); +} + +static void +buddy_state_cb(GaimBuddy *buddy, GaimPounceEvent event) +{ + gaim_pounce_execute(buddy->account, buddy->name, event); +} + +static void +buddy_status_changed_cb(GaimBuddy *buddy, GaimStatus *old_status, + GaimStatus *status) +{ + gboolean old_available, available; + + available = gaim_status_is_available(status); + old_available = gaim_status_is_available(old_status); + + if (available && !old_available) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_AWAY_RETURN); + else if (!available && old_available) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_AWAY); +} + +static void +buddy_idle_changed_cb(GaimBuddy *buddy, gboolean old_idle, gboolean idle) +{ + if (idle && !old_idle) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_IDLE); + else if (!idle && old_idle) + gaim_pounce_execute(buddy->account, buddy->name, + GAIM_POUNCE_IDLE_RETURN); +} + +static void +buddy_typing_cb(GaimAccount *account, const char *name, void *data) +{ + GaimConversation *conv; + + conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, name, account); + if (conv != NULL) + { + GaimTypingState state; + GaimPounceEvent event; + + state = gaim_conv_im_get_typing_state(GAIM_CONV_IM(conv)); + if (state == GAIM_TYPED) + event = GAIM_POUNCE_TYPED; + else if (state == GAIM_NOT_TYPING) + event = GAIM_POUNCE_TYPING_STOPPED; + else + event = GAIM_POUNCE_TYPING; + + gaim_pounce_execute(account, name, event); + } +} + +static void +received_message_cb(GaimAccount *account, const char *name, void *data) +{ + gaim_pounce_execute(account, name, GAIM_POUNCE_MESSAGE_RECEIVED); +} + +void * +gaim_pounces_get_handle(void) +{ + static int pounce_handle; + + return &pounce_handle; +} + +void +gaim_pounces_init(void) +{ + void *handle = gaim_pounces_get_handle(); + void *blist_handle = gaim_blist_get_handle(); + void *conv_handle = gaim_conversations_get_handle(); + + pounce_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, free_pounce_handler); + + gaim_signal_connect(blist_handle, "buddy-idle-changed", + handle, GAIM_CALLBACK(buddy_idle_changed_cb), NULL); + gaim_signal_connect(blist_handle, "buddy-status-changed", + handle, GAIM_CALLBACK(buddy_status_changed_cb), NULL); + gaim_signal_connect(blist_handle, "buddy-signed-on", + handle, GAIM_CALLBACK(buddy_state_cb), + GINT_TO_POINTER(GAIM_POUNCE_SIGNON)); + gaim_signal_connect(blist_handle, "buddy-signed-off", + handle, GAIM_CALLBACK(buddy_state_cb), + GINT_TO_POINTER(GAIM_POUNCE_SIGNOFF)); + + gaim_signal_connect(conv_handle, "buddy-typing", + handle, GAIM_CALLBACK(buddy_typing_cb), NULL); + gaim_signal_connect(conv_handle, "buddy-typed", + handle, GAIM_CALLBACK(buddy_typing_cb), NULL); + gaim_signal_connect(conv_handle, "buddy-typing-stopped", + handle, GAIM_CALLBACK(buddy_typing_cb), NULL); + + gaim_signal_connect(conv_handle, "received-im-msg", + handle, GAIM_CALLBACK(received_message_cb), NULL); +} + +void +gaim_pounces_uninit() +{ + if (save_timer != 0) + { + gaim_timeout_remove(save_timer); + save_timer = 0; + sync_pounces(); + } + + gaim_signals_disconnect_by_handle(gaim_pounces_get_handle()); +}