diff --git a/pidgin/plugins/disco/gtkdisco.h b/pidgin/plugins/disco/gtkdisco.h
--- a/pidgin/plugins/disco/gtkdisco.h
+++ b/pidgin/plugins/disco/gtkdisco.h
@@ -37,10 +37,10 @@ struct _PidginDiscoDialog {
 
 	GtkWidget *progress;
 
-	GtkWidget *stop_button;
-	GtkWidget *browse_button;
-	GtkWidget *register_button;
-	GtkWidget *add_button;
+	GSimpleAction *stop_action;
+	GSimpleAction *browse_action;
+	GSimpleAction *register_action;
+	GSimpleAction *add_action;
 	XmppDiscoService *selected;
 
 	GtkTreeView *tree;
@@ -48,7 +48,10 @@ struct _PidginDiscoDialog {
 	PurpleAccount *account;
 	PidginDiscoList *discolist;
 
-	gpointer *prompt_handle;
+	GtkPopoverMenu *popover;
+	GMenuModel *popover_menu;
+
+	gpointer prompt_handle;
 };
 
 #define PIDGIN_TYPE_DISCO_DIALOG (pidgin_disco_dialog_get_type())
@@ -60,7 +63,6 @@ struct _PidginDiscoList {
 	gboolean in_progress;
 	const gchar *server;
 
-	gint ref;
 	guint fetch_count;
 
 	PidginDiscoDialog *dialog;
# HG changeset patch
# Node ID 9dab83eccabd
# Parent  565b31cc5238
diff --git a/pidgin/plugins/disco/gtkdisco.c b/pidgin/plugins/disco/gtkdisco.c
--- a/pidgin/plugins/disco/gtkdisco.c
+++ b/pidgin/plugins/disco/gtkdisco.c
@@ -1,4 +1,6 @@
-/* pidgin
+/*
+ * Pidgin - Internet Messenger
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
  *
  * Pidgin is the legal property of its developers, whose names are too numerous
  * to list here.  Please refer to the COPYRIGHT file distributed with this
@@ -15,8 +17,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
@@ -42,61 +43,62 @@ enum {
 	NUM_OF_COLUMNS
 };
 
+static PidginDiscoList *
+pidgin_disco_list_new(void) {
+	return g_rc_box_new0(PidginDiscoList);
+}
+
 static void
 pidgin_disco_list_destroy(PidginDiscoList *list)
 {
 	g_hash_table_destroy(list->services);
-	if (list->dialog && list->dialog->discolist == list)
+	if (list->dialog && list->dialog->discolist == list) {
 		list->dialog->discolist = NULL;
+	}
 
 	g_free((gchar*)list->server);
-	g_free(list);
 }
 
 PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list)
 {
 	g_return_val_if_fail(list != NULL, NULL);
 
-	++list->ref;
-	purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list->ref);
-
-	return list;
+	purple_debug_misc("xmppdisco", "reffing list");
+	return g_rc_box_acquire(list);
 }
 
 void pidgin_disco_list_unref(PidginDiscoList *list)
 {
 	g_return_if_fail(list != NULL);
 
-	--list->ref;
-
-	purple_debug_misc("xmppdisco", "unreffing list, ref count now %d\n", list->ref);
-	if (list->ref == 0)
-		pidgin_disco_list_destroy(list);
+	purple_debug_misc("xmppdisco", "unreffing list");
+	g_rc_box_release_full(list, (GDestroyNotify)pidgin_disco_list_destroy);
 }
 
 void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress)
 {
 	PidginDiscoDialog *dialog = list->dialog;
 
-	if (!dialog)
+	if (!dialog) {
 		return;
+	}
 
 	list->in_progress = in_progress;
 
 	if (in_progress) {
 		gtk_widget_set_sensitive(dialog->account_chooser, FALSE);
-		gtk_widget_set_sensitive(dialog->stop_button, TRUE);
-		gtk_widget_set_sensitive(dialog->browse_button, FALSE);
+		g_simple_action_set_enabled(dialog->stop_action, TRUE);
+		g_simple_action_set_enabled(dialog->browse_action, FALSE);
 	} else {
 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), 0.0);
 
 		gtk_widget_set_sensitive(dialog->account_chooser, TRUE);
 
-		gtk_widget_set_sensitive(dialog->stop_button, FALSE);
-		gtk_widget_set_sensitive(dialog->browse_button, TRUE);
+		g_simple_action_set_enabled(dialog->stop_action, FALSE);
+		g_simple_action_set_enabled(dialog->browse_action, TRUE);
 /*
-		gtk_widget_set_sensitive(dialog->register_button, FALSE);
-		gtk_widget_set_sensitive(dialog->add_button, FALSE);
+		g_simple_action_set_enabled(dialog->register_action, FALSE);
+		g_simple_action_set_enabled(dialog->add_action, FALSE);
 */
 	}
 }
