Add some additional methods to Purple.ProtocolConversation

Mon, 13 Jan 2025 02:36:10 -0600

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 13 Jan 2025 02:36:10 -0600
changeset 43136
36eba703e2b9
parent 43135
d00a312b1d42
child 43137
8d1d2ab65c87

Add some additional methods to Purple.ProtocolConversation

This adds virtual functions for setting the title and description as well as
some additional implements methods that we were missing.

Testing Done:
Ran the tests under valgrind and called in the turtles.

Bugs closed: PIDGIN-18026

Reviewed at https://reviews.imfreedom.org/r/3738/

libpurple/purpleprotocolconversation.c file | annotate | diff | comparison | revisions
libpurple/purpleprotocolconversation.h file | annotate | diff | comparison | revisions
libpurple/tests/test_protocol_conversation.c file | annotate | diff | comparison | revisions
meson.build file | annotate | diff | comparison | revisions
--- a/libpurple/purpleprotocolconversation.c	Mon Jan 13 01:22:01 2025 -0600
+++ b/libpurple/purpleprotocolconversation.c	Mon Jan 13 02:36:10 2025 -0600
@@ -285,6 +285,26 @@
 	return FALSE;
 }
 
+gboolean
+purple_protocol_conversation_implements_set_topic(PurpleProtocolConversation *protocol)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE);
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+
+	if(iface->set_topic_async == NULL) {
+		return FALSE;
+	}
+
+	if(iface->set_topic_finish == NULL) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 void
 purple_protocol_conversation_set_topic_async(PurpleProtocolConversation *protocol,
                                              PurpleConversation *conversation,
@@ -413,6 +433,26 @@
 	return FALSE;
 }
 
+gboolean
+purple_protocol_conversation_implements_set_avatar(PurpleProtocolConversation *protocol)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE);
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+
+	if(iface->set_avatar_async == NULL) {
+		return FALSE;
+	}
+
+	if(iface->set_avatar_finish == NULL) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
 void
 purple_protocol_conversation_set_avatar_async(PurpleProtocolConversation *protocol,
                                               PurpleConversation *conversation,
@@ -532,3 +572,148 @@
 		          G_OBJECT_TYPE_NAME(protocol));
 	}
 }
+
+gboolean
+purple_protocol_conversation_implements_set_title(PurpleProtocolConversation *protocol)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE);
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface->set_title_async == NULL) {
+		return FALSE;
+	}
+
+	if(iface->set_title_finish == NULL) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void
+purple_protocol_conversation_set_title_async(PurpleProtocolConversation *protocol,
+                                             PurpleConversation *conversation,
+                                             const char *title,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer data)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol));
+	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface != NULL && iface->set_title_async != NULL) {
+		iface->set_title_async(protocol, conversation, title, cancellable,
+		                       callback, data);
+	} else {
+		g_task_report_new_error(G_OBJECT(protocol), callback, data,
+		                        purple_protocol_conversation_set_title_async,
+		                        PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0,
+		                        "%s does not implement set_title_async",
+		                        G_OBJECT_TYPE_NAME(protocol));
+	}
+}
+
+gboolean
+purple_protocol_conversation_set_title_finish(PurpleProtocolConversation *protocol,
+                                              GAsyncResult *result,
+                                              GError **error)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE);
+	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
+
+	if(g_async_result_is_tagged(result,
+	                            purple_protocol_conversation_set_title_async))
+	{
+		return g_task_propagate_boolean(G_TASK(result), error);
+	}
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface != NULL && iface->set_title_finish != NULL) {
+		return iface->set_title_finish(protocol, result, error);
+	}
+
+	g_warning("purple_protocol_conversation_set_title_finish called without "
+	          "calling purple_protocol_conversation_set_title_async");
+
+	return FALSE;
+}
+
+gboolean
+purple_protocol_conversation_implements_set_description(PurpleProtocolConversation *protocol)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE);
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface->set_description_async == NULL) {
+		return FALSE;
+	}
+
+	if(iface->set_description_finish == NULL) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+void
+purple_protocol_conversation_set_description_async(PurpleProtocolConversation *protocol,
+                                                   PurpleConversation *conversation,
+                                                   const char *description,
+                                                   GCancellable *cancellable,
+                                                   GAsyncReadyCallback callback,
+                                                   gpointer data)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol));
+	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface != NULL && iface->set_description_async != NULL) {
+		iface->set_description_async(protocol, conversation, description,
+		                             cancellable, callback, data);
+	} else {
+		g_task_report_new_error(G_OBJECT(protocol), callback, data,
+		                        purple_protocol_conversation_set_description_async,
+		                        PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0,
+		                        "%s does not implement set_description_async",
+		                        G_OBJECT_TYPE_NAME(protocol));
+	}
+}
+
+gboolean
+purple_protocol_conversation_set_description_finish(PurpleProtocolConversation *protocol,
+                                                    GAsyncResult *result,
+                                                    GError **error)
+{
+	PurpleProtocolConversationInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONVERSATION(protocol), FALSE);
+	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
+
+	if(g_async_result_is_tagged(result,
+	                            purple_protocol_conversation_set_description_async))
+	{
+		return g_task_propagate_boolean(G_TASK(result), error);
+	}
+
+	iface = PURPLE_PROTOCOL_CONVERSATION_GET_IFACE(protocol);
+	if(iface != NULL && iface->set_description_finish != NULL) {
+		return iface->set_description_finish(protocol, result, error);
+	}
+
+	g_warning("purple_protocol_conversation_set_description_finish called "
+	          "without calling "
+	          "purple_protocol_conversation_set_description_async");
+
+	return FALSE;
+}
--- a/libpurple/purpleprotocolconversation.h	Mon Jan 13 01:22:01 2025 -0600
+++ b/libpurple/purpleprotocolconversation.h	Mon Jan 13 02:36:10 2025 -0600
@@ -97,6 +97,12 @@
 
 	void (*refresh)(PurpleProtocolConversation *protocol, PurpleConversation *conversation);
 
