Coverage Report

Created: 2025-12-31 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/pounce.c
Line
Count
Source
1
/**
2
 * @file pounce.c Buddy Pounce API
3
 * @ingroup core
4
 */
5
6
/* purple
7
 *
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.
11
 *
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.
16
 *
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.
21
 *
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
25
 */
26
#include "internal.h"
27
#include "conversation.h"
28
#include "debug.h"
29
#include "pounce.h"
30
31
#include "debug.h"
32
#include "pounce.h"
33
#include "util.h"
34
35
typedef struct
36
{
37
  GString *buffer;
38
39
  PurplePounce *pounce;
40
  PurplePounceEvent events;
41
  PurplePounceOption options;
42
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;
51
52
} PounceParserData;
53
54
typedef struct
55
{
56
  char *name;
57
58
  gboolean enabled;
59
60
  GHashTable *atts;
61
62
} PurplePounceActionData;
63
64
typedef struct
65
{
66
  char *ui;
67
  PurplePounceCb cb;
68
  void (*new_pounce)(PurplePounce *);
69
  void (*free_pounce)(PurplePounce *);
70
71
} PurplePounceHandler;
72
73
74
static GHashTable *pounce_handlers = NULL;
75
static GList      *pounces = NULL;
76
static guint       save_timer = 0;
77
static gboolean    pounces_loaded = FALSE;
78
79
80
/*********************************************************************
81
 * Private utility functions                                         *
82
 *********************************************************************/
83
84
static PurplePounceActionData *
85
find_action_data(const PurplePounce *pounce, const char *name)
86
0
{
87
0
  PurplePounceActionData *action;
88
89
0
  g_return_val_if_fail(pounce != NULL, NULL);
90
0
  g_return_val_if_fail(name   != NULL, NULL);
91
92
0
  action = g_hash_table_lookup(pounce->actions, name);
93
94
0
  return action;
95
0
}
96
97
static void
98
free_action_data(gpointer data)
99
0
{
100
0
  PurplePounceActionData *action_data = data;
101
102
0
  g_free(action_data->name);
103
104
0
  g_hash_table_destroy(action_data->atts);
105
106
0
  g_free(action_data);
107
0
}
108
109
110
/*********************************************************************
111
 * Writing to disk                                                   *
112
 *********************************************************************/
