diff --git a/libpurple/purpleprotocolcontacts.h b/libpurple/purpleprotocolcontacts.h
--- a/libpurple/purpleprotocolcontacts.h
+++ b/libpurple/purpleprotocolcontacts.h
@@ -63,6 +63,9 @@
 	void (*get_profile_async)(PurpleProtocolContacts *protocol_contacts, PurpleContactInfo *info, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data);
 	char *(*get_profile_finish)(PurpleProtocolContacts *protocol_contacts, GAsyncResult *result, GError **error);
 
+	GActionGroup *(*get_actions)(PurpleProtocolContacts *protocol_contacts, PurpleContactInfo *info);
+	GMenuModel *(*get_menu)(PurpleProtocolContacts *protocol_contacts, PurpleContactInfo *info);
+
 	/*< private >*/
 	gpointer reserved[4];
 };
@@ -103,6 +106,36 @@
  */
 char *purple_protocol_contacts_get_profile_finish(PurpleProtocolContacts *protocol_contacts, GAsyncResult *result, GError **error);
 
+/**
+ * purple_protocol_contacts_get_actions:
+ * @protocol_contacts: The instance.
+ * @info: The [class@ContactInfo] to get the actions for.
+ *
+ * Gets a [iface@Gio.ActionGroup] for @info. When this action group is used,
+ * it should use the prefix of `contact`.
+ *
+ * Returns: (transfer full): The action group or %NULL.
+ *
+ * Since: 3.0.0
+ */
+GActionGroup *purple_protocol_contacts_get_actions(PurpleProtocolContacts *protocol_contacts, PurpleContactInfo *info);
+
+/*
+ * purple_protocol_contacts_get_menu:
+ * @protocol_contacts: The instance.
+ * @info: The [class@ContactInfo] to get the menu for.
+ *
+ * Gets a [class@Gio.MenuModel] for @info.
+ *
+ * This menu will have at least the action groups from [iface@ProtocolActions]
+ * and [iface@ProtocolContacts] available to it.
+ *
+ * Returns: (transfer full): The menu model or %NULL.
+ *
+ * Since: 3.0.0
+ */
+GMenuModel *purple_protocol_contacts_get_menu(PurpleProtocolContacts *protocol_contacts, PurpleContactInfo *info);
+
 G_END_DECLS
 
 #endif /* PURPLE_PROTOCOL_CONTACTS_H */
# HG changeset patch
# Node ID dcca74a6dbfc
# Parent  1707a28b1258
diff --git a/libpurple/purpleprotocolcontacts.c b/libpurple/purpleprotocolcontacts.c
--- a/libpurple/purpleprotocolcontacts.c
+++ b/libpurple/purpleprotocolcontacts.c
@@ -77,3 +77,37 @@
 
 	return NULL;
 }
+
+GActionGroup *
+purple_protocol_contacts_get_actions(PurpleProtocolContacts *protocol_contacts,
+                                     PurpleContactInfo *info)
+{
+	PurpleProtocolContactsInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONTACTS(protocol_contacts), NULL);
+	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);
+
+	iface = PURPLE_PROTOCOL_CONTACTS_GET_IFACE(protocol_contacts);
+	if(iface != NULL && iface->get_actions != NULL) {
+		return iface->get_actions(protocol_contacts, info);
+	}
+
+	return NULL;
+}
+
+GMenuModel *
+purple_protocol_contacts_get_menu(PurpleProtocolContacts *protocol_contacts,
+                                  PurpleContactInfo *info)
+{
+	PurpleProtocolContactsInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_CONTACTS(protocol_contacts), NULL);
+	g_return_val_if_fail(PURPLE_IS_CONTACT_INFO(info), NULL);
+
+	iface = PURPLE_PROTOCOL_CONTACTS_GET_IFACE(protocol_contacts);
+	if(iface != NULL && iface->get_menu != NULL) {
+		return iface->get_menu(protocol_contacts, info);
+	}
+
+	return NULL;
+}
diff --git a/libpurple/tests/test_protocol_contacts.c b/libpurple/tests/test_protocol_contacts.c
--- a/libpurple/tests/test_protocol_contacts.c
+++ b/libpurple/tests/test_protocol_contacts.c
@@ -132,6 +132,40 @@
 	g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolContactsEmpty*get_profile_finish*");
 }
 
+static void
+test_purple_protocol_contacts_empty_get_actions(void) {
+	PurpleProtocolContacts *protocol_contacts = NULL;
+	PurpleContactInfo *info = NULL;
+	GActionGroup *group = NULL;
+
+	protocol_contacts = g_object_new(test_purple_protocol_contacts_empty_get_type(),
+	                                 NULL);
+	info = purple_contact_info_new(NULL);
+
+	group = purple_protocol_contacts_get_actions(protocol_contacts, info);
+	g_assert_null(group);
+
+	g_clear_object(&info);
+	g_clear_object(&protocol_contacts);
+}
+
+static void
+test_purple_protocol_contacts_empty_get_menu(void) {
+	PurpleProtocolContacts *protocol_contacts = NULL;
+	PurpleContactInfo *info = NULL;
+	GMenuModel *model = NULL;
+
+	protocol_contacts = g_object_new(test_purple_protocol_contacts_empty_get_type(),
+	                                 NULL);
+	info = purple_contact_info_new(NULL);
+
+	model = purple_protocol_contacts_get_menu(protocol_contacts, info);
+	g_assert_null(model);
+
+	g_clear_object(&info);
+	g_clear_object(&protocol_contacts);
+}
+
 /******************************************************************************
  * TestProtocolContacts implementation
  *****************************************************************************/