+	void (*set_title_async)(PurpleProtocolConversation *protocol, PurpleConversation *conversation, const char *title, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
+	gboolean (*set_title_finish)(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
+
+	void (*set_description_async)(PurpleProtocolConversation *protocol, PurpleConversation *conversation, const char *description, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
+	gboolean (*set_description_finish)(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
+
 	/*< private >*/
 	gpointer reserved[8];
 };
@@ -284,6 +290,22 @@
 gboolean purple_protocol_conversation_send_message_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
 
 /**
+ * purple_protocol_conversation_implements_set_topic:
+ * @protocol: the instance
+ *
+ * Checks if a protocol implements setting topics.
+ *
+ * Checks if @protocol implements [vfunc@ProtocolConversation.set_topic_async]
+ * and [vfunc@ProtocolConversation.set_topic_finish].
+ *
+ * Returns: true if everything is implemented; otherwise false.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_implements_set_topic(PurpleProtocolConversation *protocol);
+
+/**
  * purple_protocol_conversation_set_topic_async:
  * @protocol: The instance.
  * @conversation: The conversation whose topic to set.
@@ -383,6 +405,23 @@
 gboolean purple_protocol_conversation_join_channel_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
 
 /**
+ * purple_protocol_conversation_implements_set_avatar:
+ * @protocol: the instance
+ *
+ * Checks if a protocol implements setting avatars.
+ *
+ * Checks if @protocol implements
+ * [vfunc@ProtocolConversation.set_avatar_async] and
+ * [vfunc@ProtocolConversation.set_avatar_finish].
+ *
+ * Returns: true if everything is implemented; otherwise false.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_implements_set_avatar(PurpleProtocolConversation *protocol);
+
+/**
  * purple_protocol_conversation_set_avatar_async:
  * @protocol: The instance.
  * @conversation: The conversation instance.
@@ -478,6 +517,113 @@
 PURPLE_AVAILABLE_IN_3_0
 void purple_protocol_conversation_refresh(PurpleProtocolConversation *protocol, PurpleConversation *conversation);
 
+/**
+ * purple_protocol_conversation_implements_set_title:
+ * @protocol: The instance.
+ *
+ * Checks if a protocol implements setting conversation titles.
+ *
+ * Checks if @protocol implements [vfunc@ProtocolConversation.set_title_async]
+ * and [vfunc@ProtocolConversation.set_title_finish].
+ *
+ * Returns: true if everything is implemented; otherwise false.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_implements_set_title(PurpleProtocolConversation *protocol);
+
+/**
+ * purple_protocol_conversation_set_title_async:
+ * @protocol: the instance
+ * @conversation: the conversation instance
+ * @title: (nullable): the new title
+ * @cancellable: (nullable): optional GCancellable object
+ * @callback: (nullable) (scope async): callback to call after the title has
+ *            been set
+ * @data: (nullable): optional user data to pass to @callback.
+ *
+ * Sets the title of a conversation.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+void purple_protocol_conversation_set_title_async(PurpleProtocolConversation *protocol, PurpleConversation *conversation, const char *title, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
+
+/**
+ * purple_protocol_conversation_set_title_finish:
+ * @protocol: the instance
+ * @result: the [iface@Gio.AsyncResult] from the previous
+ *          [method@ProtocolConversation.set_title_async] call
+ * @error: Return address for a #GError, or %NULL
+ *
+ * Finishes a previous call to
+ * [method@ProtocolConversation.set_title_async]. This should be called from
+ * the callback of that function to get the result of whether or not the title
+ * was set successfully.
+ *
+ * Returns: true if the title was set successfully, otherwise false with
+ *          @error possibly set.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_set_title_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
+
+/**
+ * purple_protocol_conversation_implements_set_description:
+ * @protocol: The instance.
+ *
+ * Checks if a protocol implements setting the description of conversations.
+ *
+ * Checks if @protocol implements
+ * [vfunc@ProtocolConversation.set_description_async] and
+ * [vfunc@ProtocolConversation.set_description_finish].
+ *
+ * Returns: true if everything is implemented; otherwise false.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_implements_set_description(PurpleProtocolConversation *protocol);
+
+/**
+ * purple_protocol_conversation_set_description_async:
+ * @protocol: the instance
+ * @conversation: the conversation instance
+ * @description: (nullable): the new description
+ * @cancellable: (nullable): optional GCancellable object
+ * @callback: (nullable) (scope async): callback to call after the description
+ *            has been set
+ * @data: (nullable): optional user data to pass to @callback.
+ *
+ * Sets the description of a conversation.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+void purple_protocol_conversation_set_description_async(PurpleProtocolConversation *protocol, PurpleConversation *conversation, const char *description, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
+
+/**
+ * purple_protocol_conversation_set_description_finish:
+ * @protocol: the instance
+ * @result: the [iface@Gio.AsyncResult] from the previous
+ *          [method@ProtocolConversation.set_description_async] call
+ * @error: Return address for a #GError, or %NULL
+ *
+ * Finishes a previous call to
+ * [method@ProtocolConversation.set_description_async]. This should be called
+ * from the callback of that function to get the result of whether or not the
+ * description was set successfully.
+ *
+ * Returns: true if the description was set successfully, otherwise false with
+ *          @error possibly set.
+ *
+ * Since: 3.0
+ */
+PURPLE_AVAILABLE_IN_3_0
+gboolean purple_protocol_conversation_set_description_finish(PurpleProtocolConversation *protocol, GAsyncResult *result, GError **error);
+
 G_END_DECLS
 
 #endif /* PURPLE_PROTOCOL_CONVERSATION_H */
--- a/libpurple/tests/test_protocol_conversation.c	Mon Jan 13 01:22:01 2025 -0600
+++ b/libpurple/tests/test_protocol_conversation.c	Mon Jan 13 02:36:10 2025 -0600
@@ -283,6 +283,18 @@
 }
 
 static void
+test_purple_protocol_conversation_empty_implements_set_topic(void) {
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+
+	g_assert_false(purple_protocol_conversation_implements_set_topic(protocol));
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
 test_purple_protocol_conversation_empty_set_topic_async(void) {
 	PurpleAccount *account = NULL;
 	PurpleConversation *conversation = NULL;
@@ -375,6 +387,18 @@
 }
 
 static void
+test_purple_protocol_conversation_empty_implements_set_avatar(void) {
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+
+	g_assert_false(purple_protocol_conversation_implements_set_avatar(protocol));
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
 test_purple_protocol_conversation_empty_set_avatar_cb(GObject *source,
                                                       GAsyncResult *result,
                                                       G_GNUC_UNUSED gpointer data)
@@ -497,6 +521,118 @@
 	g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolConversationEmpty*refresh*");
 }
 
+static void
+test_purple_protocol_conversation_empty_implements_set_title(void) {
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+
+	g_assert_false(purple_protocol_conversation_implements_set_title(protocol));
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
+test_purple_protocol_conversation_empty_set_title_cb(GObject *source,
+                                                     GAsyncResult *result,
+                                                     G_GNUC_UNUSED gpointer data)
+{
+	PurpleProtocolConversation *protocol = NULL;
+	GError *error = NULL;
+	gboolean set = FALSE;
+
+	protocol = PURPLE_PROTOCOL_CONVERSATION(source);
+	set = purple_protocol_conversation_set_title_finish(protocol, result,
+	                                                    &error);
+	g_assert_error(error, PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0);
+	g_clear_error(&error);
+	g_assert_false(set);
+}
+
+static void
+test_purple_protocol_conversation_empty_set_title_async(void) {
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		"type", PURPLE_CONVERSATION_TYPE_DM,
+		NULL);
+
+	purple_protocol_conversation_set_title_async(protocol, conversation,
+	                                             "title", NULL,
+	                                             test_purple_protocol_conversation_empty_set_title_cb,
+	                                             NULL);
+
+	g_main_context_iteration(NULL, FALSE);
+
+	g_clear_object(&account);
+	g_assert_finalize_object(conversation);
+	g_assert_finalize_object(protocol);
+}
+
+static void
+test_purple_protocol_conversation_empty_implements_set_description(void) {
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+
+	g_assert_false(purple_protocol_conversation_implements_set_description(protocol));
+
+	g_assert_finalize_object(protocol);
+}
+
+static void
+test_purple_protocol_conversation_empty_set_description_cb(GObject *source,
+                                                           GAsyncResult *result,
+                                                           G_GNUC_UNUSED gpointer data)
+{
+	PurpleProtocolConversation *protocol = NULL;
+	GError *error = NULL;
+	gboolean set = FALSE;
+
+	protocol = PURPLE_PROTOCOL_CONVERSATION(source);
+	set = purple_protocol_conversation_set_description_finish(protocol, result,
+	                                                          &error);
+	g_assert_error(error, PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0);
+	g_clear_error(&error);
+	g_assert_false(set);
+}
+
+static void
+test_purple_protocol_conversation_empty_set_description_async(void) {
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+	PurpleProtocolConversation *protocol = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_empty_get_type(),
+	                        NULL);
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		"type", PURPLE_CONVERSATION_TYPE_DM,
+		NULL);
+
+	purple_protocol_conversation_set_description_async(protocol, conversation,
+	                                                   "description", NULL,
+	                                                   test_purple_protocol_conversation_empty_set_description_cb,
+	                                                   NULL);
+
+	g_main_context_iteration(NULL, FALSE);
+
+	g_clear_object(&account);
+	g_assert_finalize_object(conversation);
+	g_assert_finalize_object(protocol);
+}
+
 /******************************************************************************
  * TestProtocolConversation Implementation
  *****************************************************************************/
@@ -531,6 +667,12 @@
 
 	guint send_typing;
 	guint refresh;
+
+	guint set_title_async;
+	guint set_title_finish;
+
+	guint set_description_async;
+	guint set_description_finish;
 };
 
 static PurpleCreateConversationDetails *