113
114
static void
115
action_parameter_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
116
0
{
117
0
  const char *name, *param_value;
118
0
  xmlnode *node, *child;
119
120
0
  name        = (const char *)key;
121
0
  param_value = (const char *)value;
122
0
  node        = (xmlnode *)user_data;
123
124
0
  child = xmlnode_new_child(node, "param");
125
0
  xmlnode_set_attrib(child, "name", name);
126
0
  xmlnode_insert_data(child, param_value, -1);
127
0
}
128
129
static void
130
action_parameter_list_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
131
0
{
132
0
  const char *action;
133
0
  PurplePounceActionData *action_data;
134
0
  xmlnode *node, *child;
135
136
0
  action      = (const char *)key;
137
0
  action_data = (PurplePounceActionData *)value;
138
0
  node        = (xmlnode *)user_data;
139
140
0
  if (!action_data->enabled)
141
0
    return;
142
143
0
  child = xmlnode_new_child(node, "action");
144
0
  xmlnode_set_attrib(child, "type", action);
145
146
0
  g_hash_table_foreach(action_data->atts, action_parameter_to_xmlnode, child);
147
0
}
148
149
static void
150
add_event_to_xmlnode(xmlnode *node, const char *type)
151
0
{
152
0
  xmlnode *child;
153
154
0
  child = xmlnode_new_child(node, "event");
155
0
  xmlnode_set_attrib(child, "type", type);
156
0
}
157
158
static void
159
add_option_to_xmlnode(xmlnode *node, const char *type)
160
0
{
161
0
  xmlnode *child;
162
163
0
  child = xmlnode_new_child(node, "option");
164
0
  xmlnode_set_attrib(child, "type", type);
165
0
}
166
167
static xmlnode *
168
pounce_to_xmlnode(PurplePounce *pounce)
169
0
{
170
0
  xmlnode *node, *child;
171
0
  PurpleAccount *pouncer;
172
0
  PurplePounceEvent events;
173
0
  PurplePounceOption options;
174
175
0
  pouncer = purple_pounce_get_pouncer(pounce);
176
0
  events  = purple_pounce_get_events(pounce);
177
0
  options = purple_pounce_get_options(pounce);
178
179
0
  node = xmlnode_new("pounce");
180
0
  xmlnode_set_attrib(node, "ui", pounce->ui_type);
181
182
0
  child = xmlnode_new_child(node, "account");
183
0
  xmlnode_set_attrib(child, "protocol", pouncer->protocol_id);
184
0
  xmlnode_insert_data(child,
185
0
      purple_normalize(pouncer, purple_account_get_username(pouncer)), -1);
186
187
0
  child = xmlnode_new_child(node, "pouncee");
188
0
  xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
189
190
  /* Write pounce options */
191
0
  child = xmlnode_new_child(node, "options");
192
0
  if (options & PURPLE_POUNCE_OPTION_AWAY)
193
0
    add_option_to_xmlnode(child, "on-away");
194
195
  /* Write pounce events */
196
0
  child = xmlnode_new_child(node, "events");
197
0
  if (events & PURPLE_POUNCE_SIGNON)
198
0
    add_event_to_xmlnode(child, "sign-on");
199
0
  if (events & PURPLE_POUNCE_SIGNOFF)
200
0
    add_event_to_xmlnode(child, "sign-off");
201
0
  if (events & PURPLE_POUNCE_AWAY)
202
0
    add_event_to_xmlnode(child, "away");
203
0
  if (events & PURPLE_POUNCE_AWAY_RETURN)
204
0
    add_event_to_xmlnode(child, "return-from-away");
205
0
  if (events & PURPLE_POUNCE_IDLE)
206
0
    add_event_to_xmlnode(child, "idle");
207
0
  if (events & PURPLE_POUNCE_IDLE_RETURN)
208
0
    add_event_to_xmlnode(child, "return-from-idle");
209
0
  if (events & PURPLE_POUNCE_TYPING)
210
0
    add_event_to_xmlnode(child, "start-typing");
211
0
  if (events & PURPLE_POUNCE_TYPED)
212
0
    add_event_to_xmlnode(child, "typed");
213
0
  if (events & PURPLE_POUNCE_TYPING_STOPPED)
214
0
    add_event_to_xmlnode(child, "stop-typing");
215
0
  if (events & PURPLE_POUNCE_MESSAGE_RECEIVED)
216
0
    add_event_to_xmlnode(child, "message-received");
217
218
  /* Write pounce actions */
219
0
  child = xmlnode_new_child(node, "actions");
220
0
  g_hash_table_foreach(pounce->actions, action_parameter_list_to_xmlnode, child);
221
222
0
  if (purple_pounce_get_save(pounce))
223
0
    xmlnode_new_child(node, "save");
224
225
0
  return node;
226
0
}
227
228
static xmlnode *
229
pounces_to_xmlnode(void)
230
0
{
231
0
  xmlnode *node, *child;
232
0
  GList *cur;
233
234
0
  node = xmlnode_new("pounces");
235
0
  xmlnode_set_attrib(node, "version", "1.0");
236
237
0
  for (cur = purple_pounces_get_all(); cur != NULL; cur = cur->next)
238
0
  {
239
0
    child = pounce_to_xmlnode(cur->data);
240
0
    xmlnode_insert_child(node, child);
241
0
  }
242
243
0
  return node;
244
0
}
245
246
static void
247
sync_pounces(void)
248
0
{
249
0
  xmlnode *node;
250
0
  char *data;
251
252
0
  if (!pounces_loaded)
253
0
  {
254
0
    purple_debug_error("pounce", "Attempted to save buddy pounces before "
255
0
             "they were read!\n");
256
0
    return;
257
0
  }
258
259
0
  node = pounces_to_xmlnode();
260
0
  data = xmlnode_to_formatted_str(node, NULL);
261
0
  purple_util_write_data_to_file("pounces.xml", data, -1);
262
0
  g_free(data);
263
0
  xmlnode_free(node);
264
0
}
265
266
static gboolean
267
save_cb(gpointer data)
268
0
{
269
0
  sync_pounces();
270
0
  save_timer = 0;
271
0
  return FALSE;
272
0
}
273
274
static void
275
schedule_pounces_save(void)
276
0
{
277
0
  if (save_timer == 0)
278
0
    save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
279
0
}
280
281
282
/*********************************************************************
283
 * Reading from disk                                                 *
284
 *********************************************************************/
285
286
static void
287
free_parser_data(gpointer user_data)
288
0
{
289
0
  PounceParserData *data = user_data;
290
291
0
  if (data->buffer != NULL)
292
0
    g_string_free(data->buffer, TRUE);
293
294
0
  g_free(data->ui_name);
295
0
  g_free(data->pouncee);
296
0
  g_free(data->protocol_id);
297
0
  g_free(data->event_type);
298
0
  g_free(data->option_type);
299
0
  g_free(data->action_name);
300
0
  g_free(data->param_name);
301
0
  g_free(data->account_name);
302
303
0
  g_free(data);
304
0
}
305
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)
312
0
{
313
0
  PounceParserData *data = user_data;
314
0
  GHashTable *atts;
315
0
  int i;
316
317
0
  atts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
318
319
0
  for (i = 0; attribute_names[i] != NULL; i++) {
320
0
    g_hash_table_insert(atts, g_strdup(attribute_names[i]),
321
0
              g_strdup(attribute_values[i]));
322
0
  }
323
324
0
  if (data->buffer != NULL) {
325
0
    g_string_free(data->buffer, TRUE);
326
0
    data->buffer = NULL;
327
0
  }
328
329
0
  if (purple_strequal(element_name, "pounce")) {
330
0
    const char *ui = g_hash_table_lookup(atts, "ui");
331
332
0
    if (ui == NULL) {
333
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
334
0
             "Unset 'ui' parameter for pounce!\n");
335
0
    }
336
0
    else
337
0
      data->ui_name = g_strdup(ui);
338
339
0
    data->events = 0;
340
0
  }
341
0
  else if (purple_strequal(element_name, "account")) {
342
0
    const char *protocol_id = g_hash_table_lookup(atts, "protocol");
343
344
0
    if (protocol_id == NULL) {
345
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
346
0
             "Unset 'protocol' parameter for account!\n");
347
0
    }
