Merge heads.
[pidgin-git.git] / libpurple / pounce.c
blob22a535cdadd0cdcb3d5e14cbd50bbe268c32b8ed
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #include "internal.h"
22 #include "conversation.h"
23 #include "debug.h"
24 #include "pounce.h"
26 #include "debug.h"
27 #include "pounce.h"
28 #include "util.h"
31 * A buddy pounce structure.
33 * Buddy pounces are actions triggered by a buddy-related event. For
34 * example, a sound can be played or an IM window opened when a buddy
35 * signs on or returns from away. Such responses are handled in the
36 * UI. The events themselves are done in the core.
38 struct _PurplePounce
40 char *ui_type; /* The type of UI. */
42 PurplePounceEvent events; /* The event(s) to pounce on. */
43 PurplePounceOption options; /* The pounce options */
44 PurpleAccount *pouncer; /* The user who is pouncing. */
46 char *pouncee; /* The buddy to pounce on. */
48 GHashTable *actions; /* The registered actions. */
50 gboolean save; /* Whether or not the pounce should
51 be saved after activation. */
52 void *data; /* Pounce-specific data. */
55 typedef struct
57 GString *buffer;
59 PurplePounce *pounce;
60 PurplePounceEvent events;
61 PurplePounceOption options;
63 char *ui_name;
64 char *pouncee;
65 char *protocol_id;
66 char *event_type;
67 char *option_type;
68 char *action_name;
69 char *param_name;
70 char *account_name;
72 } PounceParserData;
74 typedef struct
76 char *name;
78 gboolean enabled;
80 GHashTable *atts;
82 } PurplePounceActionData;
84 typedef struct
86 char *ui;
87 PurplePounceCb cb;
88 void (*new_pounce)(PurplePounce *);
89 void (*free_pounce)(PurplePounce *);
91 } PurplePounceHandler;
94 static GHashTable *pounce_handlers = NULL;
95 static GList *pounces = NULL;
96 static guint save_timer = 0;
97 static gboolean pounces_loaded = FALSE;
100 /*********************************************************************
101 * Private utility functions *
102 *********************************************************************/
104 static PurplePounceActionData *
105 find_action_data(const PurplePounce *pounce, const char *name)
107 PurplePounceActionData *action;
109 g_return_val_if_fail(pounce != NULL, NULL);
110 g_return_val_if_fail(name != NULL, NULL);
112 action = g_hash_table_lookup(pounce->actions, name);
114 return action;
117 static void
118 free_action_data(gpointer data)
120 PurplePounceActionData *action_data = data;
122 g_free(action_data->name);
124 g_hash_table_destroy(action_data->atts);
126 g_free(action_data);
130 /*********************************************************************
131 * Writing to disk *
132 *********************************************************************/
134 static void
135 action_parameter_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
137 const char *name, *param_value;
138 PurpleXmlNode *node, *child;
140 name = (const char *)key;
141 param_value = (const char *)value;
142 node = (PurpleXmlNode *)user_data;
144 child = purple_xmlnode_new_child(node, "param");
145 purple_xmlnode_set_attrib(child, "name", name);
146 purple_xmlnode_insert_data(child, param_value, -1);
149 static void
150 action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
152 const char *action;
153 PurplePounceActionData *action_data;
154 PurpleXmlNode *node, *child;
156 action = (const char *)key;
157 action_data = (PurplePounceActionData *)value;
158 node = (PurpleXmlNode *)user_data;
160 if (!action_data->enabled)
161 return;
163 child = purple_xmlnode_new_child(node, "action");
164 purple_xmlnode_set_attrib(child, "type", action);
166 g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child);
169 static void
170 add_event_to_xmlnode(PurpleXmlNode *node, const char *type)
172 PurpleXmlNode *child;
174 child = purple_xmlnode_new_child(node, "event");
175 purple_xmlnode_set_attrib(child, "type", type);
178 static void
179 add_option_to_xmlnode(PurpleXmlNode *node, const char *type)
181 PurpleXmlNode *child;
183 child = purple_xmlnode_new_child(node, "option");
184 purple_xmlnode_set_attrib(child, "type", type);
187 static PurpleXmlNode *
188 pounce_to_xmlnode(PurplePounce *pounce)
190 PurpleXmlNode *node, *child;
191 PurpleAccount *pouncer;
192 PurplePounceEvent events;
193 PurplePounceOption options;
195 pouncer = purple_pounce_get_pouncer(pounce);
196 events = purple_pounce_get_events(pounce);
197 options = purple_pounce_get_options(pounce);
199 node = purple_xmlnode_new("pounce");
200 purple_xmlnode_set_attrib(node, "ui", pounce->ui_type);
202 child = purple_xmlnode_new_child(node, "account");
203 purple_xmlnode_set_attrib(child, "protocol", purple_account_get_protocol_id(pouncer));
204 purple_xmlnode_insert_data(child,
205 purple_normalize(pouncer, purple_account_get_username(pouncer)), -1);
207 child = purple_xmlnode_new_child(node, "pouncee");
208 purple_xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
210 /* Write pounce options */
211 child = purple_xmlnode_new_child(node, "options");
212 if (options & PURPLE_POUNCE_OPTION_AWAY)
213 add_option_to_xmlnode(child, "on-away");
215 /* Write pounce events */
216 child = purple_xmlnode_new_child(node, "events");
217 if (events & PURPLE_POUNCE_SIGNON)
218 add_event_to_xmlnode(child, "sign-on");
219 if (events & PURPLE_POUNCE_SIGNOFF)
220 add_event_to_xmlnode(child, "sign-off");
221 if (events & PURPLE_POUNCE_AWAY)
222 add_event_to_xmlnode(child, "away");
223 if (events & PURPLE_POUNCE_AWAY_RETURN)
224 add_event_to_xmlnode(child, "return-from-away");
225 if (events & PURPLE_POUNCE_IDLE)
226 add_event_to_xmlnode(child, "idle");
227 if (events & PURPLE_POUNCE_IDLE_RETURN)
228 add_event_to_xmlnode(child, "return-from-idle");
229 if (events & PURPLE_POUNCE_TYPING)
230 add_event_to_xmlnode(child, "start-typing");
231 if (events & PURPLE_POUNCE_TYPED)
232 add_event_to_xmlnode(child, "typed");
233 if (events & PURPLE_POUNCE_TYPING_STOPPED)
234 add_event_to_xmlnode(child, "stop-typing");
235 if (events & PURPLE_POUNCE_MESSAGE_RECEIVED)
236 add_event_to_xmlnode(child, "message-received");
238 /* Write pounce actions */
239 child = purple_xmlnode_new_child(node, "actions");
240 g_hash_table_foreach(pounce->actions, action_parameter_list_to_xmlnode, child);
242 if (purple_pounce_get_save(pounce))
243 purple_xmlnode_new_child(node, "save");
245 return node;
248 static PurpleXmlNode *
249 pounces_to_xmlnode(void)
251 PurpleXmlNode *node, *child;
252 GList *cur;
254 node = purple_xmlnode_new("pounces");
255 purple_xmlnode_set_attrib(node, "version", "1.0");
257 for (cur = purple_pounces_get_all(); cur != NULL; cur = cur->next)
259 child = pounce_to_xmlnode(cur->data);
260 purple_xmlnode_insert_child(node, child);
263 return node;
266 static void
267 sync_pounces(void)
269 PurpleXmlNode *node;
270 char *data;
272 if (!pounces_loaded)
274 purple_debug_error("pounce", "Attempted to save buddy pounces before "
275 "they were read!\n");
276 return;
279 node = pounces_to_xmlnode();
280 data = purple_xmlnode_to_formatted_str(node, NULL);
281 purple_util_write_data_to_config_file("pounces.xml", data, -1);
282 g_free(data);
283 purple_xmlnode_free(node);
286 static gboolean
287 save_cb(gpointer data)
289 sync_pounces();
290 save_timer = 0;
291 return FALSE;
294 static void
295 schedule_pounces_save(void)
297 if (save_timer == 0)
298 save_timer = g_timeout_add_seconds(5, save_cb, NULL);
302 /*********************************************************************
303 * Reading from disk *
304 *********************************************************************/
306 static void
307 free_parser_data(gpointer user_data)
309 PounceParserData *data = user_data;
311 if (data->buffer != NULL)
312 g_string_free(data->buffer, TRUE);
314 g_free(data->ui_name);
315 g_free(data->pouncee);
316 g_free(data->protocol_id);
317 g_free(data->event_type);
318 g_free(data->option_type);
319 g_free(data->action_name);
320 g_free(data->param_name);
321 g_free(data->account_name);
323 g_free(data);
326 static void
327 start_element_handler(GMarkupParseContext *context,
328 const gchar *element_name,
329 const gchar **attribute_names,
330 const gchar **attribute_values,
331 gpointer user_data, GError **error)
333 PounceParserData *data = user_data;
334 GHashTable *atts;
335 int i;
337 atts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
339 for (i = 0; attribute_names[i] != NULL; i++) {
340 g_hash_table_insert(atts, g_strdup(attribute_names[i]),
341 g_strdup(attribute_values[i]));
344 if (data->buffer != NULL) {
345 g_string_free(data->buffer, TRUE);
346 data->buffer = NULL;
349 if (purple_strequal(element_name, "pounce")) {
350 const char *ui = g_hash_table_lookup(atts, "ui");
352 if (ui == NULL) {
353 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
354 "Unset 'ui' parameter for pounce!\n");
356 else
357 data->ui_name = g_strdup(ui);
359 data->events = 0;
361 else if (purple_strequal(element_name, "account")) {
362 const char *protocol_id = g_hash_table_lookup(atts, "protocol");
364 if (protocol_id == NULL) {
365 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
366 "Unset 'protocol' parameter for account!\n");
368 else
369 data->protocol_id = g_strdup(protocol_id);
371 else if (purple_strequal(element_name, "option")) {
372 const char *type = g_hash_table_lookup(atts, "type");
374 if (type == NULL) {
375 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
376 "Unset 'type' parameter for option!\n");
378 else
379 data->option_type = g_strdup(type);
381 else if (purple_strequal(element_name, "event")) {
382 const char *type = g_hash_table_lookup(atts, "type");
384 if (type == NULL) {
385 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
386 "Unset 'type' parameter for event!\n");
388 else
389 data->event_type = g_strdup(type);
391 else if (purple_strequal(element_name, "action")) {
392 const char *type = g_hash_table_lookup(atts, "type");
394 if (type == NULL) {
395 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
396 "Unset 'type' parameter for action!\n");
398 else
399 data->action_name = g_strdup(type);
401 else if (purple_strequal(element_name, "param")) {
402 const char *param_name = g_hash_table_lookup(atts, "name");
404 if (param_name == NULL) {
405 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
406 "Unset 'name' parameter for param!\n");
408 else
409 data->param_name = g_strdup(param_name);
412 g_hash_table_destroy(atts);
415 static void
416 end_element_handler(GMarkupParseContext *context, const gchar *element_name,
417 gpointer user_data, GError **error)
419 PounceParserData *data = user_data;
420 gchar *buffer = NULL;
422 if (data->buffer != NULL) {
423 buffer = g_string_free(data->buffer, FALSE);
424 data->buffer = NULL;
427 if (purple_strequal(element_name, "account")) {
428 g_free(data->account_name);
429 data->account_name = g_strdup(buffer);
431 else if (purple_strequal(element_name, "pouncee")) {
432 g_free(data->pouncee);
433 data->pouncee = g_strdup(buffer);
435 else if (purple_strequal(element_name, "option")) {
436 if (purple_strequal(data->option_type, "on-away"))
437 data->options |= PURPLE_POUNCE_OPTION_AWAY;
439 g_free(data->option_type);
440 data->option_type = NULL;
442 else if (purple_strequal(element_name, "event")) {
443 if (purple_strequal(data->event_type, "sign-on"))
444 data->events |= PURPLE_POUNCE_SIGNON;
445 else if (purple_strequal(data->event_type, "sign-off"))
446 data->events |= PURPLE_POUNCE_SIGNOFF;
447 else if (purple_strequal(data->event_type, "away"))
448 data->events |= PURPLE_POUNCE_AWAY;
449 else if (purple_strequal(data->event_type, "return-from-away"))
450 data->events |= PURPLE_POUNCE_AWAY_RETURN;
451 else if (purple_strequal(data->event_type, "idle"))
452 data->events |= PURPLE_POUNCE_IDLE;
453 else if (purple_strequal(data->event_type, "return-from-idle"))
454 data->events |= PURPLE_POUNCE_IDLE_RETURN;
455 else if (purple_strequal(data->event_type, "start-typing"))
456 data->events |= PURPLE_POUNCE_TYPING;
457 else if (purple_strequal(data->event_type, "typed"))
458 data->events |= PURPLE_POUNCE_TYPED;
459 else if (purple_strequal(data->event_type, "stop-typing"))
460 data->events |= PURPLE_POUNCE_TYPING_STOPPED;
461 else if (purple_strequal(data->event_type, "message-received"))
462 data->events |= PURPLE_POUNCE_MESSAGE_RECEIVED;
464 g_free(data->event_type);
465 data->event_type = NULL;
467 else if (purple_strequal(element_name, "action")) {
468 if (data->pounce != NULL) {
469 purple_pounce_action_register(data->pounce, data->action_name);
470 purple_pounce_action_set_enabled(data->pounce, data->action_name, TRUE);
473 g_free(data->action_name);
474 data->action_name = NULL;
476 else if (purple_strequal(element_name, "param")) {
477 if (data->pounce != NULL) {
478 purple_pounce_action_set_attribute(data->pounce, data->action_name,
479 data->param_name, buffer);
482 g_free(data->param_name);
483 data->param_name = NULL;
485 else if (purple_strequal(element_name, "events")) {
486 PurpleAccount *account;
488 account = purple_accounts_find(data->account_name, data->protocol_id);
490 g_free(data->account_name);
491 g_free(data->protocol_id);
493 data->account_name = NULL;
494 data->protocol_id = NULL;
496 if (account == NULL) {
497 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
498 "Account for pounce not found!\n");
500 * This pounce has effectively been removed, so make
501 * sure that we save the changes to pounces.xml
503 schedule_pounces_save();
505 else {
506 purple_debug(PURPLE_DEBUG_INFO, "pounce",
507 "Creating pounce: %s, %s\n", data->ui_name,
508 data->pouncee);
510 data->pounce = purple_pounce_new(data->ui_name, account,
511 data->pouncee, data->events,
512 data->options);
515 g_free(data->pouncee);
516 data->pouncee = NULL;
518 else if (purple_strequal(element_name, "save")) {
519 if (data->pounce != NULL)
520 purple_pounce_set_save(data->pounce, TRUE);
522 else if (purple_strequal(element_name, "pounce")) {
523 data->pounce = NULL;
524 data->events = 0;
525 data->options = 0;
527 g_free(data->ui_name);
528 g_free(data->pouncee);
529 g_free(data->protocol_id);
530 g_free(data->event_type);
531 g_free(data->option_type);
532 g_free(data->action_name);
533 g_free(data->param_name);
534 g_free(data->account_name);
536 data->ui_name = NULL;
537 data->pouncee = NULL;
538 data->protocol_id = NULL;
539 data->event_type = NULL;
540 data->option_type = NULL;
541 data->action_name = NULL;
542 data->param_name = NULL;
543 data->account_name = NULL;
546 g_free(buffer);
549 static void
550 text_handler(GMarkupParseContext *context, const gchar *text,
551 gsize text_len, gpointer user_data, GError **error)
553 PounceParserData *data = user_data;
555 if (data->buffer == NULL)
556 data->buffer = g_string_new_len(text, text_len);
557 else
558 g_string_append_len(data->buffer, text, text_len);
561 static GMarkupParser pounces_parser =
563 start_element_handler,
564 end_element_handler,
565 text_handler,
566 NULL,
567 NULL
570 static gboolean
571 purple_pounces_load(void)
573 gchar *filename = g_build_filename(purple_config_dir(), "pounces.xml", NULL);
574 gchar *contents = NULL;
575 gsize length;
576 GMarkupParseContext *context;
577 GError *error = NULL;
578 PounceParserData *parser_data;
580 if (filename == NULL) {
581 pounces_loaded = TRUE;
582 return FALSE;
585 if (!g_file_get_contents(filename, &contents, &length, &error)) {
586 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
587 "Error reading pounces: %s\n", error->message);
589 g_free(filename);
590 g_error_free(error);
592 pounces_loaded = TRUE;
593 return FALSE;
596 parser_data = g_new0(PounceParserData, 1);
598 context = g_markup_parse_context_new(&pounces_parser, 0,
599 parser_data, free_parser_data);
601 if (!g_markup_parse_context_parse(context, contents, length, NULL)) {
602 g_markup_parse_context_free(context);
603 g_free(contents);
604 g_free(filename);
606 pounces_loaded = TRUE;
608 return FALSE;
611 if (!g_markup_parse_context_end_parse(context, NULL)) {
612 purple_debug(PURPLE_DEBUG_ERROR, "pounce", "Error parsing %s\n",
613 filename);
615 g_markup_parse_context_free(context);
616 g_free(contents);
617 g_free(filename);
618 pounces_loaded = TRUE;
620 return FALSE;
623 g_markup_parse_context_free(context);
624 g_free(contents);
625 g_free(filename);
627 pounces_loaded = TRUE;
629 return TRUE;
633 PurplePounce *
634 purple_pounce_new(const char *ui_type, PurpleAccount *pouncer,
635 const char *pouncee, PurplePounceEvent event,
636 PurplePounceOption option)
638 PurplePounce *pounce;
639 PurplePounceHandler *handler;
641 g_return_val_if_fail(ui_type != NULL, NULL);
642 g_return_val_if_fail(pouncer != NULL, NULL);
643 g_return_val_if_fail(pouncee != NULL, NULL);
644 g_return_val_if_fail(event != 0, NULL);
646 pounce = g_new0(PurplePounce, 1);
648 pounce->ui_type = g_strdup(ui_type);
649 pounce->pouncer = pouncer;
650 pounce->pouncee = g_strdup(pouncee);
651 pounce->events = event;
652 pounce->options = option;
654 pounce->actions = g_hash_table_new_full(g_str_hash, g_str_equal,
655 g_free, free_action_data);
657 handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
659 if (handler != NULL && handler->new_pounce != NULL)
660 handler->new_pounce(pounce);
662 pounces = g_list_append(pounces, pounce);
664 schedule_pounces_save();
666 return pounce;
669 void
670 purple_pounce_destroy(PurplePounce *pounce)
672 PurplePounceHandler *handler;
674 g_return_if_fail(pounce != NULL);
676 handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
678 pounces = g_list_remove(pounces, pounce);
680 g_free(pounce->ui_type);
681 g_free(pounce->pouncee);
683 g_hash_table_destroy(pounce->actions);
685 if (handler != NULL && handler->free_pounce != NULL)
686 handler->free_pounce(pounce);
688 g_free(pounce);
690 schedule_pounces_save();
693 void
694 purple_pounce_destroy_all_by_account(PurpleAccount *account)
696 PurpleAccount *pouncer;
697 PurplePounce *pounce;
698 GList *l, *l_next;
700 g_return_if_fail(account != NULL);
702 for (l = purple_pounces_get_all(); l != NULL; l = l_next)
704 pounce = (PurplePounce *)l->data;
705 l_next = l->next;
707 pouncer = purple_pounce_get_pouncer(pounce);
708 if (pouncer == account)
709 purple_pounce_destroy(pounce);
713 void
714 purple_pounce_destroy_all_by_buddy(PurpleBuddy *buddy)
716 const char *pouncee, *bname;
717 PurpleAccount *pouncer, *bacct;
718 PurplePounce *pounce;
719 GList *l, *l_next;
721 g_return_if_fail(buddy != NULL);
723 bacct = purple_buddy_get_account(buddy);
724 bname = purple_buddy_get_name(buddy);
726 for (l = purple_pounces_get_all(); l != NULL; l = l_next) {
727 pounce = (PurplePounce *)l->data;
728 l_next = l->next;
730 pouncer = purple_pounce_get_pouncer(pounce);
731 pouncee = purple_pounce_get_pouncee(pounce);
733 if ( (pouncer == bacct) && (purple_strequal(pouncee, bname)) )
734 purple_pounce_destroy(pounce);
738 void
739 purple_pounce_set_events(PurplePounce *pounce, PurplePounceEvent events)
741 g_return_if_fail(pounce != NULL);
742 g_return_if_fail(events != PURPLE_POUNCE_NONE);
744 pounce->events = events;
746 schedule_pounces_save();
749 void
750 purple_pounce_set_options(PurplePounce *pounce, PurplePounceOption options)
752 g_return_if_fail(pounce != NULL);
754 pounce->options = options;
756 schedule_pounces_save();
759 void
760 purple_pounce_set_pouncer(PurplePounce *pounce, PurpleAccount *pouncer)
762 g_return_if_fail(pounce != NULL);
763 g_return_if_fail(pouncer != NULL);
765 pounce->pouncer = pouncer;
767 schedule_pounces_save();
770 void
771 purple_pounce_set_pouncee(PurplePounce *pounce, const char *pouncee)
773 g_return_if_fail(pounce != NULL);
774 g_return_if_fail(pouncee != NULL);
776 g_free(pounce->pouncee);
777 pounce->pouncee = g_strdup(pouncee);
779 schedule_pounces_save();
782 void
783 purple_pounce_set_save(PurplePounce *pounce, gboolean save)
785 g_return_if_fail(pounce != NULL);
787 pounce->save = save;
789 schedule_pounces_save();
792 void
793 purple_pounce_action_register(PurplePounce *pounce, const char *name)
795 PurplePounceActionData *action_data;
797 g_return_if_fail(pounce != NULL);
798 g_return_if_fail(name != NULL);
800 if (g_hash_table_lookup(pounce->actions, name) != NULL)
801 return;
803 action_data = g_new0(PurplePounceActionData, 1);
805 action_data->name = g_strdup(name);
806 action_data->enabled = FALSE;
807 action_data->atts = g_hash_table_new_full(g_str_hash, g_str_equal,
808 g_free, g_free);
810 g_hash_table_insert(pounce->actions, g_strdup(name), action_data);
812 schedule_pounces_save();
815 void
816 purple_pounce_action_set_enabled(PurplePounce *pounce, const char *action,
817 gboolean enabled)
819 PurplePounceActionData *action_data;
821 g_return_if_fail(pounce != NULL);
822 g_return_if_fail(action != NULL);
824 action_data = find_action_data(pounce, action);
826 g_return_if_fail(action_data != NULL);
828 action_data->enabled = enabled;
830 schedule_pounces_save();
833 void
834 purple_pounce_action_set_attribute(PurplePounce *pounce, const char *action,
835 const char *attr, const char *value)
837 PurplePounceActionData *action_data;
839 g_return_if_fail(pounce != NULL);
840 g_return_if_fail(action != NULL);
841 g_return_if_fail(attr != NULL);
843 action_data = find_action_data(pounce, action);
845 g_return_if_fail(action_data != NULL);
847 if (value == NULL)
848 g_hash_table_remove(action_data->atts, attr);
849 else
850 g_hash_table_insert(action_data->atts, g_strdup(attr),
851 g_strdup(value));
853 schedule_pounces_save();
856 void
857 purple_pounce_set_data(PurplePounce *pounce, void *data)
859 g_return_if_fail(pounce != NULL);
861 pounce->data = data;
863 schedule_pounces_save();
866 PurplePounceEvent
867 purple_pounce_get_events(const PurplePounce *pounce)
869 g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_NONE);
871 return pounce->events;
874 PurplePounceOption
875 purple_pounce_get_options(const PurplePounce *pounce)
877 g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_OPTION_NONE);
879 return pounce->options;
882 PurpleAccount *
883 purple_pounce_get_pouncer(const PurplePounce *pounce)
885 g_return_val_if_fail(pounce != NULL, NULL);
887 return pounce->pouncer;
890 const char *
891 purple_pounce_get_pouncee(const PurplePounce *pounce)
893 g_return_val_if_fail(pounce != NULL, NULL);
895 return pounce->pouncee;
898 gboolean
899 purple_pounce_get_save(const PurplePounce *pounce)
901 g_return_val_if_fail(pounce != NULL, FALSE);
903 return pounce->save;
906 gboolean
907 purple_pounce_action_is_enabled(const PurplePounce *pounce, const char *action)
909 PurplePounceActionData *action_data;
911 g_return_val_if_fail(pounce != NULL, FALSE);
912 g_return_val_if_fail(action != NULL, FALSE);
914 action_data = find_action_data(pounce, action);
916 g_return_val_if_fail(action_data != NULL, FALSE);
918 return action_data->enabled;
921 const char *
922 purple_pounce_action_get_attribute(const PurplePounce *pounce,
923 const char *action, const char *attr)
925 PurplePounceActionData *action_data;
927 g_return_val_if_fail(pounce != NULL, NULL);
928 g_return_val_if_fail(action != NULL, NULL);
929 g_return_val_if_fail(attr != NULL, NULL);
931 action_data = find_action_data(pounce, action);
933 g_return_val_if_fail(action_data != NULL, NULL);
935 return g_hash_table_lookup(action_data->atts, attr);
938 void *
939 purple_pounce_get_data(const PurplePounce *pounce)
941 g_return_val_if_fail(pounce != NULL, NULL);
943 return pounce->data;
946 void
947 purple_pounce_execute(PurpleAccount *pouncer, const char *pouncee,
948 PurplePounceEvent events)
950 PurplePounce *pounce;
951 PurplePounceHandler *handler;
952 PurplePresence *presence;
953 GList *l, *l_next;
954 char *norm_pouncee;
956 g_return_if_fail(pouncer != NULL);
957 g_return_if_fail(pouncee != NULL);
958 g_return_if_fail(events != PURPLE_POUNCE_NONE);
960 norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
962 for (l = purple_pounces_get_all(); l != NULL; l = l_next)
964 pounce = (PurplePounce *)l->data;
965 l_next = l->next;
967 presence = purple_account_get_presence(pouncer);
969 if ((purple_pounce_get_events(pounce) & events) &&
970 (purple_pounce_get_pouncer(pounce) == pouncer) &&
971 !purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
972 norm_pouncee) &&
973 (pounce->options == PURPLE_POUNCE_OPTION_NONE ||
974 (pounce->options & PURPLE_POUNCE_OPTION_AWAY &&
975 !purple_presence_is_available(presence))))
977 handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
979 if (handler != NULL && handler->cb != NULL)
981 handler->cb(pounce, events, purple_pounce_get_data(pounce));
983 if (!purple_pounce_get_save(pounce))
984 purple_pounce_destroy(pounce);
989 g_free(norm_pouncee);
992 PurplePounce *
993 purple_find_pounce(PurpleAccount *pouncer, const char *pouncee,
994 PurplePounceEvent events)
996 PurplePounce *pounce = NULL;
997 GList *l;
998 char *norm_pouncee;
1000 g_return_val_if_fail(pouncer != NULL, NULL);
1001 g_return_val_if_fail(pouncee != NULL, NULL);
1002 g_return_val_if_fail(events != PURPLE_POUNCE_NONE, NULL);
1004 norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
1006 for (l = purple_pounces_get_all(); l != NULL; l = l->next)
1008 pounce = (PurplePounce *)l->data;
1010 if ((purple_pounce_get_events(pounce) & events) &&
1011 (purple_pounce_get_pouncer(pounce) == pouncer) &&
1012 !purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
1013 norm_pouncee))
1015 break;
1018 pounce = NULL;
1021 g_free(norm_pouncee);
1023 return pounce;
1026 void
1027 purple_pounces_register_handler(const char *ui, PurplePounceCb cb,
1028 void (*new_pounce)(PurplePounce *pounce),
1029 void (*free_pounce)(PurplePounce *pounce))
1031 PurplePounceHandler *handler;
1033 g_return_if_fail(ui != NULL);
1034 g_return_if_fail(cb != NULL);
1036 handler = g_new0(PurplePounceHandler, 1);
1038 handler->ui = g_strdup(ui);
1039 handler->cb = cb;
1040 handler->new_pounce = new_pounce;
1041 handler->free_pounce = free_pounce;
1043 g_hash_table_insert(pounce_handlers, g_strdup(ui), handler);
1046 void
1047 purple_pounces_unregister_handler(const char *ui)
1049 g_return_if_fail(ui != NULL);
1051 g_hash_table_remove(pounce_handlers, ui);
1054 GList *
1055 purple_pounces_get_all(void)
1057 return pounces;
1060 GList *purple_pounces_get_all_for_ui(const char *ui)
1062 GList *list = NULL, *iter;
1063 g_return_val_if_fail(ui != NULL, NULL);
1065 for (iter = pounces; iter; iter = iter->next) {
1066 PurplePounce *pounce = iter->data;
1067 if (purple_strequal(pounce->ui_type, ui))
1068 list = g_list_prepend(list, pounce);
1070 list = g_list_reverse(list);
1071 return list;
1074 static void
1075 free_pounce_handler(gpointer user_data)
1077 PurplePounceHandler *handler = (PurplePounceHandler *)user_data;
1079 g_free(handler->ui);
1080 g_free(handler);
1083 static void
1084 buddy_state_cb(PurpleBuddy *buddy, PurplePounceEvent event)
1086 PurpleAccount *account = purple_buddy_get_account(buddy);
1087 const gchar *name = purple_buddy_get_name(buddy);
1089 purple_pounce_execute(account, name, event);
1092 static void
1093 buddy_status_changed_cb(PurpleBuddy *buddy, PurpleStatus *old_status,
1094 PurpleStatus *status)
1096 PurpleAccount *account = purple_buddy_get_account(buddy);
1097 const gchar *name = purple_buddy_get_name(buddy);
1098 gboolean old_available, available;
1100 available = purple_status_is_available(status);
1101 old_available = purple_status_is_available(old_status);
1103 if (available && !old_available)
1104 purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY_RETURN);
1105 else if (!available && old_available)
1106 purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY);
1109 static void
1110 buddy_idle_changed_cb(PurpleBuddy *buddy, gboolean old_idle, gboolean idle)
1112 PurpleAccount *account = purple_buddy_get_account(buddy);
1113 const gchar *name = purple_buddy_get_name(buddy);
1115 if (idle && !old_idle)
1116 purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE);
1117 else if (!idle && old_idle)
1118 purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE_RETURN);
1121 static void
1122 buddy_typing_cb(PurpleAccount *account, const char *name, void *data)
1124 PurpleIMConversation *im;
1126 im = purple_conversations_find_im_with_account(name, account);
1127 if (im != NULL)
1129 PurpleIMTypingState state;
1130 PurplePounceEvent event;
1132 state = purple_im_conversation_get_typing_state(im);
1133 if (state == PURPLE_IM_TYPED)
1134 event = PURPLE_POUNCE_TYPED;
1135 else if (state == PURPLE_IM_NOT_TYPING)
1136 event = PURPLE_POUNCE_TYPING_STOPPED;
1137 else
1138 event = PURPLE_POUNCE_TYPING;
1140 purple_pounce_execute(account, name, event);
1144 static void
1145 received_message_cb(PurpleAccount *account, const char *name, void *data)
1147 purple_pounce_execute(account, name, PURPLE_POUNCE_MESSAGE_RECEIVED);
1150 void *
1151 purple_pounces_get_handle(void)
1153 static int pounce_handle;
1155 return &pounce_handle;
1158 void
1159 purple_pounces_init(void)
1161 void *handle = purple_pounces_get_handle();
1162 void *blist_handle = purple_blist_get_handle();
1163 void *conv_handle = purple_conversations_get_handle();
1165 pounce_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
1166 g_free, free_pounce_handler);
1168 purple_signal_connect(blist_handle, "buddy-idle-changed",
1169 handle, PURPLE_CALLBACK(buddy_idle_changed_cb), NULL);
1170 purple_signal_connect(blist_handle, "buddy-status-changed",
1171 handle, PURPLE_CALLBACK(buddy_status_changed_cb), NULL);
1172 purple_signal_connect(blist_handle, "buddy-signed-on",
1173 handle, PURPLE_CALLBACK(buddy_state_cb),
1174 GINT_TO_POINTER(PURPLE_POUNCE_SIGNON));
1175 purple_signal_connect(blist_handle, "buddy-signed-off",
1176 handle, PURPLE_CALLBACK(buddy_state_cb),
1177 GINT_TO_POINTER(PURPLE_POUNCE_SIGNOFF));
1179 purple_signal_connect(conv_handle, "buddy-typing",
1180 handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1181 purple_signal_connect(conv_handle, "buddy-typed",
1182 handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1183 purple_signal_connect(conv_handle, "buddy-typing-stopped",
1184 handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1186 purple_signal_connect(conv_handle, "received-im-msg",
1187 handle, PURPLE_CALLBACK(received_message_cb), NULL);
1189 purple_pounces_load();
1192 void
1193 purple_pounces_uninit()
1195 if (save_timer != 0)
1197 g_source_remove(save_timer);
1198 save_timer = 0;
1199 sync_pounces();
1202 purple_signals_disconnect_by_handle(purple_pounces_get_handle());
1204 g_hash_table_destroy(pounce_handlers);
1205 pounce_handlers = NULL;