Open an explorer.exe window at the location of the file when clicking
[pidgin-git.git] / libpurple / pounce.c
blob460c8010403e91a4dc1f85ea653dd84bfb3bd281
1 /**
2 * @file pounce.c Buddy Pounce API
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 #include "internal.h"
27 #include "conversation.h"
28 #include "debug.h"
29 #include "pounce.h"
31 #include "debug.h"
32 #include "pounce.h"
33 #include "util.h"
35 typedef struct
37 GString *buffer;
39 PurplePounce *pounce;
40 PurplePounceEvent events;
41 PurplePounceOption options;
43 char *ui_name;
44 char *pouncee;
45 char *protocol_id;
46 char *event_type;
47 char *option_type;
48 char *action_name;
49 char *param_name;
50 char *account_name;
52 } PounceParserData;
54 typedef struct
56 char *name;
58 gboolean enabled;
60 GHashTable *atts;
62 } PurplePounceActionData;
64 typedef struct
66 char *ui;
67 PurplePounceCb cb;
68 void (*new_pounce)(PurplePounce *);
69 void (*free_pounce)(PurplePounce *);
71 } PurplePounceHandler;
74 static GHashTable *pounce_handlers = NULL;
75 static GList *pounces = NULL;
76 static guint save_timer = 0;
77 static gboolean pounces_loaded = FALSE;
80 /*********************************************************************
81 * Private utility functions *
82 *********************************************************************/
84 static PurplePounceActionData *
85 find_action_data(const PurplePounce *pounce, const char *name)
87 PurplePounceActionData *action;
89 g_return_val_if_fail(pounce != NULL, NULL);
90 g_return_val_if_fail(name != NULL, NULL);
92 action = g_hash_table_lookup(pounce->actions, name);
94 return action;
97 static void
98 free_action_data(gpointer data)
100 PurplePounceActionData *action_data = data;
102 g_free(action_data->name);
104 g_hash_table_destroy(action_data->atts);
106 g_free(action_data);
110 /*********************************************************************
111 * Writing to disk *
112 *********************************************************************/
114 static void
115 action_parameter_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
117 const char *name, *param_value;
118 xmlnode *node, *child;
120 name = (const char *)key;
121 param_value = (const char *)value;
122 node = (xmlnode *)user_data;
124 child = xmlnode_new_child(node, "param");
125 xmlnode_set_attrib(child, "name", name);
126 xmlnode_insert_data(child, param_value, -1);
129 static void
130 action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
132 const char *action;
133 PurplePounceActionData *action_data;
134 xmlnode *node, *child;
136 action = (const char *)key;
137 action_data = (PurplePounceActionData *)value;
138 node = (xmlnode *)user_data;
140 if (!action_data->enabled)
141 return;
143 child = xmlnode_new_child(node, "action");
144 xmlnode_set_attrib(child, "type", action);
146 g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child);
149 static void
150 add_event_to_xmlnode(xmlnode *node, const char *type)
152 xmlnode *child;
154 child = xmlnode_new_child(node, "event");
155 xmlnode_set_attrib(child, "type", type);
158 static void
159 add_option_to_xmlnode(xmlnode *node, const char *type)
161 xmlnode *child;
163 child = xmlnode_new_child(node, "option");
164 xmlnode_set_attrib(child, "type", type);
167 static xmlnode *
168 pounce_to_xmlnode(PurplePounce *pounce)
170 xmlnode *node, *child;
171 PurpleAccount *pouncer;
172 PurplePounceEvent events;
173 PurplePounceOption options;
175 pouncer = purple_pounce_get_pouncer(pounce);
176 events = purple_pounce_get_events(pounce);
177 options = purple_pounce_get_options(pounce);
179 node = xmlnode_new("pounce");
180 xmlnode_set_attrib(node, "ui", pounce->ui_type);
182 child = xmlnode_new_child(node, "account");
183 xmlnode_set_attrib(child, "protocol", pouncer->protocol_id);
184 xmlnode_insert_data(child,
185 purple_normalize(pouncer, purple_account_get_username(pouncer)), -1);
187 child = xmlnode_new_child(node, "pouncee");
188 xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
190 /* Write pounce options */
191 child = xmlnode_new_child(node, "options");
192 if (options & PURPLE_POUNCE_OPTION_AWAY)
193 add_option_to_xmlnode(child, "on-away");
195 /* Write pounce events */
196 child = xmlnode_new_child(node, "events");
197 if (events & PURPLE_POUNCE_SIGNON)
198 add_event_to_xmlnode(child, "sign-on");
199 if (events & PURPLE_POUNCE_SIGNOFF)
200 add_event_to_xmlnode(child, "sign-off");
201 if (events & PURPLE_POUNCE_AWAY)
202 add_event_to_xmlnode(child, "away");
203 if (events & PURPLE_POUNCE_AWAY_RETURN)
204 add_event_to_xmlnode(child, "return-from-away");
205 if (events & PURPLE_POUNCE_IDLE)
206 add_event_to_xmlnode(child, "idle");
207 if (events & PURPLE_POUNCE_IDLE_RETURN)
208 add_event_to_xmlnode(child, "return-from-idle");
209 if (events & PURPLE_POUNCE_TYPING)
210 add_event_to_xmlnode(child, "start-typing");
211 if (events & PURPLE_POUNCE_TYPED)
212 add_event_to_xmlnode(child, "typed");
213 if (events & PURPLE_POUNCE_TYPING_STOPPED)
214 add_event_to_xmlnode(child, "stop-typing");
215 if (events & PURPLE_POUNCE_MESSAGE_RECEIVED)
216 add_event_to_xmlnode(child, "message-received");
218 /* Write pounce actions */
219 child = xmlnode_new_child(node, "actions");
220 g_hash_table_foreach(pounce->actions, action_parameter_list_to_xmlnode, child);
222 if (purple_pounce_get_save(pounce))
223 child = xmlnode_new_child(node, "save");
225 return node;
228 static xmlnode *
229 pounces_to_xmlnode(void)
231 xmlnode *node, *child;
232 GList *cur;
234 node = xmlnode_new("pounces");
235 xmlnode_set_attrib(node, "version", "1.0");
237 for (cur = purple_pounces_get_all(); cur != NULL; cur = cur->next)
239 child = pounce_to_xmlnode(cur->data);
240 xmlnode_insert_child(node, child);
243 return node;
246 static void
247 sync_pounces(void)
249 xmlnode *node;
250 char *data;
252 if (!pounces_loaded)
254 purple_debug_error("pounce", "Attempted to save buddy pounces before "
255 "they were read!\n");
256 return;
259 node = pounces_to_xmlnode();
260 data = xmlnode_to_formatted_str(node, NULL);
261 purple_util_write_data_to_file("pounces.xml", data, -1);
262 g_free(data);
263 xmlnode_free(node);
266 static gboolean
267 save_cb(gpointer data)
269 sync_pounces();
270 save_timer = 0;
271 return FALSE;
274 static void
275 schedule_pounces_save(void)
277 if (save_timer == 0)
278 save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
282 /*********************************************************************
283 * Reading from disk *
284 *********************************************************************/
286 static void
287 free_parser_data(gpointer user_data)
289 PounceParserData *data = user_data;
291 if (data->buffer != NULL)
292 g_string_free(data->buffer, TRUE);
294 g_free(data->ui_name);
295 g_free(data->pouncee);
296 g_free(data->protocol_id);
297 g_free(data->event_type);
298 g_free(data->option_type);
299 g_free(data->action_name);
300 g_free(data->param_name);
301 g_free(data->account_name);
303 g_free(data);
306 static void
307 start_element_handler(GMarkupParseContext *context,
308 const gchar *element_name,
309 const gchar **attribute_names,
310 const gchar **attribute_values,
311 gpointer user_data, GError **error)
313 PounceParserData *data = user_data;
314 GHashTable *atts;
315 int i;
317 atts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
319 for (i = 0; attribute_names[i] != NULL; i++) {
320 g_hash_table_insert(atts, g_strdup(attribute_names[i]),
321 g_strdup(attribute_values[i]));
324 if (data->buffer != NULL) {
325 g_string_free(data->buffer, TRUE);
326 data->buffer = NULL;
329 if (purple_strequal(element_name, "pounce")) {
330 const char *ui = g_hash_table_lookup(atts, "ui");
332 if (ui == NULL) {
333 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
334 "Unset 'ui' parameter for pounce!\n");
336 else
337 data->ui_name = g_strdup(ui);
339 data->events = 0;
341 else if (purple_strequal(element_name, "account")) {
342 const char *protocol_id = g_hash_table_lookup(atts, "protocol");
344 if (protocol_id == NULL) {
345 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
346 "Unset 'protocol' parameter for account!\n");
348 else
349 data->protocol_id = g_strdup(protocol_id);
351 else if (purple_strequal(element_name, "option")) {
352 const char *type = g_hash_table_lookup(atts, "type");
354 if (type == NULL) {
355 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
356 "Unset 'type' parameter for option!\n");
358 else
359 data->option_type = g_strdup(type);
361 else if (purple_strequal(element_name, "event")) {
362 const char *type = g_hash_table_lookup(atts, "type");
364 if (type == NULL) {
365 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
366 "Unset 'type' parameter for event!\n");
368 else
369 data->event_type = g_strdup(type);
371 else if (purple_strequal(element_name, "action")) {
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 action!\n");
378 else
379 data->action_name = g_strdup(type);
381 else if (purple_strequal(element_name, "param")) {
382 const char *param_name = g_hash_table_lookup(atts, "name");
384 if (param_name == NULL) {
385 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
386 "Unset 'name' parameter for param!\n");
388 else
389 data->param_name = g_strdup(param_name);
392 g_hash_table_destroy(atts);
395 static void
396 end_element_handler(GMarkupParseContext *context, const gchar *element_name,
397 gpointer user_data, GError **error)
399 PounceParserData *data = user_data;
400 gchar *buffer = NULL;
402 if (data->buffer != NULL) {
403 buffer = g_string_free(data->buffer, FALSE);
404 data->buffer = NULL;
407 if (purple_strequal(element_name, "account")) {
408 char *tmp;
409 g_free(data->account_name);
410 data->account_name = g_strdup(buffer);
411 tmp = data->protocol_id;
412 data->protocol_id = g_strdup(_purple_oscar_convert(buffer, tmp));
413 g_free(tmp);
415 else if (purple_strequal(element_name, "pouncee")) {
416 g_free(data->pouncee);
417 data->pouncee = g_strdup(buffer);
419 else if (purple_strequal(element_name, "option")) {
420 if (purple_strequal(data->option_type, "on-away"))
421 data->options |= PURPLE_POUNCE_OPTION_AWAY;
423 g_free(data->option_type);
424 data->option_type = NULL;
426 else if (purple_strequal(element_name, "event")) {
427 if (purple_strequal(data->event_type, "sign-on"))
428 data->events |= PURPLE_POUNCE_SIGNON;
429 else if (purple_strequal(data->event_type, "sign-off"))
430 data->events |= PURPLE_POUNCE_SIGNOFF;
431 else if (purple_strequal(data->event_type, "away"))
432 data->events |= PURPLE_POUNCE_AWAY;
433 else if (purple_strequal(data->event_type, "return-from-away"))
434 data->events |= PURPLE_POUNCE_AWAY_RETURN;
435 else if (purple_strequal(data->event_type, "idle"))
436 data->events |= PURPLE_POUNCE_IDLE;
437 else if (purple_strequal(data->event_type, "return-from-idle"))
438 data->events |= PURPLE_POUNCE_IDLE_RETURN;
439 else if (purple_strequal(data->event_type, "start-typing"))
440 data->events |= PURPLE_POUNCE_TYPING;
441 else if (purple_strequal(data->event_type, "typed"))
442 data->events |= PURPLE_POUNCE_TYPED;
443 else if (purple_strequal(data->event_type, "stop-typing"))
444 data->events |= PURPLE_POUNCE_TYPING_STOPPED;
445 else if (purple_strequal(data->event_type, "message-received"))
446 data->events |= PURPLE_POUNCE_MESSAGE_RECEIVED;
448 g_free(data->event_type);
449 data->event_type = NULL;
451 else if (purple_strequal(element_name, "action")) {
452 if (data->pounce != NULL) {
453 purple_pounce_action_register(data->pounce, data->action_name);
454 purple_pounce_action_set_enabled(data->pounce, data->action_name, TRUE);
457 g_free(data->action_name);
458 data->action_name = NULL;
460 else if (purple_strequal(element_name, "param")) {
461 if (data->pounce != NULL) {
462 purple_pounce_action_set_attribute(data->pounce, data->action_name,
463 data->param_name, buffer);
466 g_free(data->param_name);
467 data->param_name = NULL;
469 else if (purple_strequal(element_name, "events")) {
470 PurpleAccount *account;
472 account = purple_accounts_find(data->account_name, data->protocol_id);
474 g_free(data->account_name);
475 g_free(data->protocol_id);
477 data->account_name = NULL;
478 data->protocol_id = NULL;
480 if (account == NULL) {
481 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
482 "Account for pounce not found!\n");
484 * This pounce has effectively been removed, so make
485 * sure that we save the changes to pounces.xml
487 schedule_pounces_save();
489 else {
490 purple_debug(PURPLE_DEBUG_INFO, "pounce",
491 "Creating pounce: %s, %s\n", data->ui_name,
492 data->pouncee);
494 data->pounce = purple_pounce_new(data->ui_name, account,
495 data->pouncee, data->events,
496 data->options);
499 g_free(data->pouncee);
500 data->pouncee = NULL;
502 else if (purple_strequal(element_name, "save")) {
503 if (data->pounce != NULL)
504 purple_pounce_set_save(data->pounce, TRUE);
506 else if (purple_strequal(element_name, "pounce")) {
507 data->pounce = NULL;
508 data->events = 0;
509 data->options = 0;
511 g_free(data->ui_name);
512 g_free(data->pouncee);
513 g_free(data->protocol_id);
514 g_free(data->event_type);
515 g_free(data->option_type);
516 g_free(data->action_name);
517 g_free(data->param_name);
518 g_free(data->account_name);
520 data->ui_name = NULL;
521 data->pounce = NULL;
522 data->protocol_id = NULL;
523 data->event_type = NULL;
524 data->option_type = NULL;
525 data->action_name = NULL;
526 data->param_name = NULL;
527 data->account_name = NULL;
530 g_free(buffer);
533 static void
534 text_handler(GMarkupParseContext *context, const gchar *text,
535 gsize text_len, gpointer user_data, GError **error)
537 PounceParserData *data = user_data;
539 if (data->buffer == NULL)
540 data->buffer = g_string_new_len(text, text_len);
541 else
542 g_string_append_len(data->buffer, text, text_len);
545 static GMarkupParser pounces_parser =
547 start_element_handler,
548 end_element_handler,
549 text_handler,
550 NULL,
551 NULL
554 gboolean
555 purple_pounces_load(void)
557 gchar *filename = g_build_filename(purple_user_dir(), "pounces.xml", NULL);
558 gchar *contents = NULL;
559 gsize length;
560 GMarkupParseContext *context;
561 GError *error = NULL;
562 PounceParserData *parser_data;
564 if (filename == NULL) {
565 pounces_loaded = TRUE;
566 return FALSE;
569 if (!g_file_get_contents(filename, &contents, &length, &error)) {
570 purple_debug(PURPLE_DEBUG_ERROR, "pounce",
571 "Error reading pounces: %s\n", error->message);
573 g_free(filename);
574 g_error_free(error);
576 pounces_loaded = TRUE;
577 return FALSE;
580 parser_data = g_new0(PounceParserData, 1);
582 context = g_markup_parse_context_new(&pounces_parser, 0,
583 parser_data, free_parser_data);
585 if (!g_markup_parse_context_parse(context, contents, length, NULL)) {
586 g_markup_parse_context_free(context);
587 g_free(contents);
588 g_free(filename);
590 pounces_loaded = TRUE;
592 return FALSE;
595 if (!g_markup_parse_context_end_parse(context, NULL)) {
596 purple_debug(PURPLE_DEBUG_ERROR, "pounce", "Error parsing %s\n",
597 filename);
599 g_markup_parse_context_free(context);
600 g_free(contents);
601 g_free(filename);
602 pounces_loaded = TRUE;
604 return FALSE;
607 g_markup_parse_context_free(context);
608 g_free(contents);
609 g_free(filename);
611 pounces_loaded = TRUE;
613 return TRUE;
617 PurplePounce *
618 purple_pounce_new(const char *ui_type, PurpleAccount *pouncer,
619 const char *pouncee, PurplePounceEvent event,
620 PurplePounceOption option)
622 PurplePounce *pounce;
623 PurplePounceHandler *handler;
625 g_return_val_if_fail(ui_type != NULL, NULL);
626 g_return_val_if_fail(pouncer != NULL, NULL);
627 g_return_val_if_fail(pouncee != NULL, NULL);
628 g_return_val_if_fail(event != 0, NULL);
630 pounce = g_new0(PurplePounce, 1);
632 pounce->ui_type = g_strdup(ui_type);
633 pounce->pouncer = pouncer;
634 pounce->pouncee = g_strdup(pouncee);
635 pounce->events = event;
636 pounce->options = option;
638 pounce->actions = g_hash_table_new_full(g_str_hash, g_str_equal,
639 g_free, free_action_data);
641 handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
643 if (handler != NULL && handler->new_pounce != NULL)
644 handler->new_pounce(pounce);
646 pounces = g_list_append(pounces, pounce);
648 schedule_pounces_save();
650 return pounce;
653 void
654 purple_pounce_destroy(PurplePounce *pounce)
656 PurplePounceHandler *handler;
658 g_return_if_fail(pounce != NULL);
660 handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
662 pounces = g_list_remove(pounces, pounce);
664 g_free(pounce->ui_type);
665 g_free(pounce->pouncee);
667 g_hash_table_destroy(pounce->actions);
669 if (handler != NULL && handler->free_pounce != NULL)
670 handler->free_pounce(pounce);
672 g_free(pounce);
674 schedule_pounces_save();
677 void
678 purple_pounce_destroy_all_by_account(PurpleAccount *account)
680 PurpleAccount *pouncer;
681 PurplePounce *pounce;
682 GList *l, *l_next;
684 g_return_if_fail(account != NULL);
686 for (l = purple_pounces_get_all(); l != NULL; l = l_next)
688 pounce = (PurplePounce *)l->data;
689 l_next = l->next;
691 pouncer = purple_pounce_get_pouncer(pounce);
692 if (pouncer == account)
693 purple_pounce_destroy(pounce);
697 void
698 purple_pounce_destroy_all_by_buddy(PurpleBuddy *buddy)
700 const char *pouncee, *bname;
701 PurpleAccount *pouncer, *bacct;
702 PurplePounce *pounce;
703 GList *l, *l_next;
705 g_return_if_fail(buddy != NULL);
707 bacct = purple_buddy_get_account(buddy);
708 bname = purple_buddy_get_name(buddy);
710 for (l = purple_pounces_get_all(); l != NULL; l = l_next) {
711 pounce = (PurplePounce *)l->data;
712 l_next = l->next;
714 pouncer = purple_pounce_get_pouncer(pounce);
715 pouncee = purple_pounce_get_pouncee(pounce);
717 if ( (pouncer == bacct) && (strcmp(pouncee, bname) == 0) )
718 purple_pounce_destroy(pounce);
722 void
723 purple_pounce_set_events(PurplePounce *pounce, PurplePounceEvent events)
725 g_return_if_fail(pounce != NULL);
726 g_return_if_fail(events != PURPLE_POUNCE_NONE);
728 pounce->events = events;
730 schedule_pounces_save();
733 void
734 purple_pounce_set_options(PurplePounce *pounce, PurplePounceOption options)
736 g_return_if_fail(pounce != NULL);
738 pounce->options = options;
740 schedule_pounces_save();
743 void
744 purple_pounce_set_pouncer(PurplePounce *pounce, PurpleAccount *pouncer)
746 g_return_if_fail(pounce != NULL);
747 g_return_if_fail(pouncer != NULL);
749 pounce->pouncer = pouncer;
751 schedule_pounces_save();
754 void
755 purple_pounce_set_pouncee(PurplePounce *pounce, const char *pouncee)
757 g_return_if_fail(pounce != NULL);
758 g_return_if_fail(pouncee != NULL);
760 g_free(pounce->pouncee);
761 pounce->pouncee = g_strdup(pouncee);
763 schedule_pounces_save();
766 void
767 purple_pounce_set_save(PurplePounce *pounce, gboolean save)
769 g_return_if_fail(pounce != NULL);
771 pounce->save = save;
773 schedule_pounces_save();
776 void
777 purple_pounce_action_register(PurplePounce *pounce, const char *name)
779 PurplePounceActionData *action_data;
781 g_return_if_fail(pounce != NULL);
782 g_return_if_fail(name != NULL);
784 if (g_hash_table_lookup(pounce->actions, name) != NULL)
785 return;
787 action_data = g_new0(PurplePounceActionData, 1);
789 action_data->name = g_strdup(name);
790 action_data->enabled = FALSE;
791 action_data->atts = g_hash_table_new_full(g_str_hash, g_str_equal,
792 g_free, g_free);
794 g_hash_table_insert(pounce->actions, g_strdup(name), action_data);
796 schedule_pounces_save();
799 void
800 purple_pounce_action_set_enabled(PurplePounce *pounce, const char *action,
801 gboolean enabled)
803 PurplePounceActionData *action_data;
805 g_return_if_fail(pounce != NULL);
806 g_return_if_fail(action != NULL);
808 action_data = find_action_data(pounce, action);
810 g_return_if_fail(action_data != NULL);
812 action_data->enabled = enabled;
814 schedule_pounces_save();
817 void
818 purple_pounce_action_set_attribute(PurplePounce *pounce, const char *action,
819 const char *attr, const char *value)
821 PurplePounceActionData *action_data;
823 g_return_if_fail(pounce != NULL);
824 g_return_if_fail(action != NULL);
825 g_return_if_fail(attr != NULL);
827 action_data = find_action_data(pounce, action);
829 g_return_if_fail(action_data != NULL);
831 if (value == NULL)
832 g_hash_table_remove(action_data->atts, attr);
833 else
834 g_hash_table_insert(action_data->atts, g_strdup(attr),
835 g_strdup(value));
837 schedule_pounces_save();
840 void
841 purple_pounce_set_data(PurplePounce *pounce, void *data)
843 g_return_if_fail(pounce != NULL);
845 pounce->data = data;
847 schedule_pounces_save();
850 PurplePounceEvent
851 purple_pounce_get_events(const PurplePounce *pounce)
853 g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_NONE);
855 return pounce->events;
858 PurplePounceOption
859 purple_pounce_get_options(const PurplePounce *pounce)
861 g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_OPTION_NONE);
863 return pounce->options;
866 PurpleAccount *
867 purple_pounce_get_pouncer(const PurplePounce *pounce)
869 g_return_val_if_fail(pounce != NULL, NULL);
871 return pounce->pouncer;
874 const char *
875 purple_pounce_get_pouncee(const PurplePounce *pounce)
877 g_return_val_if_fail(pounce != NULL, NULL);
879 return pounce->pouncee;
882 gboolean
883 purple_pounce_get_save(const PurplePounce *pounce)
885 g_return_val_if_fail(pounce != NULL, FALSE);
887 return pounce->save;
890 gboolean
891 purple_pounce_action_is_enabled(const PurplePounce *pounce, const char *action)
893 PurplePounceActionData *action_data;
895 g_return_val_if_fail(pounce != NULL, FALSE);
896 g_return_val_if_fail(action != NULL, FALSE);
898 action_data = find_action_data(pounce, action);
900 g_return_val_if_fail(action_data != NULL, FALSE);
902 return action_data->enabled;
905 const char *
906 purple_pounce_action_get_attribute(const PurplePounce *pounce,
907 const char *action, const char *attr)
909 PurplePounceActionData *action_data;
911 g_return_val_if_fail(pounce != NULL, NULL);
912 g_return_val_if_fail(action != NULL, NULL);
913 g_return_val_if_fail(attr != NULL, NULL);
915 action_data = find_action_data(pounce, action);
917 g_return_val_if_fail(action_data != NULL, NULL);
919 return g_hash_table_lookup(action_data->atts, attr);
922 void *
923 purple_pounce_get_data(const PurplePounce *pounce)
925 g_return_val_if_fail(pounce != NULL, NULL);
927 return pounce->data;
930 void
931 purple_pounce_execute(const PurpleAccount *pouncer, const char *pouncee,
932 PurplePounceEvent events)
934 PurplePounce *pounce;
935 PurplePounceHandler *handler;
936 PurplePresence *presence;
937 GList *l, *l_next;
938 char *norm_pouncee;
940 g_return_if_fail(pouncer != NULL);
941 g_return_if_fail(pouncee != NULL);
942 g_return_if_fail(events != PURPLE_POUNCE_NONE);
944 norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
946 for (l = purple_pounces_get_all(); l != NULL; l = l_next)
948 pounce = (PurplePounce *)l->data;
949 l_next = l->next;
951 presence = purple_account_get_presence(pouncer);
953 if ((purple_pounce_get_events(pounce) & events) &&
954 (purple_pounce_get_pouncer(pounce) == pouncer) &&
955 !purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
956 norm_pouncee) &&
957 (pounce->options == PURPLE_POUNCE_OPTION_NONE ||
958 (pounce->options & PURPLE_POUNCE_OPTION_AWAY &&
959 !purple_presence_is_available(presence))))
961 handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
963 if (handler != NULL && handler->cb != NULL)
965 handler->cb(pounce, events, purple_pounce_get_data(pounce));
967 if (!purple_pounce_get_save(pounce))
968 purple_pounce_destroy(pounce);
973 g_free(norm_pouncee);
976 PurplePounce *
977 purple_find_pounce(const PurpleAccount *pouncer, const char *pouncee,
978 PurplePounceEvent events)
980 PurplePounce *pounce = NULL;
981 GList *l;
982 char *norm_pouncee;
984 g_return_val_if_fail(pouncer != NULL, NULL);
985 g_return_val_if_fail(pouncee != NULL, NULL);
986 g_return_val_if_fail(events != PURPLE_POUNCE_NONE, NULL);
988 norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
990 for (l = purple_pounces_get_all(); l != NULL; l = l->next)
992 pounce = (PurplePounce *)l->data;
994 if ((purple_pounce_get_events(pounce) & events) &&
995 (purple_pounce_get_pouncer(pounce) == pouncer) &&
996 !purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
997 norm_pouncee))
999 break;
1002 pounce = NULL;
1005 g_free(norm_pouncee);
1007 return pounce;
1010 void
1011 purple_pounces_register_handler(const char *ui, PurplePounceCb cb,
1012 void (*new_pounce)(PurplePounce *pounce),
1013 void (*free_pounce)(PurplePounce *pounce))
1015 PurplePounceHandler *handler;
1017 g_return_if_fail(ui != NULL);
1018 g_return_if_fail(cb != NULL);
1020 handler = g_new0(PurplePounceHandler, 1);
1022 handler->ui = g_strdup(ui);
1023 handler->cb = cb;
1024 handler->new_pounce = new_pounce;
1025 handler->free_pounce = free_pounce;
1027 g_hash_table_insert(pounce_handlers, g_strdup(ui), handler);
1030 void
1031 purple_pounces_unregister_handler(const char *ui)
1033 g_return_if_fail(ui != NULL);
1035 g_hash_table_remove(pounce_handlers, ui);
1038 GList *
1039 purple_pounces_get_all(void)
1041 return pounces;
1044 GList *purple_pounces_get_all_for_ui(const char *ui)
1046 GList *list = NULL, *iter;
1047 g_return_val_if_fail(ui != NULL, NULL);
1049 for (iter = pounces; iter; iter = iter->next) {
1050 PurplePounce *pounce = iter->data;
1051 if (purple_strequal(pounce->ui_type, ui))
1052 list = g_list_prepend(list, pounce);
1054 list = g_list_reverse(list);
1055 return list;
1058 static void
1059 free_pounce_handler(gpointer user_data)
1061 PurplePounceHandler *handler = (PurplePounceHandler *)user_data;
1063 g_free(handler->ui);
1064 g_free(handler);
1067 static void
1068 buddy_state_cb(PurpleBuddy *buddy, PurplePounceEvent event)
1070 PurpleAccount *account = purple_buddy_get_account(buddy);
1071 const gchar *name = purple_buddy_get_name(buddy);
1073 purple_pounce_execute(account, name, event);
1076 static void
1077 buddy_status_changed_cb(PurpleBuddy *buddy, PurpleStatus *old_status,
1078 PurpleStatus *status)
1080 PurpleAccount *account = purple_buddy_get_account(buddy);
1081 const gchar *name = purple_buddy_get_name(buddy);
1082 gboolean old_available, available;
1084 available = purple_status_is_available(status);
1085 old_available = purple_status_is_available(old_status);
1087 if (available && !old_available)
1088 purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY_RETURN);
1089 else if (!available && old_available)
1090 purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY);
1093 static void
1094 buddy_idle_changed_cb(PurpleBuddy *buddy, gboolean old_idle, gboolean idle)
1096 PurpleAccount *account = purple_buddy_get_account(buddy);
1097 const gchar *name = purple_buddy_get_name(buddy);
1099 if (idle && !old_idle)
1100 purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE);
1101 else if (!idle && old_idle)
1102 purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE_RETURN);
1105 static void
1106 buddy_typing_cb(PurpleAccount *account, const char *name, void *data)
1108 PurpleConversation *conv;
1110 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, account);
1111 if (conv != NULL)
1113 PurpleTypingState state;
1114 PurplePounceEvent event;
1116 state = purple_conv_im_get_typing_state(PURPLE_CONV_IM(conv));
1117 if (state == PURPLE_TYPED)
1118 event = PURPLE_POUNCE_TYPED;
1119 else if (state == PURPLE_NOT_TYPING)
1120 event = PURPLE_POUNCE_TYPING_STOPPED;
1121 else
1122 event = PURPLE_POUNCE_TYPING;
1124 purple_pounce_execute(account, name, event);
1128 static void
1129 received_message_cb(PurpleAccount *account, const char *name, void *data)
1131 purple_pounce_execute(account, name, PURPLE_POUNCE_MESSAGE_RECEIVED);
1134 void *
1135 purple_pounces_get_handle(void)
1137 static int pounce_handle;
1139 return &pounce_handle;
1142 void
1143 purple_pounces_init(void)
1145 void *handle = purple_pounces_get_handle();
1146 void *blist_handle = purple_blist_get_handle();
1147 void *conv_handle = purple_conversations_get_handle();
1149 pounce_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
1150 g_free, free_pounce_handler);
1152 purple_signal_connect(blist_handle, "buddy-idle-changed",
1153 handle, PURPLE_CALLBACK(buddy_idle_changed_cb), NULL);
1154 purple_signal_connect(blist_handle, "buddy-status-changed",
1155 handle, PURPLE_CALLBACK(buddy_status_changed_cb), NULL);
1156 purple_signal_connect(blist_handle, "buddy-signed-on",
1157 handle, PURPLE_CALLBACK(buddy_state_cb),
1158 GINT_TO_POINTER(PURPLE_POUNCE_SIGNON));
1159 purple_signal_connect(blist_handle, "buddy-signed-off",
1160 handle, PURPLE_CALLBACK(buddy_state_cb),
1161 GINT_TO_POINTER(PURPLE_POUNCE_SIGNOFF));
1163 purple_signal_connect(conv_handle, "buddy-typing",
1164 handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1165 purple_signal_connect(conv_handle, "buddy-typed",
1166 handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1167 purple_signal_connect(conv_handle, "buddy-typing-stopped",
1168 handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1170 purple_signal_connect(conv_handle, "received-im-msg",
1171 handle, PURPLE_CALLBACK(received_message_cb), NULL);
1174 void
1175 purple_pounces_uninit()
1177 if (save_timer != 0)
1179 purple_timeout_remove(save_timer);
1180 save_timer = 0;
1181 sync_pounces();
1184 purple_signals_disconnect_by_handle(purple_pounces_get_handle());
1186 g_hash_table_destroy(pounce_handlers);
1187 pounce_handlers = NULL;