@@ -848,6 +990,88 @@
 }
 
 static void
+test_purple_protocol_conversation_set_title_async(PurpleProtocolConversation *protocol,
+                                                  PurpleConversation *conversation,
+                                                  G_GNUC_UNUSED const char *title,
+                                                  GCancellable *cancellable,
+                                                  GAsyncReadyCallback callback,
+                                                  gpointer data)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+	GTask *task = NULL;
+
+	g_assert_true(PURPLE_IS_CONVERSATION(conversation));
+
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol);
+	test_protocol->set_title_async += 1;
+
+	task = g_task_new(protocol, cancellable, callback, data);
+	if(test_protocol->should_error) {
+		g_task_return_new_error_literal(task,
+		                                TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN,
+		                                0, "error");
+	} else {
+		g_task_return_boolean(task, TRUE);
+	}
+
+	g_clear_object(&task);
+}
+
+static gboolean
+test_purple_protocol_conversation_set_title_finish(PurpleProtocolConversation *protocol,
+                                                   GAsyncResult *result,
+                                                   GError **error)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol);
+	test_protocol->set_title_finish += 1;
+
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+static void
+test_purple_protocol_conversation_set_description_async(PurpleProtocolConversation *protocol,
+                                                        PurpleConversation *conversation,
+                                                        G_GNUC_UNUSED const char *description,
+                                                        GCancellable *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer data)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+	GTask *task = NULL;
+
+	g_assert_true(PURPLE_IS_CONVERSATION(conversation));
+
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol);
+	test_protocol->set_description_async += 1;
+
+	task = g_task_new(protocol, cancellable, callback, data);
+	if(test_protocol->should_error) {
+		g_task_return_new_error_literal(task,
+		                                TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN,
+		                                0, "error");
+	} else {
+		g_task_return_boolean(task, TRUE);
+	}
+
+	g_clear_object(&task);
+}
+
+static gboolean
+test_purple_protocol_conversation_set_description_finish(PurpleProtocolConversation *protocol,
+                                                         GAsyncResult *result,
+                                                         GError **error)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(protocol);
+	test_protocol->set_description_finish += 1;
+
+	return g_task_propagate_boolean(G_TASK(result), error);
+}
+
+static void
 test_purple_protocol_conversation_iface_init(PurpleProtocolConversationInterface *iface) {
 	iface->get_create_conversation_details = test_purple_protocol_conversation_get_create_conversation_details;
 	iface->create_conversation_async = test_purple_protocol_conversation_create_conversation_async;
@@ -871,6 +1095,12 @@
 
 	iface->send_typing = test_purple_protocol_conversation_send_typing;
 	iface->refresh = test_purple_protocol_conversation_refresh;
+
+	iface->set_title_async = test_purple_protocol_conversation_set_title_async;
+	iface->set_title_finish = test_purple_protocol_conversation_set_title_finish;
+
+	iface->set_description_async = test_purple_protocol_conversation_set_description_async;
+	iface->set_description_finish = test_purple_protocol_conversation_set_description_finish;
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE(TestPurpleProtocolConversation,
@@ -904,6 +1134,12 @@
 
 	protocol->send_typing = 0;
 	protocol->refresh = 0;
+
+	protocol->set_title_async = 0;
+	protocol->set_title_finish = 0;
+
+	protocol->set_description_async = 0;
+	protocol->set_description_finish = 0;
 }
 
 static void
@@ -1460,6 +1696,129 @@
 }
 
 /******************************************************************************
+ * TestProtocolConversation set_title Tests
+ *****************************************************************************/
+static void
+test_purple_protocol_conversation_set_title_cb(GObject *obj,
+                                               GAsyncResult *res,
+                                               G_GNUC_UNUSED gpointer data)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+	PurpleProtocolConversation *protocol = NULL;
+	GError *error = NULL;
+	gboolean result = FALSE;
+
+	protocol = PURPLE_PROTOCOL_CONVERSATION(obj);
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(obj);
+
+	result = purple_protocol_conversation_set_title_finish(protocol, res,
+	                                                       &error);
+
+	if(test_protocol->should_error) {
+		g_assert_error(error, TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0);
+		g_clear_error(&error);
+		g_assert_false(result);
+	} else {
+		g_assert_no_error(error);
+		g_assert_true(result);
+	}
+}
+
+static void
+test_purple_protocol_conversation_set_title_normal(gconstpointer data) {
+	TestPurpleProtocolConversation *protocol = NULL;
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_get_type(),
+	                        NULL);
+	protocol->should_error = GPOINTER_TO_INT(data);
+
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		"type", PURPLE_CONVERSATION_TYPE_DM,
+		NULL);
+
+	purple_protocol_conversation_set_title_async(PURPLE_PROTOCOL_CONVERSATION(protocol),
+	                                             conversation, "title", NULL,
+	                                             test_purple_protocol_conversation_set_title_cb,
+	                                             NULL);
+
+	g_main_context_iteration(NULL, FALSE);
+
+	g_assert_cmpuint(protocol->set_title_async, ==, 1);
+	g_assert_cmpuint(protocol->set_title_finish, ==, 1);
+
+	g_assert_finalize_object(conversation);
+	g_assert_finalize_object(protocol);
+	g_clear_object(&account);
+}
+
+/******************************************************************************
+ * TestProtocolConversation set_description Tests
+ *****************************************************************************/
+static void
+test_purple_protocol_conversation_set_description_cb(GObject *obj,
+                                                     GAsyncResult *res,
+                                                     G_GNUC_UNUSED gpointer data)
+{
+	TestPurpleProtocolConversation *test_protocol = NULL;
+	PurpleProtocolConversation *protocol = NULL;
+	GError *error = NULL;
+	gboolean result = FALSE;
+
+	protocol = PURPLE_PROTOCOL_CONVERSATION(obj);
+	test_protocol = TEST_PURPLE_PROTOCOL_CONVERSATION(obj);
+
+	result = purple_protocol_conversation_set_description_finish(protocol, res,
+	                                                             &error);
+
+	if(test_protocol->should_error) {
+		g_assert_error(error, TEST_PURPLE_PROTOCOL_CONVERSATION_DOMAIN, 0);
+		g_clear_error(&error);
+		g_assert_false(result);
+	} else {
+		g_assert_no_error(error);
+		g_assert_true(result);
+	}
+}
+
+static void
+test_purple_protocol_conversation_set_description_normal(gconstpointer data) {
+	TestPurpleProtocolConversation *protocol = NULL;
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+
+	protocol = g_object_new(test_purple_protocol_conversation_get_type(),
+	                        NULL);
+	protocol->should_error = GPOINTER_TO_INT(data);
+
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		"type", PURPLE_CONVERSATION_TYPE_DM,
+		NULL);
+
+	purple_protocol_conversation_set_description_async(PURPLE_PROTOCOL_CONVERSATION(protocol),
+	                                                   conversation,
+	                                                   "description", NULL,
+	                                                   test_purple_protocol_conversation_set_description_cb,
+	                                                   NULL);
+
+	g_main_context_iteration(NULL, FALSE);
+
+	g_assert_cmpuint(protocol->set_description_async, ==, 1);
+	g_assert_cmpuint(protocol->set_description_finish, ==, 1);
+
+	g_assert_finalize_object(conversation);
+	g_assert_finalize_object(protocol);
+	g_clear_object(&account);
+}
+
+/******************************************************************************
  * Main
  *****************************************************************************/
 gint