348
0
    else
349
0
      data->protocol_id = g_strdup(protocol_id);
350
0
  }
351
0
  else if (purple_strequal(element_name, "option")) {
352
0
    const char *type = g_hash_table_lookup(atts, "type");
353
354
0
    if (type == NULL) {
355
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
356
0
             "Unset 'type' parameter for option!\n");
357
0
    }
358
0
    else
359
0
      data->option_type = g_strdup(type);
360
0
  }
361
0
  else if (purple_strequal(element_name, "event")) {
362
0
    const char *type = g_hash_table_lookup(atts, "type");
363
364
0
    if (type == NULL) {
365
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
366
0
             "Unset 'type' parameter for event!\n");
367
0
    }
368
0
    else
369
0
      data->event_type = g_strdup(type);
370
0
  }
371
0
  else if (purple_strequal(element_name, "action")) {
372
0
    const char *type = g_hash_table_lookup(atts, "type");
373
374
0
    if (type == NULL) {
375
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
376
0
             "Unset 'type' parameter for action!\n");
377
0
    }
378
0
    else
379
0
      data->action_name = g_strdup(type);
380
0
  }
381
0
  else if (purple_strequal(element_name, "param")) {
382
0
    const char *param_name = g_hash_table_lookup(atts, "name");
383
384
0
    if (param_name == NULL) {
385
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
386
0
             "Unset 'name' parameter for param!\n");
387
0
    }
388
0
    else
389
0
      data->param_name = g_strdup(param_name);
390
0
  }
391
392
0
  g_hash_table_destroy(atts);
393
0
}
394
395
static void
396
end_element_handler(GMarkupParseContext *context, const gchar *element_name,
397
          gpointer user_data,  GError **error)
398
0
{
399
0
  PounceParserData *data = user_data;
400
0
  gchar *buffer = NULL;
401
402
0
  if (data->buffer != NULL) {
403
0
    buffer = g_string_free(data->buffer, FALSE);
404
0
    data->buffer = NULL;
405
0
  }
406
407
0
  if (purple_strequal(element_name, "account")) {
408
0
    char *tmp;
409
0
    g_free(data->account_name);
410
0
    data->account_name = g_strdup(buffer);
411
0
    tmp = data->protocol_id;
412
0
    data->protocol_id = g_strdup(_purple_oscar_convert(buffer, tmp));
413
0
    g_free(tmp);
414
0
  }
415
0
  else if (purple_strequal(element_name, "pouncee")) {
416
0
    g_free(data->pouncee);
417
0
    data->pouncee = g_strdup(buffer);
418
0
  }
419
0
  else if (purple_strequal(element_name, "option")) {
420
0
    if (purple_strequal(data->option_type, "on-away"))
421
0
      data->options |= PURPLE_POUNCE_OPTION_AWAY;
422
423
0
    g_free(data->option_type);
424
0
    data->option_type = NULL;
425
0
  }
426
0
  else if (purple_strequal(element_name, "event")) {
427
0
    if (purple_strequal(data->event_type, "sign-on"))
428
0
      data->events |= PURPLE_POUNCE_SIGNON;
429
0
    else if (purple_strequal(data->event_type, "sign-off"))
430
0
      data->events |= PURPLE_POUNCE_SIGNOFF;
431
0
    else if (purple_strequal(data->event_type, "away"))
432
0
      data->events |= PURPLE_POUNCE_AWAY;
433
0
    else if (purple_strequal(data->event_type, "return-from-away"))
434
0
      data->events |= PURPLE_POUNCE_AWAY_RETURN;
435
0
    else if (purple_strequal(data->event_type, "idle"))
436
0
      data->events |= PURPLE_POUNCE_IDLE;
437
0
    else if (purple_strequal(data->event_type, "return-from-idle"))
438
0
      data->events |= PURPLE_POUNCE_IDLE_RETURN;
439
0
    else if (purple_strequal(data->event_type, "start-typing"))
440
0
      data->events |= PURPLE_POUNCE_TYPING;
441
0
    else if (purple_strequal(data->event_type, "typed"))
442
0
      data->events |= PURPLE_POUNCE_TYPED;
443
0
    else if (purple_strequal(data->event_type, "stop-typing"))
444
0
      data->events |= PURPLE_POUNCE_TYPING_STOPPED;
445
0
    else if (purple_strequal(data->event_type, "message-received"))
446
0
      data->events |= PURPLE_POUNCE_MESSAGE_RECEIVED;
447
448
0
    g_free(data->event_type);
449
0
    data->event_type = NULL;
450
0
  }
451
0
  else if (purple_strequal(element_name, "action")) {
452
0
    if (data->pounce != NULL) {
453
0
      purple_pounce_action_register(data->pounce, data->action_name);
454
0
      purple_pounce_action_set_enabled(data->pounce, data->action_name, TRUE);
455
0
    }
456
457
0
    g_free(data->action_name);
458
0
    data->action_name = NULL;
459
0
  }
460
0
  else if (purple_strequal(element_name, "param")) {
461
0
    if (data->pounce != NULL) {
462
0
      purple_pounce_action_set_attribute(data->pounce, data->action_name,
463
0
                       data->param_name, buffer);
464
0
    }
465
466
0
    g_free(data->param_name);
467
0
    data->param_name = NULL;
468
0
  }
469
0
  else if (purple_strequal(element_name, "events")) {
470
0
    PurpleAccount *account;
471
472
0
    account = purple_accounts_find(data->account_name, data->protocol_id);
473
474
0
    g_free(data->account_name);
475
0
    g_free(data->protocol_id);
476
477
0
    data->account_name = NULL;
478
0
    data->protocol_id  = NULL;
479
480
0
    if (account == NULL) {
481
0
      purple_debug(PURPLE_DEBUG_ERROR, "pounce",
482
0
             "Account for pounce not found!\n");
483
      /*
484
       * This pounce has effectively been removed, so make
485
       * sure that we save the changes to pounces.xml
486
       */
487
0
      schedule_pounces_save();
488
0
    }
489
0
    else {
490
0
      purple_debug(PURPLE_DEBUG_INFO, "pounce",
491
0
             "Creating pounce: %s, %s\n", data->ui_name,
492
0
             data->pouncee);
493
494
0
      data->pounce = purple_pounce_new(data->ui_name, account,
495
0
                       data->pouncee, data->events,
496
0
                       data->options);
497
0
    }
498
499
0
    g_free(data->pouncee);
500
0
    data->pouncee = NULL;
501
0
  }
502
0
  else if (purple_strequal(element_name, "save")) {
503
0
    if (data->pounce != NULL)
504
0
      purple_pounce_set_save(data->pounce, TRUE);
505
0
  }
506
0
  else if (purple_strequal(element_name, "pounce")) {
507
0
    data->pounce  = NULL;
508
0
    data->events  = 0;
509
0
    data->options = 0;
510
511
0
    g_free(data->ui_name);
512
0
    g_free(data->pouncee);
513
0
    g_free(data->protocol_id);
514
0
    g_free(data->event_type);
515
0
    g_free(data->option_type);
516
0
    g_free(data->action_name);
517
0
    g_free(data->param_name);
518
0
    g_free(data->account_name);
519
520
0
    data->ui_name      = NULL;
521
0
    data->pounce       = NULL;
522
0
    data->protocol_id  = NULL;
523
0
    data->event_type   = NULL;
524
0
    data->option_type  = NULL;
525
0
    data->action_name  = NULL;
526
0
    data->param_name   = NULL;
527
0
    data->account_name = NULL;
528
0
  }
529
530
0
  g_free(buffer);
531
0
}
532
533
static void
534
text_handler(GMarkupParseContext *context, const gchar *text,
535
       gsize text_len, gpointer user_data, GError **error)
