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
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
24 #include "accountopt.h"
25 #include "conversation.h"
33 static GHashTable
*protocols
= NULL
;
35 /**************************************************************************/
36 /* Attention Type API */
37 /**************************************************************************/
39 struct _PurpleAttentionType
41 const char *name
; /* Shown in GUI elements */
42 const char *incoming_description
; /* Shown when sent */
43 const char *outgoing_description
; /* Shown when receied */
44 const char *icon_name
; /* Icon to display (optional) */
45 const char *unlocalized_name
; /* Unlocalized name for UIs needing it */
50 purple_attention_type_new(const char *ulname
, const char *name
,
51 const char *inc_desc
, const char *out_desc
)
53 PurpleAttentionType
*attn
= g_new0(PurpleAttentionType
, 1);
55 purple_attention_type_set_name(attn
, name
);
56 purple_attention_type_set_incoming_desc(attn
, inc_desc
);
57 purple_attention_type_set_outgoing_desc(attn
, out_desc
);
58 purple_attention_type_set_unlocalized_name(attn
, ulname
);
65 purple_attention_type_set_name(PurpleAttentionType
*type
, const char *name
)
67 g_return_if_fail(type
!= NULL
);
73 purple_attention_type_set_incoming_desc(PurpleAttentionType
*type
, const char *desc
)
75 g_return_if_fail(type
!= NULL
);
77 type
->incoming_description
= desc
;
81 purple_attention_type_set_outgoing_desc(PurpleAttentionType
*type
, const char *desc
)
83 g_return_if_fail(type
!= NULL
);
85 type
->outgoing_description
= desc
;
89 purple_attention_type_set_icon_name(PurpleAttentionType
*type
, const char *name
)
91 g_return_if_fail(type
!= NULL
);
93 type
->icon_name
= name
;
97 purple_attention_type_set_unlocalized_name(PurpleAttentionType
*type
, const char *ulname
)
99 g_return_if_fail(type
!= NULL
);
101 type
->unlocalized_name
= ulname
;
105 purple_attention_type_get_name(const PurpleAttentionType
*type
)
107 g_return_val_if_fail(type
!= NULL
, NULL
);
113 purple_attention_type_get_incoming_desc(const PurpleAttentionType
*type
)
115 g_return_val_if_fail(type
!= NULL
, NULL
);
117 return type
->incoming_description
;
121 purple_attention_type_get_outgoing_desc(const PurpleAttentionType
*type
)
123 g_return_val_if_fail(type
!= NULL
, NULL
);
125 return type
->outgoing_description
;
129 purple_attention_type_get_icon_name(const PurpleAttentionType
*type
)
131 g_return_val_if_fail(type
!= NULL
, NULL
);
133 if(type
->icon_name
== NULL
|| *(type
->icon_name
) == '\0')
136 return type
->icon_name
;
140 purple_attention_type_get_unlocalized_name(const PurpleAttentionType
*type
)
142 g_return_val_if_fail(type
!= NULL
, NULL
);
144 return type
->unlocalized_name
;
147 /**************************************************************************
148 * GBoxed code for PurpleAttentionType
149 **************************************************************************/
151 static PurpleAttentionType
*
152 purple_attention_type_copy(PurpleAttentionType
*attn
)
154 PurpleAttentionType
*attn_copy
;
156 g_return_val_if_fail(attn
!= NULL
, NULL
);
158 attn_copy
= g_new(PurpleAttentionType
, 1);
165 purple_attention_type_get_type(void)
167 static GType type
= 0;
170 type
= g_boxed_type_register_static("PurpleAttentionType",
171 (GBoxedCopyFunc
)purple_attention_type_copy
,
172 (GBoxedFreeFunc
)g_free
);
178 /**************************************************************************
179 * GBoxed code for PurpleProtocolChatEntry
180 **************************************************************************/
182 static PurpleProtocolChatEntry
*
183 purple_protocol_chat_entry_copy(PurpleProtocolChatEntry
*pce
)
185 PurpleProtocolChatEntry
*pce_copy
;
187 g_return_val_if_fail(pce
!= NULL
, NULL
);
189 pce_copy
= g_new(PurpleProtocolChatEntry
, 1);
196 purple_protocol_chat_entry_get_type(void)
198 static GType type
= 0;
201 type
= g_boxed_type_register_static("PurpleProtocolChatEntry",
202 (GBoxedCopyFunc
)purple_protocol_chat_entry_copy
,
203 (GBoxedFreeFunc
)g_free
);
209 /**************************************************************************/
211 /**************************************************************************/
213 purple_protocol_got_account_idle(PurpleAccount
*account
, gboolean idle
,
216 g_return_if_fail(account
!= NULL
);
217 g_return_if_fail(purple_account_is_connected(account
));
219 purple_presence_set_idle(purple_account_get_presence(account
),
224 purple_protocol_got_account_login_time(PurpleAccount
*account
, time_t login_time
)
226 PurplePresence
*presence
;
228 g_return_if_fail(account
!= NULL
);
229 g_return_if_fail(purple_account_is_connected(account
));
232 login_time
= time(NULL
);
234 presence
= purple_account_get_presence(account
);
236 purple_presence_set_login_time(presence
, login_time
);
240 purple_protocol_got_account_status(PurpleAccount
*account
, const char *status_id
, ...)
242 PurplePresence
*presence
;
243 PurpleStatus
*status
;
246 g_return_if_fail(account
!= NULL
);
247 g_return_if_fail(status_id
!= NULL
);
248 g_return_if_fail(purple_account_is_connected(account
));
250 presence
= purple_account_get_presence(account
);
251 status
= purple_presence_get_status(presence
, status_id
);
253 g_return_if_fail(status
!= NULL
);
255 va_start(args
, status_id
);
256 purple_status_set_active_with_attrs(status
, TRUE
, args
);
261 purple_protocol_got_account_actions(PurpleAccount
*account
)
264 g_return_if_fail(account
!= NULL
);
265 g_return_if_fail(purple_account_is_connected(account
));
267 purple_signal_emit(purple_accounts_get_handle(), "account-actions-changed",
272 purple_protocol_got_user_idle(PurpleAccount
*account
, const char *name
,
273 gboolean idle
, time_t idle_time
)
275 PurplePresence
*presence
;
278 g_return_if_fail(account
!= NULL
);
279 g_return_if_fail(name
!= NULL
);
280 g_return_if_fail(purple_account_is_connected(account
) || purple_account_is_connecting(account
));
282 if ((list
= purple_blist_find_buddies(account
, name
)) == NULL
)
286 presence
= purple_buddy_get_presence(list
->data
);
287 list
= g_slist_delete_link(list
, list
);
288 purple_presence_set_idle(presence
, idle
, idle_time
);
293 purple_protocol_got_user_login_time(PurpleAccount
*account
, const char *name
,
297 PurplePresence
*presence
;
299 g_return_if_fail(account
!= NULL
);
300 g_return_if_fail(name
!= NULL
);
302 if ((list
= purple_blist_find_buddies(account
, name
)) == NULL
)
306 login_time
= time(NULL
);
309 PurpleBuddy
*buddy
= list
->data
;
310 presence
= purple_buddy_get_presence(buddy
);
311 list
= g_slist_delete_link(list
, list
);
313 if (purple_presence_get_login_time(presence
) != login_time
)
315 purple_presence_set_login_time(presence
, login_time
);
317 purple_signal_emit(purple_blist_get_handle(), "buddy-got-login-time", buddy
);
323 purple_protocol_got_user_status(PurpleAccount
*account
, const char *name
,
324 const char *status_id
, ...)
328 PurplePresence
*presence
;
329 PurpleStatus
*status
;
330 PurpleStatus
*old_status
;
333 g_return_if_fail(account
!= NULL
);
334 g_return_if_fail(name
!= NULL
);
335 g_return_if_fail(status_id
!= NULL
);
336 g_return_if_fail(purple_account_is_connected(account
) || purple_account_is_connecting(account
));
338 if((list
= purple_blist_find_buddies(account
, name
)) == NULL
)
341 for(l
= list
; l
!= NULL
; l
= l
->next
) {
344 presence
= purple_buddy_get_presence(buddy
);
345 status
= purple_presence_get_status(presence
, status_id
);
349 * TODO: This should never happen, right? We should call
350 * g_warning() or something.
354 old_status
= purple_presence_get_active_status(presence
);
356 va_start(args
, status_id
);
357 purple_status_set_active_with_attrs(status
, TRUE
, args
);
360 purple_buddy_update_status(buddy
, old_status
);
365 /* The buddy is no longer online, they are therefore by definition not
366 * still typing to us. */
367 if (!purple_status_is_online(status
)) {
368 purple_serv_got_typing_stopped(purple_account_get_connection(account
), name
);
369 purple_protocol_got_media_caps(account
, name
);
373 void purple_protocol_got_user_status_deactive(PurpleAccount
*account
, const char *name
,
374 const char *status_id
)
378 PurplePresence
*presence
;
379 PurpleStatus
*status
;
381 g_return_if_fail(account
!= NULL
);
382 g_return_if_fail(name
!= NULL
);
383 g_return_if_fail(status_id
!= NULL
);
384 g_return_if_fail(purple_account_is_connected(account
) || purple_account_is_connecting(account
));
386 if((list
= purple_blist_find_buddies(account
, name
)) == NULL
)
389 for(l
= list
; l
!= NULL
; l
= l
->next
) {
392 presence
= purple_buddy_get_presence(buddy
);
393 status
= purple_presence_get_status(presence
, status_id
);
398 if (purple_status_is_active(status
)) {
399 purple_status_set_active(status
, FALSE
);
400 purple_buddy_update_status(buddy
, status
);
408 do_protocol_change_account_status(PurpleAccount
*account
,
409 PurpleStatus
*old_status
, PurpleStatus
*new_status
)
411 PurpleProtocol
*protocol
;
413 if (purple_status_is_online(new_status
) &&
414 purple_account_is_disconnected(account
) &&
415 purple_network_is_available())
417 purple_account_connect(account
);
421 if (!purple_status_is_online(new_status
))
423 if (!purple_account_is_disconnected(account
))
424 purple_account_disconnect(account
);
425 /* Clear out the unsaved password if we switch to offline status */
426 if (!purple_account_get_remember_password(account
))
427 purple_account_set_password(account
, NULL
, NULL
, NULL
);
432 if (purple_account_is_connecting(account
))
434 * We don't need to call the set_status protocol function because
435 * the protocol will take care of setting its status during the
436 * connection process.
440 protocol
= purple_protocols_find(purple_account_get_protocol_id(account
));
442 if (protocol
== NULL
)
445 if (!purple_account_is_disconnected(account
))
446 purple_protocol_server_iface_set_status(protocol
, account
, new_status
);
450 purple_protocol_change_account_status(PurpleAccount
*account
,
451 PurpleStatus
*old_status
, PurpleStatus
*new_status
)
453 g_return_if_fail(account
!= NULL
);
454 g_return_if_fail(new_status
!= NULL
);
455 g_return_if_fail(!purple_status_is_exclusive(new_status
) || old_status
!= NULL
);
457 purple_signal_emit(purple_accounts_get_handle(), "account-status-changing",
458 account
, old_status
, new_status
);
460 do_protocol_change_account_status(account
, old_status
, new_status
);
462 purple_signal_emit(purple_accounts_get_handle(), "account-status-changed",
463 account
, old_status
, new_status
);
467 purple_protocol_get_statuses(PurpleAccount
*account
, PurplePresence
*presence
)
469 GList
*statuses
= NULL
;
471 PurpleStatus
*status
;
473 g_return_val_if_fail(account
!= NULL
, NULL
);
474 g_return_val_if_fail(presence
!= NULL
, NULL
);
476 for (l
= purple_account_get_status_types(account
); l
!= NULL
; l
= l
->next
)
478 status
= purple_status_new((PurpleStatusType
*)l
->data
, presence
);
479 statuses
= g_list_prepend(statuses
, status
);
482 statuses
= g_list_reverse(statuses
);
488 purple_protocol_attention(PurpleConversation
*conv
, const char *who
,
489 guint type
, PurpleMessageFlags flags
, time_t mtime
)
491 PurpleAccount
*account
= purple_conversation_get_account(conv
);
492 purple_signal_emit(purple_conversations_get_handle(),
493 flags
== PURPLE_MESSAGE_SEND
? "sent-attention" : "got-attention",
494 account
, who
, conv
, type
);
498 purple_protocol_send_attention(PurpleConnection
*gc
, const char *who
, guint type_code
)
500 PurpleAttentionType
*attn
;
501 PurpleProtocol
*protocol
;
502 PurpleIMConversation
*im
;
507 g_return_if_fail(gc
!= NULL
);
508 g_return_if_fail(who
!= NULL
);
510 protocol
= purple_protocols_find(purple_account_get_protocol_id(purple_connection_get_account(gc
)));
511 g_return_if_fail(PURPLE_PROTOCOL_IMPLEMENTS(protocol
, ATTENTION_IFACE
, send
));
513 attn
= purple_get_attention_type_from_code(purple_connection_get_account(gc
), type_code
);
515 if ((buddy
= purple_blist_find_buddy(purple_connection_get_account(gc
), who
)) != NULL
)
516 alias
= purple_buddy_get_contact_alias(buddy
);
520 if (attn
&& purple_attention_type_get_outgoing_desc(attn
)) {
521 description
= g_strdup_printf(purple_attention_type_get_outgoing_desc(attn
), alias
);
523 description
= g_strdup_printf(_("Requesting %s's attention..."), alias
);
526 purple_debug_info("server", "serv_send_attention: sending '%s' to %s\n",
529 if (!purple_protocol_attention_iface_send(protocol
, gc
, who
, type_code
))
532 im
= purple_im_conversation_new(purple_connection_get_account(gc
), who
);
533 purple_conversation_write_system_message(PURPLE_CONVERSATION(im
), description
, 0);
534 purple_protocol_attention(PURPLE_CONVERSATION(im
), who
, type_code
, PURPLE_MESSAGE_SEND
, time(NULL
));
540 got_attention(PurpleConnection
*gc
, int id
, const char *who
, guint type_code
)
542 PurpleMessageFlags flags
;
543 PurpleAttentionType
*attn
;
551 attn
= purple_get_attention_type_from_code(purple_connection_get_account(gc
), type_code
);
553 /* PURPLE_MESSAGE_NOTIFY is for attention messages. */
554 flags
= PURPLE_MESSAGE_SYSTEM
| PURPLE_MESSAGE_NOTIFY
| PURPLE_MESSAGE_RECV
;
556 /* TODO: if (attn->icon_name) is non-null, use it to lookup an emoticon and display
557 * it next to the attention command. And if it is null, display a generic icon. */
559 if ((buddy
= purple_blist_find_buddy(purple_connection_get_account(gc
), who
)) != NULL
)
560 alias
= purple_buddy_get_contact_alias(buddy
);
564 if (attn
&& purple_attention_type_get_incoming_desc(attn
)) {
565 description
= g_strdup_printf(purple_attention_type_get_incoming_desc(attn
), alias
);
567 description
= g_strdup_printf(_("%s has requested your attention!"), alias
);
570 purple_debug_info("server", "got_attention: got '%s' from %s\n",
574 purple_serv_got_im(gc
, who
, description
, flags
, mtime
);
576 purple_serv_got_chat_in(gc
, id
, who
, flags
, description
, mtime
);
578 /* TODO: sounds (depending on PurpleAttentionType), shaking, etc. */
584 purple_protocol_got_attention(PurpleConnection
*gc
, const char *who
, guint type_code
)
586 PurpleConversation
*conv
= NULL
;
587 PurpleAccount
*account
= purple_connection_get_account(gc
);
589 got_attention(gc
, -1, who
, type_code
);
591 purple_conversations_find_with_account(who
, account
);
593 purple_protocol_attention(conv
, who
, type_code
, PURPLE_MESSAGE_RECV
,
598 purple_protocol_got_attention_in_chat(PurpleConnection
*gc
, int id
, const char *who
, guint type_code
)
600 got_attention(gc
, id
, who
, type_code
);
604 purple_protocol_initiate_media(PurpleAccount
*account
,
606 PurpleMediaSessionType type
)
609 PurpleConnection
*gc
= NULL
;
610 PurpleProtocol
*protocol
= NULL
;
613 gc
= purple_account_get_connection(account
);
615 protocol
= purple_connection_get_protocol(gc
);
618 /* should check that the protocol supports this media type here? */
619 return purple_protocol_media_iface_initiate_session(protocol
, account
, who
, type
);
626 purple_protocol_get_media_caps(PurpleAccount
*account
, const char *who
)
629 PurpleConnection
*gc
= NULL
;
630 PurpleProtocol
*protocol
= NULL
;
633 gc
= purple_account_get_connection(account
);
635 protocol
= purple_connection_get_protocol(gc
);
638 return purple_protocol_media_iface_get_caps(protocol
, account
, who
);
640 return PURPLE_MEDIA_CAPS_NONE
;
644 purple_protocol_got_media_caps(PurpleAccount
*account
, const char *name
)
649 g_return_if_fail(account
!= NULL
);
650 g_return_if_fail(name
!= NULL
);
652 if ((list
= purple_blist_find_buddies(account
, name
)) == NULL
)
656 PurpleBuddy
*buddy
= list
->data
;
657 PurpleMediaCaps oldcaps
= purple_buddy_get_media_caps(buddy
);
658 PurpleMediaCaps newcaps
= 0;
659 const gchar
*bname
= purple_buddy_get_name(buddy
);
660 list
= g_slist_delete_link(list
, list
);
663 newcaps
= purple_protocol_get_media_caps(account
, bname
);
664 purple_buddy_set_media_caps(buddy
, newcaps
);
666 if (oldcaps
== newcaps
)
669 purple_signal_emit(purple_blist_get_handle(),
670 "buddy-caps-changed", buddy
,
677 purple_protocol_get_max_message_size(PurpleProtocol
*protocol
)
679 g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol
), 0);
681 return purple_protocol_client_iface_get_max_message_size(protocol
, NULL
);
684 /**************************************************************************/
685 /* Protocol Action API */
686 /**************************************************************************/
688 PurpleProtocolAction
*
689 purple_protocol_action_new(const char* label
,
690 PurpleProtocolActionCallback callback
)
692 PurpleProtocolAction
*action
;
694 g_return_val_if_fail(label
!= NULL
&& callback
!= NULL
, NULL
);
696 action
= g_new0(PurpleProtocolAction
, 1);
698 action
->label
= g_strdup(label
);
699 action
->callback
= callback
;
705 purple_protocol_action_free(PurpleProtocolAction
*action
)
707 g_return_if_fail(action
!= NULL
);
709 g_free(action
->label
);
713 /**************************************************************************
714 * GBoxed code for PurpleProtocolAction
715 **************************************************************************/
717 static PurpleProtocolAction
*
718 purple_protocol_action_copy(PurpleProtocolAction
*action
)
720 g_return_val_if_fail(action
!= NULL
, NULL
);
722 return purple_protocol_action_new(action
->label
, action
->callback
);
726 purple_protocol_action_get_type(void)
728 static GType type
= 0;
731 type
= g_boxed_type_register_static("PurpleProtocolAction",
732 (GBoxedCopyFunc
)purple_protocol_action_copy
,
733 (GBoxedFreeFunc
)purple_protocol_action_free
);
739 /**************************************************************************
741 **************************************************************************/
743 * Negative if a before b, 0 if equal, positive if a after b.
746 compare_protocol(PurpleProtocol
*a
, PurpleProtocol
*b
)
748 const gchar
*aname
= purple_protocol_get_name(a
);
749 const gchar
*bname
= purple_protocol_get_name(b
);
753 return strcmp(aname
, bname
);
765 purple_protocols_find(const char *id
)
767 g_return_val_if_fail(protocols
!= NULL
&& id
!= NULL
, NULL
);
769 return g_hash_table_lookup(protocols
, id
);
773 purple_protocols_add(GType protocol_type
, GError
**error
)
775 PurpleProtocol
*protocol
;
776 PurpleProtocolClass
*klass
;
778 if (protocol_type
== G_TYPE_INVALID
) {
779 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
780 _("Protocol type is not registered"));
784 if (!g_type_is_a(protocol_type
, PURPLE_TYPE_PROTOCOL
)) {
785 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
786 _("Protocol type does not inherit PurpleProtocol"));
790 if (G_TYPE_IS_ABSTRACT(protocol_type
)) {
791 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
792 _("Protocol type is abstract"));
796 protocol
= g_object_new(protocol_type
, NULL
);
797 klass
= PURPLE_PROTOCOL_GET_CLASS(protocol
);
800 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
801 _("Could not create protocol instance"));
805 if (!purple_protocol_get_id(protocol
)) {
806 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
807 _("Protocol does not provide an ID"));
809 g_object_unref(protocol
);
813 if (purple_protocols_find(purple_protocol_get_id(protocol
))) {
814 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
815 _("A protocol with the ID %s is already added."),
816 purple_protocol_get_id(protocol
));
818 g_object_unref(protocol
);
822 /* Make sure the protocol implements the required functions */
823 if (!klass
->login
|| !klass
->close
||
824 !klass
->status_types
|| !klass
->list_icon
)
826 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
827 _("Protocol %s does not implement all the functions in "
828 "PurpleProtocolClass"), purple_protocol_get_id(protocol
));
830 g_object_unref(protocol
);
834 g_hash_table_insert(protocols
, g_strdup(purple_protocol_get_id(protocol
)),
837 purple_debug_info("protocols", "Added protocol %s\n",
838 purple_protocol_get_id(protocol
));
840 purple_signal_emit(purple_protocols_get_handle(), "protocol-added",
845 gboolean
purple_protocols_remove(PurpleProtocol
*protocol
, GError
**error
)
847 g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol
), FALSE
);
848 g_return_val_if_fail(purple_protocol_get_id(protocol
) != NULL
, FALSE
);
850 if (purple_protocols_find(purple_protocol_get_id(protocol
)) == NULL
) {
851 g_set_error(error
, PURPLE_PROTOCOLS_DOMAIN
, 0,
852 _("Protocol %s is not added."),
853 purple_protocol_get_id(protocol
));
858 purple_debug_info("protocols", "Removing protocol %s\n",
859 purple_protocol_get_id(protocol
));
861 purple_signal_emit(purple_protocols_get_handle(), "protocol-removed",
864 g_hash_table_remove(protocols
, purple_protocol_get_id(protocol
));
869 purple_protocols_get_all(void)
872 PurpleProtocol
*protocol
;
875 g_hash_table_iter_init(&iter
, protocols
);
876 while (g_hash_table_iter_next(&iter
, NULL
, (gpointer
*)&protocol
))
877 ret
= g_list_insert_sorted(ret
, protocol
, (GCompareFunc
)compare_protocol
);
882 /**************************************************************************
883 * Protocols Subsystem API
884 **************************************************************************/
886 purple_protocols_init(void)
888 void *handle
= purple_protocols_get_handle();
890 protocols
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
,
891 (GDestroyNotify
)g_object_unref
);
893 purple_signal_register(handle
, "protocol-added",
894 purple_marshal_VOID__POINTER
,
895 G_TYPE_NONE
, 1, PURPLE_TYPE_PROTOCOL
);
896 purple_signal_register(handle
, "protocol-removed",
897 purple_marshal_VOID__POINTER
,
898 G_TYPE_NONE
, 1, PURPLE_TYPE_PROTOCOL
);
902 purple_protocols_get_handle(void)
910 purple_protocols_uninit(void)
912 g_hash_table_destroy(protocols
);