@@ -148,16 +150,24 @@ dialog_select_account_cb(GtkWidget *choo
 	PurpleAccount *account = pidgin_account_chooser_get_selected(PIDGIN_ACCOUNT_CHOOSER(chooser));
 	gboolean change = (account != dialog->account);
 	dialog->account = account;
-	gtk_widget_set_sensitive(dialog->browse_button, account != NULL);
+	g_simple_action_set_enabled(dialog->browse_action, account != NULL);
 
 	if (change) {
 		g_clear_pointer(&dialog->discolist, pidgin_disco_list_unref);
 	}
 }
 
-static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
+static void
+activate_register(G_GNUC_UNUSED GSimpleAction *action,
+                  G_GNUC_UNUSED GVariant *parameter,
+                  gpointer data)
 {
-	xmpp_disco_service_register(dialog->selected);
+	PidginDiscoDialog *dialog = data;
+	XmppDiscoService *service = dialog->selected;
+
+	g_return_if_fail(service != NULL);
+
+	xmpp_disco_service_register(service);
 }
 
 static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server)
@@ -171,7 +181,7 @@ static void discolist_cancel_cb(PidginDi
 static void discolist_ok_cb(PidginDiscoList *pdl, const char *server)
 {
 	pdl->dialog->prompt_handle = NULL;
-	gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE);
+	g_simple_action_set_enabled(pdl->dialog->browse_action, TRUE);
 
 	if (!server || !*server) {
 		purple_notify_error(my_plugin, _("Invalid Server"), _("Invalid Server"),
@@ -187,8 +197,12 @@ static void discolist_ok_cb(PidginDiscoL
 	xmpp_disco_start(pdl);
 }
 
-static void browse_button_cb(GtkWidget *button, PidginDiscoDialog *dialog)
+static void
+activate_browse(G_GNUC_UNUSED GSimpleAction *action,
+                G_GNUC_UNUSED GVariant *parameter,
+                gpointer data)
 {
+	PidginDiscoDialog *dialog = data;
 	PurpleConnection *pc;
 	PidginDiscoList *pdl;
 	const char *username;
@@ -196,17 +210,18 @@ static void browse_button_cb(GtkWidget *
 	char *server = NULL;
 
 	pc = purple_account_get_connection(dialog->account);
-	if (!pc)
+	if (!pc) {
 		return;
+	}
 
-	gtk_widget_set_sensitive(dialog->browse_button, FALSE);
-	gtk_widget_set_sensitive(dialog->add_button, FALSE);
-	gtk_widget_set_sensitive(dialog->register_button, FALSE);
+	g_simple_action_set_enabled(dialog->browse_action, FALSE);
+	g_simple_action_set_enabled(dialog->add_action, FALSE);
+	g_simple_action_set_enabled(dialog->register_action, FALSE);
 
 	g_clear_pointer(&dialog->discolist, pidgin_disco_list_unref);
 	gtk_tree_store_clear(dialog->model);
 
-	pdl = dialog->discolist = g_new0(PidginDiscoList, 1);
+	pdl = dialog->discolist = pidgin_disco_list_new();
 	pdl->services = g_hash_table_new_full(NULL, NULL, NULL,
 			(GDestroyNotify)gtk_tree_row_reference_free);
 	pdl->pc = pc;
@@ -226,9 +241,10 @@ static void browse_button_cb(GtkWidget *
 		server = g_strdup_printf("%.*s", (int)(slash - (at + 1)), at + 1);
 	}
 
-	if (server == NULL)
+	if (server == NULL) {
 		/* This shouldn't ever happen since the account is connected */
 		server = g_strdup("jabber.org");
+	}
 
 	/* Translators: The string "Enter an XMPP Server" is asking the user to
 	   type the name of an XMPP server which will then be queried */
@@ -242,8 +258,12 @@ static void browse_button_cb(GtkWidget *
 	g_free(server);
 }
 
-static void add_to_blist_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
+static void
+activate_add_to_blist(G_GNUC_UNUSED GSimpleAction *action,
+                      G_GNUC_UNUSED GVariant *parameter,
+                      gpointer data)
 {
+	PidginDiscoDialog *dialog = data;
 	XmppDiscoService *service = dialog->selected;
 	PurpleAccount *account;
 	const char *jid;
@@ -253,10 +273,11 @@ static void add_to_blist_cb(GtkWidget *u
 	account = purple_connection_get_account(service->list->pc);
 	jid = service->jid;
 
-	if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT)
+	if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) {
 		purple_blist_request_add_chat(account, NULL, NULL, jid);
-	else
+	} else {
 		purple_blist_request_add_buddy(account, jid, NULL, NULL);
+	}
 }
 
 static gboolean
@@ -264,20 +285,23 @@ service_click_cb(GtkTreeView *tree, GdkE
 {
 	PidginDiscoDialog *dialog = user_data;
 	XmppDiscoService *service;
-	GtkWidget *menu;
-	GtkWidget *menuitem = NULL;
 
 	GtkTreePath *path;
 	GtkTreeIter iter;
 	GValue val;
 
-	if (!gdk_event_triggers_context_menu((GdkEvent *)event))
+	GdkRectangle rect;
+
+	if (!gdk_event_triggers_context_menu((GdkEvent *)event)) {
 		return FALSE;
+	}
 
 	/* Figure out what was clicked */
 	if (!gtk_tree_view_get_path_at_pos(tree, event->x, event->y, &path,
 		                               NULL, NULL, NULL))
+	{
 		return FALSE;
+	}
 	gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
 	gtk_tree_path_free(path);
 	val.g_type = 0;
@@ -285,27 +309,19 @@ service_click_cb(GtkTreeView *tree, GdkE
 	                         SERVICE_COLUMN, &val);
 	service = g_value_get_pointer(&val);
 
-	if (!service)
+	if (!service) {
 		return FALSE;
-
-	menu = gtk_menu_new();
-
-	if (service->flags & XMPP_DISCO_ADD) {
-		menuitem = gtk_menu_item_new_with_label(_("Add to Buddy List"));
-		g_signal_connect(G_OBJECT(menuitem), "activate",
-		                 G_CALLBACK(add_to_blist_cb), dialog);
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
 	}
 
-	if (service->flags & XMPP_DISCO_REGISTER) {
-		menuitem = gtk_menu_item_new_with_label(_("Register"));
-		g_signal_connect(G_OBJECT(menuitem), "activate",
-		                 G_CALLBACK(register_button_cb), dialog);
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
-	}
+	gtk_tree_view_convert_bin_window_to_widget_coords(dialog->tree,
+	                                                  (gint)event->x,
+	                                                  (gint)event->y,
+	                                                  &rect.x, &rect.y);
+	rect.width = rect.height = 1;
 
-	gtk_widget_show_all(menu);
-	gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
+	gtk_popover_set_pointing_to(GTK_POPOVER(dialog->popover), &rect);
+	gtk_popover_popup(GTK_POPOVER(dialog->popover));
+
 	return FALSE;
 }
 
@@ -314,24 +330,21 @@ selection_changed_cb(GtkTreeSelection *s
 {
 	GtkTreeIter iter;
 	GValue val;
+	gboolean allow_add = FALSE, allow_register = FALSE;
 
 	if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
 		val.g_type = 0;
 		gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
 		                         SERVICE_COLUMN, &val);
 		dialog->selected = g_value_get_pointer(&val);
-		if (!dialog->selected) {
-			gtk_widget_set_sensitive(dialog->add_button, FALSE);
-			gtk_widget_set_sensitive(dialog->register_button, FALSE);
-			return;
+		if (dialog->selected != NULL) {
+			allow_add = (dialog->selected->flags & XMPP_DISCO_ADD);
+			allow_register = (dialog->selected->flags & XMPP_DISCO_REGISTER);
 		}
+	}
 
-		gtk_widget_set_sensitive(dialog->add_button, dialog->selected->flags & XMPP_DISCO_ADD);
-		gtk_widget_set_sensitive(dialog->register_button, dialog->selected->flags & XMPP_DISCO_REGISTER);
-	} else {
-		gtk_widget_set_sensitive(dialog->add_button, FALSE);
-		gtk_widget_set_sensitive(dialog->register_button, FALSE);
-	}
+	g_simple_action_set_enabled(dialog->add_action, allow_add);
+	g_simple_action_set_enabled(dialog->register_action, allow_register);
 }
 
 static void
@@ -360,8 +373,7 @@ row_activated_cb(GtkTreeView       *tree
 	XmppDiscoService *service;
 	GValue val;
 
-	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter,
-	                             path)) {
+	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path)) {
 		return;
 	}
 
@@ -371,18 +383,15 @@ row_activated_cb(GtkTreeView       *tree
 	service = g_value_get_pointer(&val);
 
 	if (service->flags & XMPP_DISCO_BROWSE) {
-		if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dialog->tree),
-		                               path)) {
-			gtk_tree_view_collapse_row(GTK_TREE_VIEW(dialog->tree),
-			                           path);
+		if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dialog->tree), path)) {
+			gtk_tree_view_collapse_row(GTK_TREE_VIEW(dialog->tree), path);
 		} else {
-			gtk_tree_view_expand_row(GTK_TREE_VIEW(dialog->tree),
-			                         path, FALSE);
+			gtk_tree_view_expand_row(GTK_TREE_VIEW(dialog->tree), path, FALSE);
 		}
 	} else if (service->flags & XMPP_DISCO_REGISTER) {
-		register_button_cb(NULL, dialog);
+		g_action_activate(G_ACTION(dialog->register_action), NULL);
 	} else if (service->flags & XMPP_DISCO_ADD) {
-		add_to_blist_cb(NULL, dialog);
+		g_action_activate(G_ACTION(dialog->add_action), NULL);
 	}
 }
 