536
0
{
537
0
  PounceParserData *data = user_data;
538
539
0
  if (data->buffer == NULL)
540
0
    data->buffer = g_string_new_len(text, text_len);
541
0
  else
542
0
    g_string_append_len(data->buffer, text, text_len);
543
0
}
544
545
static GMarkupParser pounces_parser =
546
{
547
  start_element_handler,
548
  end_element_handler,
549
  text_handler,
550
  NULL,
551
  NULL
552
};
553
554
gboolean
555
purple_pounces_load(void)
556
0
{
557
0
  gchar *filename = g_build_filename(purple_user_dir(), "pounces.xml", NULL);
558
0
  gchar *contents = NULL;
559
0
  gsize length;
560
0
  GMarkupParseContext *context;
561
0
  GError *error = NULL;
562
0
  PounceParserData *parser_data;
563
564
0
  if (filename == NULL) {
565
0
    pounces_loaded = TRUE;
566
0
    return FALSE;
567
0
  }
568
569
0
  if (!g_file_get_contents(filename, &contents, &length, &error)) {
570
0
    purple_debug(PURPLE_DEBUG_ERROR, "pounce",
571
0
           "Error reading pounces: %s\n", error->message);
572
573
0
    g_free(filename);
574
0
    g_error_free(error);
575
576
0
    pounces_loaded = TRUE;
577
0
    return FALSE;
578
0
  }
579
580
0
  parser_data = g_new0(PounceParserData, 1);
581
582
0
  context = g_markup_parse_context_new(&pounces_parser, 0,
583
0
                     parser_data, free_parser_data);
584
585
0
  if (!g_markup_parse_context_parse(context, contents, length, NULL)) {
586
0
    g_markup_parse_context_free(context);
587
0
    g_free(contents);
588
0
    g_free(filename);
589
590
0
    pounces_loaded = TRUE;
591
592
0
    return FALSE;
593
0
  }
594
595
0
  if (!g_markup_parse_context_end_parse(context, NULL)) {
596
0
    purple_debug(PURPLE_DEBUG_ERROR, "pounce", "Error parsing %s\n",
597
0
           filename);
598
599
0
    g_markup_parse_context_free(context);
600
0
    g_free(contents);
601
0
    g_free(filename);
602
0
    pounces_loaded = TRUE;
603
604
0
    return FALSE;
605
0
  }
606
607
0
  g_markup_parse_context_free(context);
608
0
  g_free(contents);
609
0
  g_free(filename);
610
611
0
  pounces_loaded = TRUE;
612
613
0
  return TRUE;
614
0
}
615
616
617
PurplePounce *
618
purple_pounce_new(const char *ui_type, PurpleAccount *pouncer,
619
        const char *pouncee, PurplePounceEvent event,
620
        PurplePounceOption option)
