diff --git a/libpurple/purplecontactinfo.h b/libpurple/purplecontactinfo.h
--- a/libpurple/purplecontactinfo.h
+++ b/libpurple/purplecontactinfo.h
@@ -332,17 +332,8 @@
  * purple_contact_info_get_name_for_display:
  * @info: The instance.
  *
- * Gets the name that should be displayed for @info.
- *
- * If @info is associated with a [class@Purple.Person], the value of
- * [property@Purple.Person:alias] will be returned if it is set.
- *
- * Otherwise, this will return the first set property from the following list:
- *
- *  * [property@Purple.ContactInfo:alias]
- *  * [property@Purple.ContactInfo:display-name]
- *  * [property@Purple.ContactInfo:username]
- *  * [property@Purple.ContactInfo:id]
+ * Gets the name that should be displayed for @info. See
+ * [property@Purple.ContactInfo:name-for-display] for more information.
  *
  * Returns: (transfer none): The name to display for @info.
  *
# HG changeset patch
# Node ID d377cd75a013
# Parent  d3c594113fe1
diff --git a/libpurple/purplecontactinfo.c b/libpurple/purplecontactinfo.c
--- a/libpurple/purplecontactinfo.c
+++ b/libpurple/purplecontactinfo.c
@@ -22,12 +22,14 @@
 #include "util.h"
 
 typedef struct  {
-	gchar *id;
+	char *id;
 
-	gchar *username;
-	gchar *display_name;
-	gchar *alias;
-	gchar *color;
+	char *username;
+	char *display_name;
+	char *alias;
+	char *color;
+
+	char *name_for_display;
 
 	GdkPixbuf *avatar;
 
@@ -52,6 +54,7 @@
 	PROP_TAGS,
 	PROP_PERSON,
 	PROP_PERMISSION,
+	PROP_NAME_FOR_DISPLAY,
 	N_PROPERTIES
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
@@ -60,6 +63,70 @@
                            G_TYPE_OBJECT)
 
 /******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_contact_info_update_name_for_display(PurpleContactInfo *info) {
+	PurpleContactInfoPrivate *priv = NULL;
+	const char *name_for_display = NULL;
+
+	priv = purple_contact_info_get_instance_private(info);
+
+	/* If the info has an alias set, use it. */
+	if(name_for_display == NULL && !purple_strempty(priv->alias)) {
+		name_for_display = priv->alias;
+	}
+
+	/* If info is associated with a PurplePerson that has an alias set, use the
+	 * alias of that PurplePerson.
+	 */
+	if(name_for_display == NULL && priv->person != NULL) {
+		const char *alias = purple_person_get_alias(priv->person);
+
+		if(!purple_strempty(alias)) {
+			name_for_display = alias;
+		}
+	}
+
+	/* If the info has a display name set, use it. */
+	if(name_for_display == NULL && !purple_strempty(priv->display_name)) {
+		name_for_display = priv->display_name;
+	}
+
+	/* Fallback to the username if that is set. */
+	if(name_for_display == NULL && !purple_strempty(priv->username)) {
+		name_for_display = priv->username;
+	}
+
+	/* Finally, in a last ditch effort, use the id of the info. */
+	if(name_for_display == NULL) {
+		name_for_display = priv->id;
+	}
+
+	if(!purple_strequal(name_for_display, priv->name_for_display)) {
+		/* If we have a new name for display, free the old one, dup the new one
+		 * into the struct, and then emit the notify signal.
+		 */
+		g_free(priv->name_for_display);
+		priv->name_for_display = g_strdup(name_for_display);
+
+		g_object_notify_by_pspec(G_OBJECT(info),
+		                         properties[PROP_NAME_FOR_DISPLAY]);
+	}
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+purple_contact_info_person_alias_changed_cb(G_GNUC_UNUSED GObject *obj,
+                                            G_GNUC_UNUSED GParamSpec *pspec,
+                                            gpointer data)
+{
+	purple_contact_info_update_name_for_display(data);
+}
+
+/******************************************************************************
  * GObject Implementation
  *****************************************************************************/
 static void
