Create a manager for conversations.
I Skipped unit tests because need a connection and full protocol implementation to do it properly.
Testing Done:
Compiled and ran with an IRC account.
Reviewed at https://reviews.imfreedom.org/r/677/
--- a/doc/reference/libpurple/libpurple-docs.xml Sat Jun 12 19:10:06 2021 -0500
+++ b/doc/reference/libpurple/libpurple-docs.xml Mon Jun 14 04:07:45 2021 -0500
@@ -72,6 +72,7 @@
<xi:include href="xml/purplechatconversation.xml" />
<xi:include href="xml/purplechatuser.xml" />
<xi:include href="xml/purpleconversation.xml" />
+ <xi:include href="xml/purpleconversationmanager.xml" /> <xi:include href="xml/purpleconversationuiops.xml" />
<xi:include href="xml/purplecredentialmanager.xml" />
<xi:include href="xml/purplecredentialprovider.xml" />
--- a/finch/gntconv.c Sat Jun 12 19:10:06 2021 -0500
+++ b/finch/gntconv.c Mon Jun 14 04:07:45 2021 -0500
@@ -281,55 +281,88 @@
account_signed_on_off(PurpleConnection *gc, gpointer null)
- GList *list = purple_conversations_get_ims();
- PurpleConversation *conv = list->data;
- PurpleConversation *cc = find_im_with_contact(
- purple_conversation_get_account(conv), purple_conversation_get_name(conv));
+ list = purple_conversations_get_all(); + for(l = list; l != NULL; l = l->next) { + PurpleConversation *conv = NULL; + PurpleConversation *cc = NULL; + if(!PURPLE_IS_IM_CONVERSATION(l->data)) { + conv = PURPLE_CONVERSATION(l->data); + cc = find_im_with_contact(purple_conversation_get_account(conv), + purple_conversation_get_name(conv)); generate_send_to_menu(FINCH_CONV(cc));
+ /* If we disconnected we're done for now. */ + if(!PURPLE_CONNECTION_IS_CONNECTED(gc)) { - if (PURPLE_CONNECTION_IS_CONNECTED(gc)) {
- /* We just signed on. Let's see if there's any chat that we have open,
- * and hadn't left before the disconnect. */
- list = purple_conversations_get_chats();
- PurpleConversation *conv = list->data;
- GHashTable *comps = NULL;
+ /* Since we just signed on. Let's see if there's any chat that we have open, + * and hadn't left before the disconnect. + for(l = list; l != NULL; l = l->next) { + PurpleConversation *conv = NULL; + GHashTable *comps = NULL; + conv = PURPLE_CONVERSATION(l->data); + if(!PURPLE_IS_CHAT_CONVERSATION(conv)) {
- if (purple_conversation_get_account(conv) != purple_connection_get_account(gc) ||
- !g_object_get_data(G_OBJECT(conv), "want-to-rejoin"))
+ if (purple_conversation_get_account(conv) != purple_connection_get_account(gc) || + !g_object_get_data(G_OBJECT(conv), "want-to-rejoin")) - chat = find_chat_for_conversation(conv);
- PurpleProtocol *protocol = purple_connection_get_protocol(gc);
- comps = purple_protocol_chat_info_defaults(PURPLE_PROTOCOL_CHAT(protocol), gc,
- purple_conversation_get_name(conv));
- comps = purple_chat_get_components(chat);
- purple_serv_join_chat(gc, comps);
- if (chat == NULL && comps != NULL)
- g_hash_table_destroy(comps);
+ chat = find_chat_for_conversation(conv); + PurpleProtocol *protocol = purple_connection_get_protocol(gc); + comps = purple_protocol_chat_info_defaults(PURPLE_PROTOCOL_CHAT(protocol), gc, + purple_conversation_get_name(conv)); + comps = purple_chat_get_components(chat); + purple_serv_join_chat(gc, comps); + if(chat == NULL && comps != NULL) { + g_hash_table_destroy(comps); account_signing_off(PurpleConnection *gc)
- GList *list = purple_conversations_get_chats();
+ GList *list = purple_conversations_get_all(); PurpleAccount *account = purple_connection_get_account(gc);
/* We are about to sign off. See which chats we are currently in, and mark
* them for rejoin on reconnect. */
- PurpleConversation *conv = list->data;
+ PurpleConversation *conv = NULL; + if(!PURPLE_IS_CHAT_CONVERSATION(list->data)) { + conv = PURPLE_CONVERSATION(list->data); if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv)) &&
purple_conversation_get_account(conv) == account) {
g_object_set_data(G_OBJECT(conv), "want-to-rejoin", GINT_TO_POINTER(TRUE));
@@ -339,7 +372,8 @@
"the account reconnects."),
+ list = g_list_delete_link(list, list); @@ -1001,7 +1035,7 @@
/* Print the list of users in the room */
GString *string = g_string_new(NULL);
- int count = g_list_length(users);
+ gint count = g_list_length(users); ngettext("List of %d user:\n", "List of %d users:\n", count), count);
--- a/libpurple/conversations.c Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/conversations.c Mon Jun 14 04:07:45 2021 -0500
@@ -22,219 +22,91 @@
#include "purpleprivate.h"
-#include "conversations.h"
+#include "purpleconversationmanager.h" -static GList *conversations = NULL;
-static GList *ims = NULL;
-static GList *chats = NULL;
static PurpleConversationUiOps *default_ops = NULL;
- * A hash table used for efficient lookups of conversations by name.
- * struct _purple_hconv => PurpleConversation*
-static GHashTable *conversation_cache = NULL;
- PurpleAccount *account;
-_purple_conversations_hconv_hash(struct _purple_hconv *hc)
- return g_str_hash(hc->name) ^ hc->im ^ g_direct_hash(hc->account);
-_purple_conversations_hconv_equal(struct _purple_hconv *hc1, struct _purple_hconv *hc2)
- return (hc1->im == hc2->im &&
- hc1->account == hc2->account &&
- g_str_equal(hc1->name, hc2->name));
-_purple_conversations_hconv_free_key(struct _purple_hconv *hc)
-purple_conversations_add(PurpleConversation *conv)
- PurpleAccount *account;
- struct _purple_hconv *hc;
+purple_conversations_add(PurpleConversation *conv) { + PurpleConversationManager *manager = NULL; g_return_if_fail(conv != NULL);
- if (g_list_find(conversations, conv) != NULL)
- conversations = g_list_prepend(conversations, conv);
- if (PURPLE_IS_IM_CONVERSATION(conv))
- ims = g_list_prepend(ims, conv);
- chats = g_list_prepend(chats, conv);
+ manager = purple_conversation_manager_get_default(); - account = purple_conversation_get_account(conv);
- hc = g_new(struct _purple_hconv, 1);
- hc->name = g_strdup(purple_normalize(account,
- purple_conversation_get_name(conv)));
- hc->im = PURPLE_IS_IM_CONVERSATION(conv);
- g_hash_table_insert(conversation_cache, hc, conv);
+ purple_conversation_manager_register(manager, conv); -purple_conversations_remove(PurpleConversation *conv)
- PurpleAccount *account;
- struct _purple_hconv hc;
+purple_conversations_remove(PurpleConversation *conv) { + PurpleConversationManager *manager = NULL; g_return_if_fail(conv != NULL);
- conversations = g_list_remove(conversations, conv);
- if (PURPLE_IS_IM_CONVERSATION(conv))
- ims = g_list_remove(ims, conv);
- chats = g_list_remove(chats, conv);
- account = purple_conversation_get_account(conv);
- hc.name = (gchar *)purple_normalize(account,
- purple_conversation_get_name(conv));
- hc.im = PURPLE_IS_IM_CONVERSATION(conv);
- g_hash_table_remove(conversation_cache, &hc);
+ manager = purple_conversation_manager_get_default();
-_purple_conversations_update_cache(PurpleConversation *conv, const char *name,
- PurpleAccount *account)
- PurpleAccount *old_account;
- struct _purple_hconv *hc;
- g_return_if_fail(conv != NULL);
- g_return_if_fail(account != NULL || name != NULL);
- old_account = purple_conversation_get_account(conv);
- hc = g_new(struct _purple_hconv, 1);
- hc->im = PURPLE_IS_IM_CONVERSATION(conv);
- hc->account = old_account;
- hc->name = (gchar *)purple_normalize(old_account,
- purple_conversation_get_name(conv));
- g_hash_table_remove(conversation_cache, hc);
- hc->name = g_strdup(purple_normalize(hc->account, name));
- g_hash_table_insert(conversation_cache, hc, conv);
+ purple_conversation_manager_unregister(manager, conv); -purple_conversations_get_all(void)
+purple_conversations_get_all(void) { + PurpleConversationManager *manager = NULL;
-purple_conversations_get_ims(void)
+ manager = purple_conversation_manager_get_default();
-purple_conversations_get_chats(void)
+ return purple_conversation_manager_get_all(manager); purple_conversations_find_with_account(const char *name,
- PurpleAccount *account)
+ PurpleAccount *account) - PurpleConversation *c = NULL;
- struct _purple_hconv hc;
+ PurpleConversationManager *manager = NULL; g_return_val_if_fail(name != NULL, NULL);
- hc.name = (gchar *)purple_normalize(account, name);
+ manager = purple_conversation_manager_get_default();
- c = g_hash_table_lookup(conversation_cache, &hc);
- c = g_hash_table_lookup(conversation_cache, &hc);
+ return purple_conversation_manager_find(manager, account, name); -purple_conversations_find_im_with_account(const char *name,
- PurpleAccount *account)
+purple_conversations_find_im_with_account(const gchar *name, + PurpleAccount *account) - PurpleConversation *im = NULL;
- struct _purple_hconv hc;
+ PurpleConversationManager *manager = NULL; g_return_val_if_fail(name != NULL, NULL);
- hc.name = (gchar *)purple_normalize(account, name);
+ manager = purple_conversation_manager_get_default(); - im = g_hash_table_lookup(conversation_cache, &hc);
+ return purple_conversation_manager_find_im(manager, account, name); -purple_conversations_find_chat_with_account(const char *name,
- PurpleAccount *account)
+purple_conversations_find_chat_with_account(const gchar *name, + PurpleAccount *account) - PurpleConversation *c = NULL;
- struct _purple_hconv hc;
+ PurpleConversationManager *manager = NULL; g_return_val_if_fail(name != NULL, NULL);
- hc.name = (gchar *)purple_normalize(account, name);
+ manager = purple_conversation_manager_get_default(); - c = g_hash_table_lookup(conversation_cache, &hc);
+ return purple_conversation_manager_find_chat(manager, account, name); -purple_conversations_find_chat(const PurpleConnection *gc, int id)
- PurpleConversation *chat;
+purple_conversations_find_chat(PurpleConnection *gc, gint id) { + PurpleAccount *account = NULL; + PurpleConversationManager *manager = NULL; - for (l = purple_conversations_get_chats(); l != NULL; l = l->next) {
- chat = (PurpleConversation *)l->data;
+ g_return_val_if_fail(PURPLE_IS_CONNECTION(gc), NULL); - if (purple_chat_conversation_get_id(PURPLE_CHAT_CONVERSATION(chat)) == id &&
- purple_conversation_get_connection(chat) == gc)
+ account = purple_connection_get_account(gc); + manager = purple_conversation_manager_get_default();
+ return purple_conversation_manager_find_chat_by_id(manager, account, id); @@ -262,10 +134,6 @@
void *handle = purple_conversations_get_handle();
- conversation_cache = g_hash_table_new_full((GHashFunc)_purple_conversations_hconv_hash,
- (GEqualFunc)_purple_conversations_hconv_equal,
- (GDestroyNotify)_purple_conversations_hconv_free_key, NULL);
/**********************************************************************
**********************************************************************/
@@ -458,9 +326,5 @@
purple_conversations_uninit(void)
- g_object_unref(G_OBJECT(conversations->data));
- g_hash_table_destroy(conversation_cache);
purple_signals_unregister_by_instance(purple_conversations_get_handle());
--- a/libpurple/conversations.h Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/conversations.h Mon Jun 14 04:07:45 2021 -0500
@@ -48,6 +48,7 @@
* Adds a conversation to the list of conversations.
+G_DEPRECATED_FOR(purple_conversation_manager_register) void purple_conversations_add(PurpleConversation *conv);
@@ -56,6 +57,7 @@
* Removes a conversation from the list of conversations.
+G_DEPRECATED_FOR(purple_conversation_manager_unregister) void purple_conversations_remove(PurpleConversation *conv);
@@ -67,27 +69,10 @@
* Returns: (element-type PurpleConversation) (transfer none): A GList of all conversations.
+G_DEPRECATED_FOR(purple_conversation_manager_get_all) GList *purple_conversations_get_all(void);
- * purple_conversations_get_ims:
- * Returns a list of all IMs.
- * Returns: (element-type PurpleIMConversation) (transfer none): All IMs.
-GList *purple_conversations_get_ims(void);
- * purple_conversations_get_chats:
- * Returns a list of all chats.
- * Returns: (element-type PurpleChatConversation) (transfer none): All chats.
-GList *purple_conversations_get_chats(void);
* purple_conversations_find_with_account:
* @name: The name of the conversation.
* @account: The account associated with the conversation.
@@ -96,6 +81,7 @@
* Returns: (transfer none): The conversation if found, or %NULL otherwise.
+G_DEPRECATED_FOR(purple_conversation_manager_find) PurpleConversation *purple_conversations_find_with_account(const char *name,
@@ -108,6 +94,7 @@
* Returns: (transfer none): The conversation if found, or %NULL otherwise.
+G_DEPRECATED_FOR(purple_conversation_manager_find_im) PurpleConversation *purple_conversations_find_im_with_account(const char *name, PurpleAccount *account);
@@ -119,6 +106,7 @@
* Returns: (transfer none): The conversation if found, or %NULL otherwise.
+G_DEPRECATED_FOR(purple_conversation_manager_find_chat) PurpleConversation *purple_conversations_find_chat_with_account(const char *name, PurpleAccount *account);
@@ -130,7 +118,8 @@
* Returns: (transfer none): The chat conversation.
-PurpleConversation *purple_conversations_find_chat(const PurpleConnection *gc, int id);
+G_DEPRECATED_FOR(purple_conversation_manager_find_chat_by_id) +PurpleConversation *purple_conversations_find_chat(PurpleConnection *gc, int id); * purple_conversations_set_ui_ops:
--- a/libpurple/core.c Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/core.c Mon Jun 14 04:07:45 2021 -0500
@@ -172,6 +172,7 @@
purple_savedstatuses_init();
purple_conversations_init();
+ purple_conversation_manager_startup(); @@ -226,6 +227,7 @@
_purple_smiley_custom_uninit();
_purple_smiley_parser_uninit();
+ purple_conversation_manager_shutdown(); purple_conversations_uninit();
--- a/libpurple/meson.build Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/meson.build Mon Jun 14 04:07:45 2021 -0500
@@ -48,6 +48,7 @@
'purplechatconversation.c',
+ 'purpleconversationmanager.c', 'purpleconversationuiops.c',
'purplecredentialmanager.c',
'purplecredentialprovider.c',
@@ -139,6 +140,7 @@
'purplechatconversation.h',
+ 'purpleconversationmanager.h', 'purpleconversationuiops.h',
'purplecredentialmanager.h',
'purplecredentialprovider.h',
--- a/libpurple/protocols/null/nullprpl.c Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/protocols/null/nullprpl.c Mon Jun 14 04:07:45 2021 -0500
@@ -1036,11 +1036,19 @@
/* add each chat room. the chat ids are cached in seen_ids so that each room
* is only returned once, even if multiple users are in it. */
- for (chats = purple_conversations_get_chats(); chats; chats = g_list_next(chats)) {
- PurpleChatConversation *chat = PURPLE_CHAT_CONVERSATION(chats->data);
- PurpleRoomlistRoom *room;
- const char *name = purple_conversation_get_name(PURPLE_CONVERSATION(chat));
- int id = purple_chat_conversation_get_id(chat);
+ for (chats = purple_conversations_get_all(); chats; chats = g_list_next(chats)) { + PurpleChatConversation *chat = NULL; + PurpleRoomlistRoom *room = NULL; + const gchar *name = NULL; + if(!PURPLE_IS_CHAT_CONVERSATION(chats->data)) { + chat = PURPLE_CHAT_CONVERSATION(chats->data); + name = purple_conversation_get_name(PURPLE_CONVERSATION(chat)); + id = purple_chat_conversation_get_id(chat); /* have we already added this room? */
if (g_list_find_custom(seen_ids, name, (GCompareFunc)strcmp))
--- a/libpurple/purpleconversation.c Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/purpleconversation.c Mon Jun 14 04:07:45 2021 -0500
@@ -483,8 +483,6 @@
priv = purple_conversation_get_instance_private(conv);
if(g_set_object(&priv->account, account)) {
- _purple_conversations_update_cache(conv, NULL, account);
g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_ACCOUNT]);
purple_conversation_update(conv, PURPLE_CONVERSATION_UPDATE_ACCOUNT);
@@ -585,8 +583,6 @@
priv->name = g_strdup(name);
- _purple_conversations_update_cache(conv, name, NULL);
g_object_notify_by_pspec(G_OBJECT(conv), properties[PROP_NAME]);
purple_conversation_autoset_title(conv);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpleconversationmanager.c Mon Jun 14 04:07:45 2021 -0500
@@ -0,0 +1,238 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <purpleconversationmanager.h> +#include <purplechatconversation.h> +#include <purpleimconversation.h> +#include <purpleprivate.h> +struct _PurpleConversationManager { + GHashTable *conversations; +static PurpleConversationManager *default_manager = NULL; +G_DEFINE_TYPE(PurpleConversationManager, purple_conversation_manager, +typedef gboolean (*PurpleConversationManagerCompareFunc)(PurpleConversation *conversation, gpointer userdata); +/****************************************************************************** + *****************************************************************************/ +purple_conversation_is_im(PurpleConversation *conversation, + G_GNUC_UNUSED gpointer userdata) + return PURPLE_IS_IM_CONVERSATION(conversation); +purple_conversation_is_chat(PurpleConversation *conversation, + G_GNUC_UNUSED gpointer userdata) + return PURPLE_IS_CHAT_CONVERSATION(conversation); +purple_conversation_chat_has_id(PurpleConversation *conversation, + PurpleChatConversation *chat = NULL; + gint id = GPOINTER_TO_INT(userdata); + if(!PURPLE_IS_CHAT_CONVERSATION(conversation)) { + chat = PURPLE_CHAT_CONVERSATION(conversation); + return (purple_chat_conversation_get_id(chat) == id); +static PurpleConversation * +purple_conversation_manager_find_internal(PurpleConversationManager *manager, + PurpleAccount *account, + PurpleConversationManagerCompareFunc func, + g_hash_table_iter_init(&iter, manager->conversations); + while(g_hash_table_iter_next(&iter, &key, NULL)) { + PurpleConversation *conversation = PURPLE_CONVERSATION(key); + if(purple_strequal(purple_conversation_get_name(conversation), name)) { + if(purple_conversation_get_account(conversation) == account) { + if(func != NULL && !func(conversation, userdata)) { +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +purple_conversation_manager_init(PurpleConversationManager *manager) { + manager->conversations = g_hash_table_new_full(g_direct_hash, +purple_conversation_manager_finalize(GObject *obj) { + PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj); + g_hash_table_destroy(manager->conversations); + G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj); +purple_conversation_manager_class_init(PurpleConversationManagerClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->finalize = purple_conversation_manager_finalize; +/****************************************************************************** + *****************************************************************************/ +purple_conversation_manager_startup(void) { + if(default_manager == NULL) { + default_manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL); +purple_conversation_manager_shutdown(void) { + g_clear_object(&default_manager); +/****************************************************************************** + *****************************************************************************/ +PurpleConversationManager * +purple_conversation_manager_get_default(void) { + return default_manager; +purple_conversation_manager_register(PurpleConversationManager *manager, + PurpleConversation *conversation) + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE); + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + return g_hash_table_insert(manager->conversations, + g_object_ref(conversation), NULL); +purple_conversation_manager_unregister(PurpleConversationManager *manager, + PurpleConversation *conversation) + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE); + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + return g_hash_table_remove(manager->conversations, conversation); +purple_conversation_manager_is_registered(PurpleConversationManager *manager, + PurpleConversation *conversation) + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), FALSE); + g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE); + return g_hash_table_lookup_extended(manager->conversations, conversation, +purple_conversation_manager_get_all(PurpleConversationManager *manager) { + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); + return g_hash_table_get_keys(manager->conversations); +purple_conversation_manager_find(PurpleConversationManager *manager, + PurpleAccount *account, const gchar *name) + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + g_return_val_if_fail(name != NULL, NULL); + return purple_conversation_manager_find_internal(manager, account, name, +purple_conversation_manager_find_im(PurpleConversationManager *manager, + PurpleAccount *account, const gchar *name) + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + g_return_val_if_fail(name != NULL, NULL); + return purple_conversation_manager_find_internal(manager, account, name, + purple_conversation_is_im, +purple_conversation_manager_find_chat(PurpleConversationManager *manager, + PurpleAccount *account, + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + g_return_val_if_fail(name != NULL, NULL); + return purple_conversation_manager_find_internal(manager, account, name, + purple_conversation_is_chat, +purple_conversation_manager_find_chat_by_id(PurpleConversationManager *manager, + PurpleAccount *account, gint id) + g_return_val_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager), NULL); + g_return_val_if_fail(PURPLE_IS_ACCOUNT(account), NULL); + return purple_conversation_manager_find_internal(manager, account, NULL, + purple_conversation_chat_has_id, --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/purpleconversationmanager.h Mon Jun 14 04:07:45 2021 -0500
@@ -0,0 +1,177 @@
+ * Purple - Internet Messaging Library + * Copyright (C) Pidgin Developers <devel@pidgin.im> + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION) +# error "only <purple.h> may be included directly" +#ifndef PURPLE_CONVERSATION_MANAGER_H +#define PURPLE_CONVERSATION_MANAGER_H +#include <purpleconversation.h> + * SECTION:purpleconversationmanager + * @section_id: libpurple-purpleconversationmanager + * @title: Management of conversations + * #PurpleConversationManager keeps track of all #PurpleConversation's inside of + * libpurple and allows searching of them. +#define PURPLE_TYPE_CONVERSATION_MANAGER (purple_conversation_manager_get_type()) +G_DECLARE_FINAL_TYPE(PurpleConversationManager, purple_conversation_manager, + PURPLE, CONVERSATION_MANAGER, GObject) + * purple_conversation_manager_get_default: + * Gets the default instance of #PurpleConversationManager. This instance + * can be used for any of the API including connecting to signals. + * Returns: (transfer none): The default #PurpleConversationManager instance. +PurpleConversationManager *purple_conversation_manager_get_default(void); + * purple_conversation_manager_register: + * @manager: The #PurpleConversationManager instance. + * @conversation: The #PurpleConversation to register. + * Registers @conversation with @manager. + * Returns: %TRUE if @conversation was not yet registered. +gboolean purple_conversation_manager_register(PurpleConversationManager *manager, PurpleConversation *conversation); + * purple_conversation_manager_unregister: + * @manager: The #PurpleConversationManager instance. + * @conversation: The #PurpleConversation to unregister. + * Unregisters @conversation with @manager. + * Returns: %TRUE if @conversation was found and unregistered. +gboolean purple_conversation_manager_unregister(PurpleConversationManager *manager, PurpleConversation *conversation); + * purple_conversation_manager_is_registered: + * @manager: The #PurpleConversationManager instance. + * @conversation: The #PurpleConversation instance. + * Checks if @conversation is registered with @manager. + * Returns: %TRUE if @conversation is registered with @manager, %FALSE +gboolean purple_conversation_manager_is_registered(PurpleConversationManager *manager, PurpleConversation *conversation); + * purple_conversation_manager_get_all: + * @manager: The #PurpleConversationManager instance. + * Gets a list of all conversations that are registered with @manager. + * Returns: (transfer container) (element-type PurpleConversation): A list of + * all of the registered conversations. +GList *purple_conversation_manager_get_all(PurpleConversationManager *manager); + * purple_conversation_manager_find: + * @manager: The #PurpleConversationManager instance. + * @account: The #PurpleAccount instance whose conversation to find. + * @name: The name of the conversation. + * Looks for a registered conversation belonging to @account and named @named. + * This function will return the first one matching the given criteria. If you + * specifically need an im or chat see purple_conversation_manager_find_im() + * or purple_conversation_manager_find_chat(). + * Returns: (transfer none): The #PurpleConversation if found, otherwise %NULL. +PurpleConversation *purple_conversation_manager_find(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name); + * purple_conversation_manager_find_im: + * @manager: The #PurpleConversationManager instance. + * @account: The #PurpleAccount instance whose conversation to find. + * @name: The name of the conversation. + * Looks for a registered im conversation belonging to @account and named + * Returns: (transfer none): The #PurpleConversation if found, otherwise %NULL. +PurpleConversation *purple_conversation_manager_find_im(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name); + * purple_conversation_manager_find_chat: + * @manager: The #PurpleConversationManager instance. + * @account: The #PurpleAccount instance whose conversation to find. + * @name: The name of the conversation. + * Looks for a registered chat conversation belonging to @account and named + * Returns: (transfer none): The #PurpleConversation if found, otherwise %NULL. +PurpleConversation *purple_conversation_manager_find_chat(PurpleConversationManager *manager, PurpleAccount *account, const gchar *name); + * purple_conversation_manager_find_chat_by_id: + * @manager: The #PurpleConversationManager instance. + * @account: The #PurpleAccount instance whose conversation to find. + * @id: The id of the conversation. + * Looks for a registered chat conversation belonging to @account with an id of + * This is typically only called by protocols. + * Returns: (transfer none): The #PurpleConversation if found, otherwise %NULL. +PurpleConversation *purple_conversation_manager_find_chat_by_id(PurpleConversationManager *manager, PurpleAccount *account, gint id); +#endif /* PURPLE_CONVERSATION_MANAGER_H */ --- a/libpurple/purpleprivate.h Sat Jun 12 19:10:06 2021 -0500
+++ b/libpurple/purpleprivate.h Mon Jun 14 04:07:45 2021 -0500
@@ -153,22 +153,6 @@
PurpleChatConversation *chat);
- * _purple_conversations_update_cache:
- * @conv: The conversation.
- * @name: The new name. If no change, use %NULL.
- * @account: The new account. If no change, use %NULL.
- * Updates the conversation cache to use a new conversation name and/or
- * account. This function only updates the conversation cache. It is the
- * caller's responsibility to actually update the conversation.
- * Note: This function should only be called by purple_conversation_set_name()
- * and purple_conversation_set_account() in conversation.c.
-void _purple_conversations_update_cache(PurpleConversation *conv,
- const char *name, PurpleAccount *account);
* _purple_statuses_get_primitive_scores:
* Note: This function should only be called by
@@ -197,6 +181,24 @@
_purple_conversation_write_common(PurpleConversation *conv, PurpleMessage *msg);
+ * purple_conversation_manager_startup: + * Starts up the conversation manager by creating the default instance. +void purple_conversation_manager_startup(void); + * purple_conversation_manager_shutdown: + * Shuts down the conversation manager by destroying the default instance. +void purple_conversation_manager_shutdown(void); * purple_credential_manager_startup:
* Starts up the credential manager by creating the default instance.
--- a/pidgin/gtkblist.c Sat Jun 12 19:10:06 2021 -0500
+++ b/pidgin/gtkblist.c Mon Jun 14 04:07:45 2021 -0500
@@ -54,7 +54,6 @@
#include "pidgin/pidginstylecontext.h"
#include "pidgin/pidgintooltip.h"
#include "pidgin/pidginwindow.h"
-#include "pidginmenutray.h"
#include <gdk/gdkkeysyms.h>
@@ -4014,70 +4013,10 @@
-unseen_conv_menu(GdkEvent *event)
- static GtkWidget *menu = NULL;
- gtk_widget_destroy(menu);
- ims = pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT, FALSE, 0);
- chats = pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK, FALSE, 0);
- convs = g_list_concat(ims, chats);
- /* no conversations added, don't show the menu */
- pidgin_conversations_fill_menu(menu, convs);
- gtk_widget_show_all(menu);
- gtk_menu_popup_at_pointer(GTK_MENU(menu), event);
-menutray_press_cb(GtkWidget *widget, GdkEventButton *event)
- if (event->button == GDK_BUTTON_PRIMARY) {
- convs = pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT, FALSE, 1);
- convs = pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK, FALSE, 1);
- pidgin_conv_present_conversation((PurpleConversation*)convs->data);
- } else if (gdk_event_triggers_context_menu((GdkEvent *)event)) {
- unseen_conv_menu((GdkEvent *)event);
conversation_updated_cb(PurpleConversation *conv, PurpleConversationUpdateType type,
PidginBuddyList *gtkblist)
PurpleAccount *account = purple_conversation_get_account(conv);
if (type != PURPLE_CONVERSATION_UPDATE_UNSEEN)
@@ -4087,60 +4026,6 @@
pidgin_blist_update_buddy(NULL, PURPLE_BLIST_NODE(buddy), TRUE);
- if (gtkblist->menutrayicon) {
- gtk_widget_destroy(gtkblist->menutrayicon);
- gtkblist->menutrayicon = NULL;
- ims = pidgin_conversations_get_unseen_ims(PIDGIN_UNSEEN_TEXT, FALSE, 0);
- chats = pidgin_conversations_get_unseen_chats(PIDGIN_UNSEEN_NICK, FALSE, 0);
- convs = g_list_concat(ims, chats);
- GString *tooltip_text = NULL;
- tooltip_text = g_string_new("");
- PidginConversation *gtkconv = PIDGIN_CONVERSATION((PurpleConversation *)l->data);
- count = gtkconv->unseen_count;
- else if(g_object_get_data(G_OBJECT(l->data), "unseen-count"))
- count = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(l->data), "unseen-count"));
- g_string_append_printf(tooltip_text,
- ngettext("%d unread message from %s\n", "%d unread messages from %s\n", count),
- count, purple_conversation_get_title(l->data));
- if(tooltip_text->len > 0) {
- /* get rid of the last newline */
- g_string_truncate(tooltip_text, tooltip_text->len -1);
- img = gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_PENDING,
- gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
- gtkblist->menutrayicon = gtk_event_box_new();
- gtk_container_add(GTK_CONTAINER(gtkblist->menutrayicon), img);
- gtk_widget_show(gtkblist->menutrayicon);
- g_signal_connect(G_OBJECT(gtkblist->menutrayicon), "button-press-event", G_CALLBACK(menutray_press_cb), NULL);
- pidgin_menu_tray_append(PIDGIN_MENU_TRAY(gtkblist->menutray), gtkblist->menutrayicon, tooltip_text->str);
- g_string_free(tooltip_text, TRUE);
@@ -5160,9 +5045,6 @@
g_signal_connect(G_OBJECT(gtkblist->window), "key_press_event", G_CALLBACK(gtk_blist_window_key_press_cb), gtkblist);
gtk_widget_add_events(gtkblist->window, GDK_VISIBILITY_NOTIFY_MASK);
- /******************************* Menu bar *************************************/
- gtkblist->menutray = pidgin_contact_list_get_menu_tray(PIDGIN_CONTACT_LIST(gtkblist->window));
/****************************** Notebook *************************************/
gtkblist->notebook = gtk_notebook_new();
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(gtkblist->notebook), FALSE);
--- a/pidgin/gtkblist.h Sat Jun 12 19:10:06 2021 -0500
+++ b/pidgin/gtkblist.h Mon Jun 14 04:07:45 2021 -0500
@@ -55,10 +55,8 @@
* into. Your plugin might want to pack something in it
* @treeview: It's a treeview... d'uh.
- * @treemodel: This is the treemodel.
+ * @treemodel: This is the treemodel. - * @menutray: The menu tray widget.
- * @menutrayicon: The menu tray icon.
* @refresh_timer: The timer for refreshing every 30 seconds
* @drag_timeout: The timeout for expanding contacts on drags
* @drag_rect: This is the bounding rectangle of the cell we're
@@ -97,8 +95,6 @@
GtkCellRenderer *text_rend;
- GtkWidget *menutrayicon;
--- a/pidgin/gtkconv.c Sat Jun 12 19:10:06 2021 -0500
+++ b/pidgin/gtkconv.c Mon Jun 14 04:07:45 2021 -0500
@@ -1783,24 +1783,6 @@
min_state, hidden_only, max_count);
-pidgin_conversations_get_unseen_ims(PidginUnseenState min_state,
- return pidgin_conversations_get_unseen(purple_conversations_get_ims(),
- min_state, hidden_only, max_count);
-pidgin_conversations_get_unseen_chats(PidginUnseenState min_state,
- return pidgin_conversations_get_unseen(purple_conversations_get_chats(),
- min_state, hidden_only, max_count);
unseen_conv_menu_cb(GtkMenuItem *item, PurpleConversation *conv)
@@ -4537,13 +4519,19 @@
account_signing_off(PurpleConnection *gc)
- GList *list = purple_conversations_get_chats();
+ GList *list = purple_conversations_get_all(); PurpleAccount *account = purple_connection_get_account(gc);
/* We are about to sign off. See which chats we are currently in, and mark
* them for rejoin on reconnect. */
- PurpleConversation *conv = list->data;
+ PurpleConversation *conv = NULL; + if(!PURPLE_IS_CHAT_CONVERSATION(list->data)) { + conv = PURPLE_CONVERSATION(list->data); if (!purple_chat_conversation_has_left(PURPLE_CHAT_CONVERSATION(conv)) &&
purple_conversation_get_account(conv) == account) {
g_object_set_data(G_OBJECT(conv), "want-to-rejoin", GINT_TO_POINTER(TRUE));
@@ -4553,7 +4541,8 @@
"rejoin the chat when the account reconnects."),
+ list = g_list_delete_link(list, list); @@ -4784,12 +4773,16 @@
if (PURPLE_IS_IM_CONVERSATION(conv)) {
list = g_list_copy(list);
- for (convs = purple_conversations_get_ims(); convs; convs = convs->next)
+ for (convs = purple_conversations_get_all(); convs; convs = convs->next) { + if(!PURPLE_IS_IM_CONVERSATION(convs->data)) { if (convs->data != conv &&
pidgin_conv_find_gtkconv(convs->data) == gtkconv) {
pidgin_conv_attach(convs->data);
list = g_list_concat(list, g_list_copy(purple_conversation_get_message_history(convs->data)));
list = g_list_sort(list, (GCompareFunc)message_compare);
gtkconv->attach_current = list;
list = g_list_last(list);
--- a/pidgin/gtkconv.h Sat Jun 12 19:10:06 2021 -0500
+++ b/pidgin/gtkconv.h Mon Jun 14 04:07:45 2021 -0500
@@ -188,48 +188,6 @@
- * pidgin_conversations_get_unseen_ims:
- * @min_state: The minimum unseen state.
- * @hidden_only: If %TRUE, only consider hidden conversations.
- * @max_count: Maximum number of conversations to return, or 0 for
- * Returns a list of IM conversations which have an unseen state greater
- * than or equal to the specified minimum state. Using the hidden_only
- * parameter, this search can be limited to hidden IM conversations. The
- * max_count parameter will limit the total number of IM converations
- * returned if greater than zero. The returned list should be freed by the
- * Returns: (transfer container) (element-type PurpleConversation): List of PurpleIMConversation matching criteria, or %NULL.
-pidgin_conversations_get_unseen_ims(PidginUnseenState min_state,
- * pidgin_conversations_get_unseen_chats:
- * @min_state: The minimum unseen state.
- * @hidden_only: If %TRUE, only consider hidden conversations.
- * @max_count: Maximum number of conversations to return, or 0 for
- * Returns a list of chat conversations which have an unseen state greater
- * than or equal to the specified minimum state. Using the hidden_only
- * parameter, this search can be limited to hidden chat conversations. The
- * max_count parameter will limit the total number of chat converations
- * returned if greater than zero. The returned list should be freed by the
- * Returns: (transfer container) (element-type PurpleConversation): List of PurpleChatConversation matching criteria, or %NULL.
-pidgin_conversations_get_unseen_chats(PidginUnseenState min_state,
* pidgin_conversations_fill_menu:
* @menu: Menu widget to add items to.
* @convs: (element-type PurpleConversation): List of PurpleConversation to add to menu.
--- a/po/POTFILES.in Sat Jun 12 19:10:06 2021 -0500
+++ b/po/POTFILES.in Mon Jun 14 04:07:45 2021 -0500
@@ -243,6 +243,7 @@
libpurple/purplechatconversation.c
libpurple/purplechatuser.c
libpurple/purpleconversation.c
+libpurple/purpleconversationmanager.c libpurple/purplecredentialmanager.c
libpurple/purplecredentialprovider.c