pidgin/pidgin

closing merged branch
jingle-reply-with-senders-both
2019-11-11, Gary Kramlich
2f28c8df6574
closing merged branch
/* purple
*
* Purple 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "internal.h"
#include "conversation.h"
#include "debug.h"
#include "pounce.h"
#include "debug.h"
#include "pounce.h"
#include "util.h"
/*
* A buddy pounce structure.
*
* Buddy pounces are actions triggered by a buddy-related event. For
* example, a sound can be played or an IM window opened when a buddy
* signs on or returns from away. Such responses are handled in the
* UI. The events themselves are done in the core.
*/
struct _PurplePounce
{
char *ui_type; /* The type of UI. */
PurplePounceEvent events; /* The event(s) to pounce on. */
PurplePounceOption options; /* The pounce options */
PurpleAccount *pouncer; /* The user who is pouncing. */
char *pouncee; /* The buddy to pounce on. */
GHashTable *actions; /* The registered actions. */
gboolean save; /* Whether or not the pounce should
be saved after activation. */
void *data; /* Pounce-specific data. */
};
typedef struct
{
GString *buffer;
PurplePounce *pounce;
PurplePounceEvent events;
PurplePounceOption 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;
} PurplePounceActionData;
typedef struct
{
char *ui;
PurplePounceCb cb;
void (*new_pounce)(PurplePounce *);
void (*free_pounce)(PurplePounce *);
} PurplePounceHandler;
static GHashTable *pounce_handlers = NULL;
static GList *pounces = NULL;
static guint save_timer = 0;
static gboolean pounces_loaded = FALSE;
/*********************************************************************
* Private utility functions *
*********************************************************************/
static PurplePounceActionData *
find_action_data(const PurplePounce *pounce, const char *name)
{
PurplePounceActionData *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)
{
PurplePounceActionData *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;
PurpleXmlNode *node, *child;
name = (const char *)key;
param_value = (const char *)value;
node = (PurpleXmlNode *)user_data;
child = purple_xmlnode_new_child(node, "param");
purple_xmlnode_set_attrib(child, "name", name);
purple_xmlnode_insert_data(child, param_value, -1);
}
static void
action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
{
const char *action;
PurplePounceActionData *action_data;
PurpleXmlNode *node, *child;
action = (const char *)key;
action_data = (PurplePounceActionData *)value;
node = (PurpleXmlNode *)user_data;
if (!action_data->enabled)
return;
child = purple_xmlnode_new_child(node, "action");
purple_xmlnode_set_attrib(child, "type", action);
g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child);
}
static void
add_event_to_xmlnode(PurpleXmlNode *node, const char *type)
{
PurpleXmlNode *child;
child = purple_xmlnode_new_child(node, "event");
purple_xmlnode_set_attrib(child, "type", type);
}
static void
add_option_to_xmlnode(PurpleXmlNode *node, const char *type)
{
PurpleXmlNode *child;
child = purple_xmlnode_new_child(node, "option");
purple_xmlnode_set_attrib(child, "type", type);
}
static PurpleXmlNode *
pounce_to_xmlnode(PurplePounce *pounce)
{
PurpleXmlNode *node, *child;
PurpleAccount *pouncer;
PurplePounceEvent events;
PurplePounceOption options;
pouncer = purple_pounce_get_pouncer(pounce);
events = purple_pounce_get_events(pounce);
options = purple_pounce_get_options(pounce);
node = purple_xmlnode_new("pounce");
purple_xmlnode_set_attrib(node, "ui", pounce->ui_type);
child = purple_xmlnode_new_child(node, "account");
purple_xmlnode_set_attrib(child, "protocol", purple_account_get_protocol_id(pouncer));
purple_xmlnode_insert_data(child,
purple_normalize(pouncer, purple_account_get_username(pouncer)), -1);
child = purple_xmlnode_new_child(node, "pouncee");
purple_xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
/* Write pounce options */
child = purple_xmlnode_new_child(node, "options");
if (options & PURPLE_POUNCE_OPTION_AWAY)
add_option_to_xmlnode(child, "on-away");
/* Write pounce events */
child = purple_xmlnode_new_child(node, "events");
if (events & PURPLE_POUNCE_SIGNON)
add_event_to_xmlnode(child, "sign-on");
if (events & PURPLE_POUNCE_SIGNOFF)
add_event_to_xmlnode(child, "sign-off");
if (events & PURPLE_POUNCE_AWAY)
add_event_to_xmlnode(child, "away");
if (events & PURPLE_POUNCE_AWAY_RETURN)
add_event_to_xmlnode(child, "return-from-away");
if (events & PURPLE_POUNCE_IDLE)
add_event_to_xmlnode(child, "idle");
if (events & PURPLE_POUNCE_IDLE_RETURN)
add_event_to_xmlnode(child, "return-from-idle");
if (events & PURPLE_POUNCE_TYPING)
add_event_to_xmlnode(child, "start-typing");
if (events & PURPLE_POUNCE_TYPED)
add_event_to_xmlnode(child, "typed");
if (events & PURPLE_POUNCE_TYPING_STOPPED)
add_event_to_xmlnode(child, "stop-typing");
if (events & PURPLE_POUNCE_MESSAGE_RECEIVED)
add_event_to_xmlnode(child, "message-received");
/* Write pounce actions */
child = purple_xmlnode_new_child(node, "actions");
g_hash_table_foreach(pounce->actions, action_parameter_list_to_xmlnode, child);
if (purple_pounce_get_save(pounce))
purple_xmlnode_new_child(node, "save");
return node;
}
static PurpleXmlNode *
pounces_to_xmlnode(void)
{
PurpleXmlNode *node, *child;
GList *cur;
node = purple_xmlnode_new("pounces");
purple_xmlnode_set_attrib(node, "version", "1.0");
for (cur = purple_pounces_get_all(); cur != NULL; cur = cur->next)
{
child = pounce_to_xmlnode(cur->data);
purple_xmlnode_insert_child(node, child);
}
return node;
}
static void
sync_pounces(void)
{
PurpleXmlNode *node;
char *data;
if (!pounces_loaded)
{
purple_debug_error("pounce", "Attempted to save buddy pounces before "
"they were read!\n");
return;
}
node = pounces_to_xmlnode();
data = purple_xmlnode_to_formatted_str(node, NULL);
purple_util_write_data_to_config_file("pounces.xml", data, -1);
g_free(data);
purple_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 = g_timeout_add_seconds(5, 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 (purple_strequal(element_name, "pounce")) {
const char *ui = g_hash_table_lookup(atts, "ui");
if (ui == NULL) {
purple_debug(PURPLE_DEBUG_ERROR, "pounce",
"Unset 'ui' parameter for pounce!\n");
}
else
data->ui_name = g_strdup(ui);
data->events = 0;
}
else if (purple_strequal(element_name, "account")) {
const char *protocol_id = g_hash_table_lookup(atts, "protocol");
if (protocol_id == NULL) {
purple_debug(PURPLE_DEBUG_ERROR, "pounce",
"Unset 'protocol' parameter for account!\n");
}
else
data->protocol_id = g_strdup(protocol_id);
}
else if (purple_strequal(element_name, "option")) {
const char *type = g_hash_table_lookup(atts, "type");
if (type == NULL) {
purple_debug(PURPLE_DEBUG_ERROR, "pounce",
"Unset 'type' parameter for option!\n");
}
else
data->option_type = g_strdup(type);
}
else if (purple_strequal(element_name, "event")) {
const char *type = g_hash_table_lookup(atts, "type");
if (type == NULL) {
purple_debug(PURPLE_DEBUG_ERROR, "pounce",
"Unset 'type' parameter for event!\n");
}
else
data->event_type = g_strdup(type);
}
else if (purple_strequal(element_name, "action")) {
const char *type = g_hash_table_lookup(atts, "type");
if (type == NULL) {
purple_debug(PURPLE_DEBUG_ERROR, "pounce",
"Unset 'type' parameter for action!\n");
}
else
data->action_name = g_strdup(type);
}
else if (purple_strequal(element_name, "param")) {
const char *param_name = g_hash_table_lookup(atts, "name");
if (param_name == NULL) {
purple_debug(PURPLE_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 (purple_strequal(element_name, "account")) {
g_free(data->account_name);
data->account_name = g_strdup(buffer);
}
else if (purple_strequal(element_name, "pouncee")) {
g_free(data->pouncee);
data->pouncee = g_strdup(buffer);
}
else if (purple_strequal(element_name, "option")) {
if (purple_strequal(data->option_type, "on-away"))
data->options |= PURPLE_POUNCE_OPTION_AWAY;
g_free(data->option_type);
data->option_type = NULL;
}
else if (purple_strequal(element_name, "event")) {
if (purple_strequal(data->event_type, "sign-on"))
data->events |= PURPLE_POUNCE_SIGNON;
else if (purple_strequal(data->event_type, "sign-off"))
data->events |= PURPLE_POUNCE_SIGNOFF;
else if (purple_strequal(data->event_type, "away"))
data->events |= PURPLE_POUNCE_AWAY;
else if (purple_strequal(data->event_type, "return-from-away"))
data->events |= PURPLE_POUNCE_AWAY_RETURN;
else if (purple_strequal(data->event_type, "idle"))
data->events |= PURPLE_POUNCE_IDLE;
else if (purple_strequal(data->event_type, "return-from-idle"))
data->events |= PURPLE_POUNCE_IDLE_RETURN;
else if (purple_strequal(data->event_type, "start-typing"))
data->events |= PURPLE_POUNCE_TYPING;
else if (purple_strequal(data->event_type, "typed"))
data->events |= PURPLE_POUNCE_TYPED;
else if (purple_strequal(data->event_type, "stop-typing"))
data->events |= PURPLE_POUNCE_TYPING_STOPPED;
else if (purple_strequal(data->event_type, "message-received"))
data->events |= PURPLE_POUNCE_MESSAGE_RECEIVED;
g_free(data->event_type);
data->event_type = NULL;
}
else if (purple_strequal(element_name, "action")) {
if (data->pounce != NULL) {
purple_pounce_action_register(data->pounce, data->action_name);
purple_pounce_action_set_enabled(data->pounce, data->action_name, TRUE);
}
g_free(data->action_name);
data->action_name = NULL;
}
else if (purple_strequal(element_name, "param")) {
if (data->pounce != NULL) {
purple_pounce_action_set_attribute(data->pounce, data->action_name,
data->param_name, buffer);
}
g_free(data->param_name);
data->param_name = NULL;
}
else if (purple_strequal(element_name, "events")) {
PurpleAccount *account;
account = purple_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) {
purple_debug(PURPLE_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 {
purple_debug(PURPLE_DEBUG_INFO, "pounce",
"Creating pounce: %s, %s\n", data->ui_name,
data->pouncee);
data->pounce = purple_pounce_new(data->ui_name, account,
data->pouncee, data->events,
data->options);
}
g_free(data->pouncee);
data->pouncee = NULL;
}
else if (purple_strequal(element_name, "save")) {
if (data->pounce != NULL)
purple_pounce_set_save(data->pounce, TRUE);
}
else if (purple_strequal(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->pouncee = 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
};
static gboolean
purple_pounces_load(void)
{
gchar *filename = g_build_filename(purple_config_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)) {
purple_debug(PURPLE_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)) {
purple_debug(PURPLE_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;
}
PurplePounce *
purple_pounce_new(const char *ui_type, PurpleAccount *pouncer,
const char *pouncee, PurplePounceEvent event,
PurplePounceOption option)
{
PurplePounce *pounce;
PurplePounceHandler *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(PurplePounce, 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
purple_pounce_destroy(PurplePounce *pounce)
{
PurplePounceHandler *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
purple_pounce_destroy_all_by_account(PurpleAccount *account)
{
PurpleAccount *pouncer;
PurplePounce *pounce;
GList *l, *l_next;
g_return_if_fail(account != NULL);
for (l = purple_pounces_get_all(); l != NULL; l = l_next)
{
pounce = (PurplePounce *)l->data;
l_next = l->next;
pouncer = purple_pounce_get_pouncer(pounce);
if (pouncer == account)
purple_pounce_destroy(pounce);
}
}
void
purple_pounce_destroy_all_by_buddy(PurpleBuddy *buddy)
{
const char *pouncee, *bname;
PurpleAccount *pouncer, *bacct;
PurplePounce *pounce;
GList *l, *l_next;
g_return_if_fail(buddy != NULL);
bacct = purple_buddy_get_account(buddy);
bname = purple_buddy_get_name(buddy);
for (l = purple_pounces_get_all(); l != NULL; l = l_next) {
pounce = (PurplePounce *)l->data;
l_next = l->next;
pouncer = purple_pounce_get_pouncer(pounce);
pouncee = purple_pounce_get_pouncee(pounce);
if ( (pouncer == bacct) && (purple_strequal(pouncee, bname)) )
purple_pounce_destroy(pounce);
}
}
void
purple_pounce_set_events(PurplePounce *pounce, PurplePounceEvent events)
{
g_return_if_fail(pounce != NULL);
g_return_if_fail(events != PURPLE_POUNCE_NONE);
pounce->events = events;
schedule_pounces_save();
}
void
purple_pounce_set_options(PurplePounce *pounce, PurplePounceOption options)
{
g_return_if_fail(pounce != NULL);
pounce->options = options;
schedule_pounces_save();
}
void
purple_pounce_set_pouncer(PurplePounce *pounce, PurpleAccount *pouncer)
{
g_return_if_fail(pounce != NULL);
g_return_if_fail(pouncer != NULL);
pounce->pouncer = pouncer;
schedule_pounces_save();
}
void
purple_pounce_set_pouncee(PurplePounce *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
purple_pounce_set_save(PurplePounce *pounce, gboolean save)
{
g_return_if_fail(pounce != NULL);
pounce->save = save;
schedule_pounces_save();
}
void
purple_pounce_action_register(PurplePounce *pounce, const char *name)
{
PurplePounceActionData *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(PurplePounceActionData, 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
purple_pounce_action_set_enabled(PurplePounce *pounce, const char *action,
gboolean enabled)
{
PurplePounceActionData *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
purple_pounce_action_set_attribute(PurplePounce *pounce, const char *action,
const char *attr, const char *value)
{
PurplePounceActionData *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
purple_pounce_set_data(PurplePounce *pounce, void *data)
{
g_return_if_fail(pounce != NULL);
pounce->data = data;
schedule_pounces_save();
}
PurplePounceEvent
purple_pounce_get_events(const PurplePounce *pounce)
{
g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_NONE);
return pounce->events;
}
PurplePounceOption
purple_pounce_get_options(const PurplePounce *pounce)
{
g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_OPTION_NONE);
return pounce->options;
}
PurpleAccount *
purple_pounce_get_pouncer(const PurplePounce *pounce)
{
g_return_val_if_fail(pounce != NULL, NULL);
return pounce->pouncer;
}
const char *
purple_pounce_get_pouncee(const PurplePounce *pounce)
{
g_return_val_if_fail(pounce != NULL, NULL);
return pounce->pouncee;
}
gboolean
purple_pounce_get_save(const PurplePounce *pounce)
{
g_return_val_if_fail(pounce != NULL, FALSE);
return pounce->save;
}
gboolean
purple_pounce_action_is_enabled(const PurplePounce *pounce, const char *action)
{
PurplePounceActionData *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 *
purple_pounce_action_get_attribute(const PurplePounce *pounce,
const char *action, const char *attr)
{
PurplePounceActionData *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 *
purple_pounce_get_data(const PurplePounce *pounce)
{
g_return_val_if_fail(pounce != NULL, NULL);
return pounce->data;
}
void
purple_pounce_execute(PurpleAccount *pouncer, const char *pouncee,
PurplePounceEvent events)
{
PurplePounce *pounce;
PurplePounceHandler *handler;
PurplePresence *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 != PURPLE_POUNCE_NONE);
norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
for (l = purple_pounces_get_all(); l != NULL; l = l_next)
{
pounce = (PurplePounce *)l->data;
l_next = l->next;
presence = purple_account_get_presence(pouncer);
if ((purple_pounce_get_events(pounce) & events) &&
(purple_pounce_get_pouncer(pounce) == pouncer) &&
!purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
norm_pouncee) &&
(pounce->options == PURPLE_POUNCE_OPTION_NONE ||
(pounce->options & PURPLE_POUNCE_OPTION_AWAY &&
!purple_presence_is_available(presence))))
{
handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
if (handler != NULL && handler->cb != NULL)
{
handler->cb(pounce, events, purple_pounce_get_data(pounce));
if (!purple_pounce_get_save(pounce))
purple_pounce_destroy(pounce);
}
}
}
g_free(norm_pouncee);
}
PurplePounce *
purple_find_pounce(PurpleAccount *pouncer, const char *pouncee,
PurplePounceEvent events)
{
PurplePounce *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 != PURPLE_POUNCE_NONE, NULL);
norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
for (l = purple_pounces_get_all(); l != NULL; l = l->next)
{
pounce = (PurplePounce *)l->data;
if ((purple_pounce_get_events(pounce) & events) &&
(purple_pounce_get_pouncer(pounce) == pouncer) &&
!purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
norm_pouncee))
{
break;
}
pounce = NULL;
}
g_free(norm_pouncee);
return pounce;
}
void
purple_pounces_register_handler(const char *ui, PurplePounceCb cb,
void (*new_pounce)(PurplePounce *pounce),
void (*free_pounce)(PurplePounce *pounce))
{
PurplePounceHandler *handler;
g_return_if_fail(ui != NULL);
g_return_if_fail(cb != NULL);
handler = g_new0(PurplePounceHandler, 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
purple_pounces_unregister_handler(const char *ui)
{
g_return_if_fail(ui != NULL);
g_hash_table_remove(pounce_handlers, ui);
}
GList *
purple_pounces_get_all(void)
{
return pounces;
}
GList *purple_pounces_get_all_for_ui(const char *ui)
{
GList *list = NULL, *iter;
g_return_val_if_fail(ui != NULL, NULL);
for (iter = pounces; iter; iter = iter->next) {
PurplePounce *pounce = iter->data;
if (purple_strequal(pounce->ui_type, ui))
list = g_list_prepend(list, pounce);
}
list = g_list_reverse(list);
return list;
}
static void
free_pounce_handler(gpointer user_data)
{
PurplePounceHandler *handler = (PurplePounceHandler *)user_data;
g_free(handler->ui);
g_free(handler);
}
static void
buddy_state_cb(PurpleBuddy *buddy, PurplePounceEvent event)
{
PurpleAccount *account = purple_buddy_get_account(buddy);
const gchar *name = purple_buddy_get_name(buddy);
purple_pounce_execute(account, name, event);
}
static void
buddy_status_changed_cb(PurpleBuddy *buddy, PurpleStatus *old_status,
PurpleStatus *status)
{
PurpleAccount *account = purple_buddy_get_account(buddy);
const gchar *name = purple_buddy_get_name(buddy);
gboolean old_available, available;
available = purple_status_is_available(status);
old_available = purple_status_is_available(old_status);
if (available && !old_available)
purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY_RETURN);
else if (!available && old_available)
purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY);
}
static void
buddy_idle_changed_cb(PurpleBuddy *buddy, gboolean old_idle, gboolean idle)
{
PurpleAccount *account = purple_buddy_get_account(buddy);
const gchar *name = purple_buddy_get_name(buddy);
if (idle && !old_idle)
purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE);
else if (!idle && old_idle)
purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE_RETURN);
}
static void
buddy_typing_cb(PurpleAccount *account, const char *name, void *data)
{
PurpleIMConversation *im;
im = purple_conversations_find_im_with_account(name, account);
if (im != NULL)
{
PurpleIMTypingState state;
PurplePounceEvent event;
state = purple_im_conversation_get_typing_state(im);
if (state == PURPLE_IM_TYPED)
event = PURPLE_POUNCE_TYPED;
else if (state == PURPLE_IM_NOT_TYPING)
event = PURPLE_POUNCE_TYPING_STOPPED;
else
event = PURPLE_POUNCE_TYPING;
purple_pounce_execute(account, name, event);
}
}
static void
received_message_cb(PurpleAccount *account, const char *name, void *data)
{
purple_pounce_execute(account, name, PURPLE_POUNCE_MESSAGE_RECEIVED);
}
void *
purple_pounces_get_handle(void)
{
static int pounce_handle;
return &pounce_handle;
}
void
purple_pounces_init(void)
{
void *handle = purple_pounces_get_handle();
void *blist_handle = purple_blist_get_handle();
void *conv_handle = purple_conversations_get_handle();
pounce_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, free_pounce_handler);
purple_signal_connect(blist_handle, "buddy-idle-changed",
handle, PURPLE_CALLBACK(buddy_idle_changed_cb), NULL);
purple_signal_connect(blist_handle, "buddy-status-changed",
handle, PURPLE_CALLBACK(buddy_status_changed_cb), NULL);
purple_signal_connect(blist_handle, "buddy-signed-on",
handle, PURPLE_CALLBACK(buddy_state_cb),
GINT_TO_POINTER(PURPLE_POUNCE_SIGNON));
purple_signal_connect(blist_handle, "buddy-signed-off",
handle, PURPLE_CALLBACK(buddy_state_cb),
GINT_TO_POINTER(PURPLE_POUNCE_SIGNOFF));
purple_signal_connect(conv_handle, "buddy-typing",
handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
purple_signal_connect(conv_handle, "buddy-typed",
handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
purple_signal_connect(conv_handle, "buddy-typing-stopped",
handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
purple_signal_connect(conv_handle, "received-im-msg",
handle, PURPLE_CALLBACK(received_message_cb), NULL);
purple_pounces_load();
}
void
purple_pounces_uninit()
{
if (save_timer != 0)
{
g_source_remove(save_timer);
save_timer = 0;
sync_pounces();
}
purple_signals_disconnect_by_handle(purple_pounces_get_handle());
g_hash_table_destroy(pounce_handlers);
pounce_handlers = NULL;
}