@@ -100,6 +167,10 @@
 		case PROP_PERMISSION:
 			g_value_set_enum(value, purple_contact_info_get_permission(info));
 			break;
+		case PROP_NAME_FOR_DISPLAY:
+			g_value_set_string(value,
+			                   purple_contact_info_get_name_for_display(info));
+			break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
 			break;
@@ -170,6 +241,7 @@
 	g_clear_pointer(&priv->username, g_free);
 	g_clear_pointer(&priv->display_name, g_free);
 	g_clear_pointer(&priv->alias, g_free);
+	g_clear_pointer(&priv->name_for_display, g_free);
 
 	G_OBJECT_CLASS(purple_contact_info_parent_class)->finalize(obj);
 }
@@ -187,6 +259,8 @@
 	if(priv->id == NULL) {
 		purple_contact_info_set_id(info, NULL);
 	}
+
+	purple_contact_info_update_name_for_display(info);
 }
 
 static void
@@ -348,6 +422,33 @@
 		PURPLE_CONTACT_INFO_PERMISSION_UNSET,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * PurpleContactInfo:name-for-display:
+	 *
+	 * The name that the user interface should display for this contact info.
+	 *
+	 * This will first check [property@Purple.ContactInfo:alias] and return
+	 * that if it is set.
+	 *
+	 * Next, if the [property@Purple.ContactInfo:person] points to a valid
+	 * [class@Purple.Person], the alias of [class@Purple.Person] will be
+	 * returned if it is set.
+	 *
+	 * Otherwise, this will be set to the first set property from the following
+	 * list:
+	 *
+	 *  * [property@Purple.ContactInfo:display-name]
+	 *  * [property@Purple.ContactInfo:username]
+	 *  * [property@Purple.ContactInfo:id]
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NAME_FOR_DISPLAY] = g_param_spec_string(
+		"name-for-display", "name-for-display",
+		"The name that should be displayed for the contact info",
+		NULL,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 }
 
@@ -376,15 +477,26 @@
 void
 purple_contact_info_set_id(PurpleContactInfo *info, const gchar *id) {
 	PurpleContactInfoPrivate *priv = NULL;
+	gboolean changed = FALSE;
 
 	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));
 
 	priv = purple_contact_info_get_instance_private(info);
 
+	changed = !purple_strequal(priv->id, id);
+
 	g_free(priv->id);
 	priv->id = g_strdup(id);
 
-	g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ID]);
+	if(changed) {
+		g_object_freeze_notify(G_OBJECT(info));
+
+		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ID]);
+
+		purple_contact_info_update_name_for_display(info);
+
+		g_object_thaw_notify(G_OBJECT(info));
+	}
 }
 
 const gchar *
@@ -403,15 +515,26 @@
                                  const gchar *username)
 {
 	PurpleContactInfoPrivate *priv = NULL;
+	gboolean changed = FALSE;
 
 	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));
 
 	priv = purple_contact_info_get_instance_private(info);
 
+	changed = !purple_strequal(priv->username, username);
+
 	g_free(priv->username);
 	priv->username = g_strdup(username);
 
-	g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_USERNAME]);
+	if(changed) {
+		g_object_freeze_notify(G_OBJECT(info));
+
+		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_USERNAME]);
+
+		purple_contact_info_update_name_for_display(info);
+
+		g_object_thaw_notify(G_OBJECT(info));
+	}
 }
 
 const gchar *
@@ -430,15 +553,26 @@
                                      const gchar *display_name)
 {
 	PurpleContactInfoPrivate *priv = NULL;
+	gboolean changed = FALSE;
 
 	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));
 
 	priv = purple_contact_info_get_instance_private(info);
 
+	changed = !purple_strequal(priv->display_name, display_name);
+
 	g_free(priv->display_name);
 	priv->display_name = g_strdup(display_name);
 
-	g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_DISPLAY_NAME]);
+	if(changed) {
+		g_object_freeze_notify(G_OBJECT(info));
+
+		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_DISPLAY_NAME]);
+
+		purple_contact_info_update_name_for_display(info);
+
+		g_object_thaw_notify(G_OBJECT(info));
+	}
 }
 
 const gchar *