@@ -392,14 +401,16 @@ destroy_win_cb(GtkWidget *window, G_GNUC
 	PidginDiscoDialog *dialog = PIDGIN_DISCO_DIALOG(window);
 	PidginDiscoList *list = dialog->discolist;
 
-	if (dialog->prompt_handle)
+	if (dialog->prompt_handle) {
 		purple_request_close(PURPLE_REQUEST_INPUT, dialog->prompt_handle);
+	}
 
 	if (list) {
 		list->dialog = NULL;
 
-		if (list->in_progress)
+		if (list->in_progress) {
 			list->in_progress = FALSE;
+		}
 
 		pidgin_disco_list_unref(list);
 	}
@@ -407,8 +418,13 @@ destroy_win_cb(GtkWidget *window, G_GNUC
 	dialogs = g_list_remove(dialogs, dialog);
 }
 
-static void stop_button_cb(GtkButton *button, PidginDiscoDialog *dialog)
+static void
+activate_stop(G_GNUC_UNUSED GSimpleAction *action,
+              G_GNUC_UNUSED GVariant *parameter,
+              gpointer data)
 {
+	PidginDiscoDialog *dialog = data;
+
 	pidgin_disco_list_set_in_progress(dialog->discolist, FALSE);
 }
 
@@ -491,12 +507,16 @@ disco_query_tooltip(GtkWidget *widget, i
 			break;
 	}
 
+	name = g_markup_escape_text(service->name, -1);
+	jid = g_markup_escape_text(service->jid, -1);
+	if (service->description != NULL) {
+		desc = g_markup_escape_text(service->description, -1);
+	}
+
 	markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s%s%s",
-	                         name = g_markup_escape_text(service->name, -1),
-	                         type,
-	                         jid = g_markup_escape_text(service->jid, -1),
+	                         name, type, jid,
 	                         service->description ? _("\n<b>Description:</b> ") : "",
-	                         service->description ? desc = g_markup_escape_text(service->description, -1) : "");
+	                         service->description ? desc : "");
 
 	gtk_tooltip_set_markup(tooltip, markup);
 	gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path);
