diff --git a/pidgin/pidgindisplayitem.h b/pidgin/pidgindisplayitem.h
--- a/pidgin/pidgindisplayitem.h
+++ b/pidgin/pidgindisplayitem.h
@@ -214,6 +214,32 @@
 PIDGIN_AVAILABLE_IN_3_0
 void pidgin_display_item_set_children(PidginDisplayItem *item, GListModel *children);
 
+/**
+ * pidgin_display_item_get_menu:
+ * @item: The instance.
+ *
+ * Gets the menu from @item.
+ *
+ * Returns: (transfer none) (nullable): The menu if one exists, otherwise
+ *          %NULL.
+ *
+ * Since: 3.0
+ */
+PIDGIN_AVAILABLE_IN_3_0
+GMenuModel *pidgin_display_item_get_menu(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_set_menu:
+ * @item: The instance.
+ * @menu: (nullable) (transfer none): The new menu.
+ *
+ * Sets the menu for @item to @menu.
+ *
+ * Since: 3.0
+ */
+PIDGIN_AVAILABLE_IN_3_0
+void pidgin_display_item_set_menu(PidginDisplayItem *item, GMenuModel *menu);
+
 G_END_DECLS
 
 #endif /* PIDGIN_DISPLAY_ITEM_H */
# HG changeset patch
# Node ID 21be31f4f6e500c96252bd3a9bc2e4197db535a1
# Parent  fedfa85c0b40521ffd022da2c9283ede6fdd8248
diff --git a/pidgin/pidgindisplayitem.c b/pidgin/pidgindisplayitem.c
--- a/pidgin/pidgindisplayitem.c
+++ b/pidgin/pidgindisplayitem.c
@@ -32,8 +32,9 @@
 
 	char *icon_name;
 
+	GMenuModel *menu;
+
 	gboolean needs_attention;
-
 	guint badge_number;
 
 	GListModel *children;
@@ -48,6 +49,7 @@
 	PROP_NEEDS_ATTENTION,
 	PROP_BADGE_NUMBER,
 	PROP_CHILDREN,
+	PROP_MENU,
 	N_PROPERTIES,
 };
 static GParamSpec *properties[N_PROPERTIES] = {NULL, };
@@ -85,6 +87,7 @@
 
 	g_clear_object(&item->widget);
 	g_clear_object(&item->children);
+	g_clear_object(&item->menu);
 
 	G_OBJECT_CLASS(pidgin_display_item_parent_class)->dispose(obj);
 }
@@ -107,32 +110,35 @@
 	PidginDisplayItem *item = PIDGIN_DISPLAY_ITEM(obj);
 
 	switch(param_id) {
-		case PROP_WIDGET:
-			g_value_set_object(value, pidgin_display_item_get_widget(item));
-			break;
-		case PROP_ID:
-			g_value_set_string(value, pidgin_display_item_get_id(item));
-			break;
-		case PROP_TITLE:
-			g_value_set_string(value, pidgin_display_item_get_title(item));
-			break;
-		case PROP_ICON_NAME:
-			g_value_set_string(value, pidgin_display_item_get_icon_name(item));
-			break;
-		case PROP_NEEDS_ATTENTION:
-			g_value_set_boolean(value,
-			                    pidgin_display_item_get_needs_attention(item));
-			break;
-		case PROP_BADGE_NUMBER:
-			g_value_set_uint(value,
-			                 pidgin_display_item_get_badge_number(item));
-			break;
-		case PROP_CHILDREN:
-			g_value_set_object(value, pidgin_display_item_get_children(item));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
+	case PROP_WIDGET:
+		g_value_set_object(value, pidgin_display_item_get_widget(item));
+		break;
+	case PROP_ID:
+		g_value_set_string(value, pidgin_display_item_get_id(item));
+		break;
+	case PROP_TITLE:
+		g_value_set_string(value, pidgin_display_item_get_title(item));
+		break;
+	case PROP_ICON_NAME:
+		g_value_set_string(value, pidgin_display_item_get_icon_name(item));
+		break;
+	case PROP_NEEDS_ATTENTION:
+		g_value_set_boolean(value,
+		                    pidgin_display_item_get_needs_attention(item));
+		break;
+	case PROP_BADGE_NUMBER:
+		g_value_set_uint(value,
+		                 pidgin_display_item_get_badge_number(item));
+		break;
+	case PROP_CHILDREN:
+		g_value_set_object(value, pidgin_display_item_get_children(item));
+		break;
+	case PROP_MENU:
+		g_value_set_object(value, pidgin_display_item_get_menu(item));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
 	}
 }
 