621
0
{
622
0
  PurplePounce *pounce;
623
0
  PurplePounceHandler *handler;
624
625
0
  g_return_val_if_fail(ui_type != NULL, NULL);
626
0
  g_return_val_if_fail(pouncer != NULL, NULL);
627
0
  g_return_val_if_fail(pouncee != NULL, NULL);
628
0
  g_return_val_if_fail(event   != 0,    NULL);
629
630
0
  pounce = g_new0(PurplePounce, 1);
631
632
0
  pounce->ui_type  = g_strdup(ui_type);
633
0
  pounce->pouncer  = pouncer;
634
0
  pounce->pouncee  = g_strdup(pouncee);
635
0
  pounce->events   = event;
636
0
  pounce->options  = option;
637
638
0
  pounce->actions  = g_hash_table_new_full(g_str_hash, g_str_equal,
639
0
                       g_free, free_action_data);
640
641
0
  handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
642
643
0
  if (handler != NULL && handler->new_pounce != NULL)
644
0
    handler->new_pounce(pounce);
645
646
0
  pounces = g_list_append(pounces, pounce);
647
648
0
  schedule_pounces_save();
649
650
0
  return pounce;
651
0
}
652
653
void
654
purple_pounce_destroy(PurplePounce *pounce)
655
0
{
656
0
  PurplePounceHandler *handler;
657
658
0
  g_return_if_fail(pounce != NULL);
659
660
0
  handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
661
662
0
  pounces = g_list_remove(pounces, pounce);
663
664
0
  g_free(pounce->ui_type);
665
0
  g_free(pounce->pouncee);
666
667
0
  g_hash_table_destroy(pounce->actions);
668
669
0
  if (handler != NULL && handler->free_pounce != NULL)
670
0
    handler->free_pounce(pounce);
671
672
0
  g_free(pounce);
673
674
0
  schedule_pounces_save();
675
0
}
676
677
void
678
purple_pounce_destroy_all_by_account(PurpleAccount *account)
679
0
{
680
0
  PurpleAccount *pouncer;
681
0
  PurplePounce *pounce;
682
0
  GList *l, *l_next;
683
684
0
  g_return_if_fail(account != NULL);
685
686
0
  for (l = purple_pounces_get_all(); l != NULL; l = l_next)
687
0
  {
688
0
    pounce = (PurplePounce *)l->data;
689
0
    l_next = l->next;
690
691
0
    pouncer = purple_pounce_get_pouncer(pounce);
692
0
    if (pouncer == account)
693
0
      purple_pounce_destroy(pounce);
694
0
  }
695
0
}
696
697
void
698
purple_pounce_destroy_all_by_buddy(PurpleBuddy *buddy)
699
0
{
700
0
  const char *pouncee, *bname;
701
0
  PurpleAccount *pouncer, *bacct;
702
0
  PurplePounce *pounce;
703
0
  GList *l, *l_next;
704
705
0
  g_return_if_fail(buddy != NULL);
706
707
0
  bacct = purple_buddy_get_account(buddy);
708
0
  bname = purple_buddy_get_name(buddy);
709
710
0
  for (l = purple_pounces_get_all(); l != NULL; l = l_next) {
711
0
    pounce = (PurplePounce *)l->data;
712
0
    l_next = l->next;
713
714
0
    pouncer = purple_pounce_get_pouncer(pounce);
715
0
    pouncee = purple_pounce_get_pouncee(pounce);
716
717
0
    if ( (pouncer == bacct) && (purple_strequal(pouncee, bname)) )
718
0
      purple_pounce_destroy(pounce);
719
0
  }
720
0
}
721
722
void
723
purple_pounce_set_events(PurplePounce *pounce, PurplePounceEvent events)
724
0
{
725
0
  g_return_if_fail(pounce != NULL);
726
0
  g_return_if_fail(events != PURPLE_POUNCE_NONE);
727
728
0
  pounce->events = events;
729
730
0
  schedule_pounces_save();
731
0
}
732
733
void
734
purple_pounce_set_options(PurplePounce *pounce, PurplePounceOption options)
735
0
{
736
0
  g_return_if_fail(pounce  != NULL);
737
738
0
  pounce->options = options;
739
740
0
  schedule_pounces_save();
741
0
}
742
743
void
744
purple_pounce_set_pouncer(PurplePounce *pounce, PurpleAccount *pouncer)
745
0
{
746
0
  g_return_if_fail(pounce  != NULL);
747
0
  g_return_if_fail(pouncer != NULL);
748
749
0
  pounce->pouncer = pouncer;
750
751
0
  schedule_pounces_save();
752
0
}
753
754
void
755
purple_pounce_set_pouncee(PurplePounce *pounce, const char *pouncee)
756
0
{
757
0
  g_return_if_fail(pounce  != NULL);
758
0
  g_return_if_fail(pouncee != NULL);
759
760
0
  g_free(pounce->pouncee);
761
0
  pounce->pouncee = g_strdup(pouncee);
762
763
0
  schedule_pounces_save();
764
0
}
765
766
void
767
purple_pounce_set_save(PurplePounce *pounce, gboolean save)
768
0
{
769
0
  g_return_if_fail(pounce != NULL);
770
771
0
  pounce->save = save;
772
773
0
  schedule_pounces_save();
774
0
}
775
776
void
777
purple_pounce_action_register(PurplePounce *pounce, const char *name)
778
0
{
779
0
  PurplePounceActionData *action_data;
780
781
0
  g_return_if_fail(pounce != NULL);
782
0
  g_return_if_fail(name   != NULL);
783
784
0
  if (g_hash_table_lookup(pounce->actions, name) != NULL)
785
0
    return;
786
787
0
  action_data = g_new0(PurplePounceActionData, 1);
788
789
0
  action_data->name    = g_strdup(name);
790
0
  action_data->enabled = FALSE;
791
0
  action_data->atts    = g_hash_table_new_full(g_str_hash, g_str_equal,
792
0
                         g_free, g_free);
793
794
0
  g_hash_table_insert(pounce->actions, g_strdup(name), action_data);
795
796
0
  schedule_pounces_save();
797
0
}
798
799
void
800
purple_pounce_action_set_enabled(PurplePounce *pounce, const char *action,
801
                 gboolean enabled)