@@ -521,8 +541,9 @@ void pidgin_disco_signed_off_cb(PurpleCo
 		if (list && list->pc == pc) {
 			PurpleAccount *account = NULL;
 
-			if (list->in_progress)
+			if (list->in_progress) {
 				pidgin_disco_list_set_in_progress(list, FALSE);
+			}
 
 			gtk_tree_store_clear(dialog->model);
 
@@ -532,12 +553,9 @@ void pidgin_disco_signed_off_cb(PurpleCo
 			account = pidgin_account_chooser_get_selected(
 				PIDGIN_ACCOUNT_CHOOSER(dialog->account_chooser));
 
-			gtk_widget_set_sensitive(
-			        dialog->browse_button,
-			        account != NULL);
-
-			gtk_widget_set_sensitive(dialog->register_button, FALSE);
-			gtk_widget_set_sensitive(dialog->add_button, FALSE);
+			g_simple_action_set_enabled(dialog->browse_action, account != NULL);
+			g_simple_action_set_enabled(dialog->add_action, FALSE);
+			g_simple_action_set_enabled(dialog->register_action, FALSE);
 		}
 	}
 }
@@ -571,24 +589,15 @@ pidgin_disco_dialog_class_init(PidginDis
 	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
 	                                     progress);
 	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
-	                                     stop_button);
-	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
-	                                     browse_button);
-	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
-	                                     register_button);
-	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
-	                                     add_button);
-	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
 	                                     tree);
 	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
 	                                     model);
+	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
+	                                     popover);
+	gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
+	                                     popover_menu);
 
 	gtk_widget_class_bind_template_callback(widget_class, destroy_win_cb);
