* @file pounce.c Buddy Pounce API * 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 * 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 "conversation.h" GaimPounceOption options; void (*new_pounce)(GaimPounce *); void (*free_pounce)(GaimPounce *); 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); free_action_data(gpointer data) GaimPounceActionData *action_data = data; g_free(action_data->name); g_hash_table_destroy(action_data->atts); /********************************************************************* *********************************************************************/ action_parameter_to_xmlnode(gpointer key, gpointer value, gpointer user_data) const char *name, *param_value; 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); action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data) GaimPounceActionData *action_data; action = (const char *)key; action_data = (GaimPounceActionData *)value; node = (xmlnode *)user_data; if (!action_data->enabled) child = xmlnode_new_child(node, "action"); xmlnode_set_attrib(child, "type", action); g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child); add_event_to_xmlnode(xmlnode *node, const char *type) child = xmlnode_new_child(node, "event"); xmlnode_set_attrib(child, "type", type); add_option_to_xmlnode(xmlnode *node, const char *type) child = xmlnode_new_child(node, "option"); xmlnode_set_attrib(child, "type", type); pounce_to_xmlnode(GaimPounce *pounce) 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_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"); 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); gaim_debug_error("pounce", "Attempted to save buddy pounces before " node = pounces_to_xmlnode(); data = xmlnode_to_formatted_str(node, NULL); gaim_util_write_data_to_file("pounces.xml", data, -1); schedule_pounces_save(void) save_timer = gaim_timeout_add(5000, save_cb, NULL); /********************************************************************* *********************************************************************/ free_parser_data(gpointer user_data) PounceParserData *data = user_data; if (data->buffer != NULL) g_string_free(data->buffer, TRUE); 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); 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; 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); if (!strcmp(element_name, "pounce")) { const char *ui = g_hash_table_lookup(atts, "ui"); gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Unset 'ui' parameter for pounce!\n"); data->ui_name = g_strdup(ui); 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"); data->protocol_id = g_strdup(protocol_id); else if (!strcmp(element_name, "option")) { const char *type = g_hash_table_lookup(atts, "type"); gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Unset 'type' parameter for option!\n"); data->option_type = g_strdup(type); else if (!strcmp(element_name, "event")) { const char *type = g_hash_table_lookup(atts, "type"); gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Unset 'type' parameter for event!\n"); data->event_type = g_strdup(type); else if (!strcmp(element_name, "action")) { const char *type = g_hash_table_lookup(atts, "type"); gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Unset 'type' parameter for action!\n"); 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"); data->param_name = g_strdup(param_name); g_hash_table_destroy(atts); end_element_handler(GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error) PounceParserData *data = user_data; if (data->buffer != NULL) { buffer = g_string_free(data->buffer, FALSE); if (!strcmp(element_name, "account")) { g_free(data->account_name); data->account_name = g_strdup(buffer); else if (!strcmp(element_name, "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, "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); 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); else if (!strcmp(element_name, "events")) { 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; 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 gaim_debug(GAIM_DEBUG_INFO, "pounce", "Creating pounce: %s, %s\n", data->ui_name, data->pounce = gaim_pounce_new(data->ui_name, account, data->pouncee, data->events, else if (!strcmp(element_name, "save")) { if (data->pounce != NULL) gaim_pounce_set_save(data->pounce, TRUE); else if (!strcmp(element_name, "pounce")) { 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->protocol_id = NULL; data->option_type = NULL; data->action_name = NULL; data->account_name = NULL; 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); g_string_append_len(data->buffer, text, text_len); static GMarkupParser pounces_parser = gchar *filename = g_build_filename(gaim_user_dir(), "pounces.xml", NULL); GMarkupParseContext *context; PounceParserData *parser_data; if (!g_file_get_contents(filename, &contents, &length, &error)) { gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Error reading pounces: %s\n", error->message); 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); if (!g_markup_parse_context_end_parse(context, NULL)) { gaim_debug(GAIM_DEBUG_ERROR, "pounce", "Error parsing %s\n", g_markup_parse_context_free(context); g_markup_parse_context_free(context); gaim_pounce_new(const char *ui_type, GaimAccount *pouncer, const char *pouncee, GaimPounceEvent event, 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->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); 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_hash_table_destroy(pounce->actions); if (handler != NULL && handler->free_pounce != NULL) handler->free_pounce(pounce); gaim_pounce_destroy_all_by_account(GaimAccount *account) g_return_if_fail(account != NULL); for (l = gaim_pounces_get_all(); l != NULL; l = l_next) pounce = (GaimPounce *)l->data; pouncer = gaim_pounce_get_pouncer(pounce); gaim_pounce_destroy(pounce); gaim_pounce_set_events(GaimPounce *pounce, GaimPounceEvent events) g_return_if_fail(pounce != NULL); g_return_if_fail(events != GAIM_POUNCE_NONE); gaim_pounce_set_options(GaimPounce *pounce, GaimPounceOption options) g_return_if_fail(pounce != NULL); pounce->options = options; gaim_pounce_set_pouncer(GaimPounce *pounce, GaimAccount *pouncer) g_return_if_fail(pounce != NULL); g_return_if_fail(pouncer != NULL); pounce->pouncer = pouncer; gaim_pounce_set_pouncee(GaimPounce *pounce, const char *pouncee) g_return_if_fail(pounce != NULL); g_return_if_fail(pouncee != NULL); pounce->pouncee = (pouncee == NULL ? NULL : g_strdup(pouncee)); gaim_pounce_set_save(GaimPounce *pounce, gboolean save) g_return_if_fail(pounce != NULL); 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) 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_hash_table_insert(pounce->actions, g_strdup(name), action_data); gaim_pounce_action_set_enabled(GaimPounce *pounce, const char *action, 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; 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); g_hash_table_remove(action_data->atts, attr); g_hash_table_insert(action_data->atts, g_strdup(attr), gaim_pounce_set_data(GaimPounce *pounce, void *data) g_return_if_fail(pounce != NULL); gaim_pounce_get_events(const GaimPounce *pounce) g_return_val_if_fail(pounce != NULL, GAIM_POUNCE_NONE); gaim_pounce_get_options(const GaimPounce *pounce) g_return_val_if_fail(pounce != NULL, GAIM_POUNCE_OPTION_NONE); gaim_pounce_get_pouncer(const GaimPounce *pounce) g_return_val_if_fail(pounce != NULL, NULL); gaim_pounce_get_pouncee(const GaimPounce *pounce) g_return_val_if_fail(pounce != NULL, NULL); gaim_pounce_get_save(const GaimPounce *pounce) g_return_val_if_fail(pounce != NULL, FALSE); 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; 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); gaim_pounce_get_data(const GaimPounce *pounce) g_return_val_if_fail(pounce != NULL, NULL); gaim_pounce_execute(const GaimAccount *pouncer, const char *pouncee, GaimPounceHandler *handler; 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; 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)), (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); gaim_find_pounce(const GaimAccount *pouncer, const char *pouncee, GaimPounce *pounce = NULL; 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)), 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->new_pounce = new_pounce; handler->free_pounce = free_pounce; g_hash_table_insert(pounce_handlers, g_strdup(ui), handler); gaim_pounces_unregister_handler(const char *ui) g_return_if_fail(ui != NULL); g_hash_table_remove(pounce_handlers, ui); gaim_pounces_get_all(void) free_pounce_handler(gpointer user_data) GaimPounceHandler *handler = (GaimPounceHandler *)user_data; buddy_state_cb(GaimBuddy *buddy, GaimPounceEvent event) gaim_pounce_execute(buddy->account, buddy->name, event); buddy_status_changed_cb(GaimBuddy *buddy, GaimStatus *old_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, buddy_idle_changed_cb(GaimBuddy *buddy, gboolean old_idle, gboolean idle) gaim_pounce_execute(buddy->account, buddy->name, else if (!idle && old_idle) gaim_pounce_execute(buddy->account, buddy->name, GAIM_POUNCE_IDLE_RETURN); buddy_typing_cb(GaimAccount *account, const char *name, void *data) conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, name, account); event = (gaim_conv_im_get_typing_state(GAIM_CONV_IM(conv)) == GAIM_TYPING ? GAIM_POUNCE_TYPING : GAIM_POUNCE_TYPING_STOPPED); gaim_pounce_execute(account, name, event); received_message_cb(GaimAccount *account, const char *name, void *data) gaim_pounce_execute(account, name, GAIM_POUNCE_MESSAGE_RECEIVED); gaim_pounces_get_handle(void) static int pounce_handle; 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-typing-stopped", handle, GAIM_CALLBACK(buddy_typing_cb), NULL); gaim_signal_connect(conv_handle, "received-im-msg", handle, GAIM_CALLBACK(received_message_cb), NULL); gaim_timeout_remove(save_timer); gaim_signals_disconnect_by_handle(gaim_pounces_get_handle());