Merge heads.
[pidgin-git.git] / libpurple / protocols.c
blob20917a56fa22221e412b19c4c6fe87228b0e7fa3
1 /*
2 * purple
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #include "internal.h"
24 #include "conversation.h"
25 #include "debug.h"
26 #include "network.h"
27 #include "notify.h"
28 #include "protocol.h"
29 #include "purpleaccountoption.h"
30 #include "request.h"
31 #include "util.h"
33 static GHashTable *protocols = NULL;
35 /**************************************************************************
36 * GBoxed code for PurpleProtocolChatEntry
37 **************************************************************************/
39 static PurpleProtocolChatEntry *
40 purple_protocol_chat_entry_copy(PurpleProtocolChatEntry *pce)
42 PurpleProtocolChatEntry *pce_copy;
44 g_return_val_if_fail(pce != NULL, NULL);
46 pce_copy = g_new(PurpleProtocolChatEntry, 1);
47 *pce_copy = *pce;
49 return pce_copy;
52 GType
53 purple_protocol_chat_entry_get_type(void)
55 static GType type = 0;
57 if (type == 0) {
58 type = g_boxed_type_register_static("PurpleProtocolChatEntry",
59 (GBoxedCopyFunc)purple_protocol_chat_entry_copy,
60 (GBoxedFreeFunc)g_free);
63 return type;
66 /**************************************************************************/
67 /* Protocol API */
68 /**************************************************************************/
69 void
70 purple_protocol_got_account_idle(PurpleAccount *account, gboolean idle,
71 time_t idle_time)
73 g_return_if_fail(account != NULL);
74 g_return_if_fail(purple_account_is_connected(account));
76 purple_presence_set_idle(purple_account_get_presence(account),
77 idle, idle_time);
80 void
81 purple_protocol_got_account_login_time(PurpleAccount *account, time_t login_time)
83 PurplePresence *presence;
85 g_return_if_fail(account != NULL);
86 g_return_if_fail(purple_account_is_connected(account));
88 if (login_time == 0)
89 login_time = time(NULL);
91 presence = purple_account_get_presence(account);
93 purple_presence_set_login_time(presence, login_time);
96 void
97 purple_protocol_got_account_status(PurpleAccount *account, const char *status_id, ...)
99 PurplePresence *presence;
100 PurpleStatus *status;
101 va_list args;
103 g_return_if_fail(account != NULL);
104 g_return_if_fail(status_id != NULL);
105 g_return_if_fail(purple_account_is_connected(account));
107 presence = purple_account_get_presence(account);
108 status = purple_presence_get_status(presence, status_id);
110 g_return_if_fail(status != NULL);
112 va_start(args, status_id);
113 purple_status_set_active_with_attrs(status, TRUE, args);
114 va_end(args);
117 void
118 purple_protocol_got_account_actions(PurpleAccount *account)
121 g_return_if_fail(account != NULL);
122 g_return_if_fail(purple_account_is_connected(account));
124 purple_signal_emit(purple_accounts_get_handle(), "account-actions-changed",
125 account);
128 void
129 purple_protocol_got_user_idle(PurpleAccount *account, const char *name,
130 gboolean idle, time_t idle_time)
132 PurplePresence *presence;
133 GSList *list;
135 g_return_if_fail(account != NULL);
136 g_return_if_fail(name != NULL);
137 g_return_if_fail(purple_account_is_connected(account) || purple_account_is_connecting(account));
139 if ((list = purple_blist_find_buddies(account, name)) == NULL)
140 return;
142 while (list) {
143 presence = purple_buddy_get_presence(list->data);
144 list = g_slist_delete_link(list, list);
145 purple_presence_set_idle(presence, idle, idle_time);
149 void
150 purple_protocol_got_user_login_time(PurpleAccount *account, const char *name,
151 time_t login_time)
153 GSList *list;
154 PurplePresence *presence;
156 g_return_if_fail(account != NULL);
157 g_return_if_fail(name != NULL);
159 if ((list = purple_blist_find_buddies(account, name)) == NULL)
160 return;
162 if (login_time == 0)
163 login_time = time(NULL);
165 while (list) {
166 PurpleBuddy *buddy = list->data;
167 presence = purple_buddy_get_presence(buddy);
168 list = g_slist_delete_link(list, list);
170 if (purple_presence_get_login_time(presence) != login_time)
172 purple_presence_set_login_time(presence, login_time);
174 purple_signal_emit(purple_blist_get_handle(), "buddy-got-login-time", buddy);
179 void
180 purple_protocol_got_user_status(PurpleAccount *account, const char *name,
181 const char *status_id, ...)
183 GSList *list, *l;
184 PurpleBuddy *buddy;
185 PurplePresence *presence;
186 PurpleStatus *status;
187 PurpleStatus *old_status;
188 va_list args;
190 g_return_if_fail(account != NULL);
191 g_return_if_fail(name != NULL);
192 g_return_if_fail(status_id != NULL);
193 g_return_if_fail(purple_account_is_connected(account) || purple_account_is_connecting(account));
195 if((list = purple_blist_find_buddies(account, name)) == NULL)
196 return;
198 for(l = list; l != NULL; l = l->next) {
199 buddy = l->data;
201 presence = purple_buddy_get_presence(buddy);
202 status = purple_presence_get_status(presence, status_id);
204 if(NULL == status)
206 * TODO: This should never happen, right? We should call
207 * g_warning() or something.
209 continue;
211 old_status = purple_presence_get_active_status(presence);
213 va_start(args, status_id);
214 purple_status_set_active_with_attrs(status, TRUE, args);
215 va_end(args);
217 purple_buddy_update_status(buddy, old_status);
220 g_slist_free(list);
222 /* The buddy is no longer online, they are therefore by definition not
223 * still typing to us. */
224 if (!purple_status_is_online(status)) {
225 purple_serv_got_typing_stopped(purple_account_get_connection(account), name);
226 purple_protocol_got_media_caps(account, name);
230 void purple_protocol_got_user_status_deactive(PurpleAccount *account, const char *name,
231 const char *status_id)
233 GSList *list, *l;
234 PurpleBuddy *buddy;
235 PurplePresence *presence;
236 PurpleStatus *status;
238 g_return_if_fail(account != NULL);
239 g_return_if_fail(name != NULL);
240 g_return_if_fail(status_id != NULL);
241 g_return_if_fail(purple_account_is_connected(account) || purple_account_is_connecting(account));
243 if((list = purple_blist_find_buddies(account, name)) == NULL)
244 return;
246 for(l = list; l != NULL; l = l->next) {
247 buddy = l->data;
249 presence = purple_buddy_get_presence(buddy);
250 status = purple_presence_get_status(presence, status_id);
252 if(NULL == status)
253 continue;
255 if (purple_status_is_active(status)) {
256 purple_status_set_active(status, FALSE);
257 purple_buddy_update_status(buddy, status);
261 g_slist_free(list);
264 static void
265 do_protocol_change_account_status(PurpleAccount *account,
266 PurpleStatus *old_status, PurpleStatus *new_status)
268 PurpleProtocol *protocol;
270 if (purple_status_is_online(new_status) &&
271 purple_account_is_disconnected(account) &&
272 purple_network_is_available())
274 purple_account_connect(account);
275 return;
278 if (!purple_status_is_online(new_status))
280 if (!purple_account_is_disconnected(account))
281 purple_account_disconnect(account);
282 /* Clear out the unsaved password if we switch to offline status */
283 if (!purple_account_get_remember_password(account))
284 purple_account_set_password(account, NULL, NULL, NULL);
286 return;
289 if (purple_account_is_connecting(account))
291 * We don't need to call the set_status protocol function because
292 * the protocol will take care of setting its status during the
293 * connection process.
295 return;
297 protocol = purple_protocols_find(purple_account_get_protocol_id(account));
299 if (protocol == NULL)
300 return;
302 if (!purple_account_is_disconnected(account))
303 purple_protocol_server_iface_set_status(protocol, account, new_status);
306 void
307 purple_protocol_change_account_status(PurpleAccount *account,
308 PurpleStatus *old_status, PurpleStatus *new_status)
310 g_return_if_fail(account != NULL);
311 g_return_if_fail(new_status != NULL);
312 g_return_if_fail(!purple_status_is_exclusive(new_status) || old_status != NULL);
314 purple_signal_emit(purple_accounts_get_handle(), "account-status-changing",
315 account, old_status, new_status);
317 do_protocol_change_account_status(account, old_status, new_status);
319 purple_signal_emit(purple_accounts_get_handle(), "account-status-changed",
320 account, old_status, new_status);
323 GList *
324 purple_protocol_get_statuses(PurpleAccount *account, PurplePresence *presence)
326 GList *statuses = NULL;
327 GList *l;
328 PurpleStatus *status;
330 g_return_val_if_fail(account != NULL, NULL);
331 g_return_val_if_fail(presence != NULL, NULL);
333 for (l = purple_account_get_status_types(account); l != NULL; l = l->next)
335 status = purple_status_new((PurpleStatusType *)l->data, presence);
336 statuses = g_list_prepend(statuses, status);
339 statuses = g_list_reverse(statuses);
341 return statuses;
344 static void
345 purple_protocol_attention(PurpleConversation *conv, const char *who,
346 guint type, PurpleMessageFlags flags, time_t mtime)
348 PurpleAccount *account = purple_conversation_get_account(conv);
349 purple_signal_emit(purple_conversations_get_handle(),
350 flags == PURPLE_MESSAGE_SEND ? "sent-attention" : "got-attention",
351 account, who, conv, type);
354 void
355 purple_protocol_send_attention(PurpleConnection *gc, const char *who, guint type_code)
357 PurpleAttentionType *attn;
358 PurpleProtocol *protocol;
359 PurpleIMConversation *im;
360 PurpleBuddy *buddy;
361 const char *alias;
362 gchar *description;
364 g_return_if_fail(gc != NULL);
365 g_return_if_fail(who != NULL);
367 protocol = purple_protocols_find(purple_account_get_protocol_id(purple_connection_get_account(gc)));
368 g_return_if_fail(PURPLE_IS_PROTOCOL_ATTENTION(protocol));
370 attn = purple_get_attention_type_from_code(purple_connection_get_account(gc), type_code);
372 if ((buddy = purple_blist_find_buddy(purple_connection_get_account(gc), who)) != NULL)
373 alias = purple_buddy_get_contact_alias(buddy);
374 else
375 alias = who;
377 if (attn && purple_attention_type_get_outgoing_desc(attn)) {
378 description = g_strdup_printf(purple_attention_type_get_outgoing_desc(attn), alias);
379 } else {
380 description = g_strdup_printf(_("Requesting %s's attention..."), alias);
383 purple_debug_info("server", "serv_send_attention: sending '%s' to %s\n",
384 description, who);
386 if (!purple_protocol_attention_send(PURPLE_PROTOCOL_ATTENTION(protocol), gc, who, type_code))
387 return;
389 im = purple_im_conversation_new(purple_connection_get_account(gc), who);
390 purple_conversation_write_system_message(PURPLE_CONVERSATION(im), description, 0);
391 purple_protocol_attention(PURPLE_CONVERSATION(im), who, type_code, PURPLE_MESSAGE_SEND, time(NULL));
393 g_free(description);
396 static void
397 got_attention(PurpleConnection *gc, int id, const char *who, guint type_code)
399 PurpleMessageFlags flags;
400 PurpleAttentionType *attn;
401 PurpleBuddy *buddy;
402 const char *alias;
403 gchar *description;
404 time_t mtime;
406 mtime = time(NULL);
408 attn = purple_get_attention_type_from_code(purple_connection_get_account(gc), type_code);
410 /* PURPLE_MESSAGE_NOTIFY is for attention messages. */
411 flags = PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_RECV;
413 /* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display
414 * it next to the attention command. And if it is null, display a generic icon. */
416 if ((buddy = purple_blist_find_buddy(purple_connection_get_account(gc), who)) != NULL)
417 alias = purple_buddy_get_contact_alias(buddy);
418 else
419 alias = who;
421 if (attn && purple_attention_type_get_incoming_desc(attn)) {
422 description = g_strdup_printf(purple_attention_type_get_incoming_desc(attn), alias);
423 } else {
424 description = g_strdup_printf(_("%s has requested your attention!"), alias);
427 purple_debug_info("server", "got_attention: got '%s' from %s\n",
428 description, who);
430 if (id == -1)
431 purple_serv_got_im(gc, who, description, flags, mtime);
432 else
433 purple_serv_got_chat_in(gc, id, who, flags, description, mtime);
435 /* TODO: sounds (depending on PurpleAttentionType), shaking, etc. */
437 g_free(description);
440 void
441 purple_protocol_got_attention(PurpleConnection *gc, const char *who, guint type_code)
443 PurpleConversation *conv = NULL;
444 PurpleAccount *account = purple_connection_get_account(gc);
446 got_attention(gc, -1, who, type_code);
447 conv =
448 purple_conversations_find_with_account(who, account);
449 if (conv)
450 purple_protocol_attention(conv, who, type_code, PURPLE_MESSAGE_RECV,
451 time(NULL));
454 void
455 purple_protocol_got_attention_in_chat(PurpleConnection *gc, int id, const char *who, guint type_code)
457 got_attention(gc, id, who, type_code);
460 gboolean
461 purple_protocol_initiate_media(PurpleAccount *account,
462 const char *who,
463 PurpleMediaSessionType type)
465 #ifdef USE_VV
466 PurpleConnection *gc = NULL;
467 PurpleProtocol *protocol = NULL;
469 if (account)
470 gc = purple_account_get_connection(account);
471 if (gc)
472 protocol = purple_connection_get_protocol(gc);
474 if (protocol) {
475 /* should check that the protocol supports this media type here? */
476 return purple_protocol_media_iface_initiate_session(protocol, account, who, type);
477 } else
478 #endif
479 return FALSE;
482 PurpleMediaCaps
483 purple_protocol_get_media_caps(PurpleAccount *account, const char *who)
485 #ifdef USE_VV
486 PurpleConnection *gc = NULL;
487 PurpleProtocol *protocol = NULL;
489 if (account)
490 gc = purple_account_get_connection(account);
491 if (gc)
492 protocol = purple_connection_get_protocol(gc);
494 if (protocol)
495 return purple_protocol_media_iface_get_caps(protocol, account, who);
496 #endif
497 return PURPLE_MEDIA_CAPS_NONE;
500 void
501 purple_protocol_got_media_caps(PurpleAccount *account, const char *name)
503 #ifdef USE_VV
504 GSList *list;
506 g_return_if_fail(account != NULL);
507 g_return_if_fail(name != NULL);
509 if ((list = purple_blist_find_buddies(account, name)) == NULL)
510 return;
512 while (list) {
513 PurpleBuddy *buddy = list->data;
514 PurpleMediaCaps oldcaps = purple_buddy_get_media_caps(buddy);
515 PurpleMediaCaps newcaps = 0;
516 const gchar *bname = purple_buddy_get_name(buddy);
517 list = g_slist_delete_link(list, list);
520 newcaps = purple_protocol_get_media_caps(account, bname);
521 purple_buddy_set_media_caps(buddy, newcaps);
523 if (oldcaps == newcaps)
524 continue;
526 purple_signal_emit(purple_blist_get_handle(),
527 "buddy-caps-changed", buddy,
528 newcaps, oldcaps);
530 #endif
533 gssize
534 purple_protocol_get_max_message_size(PurpleProtocol *protocol)
536 g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), 0);
538 return purple_protocol_client_iface_get_max_message_size(protocol, NULL);
541 /**************************************************************************
542 * Protocols API
543 **************************************************************************/
545 * Negative if a before b, 0 if equal, positive if a after b.
547 static gint
548 compare_protocol(PurpleProtocol *a, PurpleProtocol *b)
550 const gchar *aname = purple_protocol_get_name(a);
551 const gchar *bname = purple_protocol_get_name(b);
553 if (aname) {
554 if (bname)
555 return strcmp(aname, bname);
556 else
557 return -1;
558 } else {
559 if (bname)
560 return 1;
561 else
562 return 0;
566 PurpleProtocol *
567 purple_protocols_find(const char *id)
569 g_return_val_if_fail(protocols != NULL && id != NULL, NULL);
571 return g_hash_table_lookup(protocols, id);
574 PurpleProtocol *
575 purple_protocols_add(GType protocol_type, GError **error)
577 PurpleProtocol *protocol;
578 PurpleProtocolClass *klass;
580 if (protocol_type == G_TYPE_INVALID) {
581 g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
582 _("Protocol type is not registered"));
583 return NULL;
586 if (!g_type_is_a(protocol_type, PURPLE_TYPE_PROTOCOL)) {
587 g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
588 _("Protocol type does not inherit PurpleProtocol"));
589 return NULL;
592 if (G_TYPE_IS_ABSTRACT(protocol_type)) {
593 g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
594 _("Protocol type is abstract"));
595 return NULL;
598 protocol = g_object_new(protocol_type, NULL);
599 if (!protocol) {
600 g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
601 _("Could not create protocol instance"));
602 return NULL;
605 if (!purple_protocol_get_id(protocol)) {
606 g_set_error_literal(error, PURPLE_PROTOCOLS_DOMAIN, 0,
607 _("Protocol does not provide an ID"));
609 g_object_unref(protocol);
610 return NULL;
613 if (purple_protocols_find(purple_protocol_get_id(protocol))) {
614 g_set_error(error, PURPLE_PROTOCOLS_DOMAIN, 0,
615 _("A protocol with the ID %s is already added."),
616 purple_protocol_get_id(protocol));
618 g_object_unref(protocol);
619 return NULL;
622 /* Make sure the protocol implements the required functions */
623 klass = PURPLE_PROTOCOL_GET_CLASS(protocol);
625 if (!klass->login || !klass->close ||
626 !klass->status_types || !klass->list_icon )
628 g_set_error(error, PURPLE_PROTOCOLS_DOMAIN, 0,
629 _("Protocol %s does not implement all the functions in "
630 "PurpleProtocolClass"), purple_protocol_get_id(protocol));
632 g_object_unref(protocol);
633 return NULL;
636 g_hash_table_insert(protocols, g_strdup(purple_protocol_get_id(protocol)),
637 protocol);
639 purple_debug_info("protocols", "Added protocol %s\n",
640 purple_protocol_get_id(protocol));
642 purple_signal_emit(purple_protocols_get_handle(), "protocol-added",
643 protocol);
644 return protocol;
647 gboolean purple_protocols_remove(PurpleProtocol *protocol, GError **error)
649 g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), FALSE);
650 g_return_val_if_fail(purple_protocol_get_id(protocol) != NULL, FALSE);
652 if (purple_protocols_find(purple_protocol_get_id(protocol)) == NULL) {
653 g_set_error(error, PURPLE_PROTOCOLS_DOMAIN, 0,
654 _("Protocol %s is not added."),
655 purple_protocol_get_id(protocol));
657 return FALSE;
660 purple_debug_info("protocols", "Removing protocol %s\n",
661 purple_protocol_get_id(protocol));
663 purple_signal_emit(purple_protocols_get_handle(), "protocol-removed",
664 protocol);
666 g_hash_table_remove(protocols, purple_protocol_get_id(protocol));
667 return TRUE;
670 GList *
671 purple_protocols_get_all(void)
673 GList *ret = NULL;
674 PurpleProtocol *protocol;
675 GHashTableIter iter;
677 g_hash_table_iter_init(&iter, protocols);
678 while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&protocol))
679 ret = g_list_insert_sorted(ret, protocol, (GCompareFunc)compare_protocol);
681 return ret;
684 /**************************************************************************
685 * Protocols Subsystem API
686 **************************************************************************/
687 void
688 purple_protocols_init(void)
690 void *handle = purple_protocols_get_handle();
692 protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
693 (GDestroyNotify)g_object_unref);
695 purple_signal_register(handle, "protocol-added",
696 purple_marshal_VOID__POINTER,
697 G_TYPE_NONE, 1, PURPLE_TYPE_PROTOCOL);
698 purple_signal_register(handle, "protocol-removed",
699 purple_marshal_VOID__POINTER,
700 G_TYPE_NONE, 1, PURPLE_TYPE_PROTOCOL);
703 void *
704 purple_protocols_get_handle(void)
706 static int handle;
708 return &handle;
711 void
712 purple_protocols_uninit(void)
714 g_hash_table_destroy(protocols);