@@ -455,15 +589,26 @@
 void
 purple_contact_info_set_alias(PurpleContactInfo *info, const gchar *alias) {
 	PurpleContactInfoPrivate *priv = NULL;
+	gboolean changed = FALSE;
 
 	g_return_if_fail(PURPLE_IS_CONTACT_INFO(info));
 
 	priv = purple_contact_info_get_instance_private(info);
 
+	changed = !purple_strequal(priv->alias, alias);
+
 	g_free(priv->alias);
 	priv->alias = g_strdup(alias);
 
-	g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ALIAS]);
+	if(changed) {
+		g_object_freeze_notify(G_OBJECT(info));
+
+		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ALIAS]);
+
+		purple_contact_info_update_name_for_display(info);
+
+		g_object_thaw_notify(G_OBJECT(info));
+	}
 }
 
 const char *
@@ -548,7 +693,26 @@
 	priv = purple_contact_info_get_instance_private(info);
 
 	if(g_set_object(&priv->person, person)) {
+		/* If we got a new person, we need to connect to the notify::alias
+		 * signal.
+		 */
+		if(PURPLE_IS_PERSON(priv->person)) {
+			g_signal_connect_object(priv->person, "notify::alias",
+			                        G_CALLBACK(purple_contact_info_person_alias_changed_cb),
+			                        info, 0);
+		}
+
+		/* Freeze notifications as the person update could change the
+		 * name-for-display property.
+		 */
+		g_object_freeze_notify(G_OBJECT(info));
+
 		g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_PERSON]);
+
+		/* Update the name-for-display property */
+		purple_contact_info_update_name_for_display(info);
+
+		g_object_thaw_notify(G_OBJECT(info));
 	}
 }
 
@@ -598,34 +762,7 @@
 
 	priv = purple_contact_info_get_instance_private(info);
 
-	/* If info is associated with a PurplePerson that has an alias set,
-	 * return the alias of that PurplePerson.
-	 */
-	if(priv->person != NULL) {
-		const char *alias = purple_person_get_alias(priv->person);
-
-		if(alias != NULL && alias[0] != '\0') {
-			return alias;
-		}
-	}
-
-	/* If the purple user set an alias for the info, return that. */
-	if(priv->alias != NULL && priv->alias[0] != '\0') {
-		return priv->alias;
-	}
-
-	/* If the info has a display name set, return that. */
-	if(priv->display_name != NULL && priv->display_name[0] != '\0') {
-		return priv->display_name;
-	}
-
-	/* Fallback to the username if that is set. */
-	if(priv->username != NULL && priv->username[0] != '\0') {
-		return priv->username;
-	}
-
-	/* Finally, in a last ditch effort, return the id of the info. */
-	return priv->id;
+	return priv->name_for_display;
 }
 
 int
diff --git a/libpurple/tests/test_contact_info.c b/libpurple/tests/test_contact_info.c
--- a/libpurple/tests/test_contact_info.c
+++ b/libpurple/tests/test_contact_info.c
@@ -46,11 +46,12 @@
 	PurpleTags *tags = NULL;
 	GdkPixbuf *avatar = NULL;
 	GdkPixbuf *avatar1 = NULL;
-	gchar *id = NULL;
-	gchar *username = NULL;
-	gchar *display_name = NULL;
-	gchar *alias = NULL;
-	gchar *color = NULL;
+	char *id = NULL;
+	char *username = NULL;
+	char *display_name = NULL;
+	char *alias = NULL;
+	char *color = NULL;
+	char *name_for_display = NULL;
 
 	avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 1, 1);
 	person = purple_person_new();
@@ -83,6 +84,7 @@
 		"tags", &tags,
 		"person", &person1,
 		"permission", &permission,
+		"name-for-display", &name_for_display,
 		NULL);
 
 	/* Compare all the things. */