802
0
{
803
0
  PurplePounceActionData *action_data;
804
805
0
  g_return_if_fail(pounce != NULL);
806
0
  g_return_if_fail(action != NULL);
807
808
0
  action_data = find_action_data(pounce, action);
809
810
0
  g_return_if_fail(action_data != NULL);
811
812
0
  action_data->enabled = enabled;
813
814
0
  schedule_pounces_save();
815
0
}
816
817
void
818
purple_pounce_action_set_attribute(PurplePounce *pounce, const char *action,
819
                 const char *attr, const char *value)
820
0
{
821
0
  PurplePounceActionData *action_data;
822
823
0
  g_return_if_fail(pounce != NULL);
824
0
  g_return_if_fail(action != NULL);
825
0
  g_return_if_fail(attr   != NULL);
826
827
0
  action_data = find_action_data(pounce, action);
828
829
0
  g_return_if_fail(action_data != NULL);
830
831
0
  if (value == NULL)
832
0
    g_hash_table_remove(action_data->atts, attr);
833
0
  else
834
0
    g_hash_table_insert(action_data->atts, g_strdup(attr),
835
0
              g_strdup(value));
836
837
0
  schedule_pounces_save();
838
0
}
839
840
void
841
purple_pounce_set_data(PurplePounce *pounce, void *data)
842
0
{
843
0
  g_return_if_fail(pounce != NULL);
844
845
0
  pounce->data = data;
846
847
0
  schedule_pounces_save();
848
0
}
849
850
PurplePounceEvent
851
purple_pounce_get_events(const PurplePounce *pounce)
852
0
{
853
0
  g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_NONE);
854
855
0
  return pounce->events;
856
0
}
857
858
PurplePounceOption
859
purple_pounce_get_options(const PurplePounce *pounce)
860
0
{
861
0
  g_return_val_if_fail(pounce != NULL, PURPLE_POUNCE_OPTION_NONE);
862
863
0
  return pounce->options;
864
0
}
865
866
PurpleAccount *
867
purple_pounce_get_pouncer(const PurplePounce *pounce)
868
0
{
869
0
  g_return_val_if_fail(pounce != NULL, NULL);
870
871
0
  return pounce->pouncer;
872
0
}
873
874
const char *
875
purple_pounce_get_pouncee(const PurplePounce *pounce)
876
0
{
877
0
  g_return_val_if_fail(pounce != NULL, NULL);
878
879
0
  return pounce->pouncee;
880
0
}
881
882
gboolean
883
purple_pounce_get_save(const PurplePounce *pounce)
884
0
{
885
0
  g_return_val_if_fail(pounce != NULL, FALSE);
886
887
0
  return pounce->save;
888
0
}
889
890
gboolean
891
purple_pounce_action_is_enabled(const PurplePounce *pounce, const char *action)
892
0
{
893
0
  PurplePounceActionData *action_data;
894
895
0
  g_return_val_if_fail(pounce != NULL, FALSE);
896
0
  g_return_val_if_fail(action != NULL, FALSE);
897
898
0
  action_data = find_action_data(pounce, action);
899
900
0
  g_return_val_if_fail(action_data != NULL, FALSE);
901
902
0
  return action_data->enabled;
903
0
}
904
905
const char *
906
purple_pounce_action_get_attribute(const PurplePounce *pounce,
907
                 const char *action, const char *attr)
908
0
{
909
0
  PurplePounceActionData *action_data;
910
911
0
  g_return_val_if_fail(pounce != NULL, NULL);
912
0
  g_return_val_if_fail(action != NULL, NULL);
913
0
  g_return_val_if_fail(attr   != NULL, NULL);
914
915
0
  action_data = find_action_data(pounce, action);
916
917
0
  g_return_val_if_fail(action_data != NULL, NULL);
918
919
0
  return g_hash_table_lookup(action_data->atts, attr);
920
0
}
921
922
void *
923
purple_pounce_get_data(const PurplePounce *pounce)
924
0
{
925
0
  g_return_val_if_fail(pounce != NULL, NULL);
926
927
0
  return pounce->data;
928
0
}
929
930
void
931
purple_pounce_execute(const PurpleAccount *pouncer, const char *pouncee,
932
          PurplePounceEvent events)