@@ -1488,8 +1847,13 @@
 	g_test_add_func("/protocol-conversation/empty/send-message-async",
 	                test_purple_protocol_conversation_empty_send_message_async);
 
+	g_test_add_func("/protocol-conversation/empty/implements-set-topic",
+	                test_purple_protocol_conversation_empty_implements_set_topic);
 	g_test_add_func("/protocol-conversation/empty/set-topic-async",
 	                test_purple_protocol_conversation_empty_set_topic_async);
+
+	g_test_add_func("/protocol-conversation/empty/implements-set-avatar",
+	                test_purple_protocol_conversation_empty_implements_set_avatar);
 	g_test_add_func("/protocol-conversation/empty/set-avatar-async",
 	                test_purple_protocol_conversation_empty_set_avatar_async);
 
@@ -1499,6 +1863,16 @@
 	g_test_add_func("/protocol-conversation/empty/join_channel_async",
 	                test_purple_protocol_conversation_empty_join_channel_async);
 
+	g_test_add_func("/protocol-conversation/empty/implements-set-title",
+	                test_purple_protocol_conversation_empty_implements_set_title);
+	g_test_add_func("/protocol-conversation/empty/set-title-async",
+	                test_purple_protocol_conversation_empty_set_title_async);
+
+	g_test_add_func("/protocol-conversation/empty/implements-set-description",
+	                test_purple_protocol_conversation_empty_implements_set_description);
+	g_test_add_func("/protocol-conversation/empty/set-description-async",
+	                test_purple_protocol_conversation_empty_set_description_async);
+
 	/* Empty send typing tests. */
 	g_test_add_func("/protocol-conversation/empty/implements-send-typing",
 	                test_purple_protocol_conversation_empty_implements_send_typing);