-	gtk_widget_class_bind_template_callback(widget_class, stop_button_cb);
-	gtk_widget_class_bind_template_callback(widget_class, browse_button_cb);
-	gtk_widget_class_bind_template_callback(widget_class,
-	                                        register_button_cb);
-	gtk_widget_class_bind_template_callback(widget_class, add_to_blist_cb);
 	gtk_widget_class_bind_template_callback(widget_class, close_button_cb);
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        dialog_select_account_cb);
@@ -607,6 +616,15 @@ pidgin_disco_dialog_class_finalize(Pidgi
 static void
 pidgin_disco_dialog_init(PidginDiscoDialog *dialog)
 {
+	GActionEntry entries[] = {
+		{ .name = "stop", .activate = activate_stop },
+		{ .name = "browse", .activate = activate_browse },
+		{ .name = "register", .activate = activate_register },
+		{ .name = "add", .activate = activate_add_to_blist },
+	};
+	GSimpleActionGroup *action_group = NULL;
+	GActionMap *action_map = NULL;
+
 	dialogs = g_list_prepend(dialogs, dialog);
 
 	gtk_widget_init_template(GTK_WIDGET(dialog));
@@ -615,12 +633,36 @@ pidgin_disco_dialog_init(PidginDiscoDial
 	dialog->account = pidgin_account_chooser_get_selected(
 		PIDGIN_ACCOUNT_CHOOSER(dialog->account_chooser));
 
-	/* browse button */
-	gtk_widget_set_sensitive(dialog->browse_button, dialog->account != NULL);
-
 	gtk_widget_set_has_tooltip(GTK_WIDGET(dialog->tree), TRUE);
 	g_signal_connect(G_OBJECT(dialog->tree), "query-tooltip",
 	                 G_CALLBACK(disco_query_tooltip), dialog);
+
+	action_group = g_simple_action_group_new();
+	action_map = G_ACTION_MAP(action_group);
+	g_action_map_add_action_entries(action_map, entries, G_N_ELEMENTS(entries),
+	                                dialog);
+
+	dialog->stop_action = G_SIMPLE_ACTION(
+			g_action_map_lookup_action(action_map, "stop"));
+	g_simple_action_set_enabled(dialog->stop_action, FALSE);
+
+	dialog->browse_action = G_SIMPLE_ACTION(
+			g_action_map_lookup_action(action_map, "browse"));
+	g_simple_action_set_enabled(dialog->browse_action, dialog->account != NULL);
+
+	dialog->register_action = G_SIMPLE_ACTION(
+			g_action_map_lookup_action(action_map, "register"));
+	g_simple_action_set_enabled(dialog->register_action, FALSE);
+
+	dialog->add_action = G_SIMPLE_ACTION(
+			g_action_map_lookup_action(action_map, "add"));
+	g_simple_action_set_enabled(dialog->add_action, FALSE);
+
+	gtk_widget_insert_action_group(GTK_WIDGET(dialog), "disco",
+	                               G_ACTION_GROUP(action_group));
+
+	gtk_popover_bind_model(GTK_POPOVER(dialog->popover), dialog->popover_menu,
+	                       NULL);
 }
 
 /******************************************************************************
@@ -636,8 +678,7 @@ pidgin_disco_dialog_register(PurplePlugi
 PidginDiscoDialog *
 pidgin_disco_dialog_new(void)
 {
-	PidginDiscoDialog *dialog =
-	        g_object_new(PIDGIN_TYPE_DISCO_DIALOG, NULL);
+	PidginDiscoDialog *dialog = g_object_new(PIDGIN_TYPE_DISCO_DIALOG, NULL);
 	gtk_widget_show_all(GTK_WIDGET(dialog));
 	return dialog;
 }
@@ -652,10 +693,12 @@ void pidgin_disco_add_service(PidginDisc
 	dialog = pdl->dialog;
 	g_return_if_fail(dialog != NULL);
 
-	if (service != NULL)
-		purple_debug_info("xmppdisco", "Adding service \"%s\"\n", service->name);
-	else
-		purple_debug_info("xmppdisco", "Service \"%s\" has no childrens\n", parent->name);
+	if (service != NULL) {
+		purple_debug_info("xmppdisco", "Adding service \"%s\"", service->name);
+	} else {
+		purple_debug_info("xmppdisco", "Service \"%s\" has no children",
+		                  parent->name);
+	}
 
 	gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress));
 
@@ -677,15 +720,17 @@ void pidgin_disco_add_service(PidginDisc
 				gtk_tree_model_get(
 				        GTK_TREE_MODEL(dialog->model), &child,
 				        SERVICE_COLUMN, &tmp, -1);
-				if (!tmp)
+				if (!tmp) {
 					append = FALSE;
+				}
 			}
 		}
 	}
 
 	if (service == NULL) {
-		if (parent != NULL && !append)
+		if (parent != NULL && !append) {
 			gtk_tree_store_remove(dialog->model, &child);
+		}
 		return;
 	}
 
@@ -716,7 +761,8 @@ void pidgin_disco_add_service(PidginDisc
 	                   NAME_COLUMN, service->name, DESCRIPTION_COLUMN,
 	                   service->description, SERVICE_COLUMN, service, -1);
 
-	if (pixbuf)
+	if (pixbuf) {
 		g_object_unref(pixbuf);
+	}
 }
 
diff --git a/pidgin/plugins/disco/resources/disco.ui b/pidgin/plugins/disco/resources/disco.ui
--- a/pidgin/plugins/disco/resources/disco.ui
+++ b/pidgin/plugins/disco/resources/disco.ui
@@ -26,6 +26,23 @@ Foundation, Inc., 51 Franklin Street, Fi
   <!-- interface-name Pidgin -->
   <!-- interface-description Internet Messenger -->
   <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+  <menu id="popover_menu">
+    <section>
+      <item>
+        <attribute name="action">disco.add</attribute>
+        <attribute name="label" translatable="yes">Add to Buddy List</attribute>
+      </item>
+      <item>
+        <attribute name="action">disco.register</attribute>
+        <attribute name="label" translatable="yes">Register</attribute>
+      </item>
+    </section>
+  </menu>
+  <object class="GtkPopoverMenu" id="popover">
+    <property name="can_focus">False</property>
+    <property name="relative-to">tree</property>
+    <property name="position">bottom</property>
+  </object>
   <object class="PidginAccountStore" id="accounts"/>
   <object class="PidginAccountFilterConnected" id="accounts_connected">
     <property name="child_model">accounts</property>
@@ -52,102 +69,11 @@ Foundation, Inc., 51 Franklin Street, Fi
     <property name="role">service discovery</property>
     <property name="type_hint">dialog</property>
     <signal name="destroy" handler="destroy_win_cb" swapped="no"/>
-    <child>
-      <placeholder/>
-    </child>
     <child internal-child="vbox">
       <object class="GtkBox">
         <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox">
-            <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="stop_button">
-                <property name="label" translatable="yes">_Stop</property>
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_underline">True</property>
-                <signal name="clicked" handler="stop_button_cb" object="PidginDiscoDialog" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="browse_button">
-                <property name="label" translatable="yes">_Browse</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_underline">True</property>
-                <signal name="clicked" handler="browse_button_cb" object="PidginDiscoDialog" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="register_button">
-                <property name="label" translatable="yes">Register</property>
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <signal name="clicked" handler="register_button_cb" object="PidginDiscoDialog" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="add_button">
-                <property name="label" translatable="yes">_Add</property>
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_underline">True</property>
-                <signal name="clicked" handler="add_to_blist_cb" object="PidginDiscoDialog" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">3</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="close_button">
-                <property name="label" translatable="yes">_Close</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_underline">True</property>
-                <signal name="clicked" handler="close_button_cb" swapped="no"/>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">4</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkBox">
             <property name="visible">True</property>
@@ -277,5 +203,61 @@ Foundation, Inc., 51 Franklin Street, Fi
         </child>
       </object>
     </child>
+    <child type="action">
+      <object class="GtkButton" id="stop_button">
+        <property name="label" translatable="yes">_Stop</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_underline">True</property>
+        <property name="action-name">disco.stop</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="browse_button">
+        <property name="label" translatable="yes">_Browse</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_underline">True</property>
+        <property name="action-name">disco.browse</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="register_button">
+        <property name="label" translatable="yes">Register</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="action-name">disco.register</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="add_button">
+        <property name="label" translatable="yes">_Add</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_underline">True</property>
+        <property name="action-name">disco.add</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="close_button">
+        <property name="label" translatable="yes">_Close</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_underline">True</property>
+        <signal name="clicked" handler="close_button_cb" swapped="no"/>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">stop_button</action-widget>
+      <action-widget response="1">browse_button</action-widget>
+      <action-widget response="2">register_button</action-widget>
+      <action-widget response="3">add_button</action-widget>
+      <action-widget response="close">close_button</action-widget>
+    </action-widgets>
   </template>
 </interface>
diff --git a/pidgin/plugins/disco/xmppdisco.c b/pidgin/plugins/disco/xmppdisco.c
--- a/pidgin/plugins/disco/xmppdisco.c
+++ b/pidgin/plugins/disco/xmppdisco.c
@@ -1,5 +1,10 @@
 /*
  * Purple - XMPP Service Disco Browser
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -12,9 +17,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
- *
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 /* TODO list (a little bit of a brain dump):
@@ -116,8 +119,9 @@ xmpp_iq_received(PurpleConnection *pc, c
 	struct xmpp_iq_cb_data *cb_data;
 
 	cb_data = g_hash_table_lookup(iq_callbacks, id);
-	if (!cb_data)
+	if (!cb_data) {
 		return FALSE;
+	}
 
 	cb_data->cb(cb_data->pc, type, id, from, iq, cb_data->context);
 
@@ -170,16 +174,18 @@ xmpp_disco_info_do(PurpleConnection *pc,
 
 	query = purple_xmlnode_new_child(iq, "query");
 	purple_xmlnode_set_namespace(query, NS_DISCO_INFO);
-	if (node)
+	if (node) {
 		purple_xmlnode_set_attrib(query, "node", node);
+	}
 
 	/* Steals id */
 	xmpp_iq_register_callback(pc, id, cbdata, cb);
 
 	purple_signal_emit(purple_connection_get_protocol(pc), "jabber-sending-xmlnode",
 	                   pc, &iq);
-	if (iq != NULL)
+	if (iq != NULL) {
 		purple_xmlnode_free(iq);
+	}
 }
 
 static void
@@ -196,16 +202,18 @@ xmpp_disco_items_do(PurpleConnection *pc
 
 	query = purple_xmlnode_new_child(iq, "query");
 	purple_xmlnode_set_namespace(query, NS_DISCO_ITEMS);
-	if (node)
+	if (node) {
 		purple_xmlnode_set_attrib(query, "node", node);
+	}
 
 	/* Steals id */
 	xmpp_iq_register_callback(pc, id, cbdata, cb);
 
 	purple_signal_emit(purple_connection_get_protocol(pc), "jabber-sending-xmlnode",
 	                   pc, &iq);
-	if (iq != NULL)
+	if (iq != NULL) {
 		purple_xmlnode_free(iq);
+	}
 }
 
 static XmppDiscoServiceType
@@ -213,29 +221,31 @@ disco_service_type_from_identity(PurpleX
 {
 	const char *category, *type;
 
-	if (!identity)
+	if (!identity) {
 		return XMPP_DISCO_SERVICE_TYPE_OTHER;
+	}
 
 	category = purple_xmlnode_get_attrib(identity, "category");
 	type = purple_xmlnode_get_attrib(identity, "type");
 
-	if (!category)
+	if (!category) {
 		return XMPP_DISCO_SERVICE_TYPE_OTHER;
+	}
 
-	if (purple_strequal(category, "conference"))
+	if (purple_strequal(category, "conference")) {
 		return XMPP_DISCO_SERVICE_TYPE_CHAT;
-	else if (purple_strequal(category, "directory"))
+	} else if (purple_strequal(category, "directory")) {
 		return XMPP_DISCO_SERVICE_TYPE_DIRECTORY;
-	else if (purple_strequal(category, "gateway"))
+	} else if (purple_strequal(category, "gateway")) {
 		return XMPP_DISCO_SERVICE_TYPE_GATEWAY;
-	else if (purple_strequal(category, "pubsub")) {
-		if (!type || purple_strequal(type, "collection"))
+	} else if (purple_strequal(category, "pubsub")) {
+		if (!type || purple_strequal(type, "collection")) {
 			return XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION;
-		else if (purple_strequal(type, "leaf"))
+		} else if (purple_strequal(type, "leaf")) {
 			return XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF;
-		else if (purple_strequal(type, "service"))
+		} else if (purple_strequal(type, "service")) {
 			return XMPP_DISCO_SERVICE_TYPE_OTHER;
-		else {
+		} else {
 			purple_debug_warning("xmppdisco", "Unknown pubsub type '%s'\n", type);
 			return XMPP_DISCO_SERVICE_TYPE_OTHER;
 		}
@@ -262,8 +272,9 @@ disco_type_from_string(const gchar *str)
 	g_return_val_if_fail(str != NULL, "");
 
 	for ( ; disco_type_mappings[i].from; ++i) {
-		if (!g_ascii_strcasecmp(str, disco_type_mappings[i].from))
+		if (!g_ascii_strcasecmp(str, disco_type_mappings[i].from)) {
 			return disco_type_mappings[i].to;
+		}
 	}
 
 	/* fallback to the string itself */
@@ -280,8 +291,9 @@ got_info_cb(PurpleConnection *pc, const 
 
 	--list->fetch_count;
 
-	if (!list->in_progress)
+	if (!list->in_progress) {
 		goto out;
+	}
 
 	if (purple_strequal(type, "result") &&
 			(query = purple_xmlnode_get_child(iq, "query"))) {
@@ -300,26 +312,31 @@ got_info_cb(PurpleConnection *pc, const 
 			if (item_data->name) {
 				service->name = item_data->name;
 				item_data->name = NULL;
-			} else
+			} else {
 				service->name = g_strdup(item_data->node);
+			}
 
 			service->node = item_data->node;
 			item_data->node = NULL;
 
-			if (service->type == XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION)
+			if (service->type == XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION) {
 				service->flags |= XMPP_DISCO_BROWSE;
-		} else
+			}
+		} else {
 			service->name = g_strdup(from);
+		}
 
-		if (!service->node)
+		if (!service->node) {
 			/* Only support adding JIDs, not JID+node combos */
 			service->flags |= XMPP_DISCO_ADD;
+		}
 
 		if (item_data->name) {
 			service->description = item_data->name;
 			item_data->name = NULL;
-		} else if (identity)
+		} else if (identity) {
 			service->description = g_strdup(purple_xmlnode_get_attrib(identity, "name"));
+		}
 
 		/* TODO: Overlap with service->name a bit */
 		service->jid = g_strdup(from);
@@ -327,29 +344,32 @@ got_info_cb(PurpleConnection *pc, const 
 		for (feature = purple_xmlnode_get_child(query, "feature"); feature;
 				feature = purple_xmlnode_get_next_twin(feature)) {
 			const char *var;
-			if (!(var = purple_xmlnode_get_attrib(feature, "var")))
+			if (!(var = purple_xmlnode_get_attrib(feature, "var"))) {
 				continue;
+			}
 
-			if (purple_strequal(var, NS_REGISTER))
+			if (purple_strequal(var, NS_REGISTER)) {
 				service->flags |= XMPP_DISCO_REGISTER;
-			else if (purple_strequal(var, NS_DISCO_ITEMS))
+			} else if (purple_strequal(var, NS_DISCO_ITEMS)) {
 				service->flags |= XMPP_DISCO_BROWSE;
-			else if (purple_strequal(var, NS_MUC)) {
+			} else if (purple_strequal(var, NS_MUC)) {
 				service->flags |= XMPP_DISCO_BROWSE;
 				service->type = XMPP_DISCO_SERVICE_TYPE_CHAT;
 			}
 		}
 
-		if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY)
+		if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY) {
 			service->gateway_type = g_strdup(disco_type_from_string(
 					purple_xmlnode_get_attrib(identity, "type")));
+		}
 
 		pidgin_disco_add_service(list, service, service->parent);
 	}
 
 out:
-	if (list->fetch_count == 0)
+	if (list->fetch_count == 0) {
 		pidgin_disco_list_set_in_progress(list, FALSE);
+	}
 
 	g_free(item_data->name);
 	g_free(item_data->node);
@@ -368,8 +388,9 @@ got_items_cb(PurpleConnection *pc, const
 
 	--list->fetch_count;
 
-	if (!list->in_progress)
+	if (!list->in_progress) {
 		goto out;
+	}
 
 	if (purple_strequal(type, "result") &&
 			(query = purple_xmlnode_get_child(iq, "query"))) {
@@ -400,6 +421,9 @@ got_items_cb(PurpleConnection *pc, const
 				service->name = g_strdup(name);
 				service->jid = g_strdup(jid);
 				service->node = g_strdup(node);
+				if (service->name == NULL) {
+					service->name = g_strdup(jid);
+				}
 				pidgin_disco_add_service(list, service, item_data->parent);
 			} else {
 				struct item_data *item_data2 = g_new0(struct item_data, 1);
@@ -416,12 +440,14 @@ got_items_cb(PurpleConnection *pc, const
 		}
 	}
 
-	if (!has_items)
+	if (!has_items) {
 		pidgin_disco_add_service(list, NULL, item_data->parent);
+	}
 
 out:
-	if (list->fetch_count == 0)
+	if (list->fetch_count == 0) {
 		pidgin_disco_list_set_in_progress(list, FALSE);
+	}
 
 	g_free(item_data);
 	pidgin_disco_list_unref(list);
@@ -449,8 +475,9 @@ server_items_cb(PurpleConnection *pc, co
 			const char *node = purple_xmlnode_get_attrib(item, "node");
 			struct item_data *item_data;
 
-			if (!jid)
+			if (!jid) {
 				continue;
+			}
 
 			item_data = g_new0(struct item_data, 1);
 			item_data->list = list;
@@ -463,8 +490,9 @@ server_items_cb(PurpleConnection *pc, co
 		}
 	}
 
-	if (list->fetch_count == 0)
+	if (list->fetch_count == 0) {
 		pidgin_disco_list_set_in_progress(list, FALSE);
+	}
 
 	pidgin_disco_list_unref(list);
 }
@@ -511,8 +539,7 @@ server_info_cb(PurpleConnection *pc, con
 			purple_notify_error(my_plugin, _("Error"),
 			                    _("Server does not exist"),
  			                    NULL, NULL);
-		}
-		else {
+		} else {
 			purple_notify_error(my_plugin, _("Error"),
 			                    _("Server does not support service discovery"),
 			                    NULL, NULL);
@@ -545,8 +572,9 @@ void xmpp_disco_service_expand(XmppDisco
 
 	g_return_if_fail(service != NULL);
 
-	if (service->expanded)
+	if (service->expanded) {
 		return;
+	}
 
 	item_data = g_new0(struct item_data, 1);
 	item_data->list = service->list;
@@ -577,8 +605,9 @@ void xmpp_disco_service_register(XmppDis
 
 	purple_signal_emit(purple_connection_get_protocol(service->list->pc),
 			"jabber-sending-xmlnode", service->list->pc, &iq);
-	if (iq != NULL)
+	if (iq != NULL) {
 		purple_xmlnode_free(iq);
+	}
 	g_free(id);
 }
 
