diff --git a/libpurple/purpleconversation.h b/libpurple/purpleconversation.h
--- a/libpurple/purpleconversation.h
+++ b/libpurple/purpleconversation.h
@@ -158,8 +158,7 @@
  * purple_conversation_present:
  * @conversation: The conversation to present
  *
- * Present a conversation to the user. This allows core code to initiate a
- * conversation by displaying the IM dialog.
+ * Emits [signal@Conversation::present].
  *
  * Since: 2.0
  */
# HG changeset patch
# Node ID 824620979907
# Parent  441ec6ccbd93
diff --git a/libpurple/purpleconversation.c b/libpurple/purpleconversation.c
--- a/libpurple/purpleconversation.c
+++ b/libpurple/purpleconversation.c
@@ -95,6 +95,7 @@
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
 
 enum {
+	SIG_PRESENT,
 	SIG_MEMBER_ADDED,
 	SIG_MEMBER_REMOVED,
 	N_SIGNALS,
@@ -910,6 +911,26 @@
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 
 	/**
+	 * PurpleConversation::present:
+	 * @conversation: The instance.
+	 *
+	 * Emitted by [method@Conversation.present] when something wants the
+	 * conversation presented to the user.
+	 *
+	 * Since: 3.0
+	 */
+	signals[SIG_PRESENT] = g_signal_new_class_handler(
+		"present",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		0);
+
+	/**
 	 * PurpleConversation::member-added:
 	 * @conversation: The instance.
 	 * @member: The [class@Purple.ConversationMember] instance.
@@ -992,7 +1013,10 @@
 }
 
 void
-purple_conversation_present(G_GNUC_UNUSED PurpleConversation *conversation) {
+purple_conversation_present(PurpleConversation *conversation) {
+	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
+
+	g_signal_emit(conversation, signals[SIG_PRESENT], 0);
 }
 
 void
diff --git a/libpurple/purpleconversationmanager.c b/libpurple/purpleconversationmanager.c
--- a/libpurple/purpleconversationmanager.c
+++ b/libpurple/purpleconversationmanager.c
@@ -30,6 +30,7 @@
 	SIG_REGISTERED,
 	SIG_UNREGISTERED,
 	SIG_CONVERSATION_CHANGED,
+	SIG_PRESENT_CONVERSATION,
 	N_SIGNALS,
 };
 static guint signals[N_SIGNALS] = {0, };
@@ -105,8 +106,6 @@
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
-
-/* This callback propagates the notify signal from conversations. */
 static void
 purple_conversation_manager_conversation_changed_cb(GObject *source,
                                                     GParamSpec *pspec,
@@ -117,6 +116,13 @@
 	              source, pspec);
 }
 
+static void
+purple_conversation_manager_present_conversation_cb(PurpleConversation *conversation,
+                                                    gpointer data)
+{
+	g_signal_emit(data, signals[SIG_PRESENT_CONVERSATION], 0, conversation);
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -212,6 +218,29 @@
 		2,
 		PURPLE_TYPE_CONVERSATION,
 		G_TYPE_PARAM);
+
+	/**
+	 * PurpleConversationManager::present-conversation:
+	 * @manager: The instance.
+	 * @conversation: The conversation that should be presented.
+	 *
+	 * This is a propagation of [signal@Conversation::present]. This means that
+	 * your callback will be called for any conversation that @manager knows
+	 * about.
+	 *
+	 * Since: 3.0
+	 */
+	signals[SIG_PRESENT_CONVERSATION] = g_signal_new_class_handler(
+		"present-conversation",
+		G_OBJECT_CLASS_TYPE(klass),
+		G_SIGNAL_RUN_LAST,
+		NULL,
+		NULL,
+		NULL,
+		NULL,
+		G_TYPE_NONE,
+		1,
+		PURPLE_TYPE_CONVERSATION);
 }
 
 /******************************************************************************
@@ -258,6 +287,9 @@
 		g_signal_connect_object(conversation, "notify",
 		                        G_CALLBACK(purple_conversation_manager_conversation_changed_cb),
 		                        manager, 0);
+		g_signal_connect_object(conversation, "present",
+		                        G_CALLBACK(purple_conversation_manager_present_conversation_cb),
+		                        manager, 0);
 
 		/* Tell everyone about the new conversation. */
 		g_signal_emit(manager, signals[SIG_REGISTERED], 0, conversation);
@@ -284,6 +316,9 @@
 		g_signal_handlers_disconnect_by_func(conversation,
 		                                     purple_conversation_manager_conversation_changed_cb,
 		                                     manager);