@@ -143,32 +149,35 @@
 	PidginDisplayItem *item = PIDGIN_DISPLAY_ITEM(obj);
 
 	switch(param_id) {
-		case PROP_WIDGET:
-			pidgin_display_item_set_widget(item, g_value_get_object(value));
-			break;
-		case PROP_ID:
-			pidgin_display_item_set_id(item, g_value_get_string(value));
-			break;
-		case PROP_TITLE:
-			pidgin_display_item_set_title(item, g_value_get_string(value));
-			break;
-		case PROP_ICON_NAME:
-			pidgin_display_item_set_icon_name(item, g_value_get_string(value));
-			break;
-		case PROP_NEEDS_ATTENTION:
-			pidgin_display_item_set_needs_attention(item,
-			                                        g_value_get_boolean(value));
-			break;
-		case PROP_BADGE_NUMBER:
-			pidgin_display_item_set_badge_number(item,
-			                                     g_value_get_uint(value));
-			break;
-		case PROP_CHILDREN:
-			pidgin_display_item_set_children(item, g_value_get_object(value));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
+	case PROP_WIDGET:
+		pidgin_display_item_set_widget(item, g_value_get_object(value));
+		break;
+	case PROP_ID:
+		pidgin_display_item_set_id(item, g_value_get_string(value));
+		break;
+	case PROP_TITLE:
+		pidgin_display_item_set_title(item, g_value_get_string(value));
+		break;
+	case PROP_ICON_NAME:
+		pidgin_display_item_set_icon_name(item, g_value_get_string(value));
+		break;
+	case PROP_NEEDS_ATTENTION:
+		pidgin_display_item_set_needs_attention(item,
+		                                        g_value_get_boolean(value));
+		break;
+	case PROP_BADGE_NUMBER:
+		pidgin_display_item_set_badge_number(item,
+		                                     g_value_get_uint(value));
+		break;
+	case PROP_CHILDREN:
+		pidgin_display_item_set_children(item, g_value_get_object(value));
+		break;
+	case PROP_MENU:
+		pidgin_display_item_set_menu(item, g_value_get_object(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
 	}
 }
 
@@ -193,8 +202,7 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_WIDGET] = g_param_spec_object(
-		"widget", "widget",
-		"The widget for this item",
+		"widget", NULL, NULL,
 		GTK_TYPE_WIDGET,
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
@@ -207,8 +215,7 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_ID] = g_param_spec_string(
-		"id", "id",
-		"A unique identifier for the item",
+		"id", NULL, NULL,
 		NULL,
 		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
@@ -220,8 +227,7 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_TITLE] = g_param_spec_string(
-		"title", "title",
-		"The title for the item",
+		"title", NULL, NULL,
 		NULL,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
@@ -233,8 +239,7 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_ICON_NAME] = g_param_spec_string(
-		"icon-name", "icon-name",
-		"The icon-name to use for this item",
+		"icon-name", NULL, NULL,
 		NULL,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
@@ -246,8 +251,7 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_NEEDS_ATTENTION] = g_param_spec_boolean(
-		"needs-attention", "needs-attention",
-		"Whether or not the item needs attention",
+		"needs-attention", NULL, NULL,
 		FALSE,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
@@ -260,8 +264,7 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_BADGE_NUMBER] = g_param_spec_uint(
-		"badge-number", "badge-number",
-		"The number to show in the badge",
+		"badge-number", NULL, NULL,
 		0, G_MAXUINT, 0,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
@@ -274,11 +277,22 @@
 	 * Since: 3.0
 	 */
 	properties[PROP_CHILDREN] = g_param_spec_object(
-		"children", "children",
-		"A GListModel of child items",
+		"children", NULL, NULL,
 		G_TYPE_LIST_MODEL,
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
+	/**
+	 * PidginDisplayItem:menu:
+	 *
+	 * An optional menu to show on the item.
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_MENU] = g_param_spec_object(
+		"menu", NULL, NULL,
+		G_TYPE_MENU_MODEL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
 	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
 }
 
@@ -404,3 +418,19 @@
 		g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_CHILDREN]);
 	}
 }
+
+GMenuModel *
+pidgin_display_item_get_menu(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), NULL);
+
+	return item->menu;
+}
+
+void
+pidgin_display_item_set_menu(PidginDisplayItem *item, GMenuModel *model) {
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(g_set_object(&item->menu, model)) {
+		g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_MENU]);
+	}
+}
diff --git a/pidgin/pidgindisplaywindow.c b/pidgin/pidgindisplaywindow.c
--- a/pidgin/pidgindisplaywindow.c
+++ b/pidgin/pidgindisplaywindow.c
@@ -265,6 +265,36 @@
 	pidgin_display_window_select(data, conversation);
 }
 
+static gboolean
+pidgin_display_window_show_item_menu_cb(G_GNUC_UNUSED GObject *source,
+                                        PidginDisplayItem *item,
+                                        G_GNUC_UNUSED gpointer data)
+{
+	GMenuModel *model = NULL;
+
+	if(!PIDGIN_IS_DISPLAY_ITEM(item)) {
+		return FALSE;
+	}
+
+	model = pidgin_display_item_get_menu(item);
+
+	return G_IS_MENU_MODEL(model);
+}
+
+static char *
+pidgin_display_window_show_item_actions_cb(G_GNUC_UNUSED GObject *source,
+                                           gboolean item_selected,
+                                           gboolean item_contains_pointer,
+                                           gboolean menu_button_active,
+                                           G_GNUC_UNUSED gpointer data)
+{
+	if(item_selected || item_contains_pointer || menu_button_active) {
+		return g_strdup("actions");
+	}
+
+	return g_strdup("notifications");
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -380,6 +410,10 @@
 	                                        pidgin_display_window_key_pressed_cb);
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_display_window_selected_item_changed_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_display_window_show_item_menu_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_display_window_show_item_actions_cb);
 }
 
 /******************************************************************************
diff --git a/pidgin/resources/display-item.ui b/pidgin/resources/display-item.ui
--- a/pidgin/resources/display-item.ui
+++ b/pidgin/resources/display-item.ui
@@ -16,6 +16,9 @@
               </lookup>
             </binding>
             <child>
+              <object class="GtkEventControllerMotion" id="motion_controller"/>
+            </child>
+            <child>
               <object class="GtkImage">
                 <binding name="icon-name">
                   <lookup name="icon-name" type="PidginDisplayItem">
@@ -37,27 +40,71 @@
               </object>
             </child>
             <child>
-              <object class="GtkBox">
-                <property name="spacing">6</property>
-                <property name="visible" bind-source="GtkListItem" bind-property="selected" bind-flags="sync-create|invert-boolean"/>
+              <object class="GtkStack">
+                <property name="transition-type">crossfade</property>
+                <binding name="visible-child-name">
+                  <closure type="gchararray" function="pidgin_display_window_show_item_actions_cb">
+                    <lookup name="selected">GtkListItem</lookup>
+                    <lookup name="contains-pointer">motion_controller</lookup>
+                    <lookup name="active">menu_button</lookup>
+                  </closure>
+                </binding>
                 <child>
-                  <object class="GtkLabel">
-                    <style>
-                      <class name="notification-count"/>
-                    </style>
-                    <property name="halign">end</property>
-                    <property name="hexpand">true</property>
-                    <property name="valign">center</property>
-                    <binding name="visible">
-                      <lookup name="needs-attention" type="PidginDisplayItem">
-                        <lookup name="item">expander</lookup>
-                      </lookup>
-                    </binding>
-                    <binding name="label">
-                      <lookup name="badge-number" type="PidginDisplayItem">
-                        <lookup name="item">expander</lookup>
-                      </lookup>
-                    </binding>
+                  <object class="GtkStackPage">
+                    <property name="name">notifications</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <style>
+                              <class name="notification-count"/>
+                            </style>
+                            <property name="halign">end</property>
+                            <property name="hexpand">true</property>
+                            <property name="valign">center</property>
+                            <binding name="visible">
+                              <lookup name="needs-attention" type="PidginDisplayItem">
+                                <lookup name="item">expander</lookup>
+                              </lookup>
+                            </binding>
+                            <binding name="label">
+                              <lookup name="badge-number" type="PidginDisplayItem">
+                                <lookup name="item">expander</lookup>
+                              </lookup>
+                            </binding>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">actions</property>
+                    <property name="child">
+                      <object class="GtkBox">
+                        <property name="orientation">horizontal</property>
+                        <child>
+                          <object class="GtkMenuButton" id="menu_button">
+                            <style>
+                              <class name="flat"/>
+                            </style>
+                            <property name="icon-name">view-more-symbolic</property>
+                            <binding name="menu-model">
+                              <lookup name="menu" type="PidginDisplayItem">
+                                <lookup name="item">expander</lookup>
+                              </lookup>
+                            </binding>
+                            <binding name="visible">
+                              <closure type="gboolean" function="pidgin_display_window_show_item_menu_cb">
+                                <lookup name="item">expander</lookup>
+                              </closure>
+                            </binding>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
                   </object>
                 </child>
               </object>
diff --git a/pidgin/resources/display-window.ui b/pidgin/resources/display-window.ui
--- a/pidgin/resources/display-window.ui
+++ b/pidgin/resources/display-window.ui
@@ -67,6 +67,9 @@
         <property name="icon-name">application-x-addon-symbolic</property>
         <property name="id">__plugins__</property>
         <property name="title" translatable="1">Plugins</property>
+        <property name="menu">
+          <object class="PidginPluginsMenu"/>
+        </property>
         <property name="widget">
           <object class="GtkBox">
             <property name="orientation">vertical</property>