@@ -91,6 +93,7 @@
 	g_assert_cmpstr(display_name, ==, "display-name");
 	g_assert_cmpstr(alias, ==, "alias");
 	g_assert_cmpstr(color, ==, "#e9c636");
+	g_assert_cmpstr(name_for_display, ==, "alias");
 	g_assert_true(avatar1 == avatar);
 	g_assert_nonnull(presence1);
 	g_assert_nonnull(tags);
@@ -103,6 +106,7 @@
 	g_clear_pointer(&display_name, g_free);
 	g_clear_pointer(&alias, g_free);
 	g_clear_pointer(&color, g_free);
+	g_clear_pointer(&name_for_display, g_free);
 	g_clear_object(&avatar1);
 	g_clear_object(&presence1);
 	g_clear_object(&tags);
@@ -123,13 +127,18 @@
 	const char *alias = NULL;
 
 	person = purple_person_new();
-	purple_person_set_alias(person, "this is the alias");
+	purple_person_set_alias(person, "person alias");
 
-	info = purple_contact_info_new(NULL);
+	info = purple_contact_info_new("id");
+	/* we don't set the alias on the contact info, as that takes priority over
+	 * the person's alias.
+	 */
+	purple_contact_info_set_username(info, "username");
+	purple_contact_info_set_display_name(info, "display name");
 	purple_contact_info_set_person(info, person);
 
 	alias = purple_contact_info_get_name_for_display(info);
-	g_assert_cmpstr(alias, ==, "this is the alias");
+	g_assert_cmpstr(alias, ==, "person alias");
 
 	g_clear_object(&info);
 	g_clear_object(&person);
@@ -142,14 +151,17 @@
 	const char *alias = NULL;
 
 	person = purple_person_new();
+	purple_person_set_alias(person, "person alias");
 
-	info = purple_contact_info_new(NULL);
+	info = purple_contact_info_new("id");
 	purple_contact_info_set_person(info, person);
+	purple_contact_info_set_alias(info, "contact alias");
+	purple_contact_info_set_username(info, "username");
+	purple_contact_info_set_display_name(info, "display name");
 
-	purple_contact_info_set_alias(info, "this is the alias");
 
 	alias = purple_contact_info_get_name_for_display(info);
-	g_assert_cmpstr(alias, ==, "this is the alias");
+	g_assert_cmpstr(alias, ==, "contact alias");
 
 	g_clear_object(&info);
 	g_clear_object(&person);
@@ -163,13 +175,14 @@
 
 	person = purple_person_new();
 
-	info = purple_contact_info_new(NULL);
+	info = purple_contact_info_new("id");
 	purple_contact_info_set_person(info, person);
 
-	purple_contact_info_set_display_name(info, "this is the display name");
+	purple_contact_info_set_display_name(info, "display name");
+	purple_contact_info_set_username(info, "username");
 
 	alias = purple_contact_info_get_name_for_display(info);
-	g_assert_cmpstr(alias, ==, "this is the display name");
+	g_assert_cmpstr(alias, ==, "display name");
 
 	g_clear_object(&info);
 	g_clear_object(&person);
@@ -183,7 +196,7 @@
 
 	person = purple_person_new();
 
-	info = purple_contact_info_new(NULL);
+	info = purple_contact_info_new("id");
 	purple_contact_info_set_username(info, "username");
 	purple_contact_info_set_person(info, person);
 
diff --git a/libpurple/util.h b/libpurple/util.h
--- a/libpurple/util.h
+++ b/libpurple/util.h
@@ -234,6 +234,21 @@
 }
 
 /**
+ * purple_strempty:
+ * @str: A string to check if it is empty.
+ *
+ * Determines if @str is empty. That is, if it is %NULL or an empty string.
+ *
+ * Returns: %TRUE if the @str is %NULL or an empty string.
+ *
+ * Since: 3.0.0
+ */
+static inline gboolean
+purple_strempty(const char *str) {
+	return (str == NULL || str[0] == '\0');
+}
+
+/**
  * purple_normalize:
  * @account:  The account the string belongs to, or NULL if you do
  *                 not know the account.  If you use NULL, the string