933
0
{
934
0
  PurplePounce *pounce;
935
0
  PurplePounceHandler *handler;
936
0
  PurplePresence *presence;
937
0
  GList *l, *l_next;
938
0
  char *norm_pouncee;
939
940
0
  g_return_if_fail(pouncer != NULL);
941
0
  g_return_if_fail(pouncee != NULL);
942
0
  g_return_if_fail(events  != PURPLE_POUNCE_NONE);
943
944
0
  norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
945
946
0
  for (l = purple_pounces_get_all(); l != NULL; l = l_next)
947
0
  {
948
0
    pounce = (PurplePounce *)l->data;
949
0
    l_next = l->next;
950
951
0
    presence = purple_account_get_presence(pouncer);
952
953
0
    if ((purple_pounce_get_events(pounce) & events) &&
954
0
      (purple_pounce_get_pouncer(pounce) == pouncer) &&
955
0
      !purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
956
0
                  norm_pouncee) &&
957
0
      (pounce->options == PURPLE_POUNCE_OPTION_NONE ||
958
0
       (pounce->options & PURPLE_POUNCE_OPTION_AWAY &&
959
0
        !purple_presence_is_available(presence))))
960
0
    {
961
0
      handler = g_hash_table_lookup(pounce_handlers, pounce->ui_type);
962
963
0
      if (handler != NULL && handler->cb != NULL)
964
0
      {
965
0
        handler->cb(pounce, events, purple_pounce_get_data(pounce));
966
967
0
        if (!purple_pounce_get_save(pounce))
968
0
          purple_pounce_destroy(pounce);
969
0
      }
970
0
    }
971
0
  }
972
973
0
  g_free(norm_pouncee);
974
0
}
975
976
PurplePounce *
977
purple_find_pounce(const PurpleAccount *pouncer, const char *pouncee,
978
         PurplePounceEvent events)
979
0
{
980
0
  PurplePounce *pounce = NULL;
981
0
  GList *l;
982
0
  char *norm_pouncee;
983
984
0
  g_return_val_if_fail(pouncer != NULL, NULL);
985
0
  g_return_val_if_fail(pouncee != NULL, NULL);
986
0
  g_return_val_if_fail(events  != PURPLE_POUNCE_NONE, NULL);
987
988
0
  norm_pouncee = g_strdup(purple_normalize(pouncer, pouncee));
989
990
0
  for (l = purple_pounces_get_all(); l != NULL; l = l->next)
991
0
  {
992
0
    pounce = (PurplePounce *)l->data;
993
994
0
    if ((purple_pounce_get_events(pounce) & events) &&
995
0
      (purple_pounce_get_pouncer(pounce) == pouncer) &&
996
0
      !purple_utf8_strcasecmp(purple_normalize(pouncer, purple_pounce_get_pouncee(pounce)),
997
0
                  norm_pouncee))
998
0
    {
999
0
      break;
1000
0
    }
1001
1002
0
    pounce = NULL;
1003
0
  }
1004
1005
0
  g_free(norm_pouncee);
1006
1007
0
  return pounce;
1008
0
}
1009
1010
void
1011
purple_pounces_register_handler(const char *ui, PurplePounceCb cb,
1012
                void (*new_pounce)(PurplePounce *pounce),
1013
                void (*free_pounce)(PurplePounce *pounce))
1014
0
{
1015
0
  PurplePounceHandler *handler;
1016
1017
0
  g_return_if_fail(ui != NULL);
1018
0
  g_return_if_fail(cb != NULL);
1019
1020
0
  handler = g_new0(PurplePounceHandler, 1);
1021
1022
0
  handler->ui          = g_strdup(ui);
1023
0
  handler->cb          = cb;
1024
0
  handler->new_pounce  = new_pounce;
1025
0
  handler->free_pounce = free_pounce;
1026
1027
0
  g_hash_table_insert(pounce_handlers, g_strdup(ui), handler);
1028
0
}
1029
1030
void
1031
purple_pounces_unregister_handler(const char *ui)
1032
0
{
1033
0
  g_return_if_fail(ui != NULL);
1034
1035
0
  g_hash_table_remove(pounce_handlers, ui);
1036
0
}
1037
1038
GList *
1039
purple_pounces_get_all(void)
1040
0
{
1041
0
  return pounces;
1042
0
}
1043
1044
GList *purple_pounces_get_all_for_ui(const char *ui)
1045
0
{
1046
0
  GList *list = NULL, *iter;
1047
0
  g_return_val_if_fail(ui != NULL, NULL);
1048
1049
0
  for (iter = pounces; iter; iter = iter->next) {
1050
0
    PurplePounce *pounce = iter->data;
1051
0
    if (purple_strequal(pounce->ui_type, ui))
1052
0
      list = g_list_prepend(list, pounce);
1053
0
  }
1054
0
  list = g_list_reverse(list);
1055
0
  return list;
1056
0
}
1057
1058
static void
1059
free_pounce_handler(gpointer user_data)
1060
0
{
1061
0
  PurplePounceHandler *handler = (PurplePounceHandler *)user_data;
1062
1063
0
  g_free(handler->ui);
1064
0
  g_free(handler);
1065
0
}
1066
1067
static void
1068
buddy_state_cb(PurpleBuddy *buddy, PurplePounceEvent event)
1069
0
{
1070
0
  PurpleAccount *account = purple_buddy_get_account(buddy);
1071
0
  const gchar *name = purple_buddy_get_name(buddy);
1072
1073
0
  purple_pounce_execute(account, name, event);
1074
0
}
1075
1076
static void
1077
buddy_status_changed_cb(PurpleBuddy *buddy, PurpleStatus *old_status,
1078
                        PurpleStatus *status)