@@ -1581,5 +1955,21 @@
 	g_test_add_func("/protocol-conversation/normal/refresh",
 	                test_purple_protocol_conversation_refresh_normal);
 
+	/* Normal set title tests. */
+	g_test_add_data_func("/protocol-contacts/normal/set-title-normal",
+	                     GINT_TO_POINTER(FALSE),
+	                     test_purple_protocol_conversation_set_title_normal);
+	g_test_add_data_func("/protocol-contacts/normal/set-title-error",
+	                     GINT_TO_POINTER(TRUE),
+	                     test_purple_protocol_conversation_set_title_normal);
+
+	/* Normal set description tests. */
+	g_test_add_data_func("/protocol-contacts/normal/set-description-normal",
+	                     GINT_TO_POINTER(FALSE),
+	                     test_purple_protocol_conversation_set_description_normal);
+	g_test_add_data_func("/protocol-contacts/normal/set-description-error",
+	                     GINT_TO_POINTER(TRUE),
+	                     test_purple_protocol_conversation_set_description_normal);
+
 	return g_test_run();
 }
--- a/meson.build	Mon Jan 13 01:22:01 2025 -0600
+++ b/meson.build	Mon Jan 13 02:36:10 2025 -0600
@@ -171,13 +171,13 @@
 #######################################################################
 # Check for GLib (required)
 #######################################################################
-glib = dependency('glib-2.0', version : '>= 2.78.0')
+glib = dependency('glib-2.0', version : '>= 2.80.0')
 gio = dependency('gio-2.0')
 gnome = import('gnome')
 
 add_project_arguments(
-	'-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_78',
-	'-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_78',
+	'-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_80',
+	'-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_80',
 	language : 'c',)
 
 #######################################################################

mercurial