@@ -186,10 +220,31 @@
 	return g_task_propagate_pointer(G_TASK(result), error);
 }
 
+static GActionGroup *
+test_purple_protocol_contacts_get_actions(G_GNUC_UNUSED PurpleProtocolContacts *protocol_contacts,
+                                          G_GNUC_UNUSED PurpleContactInfo *info)
+{
+	GSimpleActionGroup *ag = g_simple_action_group_new();
+
+	return G_ACTION_GROUP(ag);
+}
+
+static GMenuModel *
+test_purple_protocol_contacts_get_menu(G_GNUC_UNUSED PurpleProtocolContacts *protocol_contacts,
+                                       G_GNUC_UNUSED PurpleContactInfo *info)
+{
+	GMenu *menu = g_menu_new();
+
+	return G_MENU_MODEL(menu);
+}
+
 static void
 test_purple_protocol_contacts_iface_init(PurpleProtocolContactsInterface *iface) {
 	iface->get_profile_async = test_purple_protocol_contacts_get_profile_async;
 	iface->get_profile_finish = test_purple_protocol_contacts_get_profile_finish;
+
+	iface->get_actions = test_purple_protocol_contacts_get_actions;
+	iface->get_menu = test_purple_protocol_contacts_get_menu;
 }
 
 G_DEFINE_TYPE_WITH_CODE(TestPurpleProtocolContacts, test_purple_protocol_contacts,
@@ -278,7 +333,7 @@
 ;}
 
 static void
-test_purple_protocol_contacts_get_profile_error(void) {
+test_purple_protocol_contacts_get_profile_error_normal(void) {
 	TestPurpleProtocolContacts *protocol_contacts = NULL;
 
 	protocol_contacts = g_object_new(test_purple_protocol_contacts_get_type(),
@@ -297,6 +352,42 @@
 	g_clear_object(&protocol_contacts);
 }
 
+static void
+test_purple_protocol_contacts_get_actions_normal(void) {
+	PurpleProtocolContacts *protocol_contacts = NULL;
+	PurpleContactInfo *info = NULL;
+	GActionGroup *group = NULL;
+
+	protocol_contacts = g_object_new(test_purple_protocol_contacts_get_type(),
+	                                 NULL);
+
+	info = purple_contact_info_new(NULL);
+	group = purple_protocol_contacts_get_actions(protocol_contacts, info);
+	g_assert_true(G_IS_ACTION_GROUP(group));
+
+	g_clear_object(&group);
+	g_clear_object(&info);
+	g_clear_object(&protocol_contacts);
+}
+
+static void
+test_purple_protocol_contacts_get_menu_normal(void) {
+	PurpleProtocolContacts *protocol_contacts = NULL;
+	PurpleContactInfo *info = NULL;
+	GMenuModel *menu = NULL;
+
+	protocol_contacts = g_object_new(test_purple_protocol_contacts_get_type(),
+	                                 NULL);
+
+	info = purple_contact_info_new(NULL);
+	menu = purple_protocol_contacts_get_menu(protocol_contacts, info);
+	g_assert_true(G_IS_MENU_MODEL(menu));
+
+	g_clear_object(&menu);
+	g_clear_object(&info);
+	g_clear_object(&protocol_contacts);
+}
+
 /******************************************************************************
  * Main
  *****************************************************************************/
@@ -314,11 +405,19 @@
 	                test_purple_protocol_contacts_empty_get_profile_async);
 	g_test_add_func("/protocol-contacts/empty/get-profile-finish",
 	                test_purple_protocol_contacts_empty_get_profile_finish);
+	g_test_add_func("/protocol-contacts/empty/get-actions",
+	                test_purple_protocol_contacts_empty_get_actions);
+	g_test_add_func("/protocol-contacts/empty/get-menu",
+	                test_purple_protocol_contacts_empty_get_menu);
 
 	g_test_add_func("/protocol-contacts/normal/get-profile-normal",
 	                test_purple_protocol_contacts_get_profile_normal);
 	g_test_add_func("/protocol-contacts/normal/get-profile-error",
-	                test_purple_protocol_contacts_get_profile_error);
+	                test_purple_protocol_contacts_get_profile_error_normal);
+	g_test_add_func("/protocol-contacts/normal/get-actions",
+	                test_purple_protocol_contacts_get_actions_normal);
+	g_test_add_func("/protocol-contacts/normal/get-menu",
+	                test_purple_protocol_contacts_get_menu_normal);
 
 	ret = g_test_run();
 