+		g_signal_handlers_disconnect_by_func(conversation,
+		                                     purple_conversation_manager_present_conversation_cb,
+		                                     manager);
 
 		/* Tell everyone about the unregistered conversation. */
 		g_signal_emit(manager, signals[SIG_UNREGISTERED], 0, conversation);
diff --git a/libpurple/tests/test_conversation.c b/libpurple/tests/test_conversation.c
--- a/libpurple/tests/test_conversation.c
+++ b/libpurple/tests/test_conversation.c
@@ -448,6 +448,46 @@
 }
 
 /******************************************************************************
+ * Signal tests
+ *****************************************************************************/
+static void
+test_purple_conversation_present_cb(PurpleConversation *conversation,
+                                    gpointer data)
+{
+	guint *counter = data;
+
+	g_assert_true(PURPLE_IS_CONVERSATION(conversation));
+
+	*counter = *counter + 1;
+}
+
+static void
+test_purple_conversation_signals_present(void) {
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+	guint counter = 0;
+
+	account = purple_account_new("test", "test");
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		"type", PURPLE_CONVERSATION_TYPE_DM,
+		"name", "bleh",
+		NULL);
+
+	g_signal_connect(conversation, "present",
+	                 G_CALLBACK(test_purple_conversation_present_cb),
+	                 &counter);
+
+	g_assert_cmpuint(counter, ==, 0);
+	purple_conversation_present(conversation);
+	g_assert_cmpuint(counter, ==, 1);
+
+	g_assert_finalize_object(conversation);
+	g_clear_object(&account);
+}
+
+/******************************************************************************
  * Main
  *****************************************************************************/
 gint
@@ -477,6 +517,9 @@
 	g_test_add_func("/conversation/message/write-one",
 	                test_purple_conversation_message_write_one);
 
+	g_test_add_func("/conversation/signals/present",
+	                test_purple_conversation_signals_present);
+
 	ret = g_test_run();
 
 	test_ui_purple_uninit();
diff --git a/libpurple/tests/test_conversation_manager.c b/libpurple/tests/test_conversation_manager.c
--- a/libpurple/tests/test_conversation_manager.c
+++ b/libpurple/tests/test_conversation_manager.c
@@ -26,12 +26,15 @@
  * Callbacks
  *****************************************************************************/
 static void
-test_purple_conversation_manager_counter_cb(G_GNUC_UNUSED PurpleConversationManager *manager,
-                                            G_GNUC_UNUSED PurpleConversation *conversation,
+test_purple_conversation_manager_counter_cb(PurpleConversationManager *manager,
+                                            PurpleConversation *conversation,
                                             gpointer data)
 {
 	guint *counter = data;
 
+	g_assert_true(PURPLE_IS_CONVERSATION_MANAGER(manager));
+	g_assert_true(PURPLE_IS_CONVERSATION(conversation));
+
 	*counter = *counter + 1;
 }
 
@@ -163,6 +166,43 @@
 	g_clear_object(&manager);
 }
 
+static void
+test_purple_conversation_manager_signal_present_conversation(void) {
+	PurpleAccount *account = NULL;
+	PurpleConversation *conversation = NULL;
+	PurpleConversationManager *manager = NULL;
+	guint counter = 0;
+	gboolean ret = FALSE;
+
+	account = purple_account_new("test", "test");
+
+	manager = g_object_new(PURPLE_TYPE_CONVERSATION_MANAGER, NULL);
+	g_signal_connect(manager, "present-conversation",
+	                 G_CALLBACK(test_purple_conversation_manager_counter_cb),
+	                 &counter);
+
+	conversation = g_object_new(
+		PURPLE_TYPE_CONVERSATION,
+		"account", account,
+		"type", PURPLE_CONVERSATION_TYPE_DM,
+		"name", "bleh",
+		NULL);
+
+	ret = purple_conversation_manager_register(manager, conversation);
+	g_assert_true(ret);
+
+	g_assert_cmpuint(counter, ==, 0);
+	purple_conversation_present(conversation);
+	g_assert_cmpuint(counter, ==, 1);
+
+	ret = purple_conversation_manager_unregister(manager, conversation);
+	g_assert_true(ret);
+
+	g_assert_finalize_object(manager);
+	g_assert_finalize_object(conversation);
+	g_clear_object(&account);
+}
+
 /******************************************************************************
  * find-dm tests
  *****************************************************************************/
@@ -304,6 +344,8 @@
 
 	g_test_add_func("/conversation-manager/signals/conversation-changed",
 	                test_purple_conversation_manager_signal_conversation_changed);
+	g_test_add_func("/conversation-manager/signals/present-conversation",
+	                test_purple_conversation_manager_signal_present_conversation);
 
 	g_test_add_func("/conversation-manager/find-dm/empty",
 	                test_purple_conversation_manager_find_dm_empty);