1079
0
{
1080
0
  PurpleAccount *account = purple_buddy_get_account(buddy);
1081
0
  const gchar *name = purple_buddy_get_name(buddy);
1082
0
  gboolean old_available, available;
1083
1084
0
  available = purple_status_is_available(status);
1085
0
  old_available = purple_status_is_available(old_status);
1086
1087
0
  if (available && !old_available)
1088
0
    purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY_RETURN);
1089
0
  else if (!available && old_available)
1090
0
    purple_pounce_execute(account, name, PURPLE_POUNCE_AWAY);
1091
0
}
1092
1093
static void
1094
buddy_idle_changed_cb(PurpleBuddy *buddy, gboolean old_idle, gboolean idle)
1095
0
{
1096
0
  PurpleAccount *account = purple_buddy_get_account(buddy);
1097
0
  const gchar *name = purple_buddy_get_name(buddy);
1098
1099
0
  if (idle && !old_idle)
1100
0
    purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE);
1101
0
  else if (!idle && old_idle)
1102
0
    purple_pounce_execute(account, name, PURPLE_POUNCE_IDLE_RETURN);
1103
0
}
1104
1105
static void
1106
buddy_typing_cb(PurpleAccount *account, const char *name, void *data)
1107
0
{
1108
0
  PurpleConversation *conv;
1109
1110
0
  conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, name, account);
1111
0
  if (conv != NULL)
1112
0
  {
1113
0
    PurpleTypingState state;
1114
0
    PurplePounceEvent event;
1115
1116
0
    state = purple_conv_im_get_typing_state(PURPLE_CONV_IM(conv));
1117
0
    if (state == PURPLE_TYPED)
1118
0
      event = PURPLE_POUNCE_TYPED;
1119
0
    else if (state == PURPLE_NOT_TYPING)
1120
0
      event = PURPLE_POUNCE_TYPING_STOPPED;
1121
0
    else
1122
0
      event = PURPLE_POUNCE_TYPING;
1123
1124
0
    purple_pounce_execute(account, name, event);
1125
0
  }
1126
0
}
1127
1128
static void
1129
received_message_cb(PurpleAccount *account, const char *name, void *data)
1130
0
{
1131
0
  purple_pounce_execute(account, name, PURPLE_POUNCE_MESSAGE_RECEIVED);
1132
0
}
1133
1134
void *
1135
purple_pounces_get_handle(void)
1136
0
{
1137
0
  static int pounce_handle;
1138
1139
0
  return &pounce_handle;
1140
0
}
1141
1142
void
1143
purple_pounces_init(void)
1144
0
{
1145
0
  void *handle       = purple_pounces_get_handle();
1146
0
  void *blist_handle = purple_blist_get_handle();
1147
0
  void *conv_handle  = purple_conversations_get_handle();
1148
1149
0
  pounce_handlers = g_hash_table_new_full(g_str_hash, g_str_equal,
1150
0
                      g_free, free_pounce_handler);
1151
1152
0
  purple_signal_connect(blist_handle, "buddy-idle-changed",
1153
0
                      handle, PURPLE_CALLBACK(buddy_idle_changed_cb), NULL);
1154
0
  purple_signal_connect(blist_handle, "buddy-status-changed",
1155
0
                      handle, PURPLE_CALLBACK(buddy_status_changed_cb), NULL);
1156
0
  purple_signal_connect(blist_handle, "buddy-signed-on",
1157
0
            handle, PURPLE_CALLBACK(buddy_state_cb),
1158
0
            GINT_TO_POINTER(PURPLE_POUNCE_SIGNON));
1159
0
  purple_signal_connect(blist_handle, "buddy-signed-off",
1160
0
            handle, PURPLE_CALLBACK(buddy_state_cb),
1161
0
            GINT_TO_POINTER(PURPLE_POUNCE_SIGNOFF));
1162
1163
0
  purple_signal_connect(conv_handle, "buddy-typing",
1164
0
            handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1165
0
  purple_signal_connect(conv_handle, "buddy-typed",
1166
0
            handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1167
0
  purple_signal_connect(conv_handle, "buddy-typing-stopped",
1168
0
            handle, PURPLE_CALLBACK(buddy_typing_cb), NULL);
1169
1170
0
  purple_signal_connect(conv_handle, "received-im-msg",
1171
0
            handle, PURPLE_CALLBACK(received_message_cb), NULL);
1172
0
}
1173
1174
void
1175
purple_pounces_uninit()
1176
0
{
1177
0
  if (save_timer != 0)
1178
0
  {
1179
0
    purple_timeout_remove(save_timer);
1180
0
    save_timer = 0;
1181
0
    sync_pounces();
1182
0
  }
1183
1184
0
  purple_signals_disconnect_by_handle(purple_pounces_get_handle());
1185
1186
0
  g_hash_table_destroy(pounce_handlers);
1187
  pounce_handlers = NULL;
1188
0
}