diff --git a/finch/finchui.h b/finch/finchui.h
new file mode 100644
--- /dev/null
+++ b/finch/finchui.h
@@ -0,0 +1,51 @@
+/*
+ * Finch - Universal Text Chat Client
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Finch 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(FINCH_GLOBAL_HEADER_INSIDE) && !defined(FINCH_COMPILATION)
+# error "only <finch.h> may be included directly"
+#endif
+
+#ifndef FINCH_UI_H
+#define FINCH_UI_H
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+#define FINCH_TYPE_UI (finch_ui_get_type())
+G_DECLARE_FINAL_TYPE(FinchUi, finch_ui, FINCH, UI, PurpleUi)
+
+/**
+ * finch_ui_new:
+ *
+ * Creates the [class@Purple.Ui] for finch.
+ *
+ * Note: This isn't really useful outside of Finch itself.
+ *
+ * Since: 3.0.0
+ */
+PurpleUi *finch_ui_new(void);
+
+G_END_DECLS
+
+#endif /* FINCH_UI_H */
+
# HG changeset patch
# Node ID c16d9d8e9ef5
# Parent  86302ce7e3c1
diff --git a/finch/finchui.c b/finch/finchui.c
new file mode 100644
--- /dev/null
+++ b/finch/finchui.c
@@ -0,0 +1,246 @@
+/*
+ * Finch - Universal Text Chat Client
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Finch 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
+#include "finchui.h"
+
+#include "finchnotifications.h"
+#include "gntaccount.h"
+#include "gntblist.h"
+#include "gntconn.h"
+#include "gntconv.h"
+#include "gntdebug.h"
+#include "gntmedia.h"
+#include "gntnotify.h"
+#include "gntplugin.h"
+#include "gntprefs.h"
+#include "gntprefs.h"
+#include "gntrequest.h"
+#include "gntroomlist.h"
+#include "gntstatus.h"
+#include "gntxfer.h"
+
+struct _FinchUi {
+	PurpleUi parent;
+};
+
+G_DEFINE_TYPE(FinchUi, finch_ui, PURPLE_TYPE_UI)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static gboolean
+finch_history_init(GError **error) {
+	PurpleHistoryManager *manager = NULL;
+	PurpleHistoryAdapter *adapter = NULL;
+	gchar *filename = NULL;
+	const gchar *id = NULL;
+
+	manager = purple_history_manager_get_default();
+
+	/* Attempt to create the config directory. */
+	g_mkdir_with_parents(purple_config_dir(), 0700);
+
+	filename = g_build_filename(purple_config_dir(), "history.db", NULL);
+	adapter = purple_sqlite_history_adapter_new(filename);
+	g_free(filename);
+
+	id = purple_history_adapter_get_id(adapter);
+	if(!purple_history_manager_register(manager, adapter, error)) {
+		g_clear_object(&adapter);
+
+		return FALSE;
+	}
+
+	/* The manager adds a ref to the adapter on registration, so we can remove
+	 * our reference.
+	 */
+	g_clear_object(&adapter);
+
+	return purple_history_manager_set_active(manager, id, error);
+}
+
+/******************************************************************************
+ * PurpleUi Implementation
+ *****************************************************************************/
+static void
+finch_ui_prefs_init(G_GNUC_UNUSED PurpleUi *ui) {
+	finch_prefs_init();
+}
+
+static void
+finch_ui_start(G_GNUC_UNUSED PurpleUi *ui) {
+	GError *error = NULL;
+
+	finch_debug_init();
+
+#ifdef STANDALONE
+#ifdef _WIN32 /* TODO: don't change it when using FHS under win32 */
+	gnt_set_config_dir(purple_config_dir());
+#endif /* _WIN32 */
+
+	gnt_init();
+#endif /* STANDALONE */
+
+	if(!finch_history_init(&error)) {
+		g_critical("failed to initialize the history api: %s",
+		           error != NULL ? error->message : "unknown");
+		g_clear_error(&error);
+	}
+
+	purple_prefs_add_none("/purple/gnt");
+
+	/* Accounts */
+	finch_accounts_init();
+
+	/* Connections */
+	finch_connections_init();
+	purple_connections_set_ui_ops(finch_connections_get_ui_ops());
+
+	/* Initialize the buddy list */
+	finch_blist_init();
+	purple_blist_set_ui(FINCH_TYPE_BUDDY_LIST);
+
+	/* Now the conversations */
+	finch_conversation_init();
+	purple_conversations_set_ui_ops(finch_conv_get_ui_ops());
+
+	/* Notify */
+	finch_notify_init();
+	purple_notify_set_ui_ops(finch_notify_get_ui_ops());
+
+	/* Request */
+	finch_request_init();
+	purple_request_set_ui_ops(finch_request_get_ui_ops());
+
+	/* File transfer */
+	finch_xfers_init();
+	purple_xfers_set_ui_ops(finch_xfers_get_ui_ops());
+
+	/* Roomlist */
+	finch_roomlist_init();
+	purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops());
+
+	/* Media */
+	finch_media_manager_init();
+
+	gnt_register_action(_("Accounts"), finch_accounts_show_all);
+	gnt_register_action(_("Buddy List"), finch_blist_show);
+	gnt_register_action(_("Notifications"), finch_notifications_window_show);
+	gnt_register_action(_("Debug Window"), finch_debug_window_show);
+	gnt_register_action(_("File Transfers"), finch_xfer_dialog_show);
+	gnt_register_action(_("Plugins"), finch_plugins_show_all);
+	gnt_register_action(_("Room List"), finch_roomlist_show_all);
+	gnt_register_action(_("Preferences"), finch_prefs_show_all);
+	gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
+
+#ifdef STANDALONE
+}
+
+static void
+finch_ui_quit(G_GNUC_UNUSED PurpleUi *ui) {
+	purple_accounts_set_ui_ops(NULL);
+	finch_accounts_uninit();
+
+	purple_connections_set_ui_ops(NULL);
+	finch_connections_uninit();
+
+	purple_blist_set_ui(G_TYPE_INVALID);
+	finch_blist_uninit();
+
+	purple_conversations_set_ui_ops(NULL);
+	finch_conversation_uninit();
+
+	purple_notify_set_ui_ops(NULL);
+	finch_notify_uninit();
+
+	purple_request_set_ui_ops(NULL);
+	finch_request_uninit();
+
+	finch_xfers_uninit();
+	purple_xfers_set_ui_ops(NULL);
+
+	finch_roomlist_uninit();
+	purple_roomlist_set_ui_ops(NULL);
+
+	finch_media_manager_uninit();
+
+	gnt_quit();
+
+	finch_debug_uninit();
+
+#ifdef _WIN32
+	gnt_set_config_dir(NULL);
+#endif /* _WIN32 */
+#endif /* STANDALONE */
+}
+
+static gpointer
+finch_ui_get_settings_backend(G_GNUC_UNUSED PurpleUi *ui) {
+	GSettingsBackend *backend = NULL;
+	char *config = NULL;
+
+	config = g_build_filename(purple_config_dir(), "finch3.ini", NULL);
+	backend = g_keyfile_settings_backend_new(config, "/", NULL);
+
+	g_free(config);
+
+	return backend;
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+finch_ui_init(G_GNUC_UNUSED FinchUi *ui) {
+}
+
+static void
+finch_ui_class_init(FinchUiClass *klass) {
+	PurpleUiClass *ui_class = PURPLE_UI_CLASS(klass);
+
+	ui_class->prefs_init = finch_ui_prefs_init;
+	ui_class->start = finch_ui_start;
+	ui_class->quit = finch_ui_quit;
+	ui_class->get_settings_backend = finch_ui_get_settings_backend;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+PurpleUi *
+finch_ui_new(void) {
+	return g_object_new(
+		FINCH_TYPE_UI,
+		"id", "finch3",
+		"name", _("Finch"),
+		"version", VERSION,
+		"website", "https://pidgin.im",
+		"support-website", "https://pidgin.im/contact/",
+		"client-type", "console",
+		NULL);
+}
diff --git a/finch/gntui.h b/finch/gntui.h
deleted file mode 100644
--- a/finch/gntui.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * finch
- *
- * Finch 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * 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
- */
-
-#if !defined(FINCH_GLOBAL_HEADER_INSIDE) && !defined(FINCH_COMPILATION)
-# error "only <finch.h> may be included directly"
-#endif
-
-#ifndef FINCH_UI_H
-#define FINCH_UI_H
-
-#include "gnt.h"
-
-void finch_ui_init(void);
-void finch_ui_uninit(void);
-
-#endif /* FINCH_UI_H */
-
diff --git a/finch/gntui.c b/finch/gntui.c
deleted file mode 100644
--- a/finch/gntui.c
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * finch
- *
- * Finch 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * 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
- */
-
-#include <config.h>
-
-#include <glib/gi18n-lib.h>
-
-#include <purple.h>
-
-#include "gntui.h"
-
-#include "finchnotifications.h"
-#include "gntaccount.h"
-#include "gntblist.h"
-#include "gntconn.h"
-#include "gntconv.h"
-#include "gntdebug.h"
-#include "gntxfer.h"
-#include "gntmedia.h"
-#include "gntnotify.h"
-#include "gntplugin.h"
-#include "gntprefs.h"
-#include "gntrequest.h"
-#include "gntroomlist.h"
-#include "gntstatus.h"
-
-static gboolean
-finch_history_init(GError **error) {
-	PurpleHistoryManager *manager = NULL;
-	PurpleHistoryAdapter *adapter = NULL;
-	gchar *filename = NULL;
-	const gchar *id = NULL;
-
-	manager = purple_history_manager_get_default();
-
-	/* Attempt to create the config directory. */
-	g_mkdir_with_parents(purple_config_dir(), 0700);
-
-	filename = g_build_filename(purple_config_dir(), "history.db", NULL);
-	adapter = purple_sqlite_history_adapter_new(filename);
-	g_free(filename);
-
-	id = purple_history_adapter_get_id(adapter);
-	if(!purple_history_manager_register(manager, adapter, error)) {
-		g_clear_object(&adapter);
-
-		return FALSE;
-	}
-
-	/* The manager adds a ref to the adapter on registration, so we can remove
-	 * our reference.
-	 */
-	g_clear_object(&adapter);
-
-	return purple_history_manager_set_active(manager, id, error);
-}
-
-void finch_ui_init()
-{
-	GError *error = NULL;
-
-	finch_debug_init();
-
-#ifdef STANDALONE
-#ifdef _WIN32 /* TODO: don't change it when using FHS under win32 */
-	gnt_set_config_dir(purple_config_dir());
-#endif /* _WIN32 */
-
-	gnt_init();
-#endif /* STANDALONE */
-
-	if(!finch_history_init(&error)) {
-		g_critical("failed to initialize the history api: %s",
-		           error != NULL ? error->message : "unknown");
-		g_clear_error(&error);
-	}
-
-	purple_prefs_add_none("/purple/gnt");
-
-	/* Accounts */
-	finch_accounts_init();
-
-	/* Connections */
-	finch_connections_init();
-	purple_connections_set_ui_ops(finch_connections_get_ui_ops());
-
-	/* Initialize the buddy list */
-	finch_blist_init();
-	purple_blist_set_ui(FINCH_TYPE_BUDDY_LIST);
-
-	/* Now the conversations */
-	finch_conversation_init();
-	purple_conversations_set_ui_ops(finch_conv_get_ui_ops());
-
-	/* Notify */
-	finch_notify_init();
-	purple_notify_set_ui_ops(finch_notify_get_ui_ops());
-
-	/* Request */
-	finch_request_init();
-	purple_request_set_ui_ops(finch_request_get_ui_ops());
-
-	/* File transfer */
-	finch_xfers_init();
-	purple_xfers_set_ui_ops(finch_xfers_get_ui_ops());
-
-	/* Roomlist */
-	finch_roomlist_init();
-	purple_roomlist_set_ui_ops(finch_roomlist_get_ui_ops());
-
-	/* Media */
-	finch_media_manager_init();
-
-	gnt_register_action(_("Accounts"), finch_accounts_show_all);
-	gnt_register_action(_("Buddy List"), finch_blist_show);
-	gnt_register_action(_("Notifications"), finch_notifications_window_show);
-	gnt_register_action(_("Debug Window"), finch_debug_window_show);
-	gnt_register_action(_("File Transfers"), finch_xfer_dialog_show);
-	gnt_register_action(_("Plugins"), finch_plugins_show_all);
-	gnt_register_action(_("Room List"), finch_roomlist_show_all);
-	gnt_register_action(_("Preferences"), finch_prefs_show_all);
-	gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
-
-#ifdef STANDALONE
-}
-
-void finch_ui_uninit()
-{
-	purple_accounts_set_ui_ops(NULL);
-	finch_accounts_uninit();
-
-	purple_connections_set_ui_ops(NULL);
-	finch_connections_uninit();
-
-	purple_blist_set_ui(G_TYPE_INVALID);
-	finch_blist_uninit();
-
-	purple_conversations_set_ui_ops(NULL);
-	finch_conversation_uninit();
-
-	purple_notify_set_ui_ops(NULL);
-	finch_notify_uninit();
-
-	purple_request_set_ui_ops(NULL);
-	finch_request_uninit();
-
-	finch_xfers_uninit();
-	purple_xfers_set_ui_ops(NULL);
-
-	finch_roomlist_uninit();
-	purple_roomlist_set_ui_ops(NULL);
-
-	finch_media_manager_uninit();
-
-	gnt_quit();
-
-	finch_debug_uninit();
-
-#ifdef _WIN32
-	gnt_set_config_dir(NULL);
-#endif /* _WIN32 */
-#endif /* STANDALONE */
-}
diff --git a/finch/libfinch.c b/finch/libfinch.c
--- a/finch/libfinch.c
+++ b/finch/libfinch.c
@@ -26,54 +26,19 @@
 #include <glib/gi18n-lib.h>
 #include <glib/gstdio.h>
 
-#define G_SETTINGS_ENABLE_BACKEND
-#include <gio/gsettingsbackend.h>
-
 #include <locale.h>
 
 #include <purple.h>
 
+#include "finchui.h"
 #include "gntdebug.h"
 #include "gntidle.h"
 #include "gntprefs.h"
-#include "gntui.h"
 #include "libfinch.h"
 
 #include "config.h"
 #include "package_revision.h"
 
-static void
-finch_quit(void)
-{
-	finch_ui_uninit();
-}
-
-static gpointer
-finch_get_settings_backend(void) {
-	GSettingsBackend *backend = NULL;
-	char *config = NULL;
-
-	config = g_build_filename(purple_config_dir(), "finch3.ini", NULL);
-	backend = g_keyfile_settings_backend_new(config, "/", NULL);
-
-	g_free(config);
-
-	return backend;
-}
-
-static PurpleCoreUiOps core_ops = {
-	.ui_prefs_init = finch_prefs_init,
-	.ui_init = finch_ui_init,
-	.quit = finch_quit,
-	.get_settings_backend = finch_get_settings_backend,
-};
-
-static PurpleCoreUiOps *
-gnt_core_get_ui_ops(void)
-{
-	return &core_ops;
-}
-
 static gboolean
 start_with_debugwin(gpointer null)
 {
@@ -106,7 +71,6 @@
 static int
 init_libpurple(int argc, char **argv)
 {
-	PurpleUiInfo *ui_info = NULL;
 	gboolean opt_nologin = FALSE;
 	gboolean opt_version = FALSE;
 	gboolean opt_debug = FALSE;
@@ -196,14 +160,9 @@
 		g_timeout_add(0, start_with_debugwin, NULL);
 	}
 
-	purple_core_set_ui_ops(gnt_core_get_ui_ops());
 	purple_idle_set_ui(finch_idle_new());
 
-	ui_info = purple_ui_info_new("finch3", _("Finch"), VERSION,
-	                             "https://pidgin.im",
-	                             "https://developer.pidgin.im", "console");
-
-	if (!purple_core_init(ui_info))
+	if (!purple_core_init(finch_ui_new()))
 	{
 		fprintf(stderr,
 				"Initialization of the Purple core failed. Dumping core.\n"
diff --git a/finch/meson.build b/finch/meson.build
--- a/finch/meson.build
+++ b/finch/meson.build
@@ -85,6 +85,7 @@
 
 libfinch_SOURCES = [
 	'finchnotifications.c',
+	'finchui.c',
 	'gntaccount.c',
 	'gntblist.c',
 	'gntconn.c',
@@ -99,7 +100,6 @@
 	'gntrequest.c',
 	'gntroomlist.c',
 	'gntstatus.c',
-	'gntui.c',
 	'gntxfer.c',
 	package_revision,
 	'libfinch.c'
@@ -107,6 +107,7 @@
 
 libfinch_headers = [
 	'finchnotifications.h',
+	'finchui.h',
 	'gntaccount.h',
 	'gntblist.h',
 	'gntconn.h',
@@ -121,7 +122,6 @@
 	'gntrequest.h',
 	'gntroomlist.h',
 	'gntstatus.h',
-	'gntui.h',
 	'gntxfer.h',
 	'libfinch.h'
 ]
diff --git a/libpurple/core.h b/libpurple/core.h
--- a/libpurple/core.h
+++ b/libpurple/core.h
@@ -29,7 +29,7 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#include <libpurple/purplecoreuiops.h>
+#include <libpurple/purpleui.h>
 
 typedef struct PurpleCore PurpleCore;
 
@@ -37,7 +37,7 @@
 
 /**
  * purple_core_init:
- * @ui_info: (transfer full): The [class@UiInfo] of the UI using the core.
+ * @ui: (transfer full): The [class@Purple.Ui] of the UI using the core.
  *
  * Initializes the core of purple.
  *
@@ -45,7 +45,7 @@
  *
  * Returns: %TRUE if successful, or %FALSE otherwise.
  */
-gboolean purple_core_init(PurpleUiInfo *ui_info);
+gboolean purple_core_init(PurpleUi *ui);
 
 /**
  * purple_core_quit:
@@ -112,30 +112,13 @@
 gpointer purple_core_get_settings_backend(void);
 
 /**
- * purple_core_set_ui_ops:
- * @ops: A UI ops structure for the core.
+ * purple_core_get_ui:
  *
- * Sets the UI ops for the core.
- */
-void purple_core_set_ui_ops(PurpleCoreUiOps *ops);
-
-/**
- * purple_core_get_ui_ops:
- *
- * Returns the UI ops for the core.
+ * Gets the [class@Purple.Ui] that is running.
  *
- * Returns: The core's UI ops structure.
+ * Returns: (transfer none): The ui.
  */
-PurpleCoreUiOps *purple_core_get_ui_ops(void);
-
-/**
- * purple_core_get_ui_info:
- *
- * Returns a #PurpleUiInfo that contains information about the user interface.
- *
- * Returns: (transfer none): A #PurpleUiInfo instance.
- */
-PurpleUiInfo *purple_core_get_ui_info(void);
+PurpleUi *purple_core_get_ui(void);
 
 G_END_DECLS
 
diff --git a/libpurple/core.c b/libpurple/core.c
--- a/libpurple/core.c
+++ b/libpurple/core.c
@@ -54,26 +54,25 @@
 
 struct PurpleCore
 {
-	PurpleUiInfo *ui_info;
+	PurpleUi *ui;
 
 	void *reserved;
 };
 
-static PurpleCoreUiOps *_ops  = NULL;
 static PurpleCore      *_core = NULL;
 static GSettingsBackend *settings_backend = NULL;
 
 static void
 purple_core_print_version(void)
 {
-	PurpleUiInfo *ui_info = purple_core_get_ui_info();
+	PurpleUi *ui = purple_core_get_ui();
 	const gchar *ui_name = NULL;
 	const gchar *ui_version = NULL;
 	gchar *ui_full_name = NULL;
 
-	if(PURPLE_IS_UI_INFO(ui_info)) {
-		ui_name = purple_ui_info_get_name(ui_info);
-		ui_version = purple_ui_info_get_version(ui_info);
+	if(PURPLE_IS_UI(ui)) {
+		ui_name = purple_ui_get_name(ui);
+		ui_version = purple_ui_get_version(ui);
 	}
 
 	if (ui_name) {
@@ -93,11 +92,10 @@
 }
 
 gboolean
-purple_core_init(PurpleUiInfo *ui_info) {
-	PurpleCoreUiOps *ops;
+purple_core_init(PurpleUi *ui) {
 	PurpleCore *core;
 
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(ui_info), FALSE);
+	g_return_val_if_fail(PURPLE_IS_UI(ui), FALSE);
 	g_return_val_if_fail(purple_get_core() == NULL, FALSE);
 
 	bindtextdomain(PACKAGE, PURPLE_LOCALEDIR);
@@ -107,11 +105,9 @@
 #endif
 
 	_core = core = g_new0(PurpleCore, 1);
-	core->ui_info = ui_info;
+	core->ui = ui;
 	core->reserved = NULL;
 
-	ops = purple_core_get_ui_ops();
-
 	/* The signals subsystem is important and should be first. */
 	purple_signals_init();
 
@@ -134,15 +130,12 @@
 	/* The prefs subsystem needs to be initialized before static protocols
 	 * for protocol prefs to work. */
 	purple_prefs_init();
-	settings_backend = ops->get_settings_backend();
+
+	settings_backend = purple_ui_get_settings_backend(core->ui);
 
 	purple_debug_init();
 
-	if (ops != NULL) {
-		if (ops->ui_prefs_init != NULL) {
-			ops->ui_prefs_init();
-		}
-	}
+	purple_ui_prefs_init(core->ui);
 
 	purple_notification_manager_startup();
 
@@ -188,8 +181,7 @@
 	 */
 	purple_network_discover_my_ip();
 
-	if (ops != NULL && ops->ui_init != NULL)
-		ops->ui_init();
+	purple_ui_start(core->ui);
 
 	/* Load the buddy list after UI init */
 	purple_blist_boot();
@@ -202,7 +194,6 @@
 void
 purple_core_quit(void)
 {
-	PurpleCoreUiOps *ops;
 	PurpleCore *core = purple_get_core();
 	PurpleCredentialManager *credential_manager = NULL;
 	PurpleHistoryManager *history_manager = NULL;
@@ -240,9 +231,7 @@
 	_purple_image_store_uninit();
 	purple_network_uninit();
 
-	ops = purple_core_get_ui_ops();
-	if (ops != NULL && ops->quit != NULL)
-		ops->quit();
+	purple_ui_quit(core->ui);
 
 	/* Everything after prefs_uninit must not try to read any prefs */
 	g_clear_object(&settings_backend);
@@ -266,7 +255,7 @@
 
 	purple_signals_uninit();
 
-	g_clear_object(&core->ui_info);
+	g_clear_object(&core->ui);
 	g_free(core);
 
 #ifdef _WIN32
@@ -301,19 +290,7 @@
 	return settings_backend;
 }
 
-void
-purple_core_set_ui_ops(PurpleCoreUiOps *ops)
-{
-	_ops = ops;
+PurpleUi *
+purple_core_get_ui() {
+	return _core->ui;
 }
-
-PurpleCoreUiOps *
-purple_core_get_ui_ops(void)
-{
-	return _ops;
-}
-
-PurpleUiInfo *
-purple_core_get_ui_info() {
-	return _core->ui_info;
-}
diff --git a/libpurple/meson.build b/libpurple/meson.build
--- a/libpurple/meson.build
+++ b/libpurple/meson.build
@@ -50,7 +50,6 @@
 	'purpleconversation.c',
 	'purpleconversationmanager.c',
 	'purpleconversationuiops.c',
-	'purplecoreuiops.c',
 	'purplecredentialmanager.c',
 	'purplecredentialprovider.c',
 	'purpledebugui.c',
@@ -88,7 +87,7 @@
 	'purplesqlite3.c',
 	'purplesqlitehistoryadapter.c',
 	'purpletags.c',
-	'purpleuiinfo.c',
+	'purpleui.c',
 	'purplewhiteboard.c',
 	'purplewhiteboardmanager.c',
 	'purplewhiteboarduiops.c',
@@ -156,7 +155,6 @@
 	'purpleconversation.h',
 	'purpleconversationmanager.h',
 	'purpleconversationuiops.h',
-	'purplecoreuiops.h',
 	'purplecredentialmanager.h',
 	'purplecredentialprovider.h',
 	'purpledebugui.h',
@@ -195,7 +193,7 @@
 	'purplesqlite3.h',
 	'purplesqlitehistoryadapter.h',
 	'purpletags.h',
-	'purpleuiinfo.h',
+	'purpleui.h',
 	'purplewhiteboard.h',
 	'purplewhiteboardmanager.h',
 	'purplewhiteboardops.h',
diff --git a/libpurple/plugins/kwallet/purplekwallet.cpp b/libpurple/plugins/kwallet/purplekwallet.cpp
--- a/libpurple/plugins/kwallet/purplekwallet.cpp
+++ b/libpurple/plugins/kwallet/purplekwallet.cpp
@@ -53,12 +53,12 @@
  *****************************************************************************/
 static QString
 purple_kwallet_get_ui_name(void) {
-	PurpleUiInfo *ui_info = NULL;
+	PurpleUi *ui = NULL;
 	QString ui_name = NULL;
 
-	ui_info = purple_core_get_ui_info();
-	if(PURPLE_IS_UI_INFO(ui_info)) {
-		ui_name = purple_ui_info_get_name(ui_info);
+	ui = purple_core_get_ui();
+	if(PURPLE_IS_UI(ui)) {
+		ui_name = purple_ui_get_name(ui);
 	}
 
 	if(ui_name.isEmpty()) {
diff --git a/libpurple/protocols/jabber/bosh.c b/libpurple/protocols/jabber/bosh.c
--- a/libpurple/protocols/jabber/bosh.c
+++ b/libpurple/protocols/jabber/bosh.c
@@ -65,13 +65,13 @@
 void
 jabber_bosh_init(void)
 {
-	PurpleUiInfo *ui_info = purple_core_get_ui_info();
+	PurpleUi *ui = purple_core_get_ui();
 	const gchar *ui_name = NULL;
 	const gchar *ui_version = NULL;
 
-	if(PURPLE_IS_UI_INFO(ui_info)) {
-		ui_name = purple_ui_info_get_name(ui_info);
-		ui_version = purple_ui_info_get_version(ui_info);
+	if(PURPLE_IS_UI(ui)) {
+		ui_name = purple_ui_get_name(ui);
+		ui_version = purple_ui_get_version(ui);
 	}
 
 	if(ui_name) {
diff --git a/libpurple/protocols/jabber/iq.c b/libpurple/protocols/jabber/iq.c
--- a/libpurple/protocols/jabber/iq.c
+++ b/libpurple/protocols/jabber/iq.c
@@ -224,7 +224,7 @@
 	PurpleXmlNode *query;
 
 	if(type == JABBER_IQ_GET) {
-		PurpleUiInfo *ui_info;
+		PurpleUi *ui;
 		const char *ui_name = NULL, *ui_version = NULL;
 
 		iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:version");
@@ -234,11 +234,11 @@
 
 		query = purple_xmlnode_get_child(iq->node, "query");
 
-		ui_info = purple_core_get_ui_info();
+		ui = purple_core_get_ui();
 
-		if(PURPLE_IS_UI_INFO(ui_info)) {
-			ui_name = purple_ui_info_get_name(ui_info);
-			ui_version = purple_ui_info_get_version(ui_info);
+		if(PURPLE_IS_UI(ui)) {
+			ui_name = purple_ui_get_name(ui);
+			ui_version = purple_ui_get_version(ui);
 		}
 
 		if(NULL != ui_name && NULL != ui_version) {
diff --git a/libpurple/protocols/jabber/jabber.c b/libpurple/protocols/jabber/jabber.c
--- a/libpurple/protocols/jabber/jabber.c
+++ b/libpurple/protocols/jabber/jabber.c
@@ -3686,7 +3686,7 @@
 static void
 jabber_do_init(void)
 {
-	PurpleUiInfo *ui_info = purple_core_get_ui_info();
+	PurpleUi *ui = purple_core_get_ui();
 	const gchar *ui_type;
 	const gchar *type = "pc"; /* default client type, if unknown or
 								unspecified */
@@ -3720,7 +3720,7 @@
 
 	jabber_cmds = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, cmds_free_func);
 
-	ui_type = ui_info ? purple_ui_info_get_client_type(ui_info) : NULL;
+	ui_type = ui ? purple_ui_get_client_type(ui) : NULL;
 	if (ui_type) {
 		if (purple_strequal(ui_type, "pc") ||
 			purple_strequal(ui_type, "console") ||
@@ -3732,8 +3732,8 @@
 		}
 	}
 
-	if (ui_info)
-		ui_name = purple_ui_info_get_name(ui_info);
+	if (ui)
+		ui_name = purple_ui_get_name(ui);
 	if (ui_name == NULL)
 		ui_name = PACKAGE;
 
diff --git a/libpurple/protocols/silc/silc.c b/libpurple/protocols/silc/silc.c
--- a/libpurple/protocols/silc/silc.c
+++ b/libpurple/protocols/silc/silc.c
@@ -693,14 +693,14 @@
 static gchar *
 silcpurple_get_quit_message(void)
 {
-	PurpleUiInfo *ui_info;
+	PurpleUi *ui;
 	const char *ui_name = NULL, *ui_website = NULL;
 	char *quit_msg;
 
-	ui_info = purple_core_get_ui_info();
-	if (PURPLE_IS_UI_INFO(ui_info)) {
-		ui_name = purple_ui_info_get_name(ui_info);
-		ui_website = purple_ui_info_get_website(ui_info);
+	ui = purple_core_get_ui();
+	if (PURPLE_IS_UI(ui)) {
+		ui_name = purple_ui_get_name(ui);
+		ui_website = purple_ui_get_website(ui);
 	}
 
 	if (!ui_name || !ui_website) {
diff --git a/libpurple/purplecoreuiops.h b/libpurple/purplecoreuiops.h
deleted file mode 100644
--- a/libpurple/purplecoreuiops.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * 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, see <https://www.gnu.org/licenses/>.
- */
-
-#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
-# error "only <purple.h> may be included directly"
-#endif
-
-#ifndef PURPLE_CORE_UIOPS_H
-#define PURPLE_CORE_UIOPS_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include <libpurple/purpleuiinfo.h>
-
-#define PURPLE_TYPE_CORE_UI_OPS (purple_core_ui_ops_get_type())
-typedef struct _PurpleCoreUiOps PurpleCoreUiOps;
-
-G_BEGIN_DECLS
-
-/**
- * PurpleCoreUiOps:
- * @ui_prefs_init: Called just after the preferences subsystem is initialized;
- *                 the UI could use this callback to add some preferences it
- *                 needs to be in place when other subsystems are initialized.
- * @ui_init:       Called after all of libpurple has been initialized. The UI
- *                 should use this hook to set all other necessary
- *                 <link linkend="chapter-ui-ops"><literal>UiOps structures</literal></link>.
- * @quit:          Called after most of libpurple has been uninitialized.
- * @get_settings_backend: Called to get the [class@Gio.SettingsBackend] that
- *                        the UI is using.
- *
- * Callbacks that fire at different points of the initialization and teardown
- * of libpurple, along with a hook to return descriptive information about the
- * UI.
- */
-struct _PurpleCoreUiOps {
-	/*< public >*/
-	void (*ui_prefs_init)(void);
-	void (*ui_init)(void);
-
-	void (*quit)(void);
-
-	gpointer (*get_settings_backend)(void);
-
-	/*< private >*/
-	gpointer reserved[4];
-};
-
-/**
- * purple_core_ui_ops_get_type:
- *
- * Returns: The #GType for the #PurpleCoreUiOps boxed structure.
- */
-GType purple_core_ui_ops_get_type(void);
-
-
-G_END_DECLS
-
-#endif /* PURPLE_CORE_UIOPS_H */
diff --git a/libpurple/purplecoreuiops.c b/libpurple/purplecoreuiops.c
deleted file mode 100644
--- a/libpurple/purplecoreuiops.c
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * 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, see <https://www.gnu.org/licenses/>.
- */
-
-#include "purplecoreuiops.h"
-
-static PurpleCoreUiOps *
-purple_core_ui_ops_copy(PurpleCoreUiOps *ops)
-{
-	PurpleCoreUiOps *ops_new;
-
-	g_return_val_if_fail(ops != NULL, NULL);
-
-	ops_new = g_new(PurpleCoreUiOps, 1);
-	*ops_new = *ops;
-
-	return ops_new;
-}
-
-GType
-purple_core_ui_ops_get_type(void)
-{
-	static GType type = 0;
-
-	if (type == 0) {
-		type = g_boxed_type_register_static("PurpleCoreUiOps",
-				(GBoxedCopyFunc)purple_core_ui_ops_copy,
-				(GBoxedFreeFunc)g_free);
-	}
-
-	return type;
-}
-
-
diff --git a/libpurple/purplepath.c b/libpurple/purplepath.c
--- a/libpurple/purplepath.c
+++ b/libpurple/purplepath.c
@@ -19,7 +19,7 @@
 #include <libpurple/purplepath.h>
 
 #include <libpurple/core.h>
-#include <libpurple/purpleuiinfo.h>
+#include <libpurple/purpleui.h>
 
 #ifdef _WIN32
 # include "win32/win32dep.h"
@@ -43,10 +43,10 @@
 {
 	if (!*xdg_dir) {
 		if (!custom_user_dir) {
-			PurpleUiInfo *info = purple_core_get_ui_info();
+			PurpleUi *ui = purple_core_get_ui();
 			const gchar *id = NULL;
 
-			id = purple_ui_info_get_id(info);
+			id = purple_ui_get_id(ui);
 			if(id == NULL) {
 				id = "purple";
 			}
diff --git a/libpurple/purpleui.h b/libpurple/purpleui.h
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleui.h
@@ -0,0 +1,201 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PURPLE_UI_H
+#define PURPLE_UI_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PURPLE_TYPE_UI (purple_ui_get_type())
+G_DECLARE_DERIVABLE_TYPE(PurpleUi, purple_ui, PURPLE, UI, GObject)
+
+/**
+ * PurpleUi:
+ *
+ * An abstract class representing a user interface. All user interfaces must
+ * implement this call and [func@Purple.Core.init] with it.
+ *
+ * Since: 3.0.0
+ */
+
+/**
+ * PurpleUiClass:
+ * @prefs_init: This function is called to initialize the ui's preferences.
+ *              This is slowly being phased out for @get_settings_backend, but
+ *              is still required.
+ * @start: Called when libpurple is done with it's initialization when the user
+ *         interface should start telling libpurple about the rest of the user
+ *         interface's interfaces.
+ * @quit: Called after most of libpurple has been uninitialized.
+ * @get_settings_backend: Called to get the [class@Gio.SettingsBackend] that
+ *                        the UI is using.
+ *
+ * The base class for all user interfaces which is used to identify themselves
+ * to libpurple when calling [func@Purple.core_init].
+ *
+ * Since: 3.0.0
+ */
+struct _PurpleUiClass {
+	/*< private >*/
+	GObjectClass parent;
+
+	/*< public >*/
+	void (*prefs_init)(PurpleUi *ui);
+	void (*start)(PurpleUi *ui);
+	void (*quit)(PurpleUi *ui);
+
+	gpointer (*get_settings_backend)(PurpleUi *ui);
+
+	/*< private >*/
+	gpointer reserved[4];
+};
+
+/**
+ * purple_ui_get_id:
+ * @ui: The instance.
+ *
+ * Gets the id for the user interface.
+ *
+ * Returns: id of the user interface.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_ui_get_id(PurpleUi *ui);
+
+/**
+ * purple_ui_get_name:
+ * @ui: The instance.
+ *
+ * Gets the name of @ui. This should be translated.
+ *
+ * Returns: The name of the user interface.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_ui_get_name(PurpleUi *ui);
+
+/**
+ * purple_ui_get_version:
+ * @ui: The instance.
+ *
+ * Gets the version of @ui.
+ *
+ * Returns: The version of the user interface.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_ui_get_version(PurpleUi *ui);
+
+/**
+ * purple_ui_get_website:
+ * @ui: The instance.
+ *
+ * Gets the website from @ui.
+ *
+ * Returns: (nullable): The website URI of the user interface or %NULL.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_ui_get_website(PurpleUi *ui);
+
+/**
+ * purple_ui_get_support_website:
+ * @ui: The instance.
+ *
+ * Gets the support website from @ui.
+ *
+ * Returns: (nullable): The support website URI of the user interface or %NULL.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_ui_get_support_website(PurpleUi *ui);
+
+/**
+ * purple_ui_get_client_type:
+ * @ui: The instance.
+ *
+ * Gets the client type from @ui.
+ *
+ * Returns: (transfer none): The client type of the user interface.
+ *
+ * Since: 3.0.0
+ */
+const char *purple_ui_get_client_type(PurpleUi *ui);
+
+/**
+ * purple_ui_prefs_init:
+ * @ui: The instance.
+ *
+ * Tells @ui that it should be initializing its preferences.
+ *
+ * Note: This should only be called by libpurple.
+ *
+ * Since: 3.0.0
+ */
+void purple_ui_prefs_init(PurpleUi *ui);
+
+/**
+ * purple_ui_start:
+ * @ui: The instance.
+ *
+ * Tells @ui that libpurple is done initializing and that @ui should continue
+ * its initialization.
+ *
+ * Note: This should only be called by libpurple.
+ *
+ * Since: 3.0.0
+ */
+void purple_ui_start(PurpleUi *ui);
+
+/**
+ * purple_ui_quit:
+ * @ui: The instance.
+ *
+ * Tells @ui that libpurple is done shutting down.
+ *
+ * Note: This should only be called by libpurple.
+ *
+ * Since: 3.0.0
+ */
+void purple_ui_quit(PurpleUi *ui);
+
+/**
+ * purple_ui_get_settings_backend:
+ * @ui: The instance:
+ *
+ * Get the [class@Gio.SettingsBackend] that @ui is using for it's settings.
+ *
+ * Note: This should only be called by libpurple.
+ *
+ * Returns: (transfer full): The settings that @ui is using.
+ *
+ * Since: 3.0.0
+ */
+gpointer purple_ui_get_settings_backend(PurpleUi *ui);
+
+
+G_END_DECLS
+
+#endif /* PURPLE_UI_H */
diff --git a/libpurple/purpleui.c b/libpurple/purpleui.c
new file mode 100644
--- /dev/null
+++ b/libpurple/purpleui.c
@@ -0,0 +1,425 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "purpleui.h"
+
+typedef struct {
+	char *id;
+	char *name;
+	char *version;
+	char *website;
+	char *support_website;
+	char *client_type;
+} PurpleUiPrivate;
+
+enum {
+	PROP_0,
+	PROP_ID,
+	PROP_NAME,
+	PROP_VERSION,
+	PROP_WEBSITE,
+	PROP_SUPPORT_WEBSITE,
+	PROP_CLIENT_TYPE,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL,};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(PurpleUi, purple_ui, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+purple_ui_set_id(PurpleUi *ui, const char *id) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	priv = purple_ui_get_instance_private(ui);
+
+	g_free(priv->id);
+	priv->id = g_strdup(id);
+
+	g_object_notify_by_pspec(G_OBJECT(ui), properties[PROP_ID]);
+}
+
+static void
+purple_ui_set_name(PurpleUi *ui, const char *name) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	priv = purple_ui_get_instance_private(ui);
+
+	g_free(priv->name);
+	priv->name = g_strdup(name);
+
+	g_object_notify_by_pspec(G_OBJECT(ui), properties[PROP_NAME]);
+}
+
+static void
+purple_ui_set_version(PurpleUi *ui, const char *version) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	priv = purple_ui_get_instance_private(ui);
+
+	g_free(priv->version);
+	priv->version = g_strdup(version);
+
+	g_object_notify_by_pspec(G_OBJECT(ui), properties[PROP_VERSION]);
+}
+
+static void
+purple_ui_set_website(PurpleUi *ui, const char *website) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	priv = purple_ui_get_instance_private(ui);
+
+	g_free(priv->website);
+	priv->website = g_strdup(website);
+
+	g_object_notify_by_pspec(G_OBJECT(ui), properties[PROP_WEBSITE]);
+}
+
+static void
+purple_ui_set_support_website(PurpleUi *ui, const char *support_website) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	priv = purple_ui_get_instance_private(ui);
+
+	g_free(priv->support_website);
+	priv->support_website = g_strdup(support_website);
+
+	g_object_notify_by_pspec(G_OBJECT(ui), properties[PROP_SUPPORT_WEBSITE]);
+}
+
+static void
+purple_ui_set_client_type(PurpleUi *ui, const char *client_type) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	priv = purple_ui_get_instance_private(ui);
+
+	g_free(priv->client_type);
+	priv->client_type = g_strdup(client_type);
+
+	g_object_notify_by_pspec(G_OBJECT(ui), properties[PROP_CLIENT_TYPE]);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+purple_ui_get_property(GObject *obj, guint param_id, GValue *value,
+                       GParamSpec *pspec)
+{
+	PurpleUi *ui = PURPLE_UI(obj);
+
+	switch(param_id) {
+		case PROP_ID:
+			g_value_set_string(value, purple_ui_get_id(ui));
+			break;
+		case PROP_NAME:
+			g_value_set_string(value, purple_ui_get_name(ui));
+			break;
+		case PROP_VERSION:
+			g_value_set_string(value, purple_ui_get_version(ui));
+			break;
+		case PROP_WEBSITE:
+			g_value_set_string(value, purple_ui_get_website(ui));
+			break;
+		case PROP_SUPPORT_WEBSITE:
+			g_value_set_string(value, purple_ui_get_support_website(ui));
+			break;
+		case PROP_CLIENT_TYPE:
+			g_value_set_string(value, purple_ui_get_client_type(ui));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_ui_set_property(GObject *obj, guint param_id, const GValue *value,
+                       GParamSpec *pspec)
+{
+	PurpleUi *ui = PURPLE_UI(obj);
+
+	switch(param_id) {
+		case PROP_ID:
+			purple_ui_set_id(ui, g_value_get_string(value));
+			break;
+		case PROP_NAME:
+			purple_ui_set_name(ui, g_value_get_string(value));
+			break;
+		case PROP_VERSION:
+			purple_ui_set_version(ui, g_value_get_string(value));
+			break;
+		case PROP_WEBSITE:
+			purple_ui_set_website(ui, g_value_get_string(value));
+			break;
+		case PROP_SUPPORT_WEBSITE:
+			purple_ui_set_support_website(ui, g_value_get_string(value));
+			break;
+		case PROP_CLIENT_TYPE:
+			purple_ui_set_client_type(ui, g_value_get_string(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+purple_ui_finalize(GObject *obj) {
+	PurpleUiPrivate *priv = purple_ui_get_instance_private(PURPLE_UI(obj));
+
+	g_clear_pointer(&priv->id, g_free);
+	g_clear_pointer(&priv->name, g_free);
+	g_clear_pointer(&priv->version, g_free);
+	g_clear_pointer(&priv->website, g_free);
+	g_clear_pointer(&priv->support_website, g_free);
+	g_clear_pointer(&priv->client_type, g_free);
+
+	G_OBJECT_CLASS(purple_ui_parent_class)->finalize(obj);
+}
+
+static void
+purple_ui_init(G_GNUC_UNUSED PurpleUi *ui) {
+}
+
+static void
+purple_ui_class_init(PurpleUiClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->get_property = purple_ui_get_property;
+	obj_class->set_property = purple_ui_set_property;
+	obj_class->finalize = purple_ui_finalize;
+
+	/**
+	 * PurpleUi:id:
+	 *
+	 * The identifier of the user interface. This is used in places where a
+	 * constant string is need to represent the user interface.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ID] = g_param_spec_string(
+		"id", "id",
+		"The identifier of the user interface",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleUi:name:
+	 *
+	 * The name of the user interface. This is used in places where it will be
+	 * displayed to users, so it should be translated.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NAME] = g_param_spec_string(
+		"name", "name",
+		"The name of the user interface",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleUi:version:
+	 *
+	 * The version number of the user interface.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_VERSION] = g_param_spec_string(
+		"version", "version",
+		"The version of the user interface",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleUi:website:
+	 *
+	 * The website of the user interface. This should be the main website.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_WEBSITE] = g_param_spec_string(
+		"website", "website",
+		"The website of the user interface",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleUi:support-website:
+	 *
+	 * The support website of the user interface. This should link to a page
+	 * that specifically directs users how to get support for your user
+	 * interface.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_SUPPORT_WEBSITE] = g_param_spec_string(
+		"support-website", "support-website",
+		"The support website of the user interface",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PurpleUi:client-type:
+	 *
+	 * The client type of the user interface. Common values include `bot`,
+	 * `console`, `mobile`, `pc`, `web`, etc.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_CLIENT_TYPE] = g_param_spec_string(
+		"client-type", "client-type",
+		"The client type of the user interface",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+const char *
+purple_ui_get_id(PurpleUi *ui) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	priv = purple_ui_get_instance_private(ui);
+
+	return priv->id;
+}
+
+const char *
+purple_ui_get_name(PurpleUi *ui) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	priv = purple_ui_get_instance_private(ui);
+
+	return priv->name;
+}
+
+const char *
+purple_ui_get_version(PurpleUi *ui) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	priv = purple_ui_get_instance_private(ui);
+
+	return priv->version;
+}
+
+const char *
+purple_ui_get_website(PurpleUi *ui) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	priv = purple_ui_get_instance_private(ui);
+
+	return priv->website;
+}
+
+const char *
+purple_ui_get_support_website(PurpleUi *ui) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	priv = purple_ui_get_instance_private(ui);
+
+	return priv->support_website;
+}
+
+const char *
+purple_ui_get_client_type(PurpleUi *ui) {
+	PurpleUiPrivate *priv = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	priv = purple_ui_get_instance_private(ui);
+
+	return priv->client_type;
+}
+
+void
+purple_ui_prefs_init(PurpleUi *ui) {
+	PurpleUiClass *klass = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	klass = PURPLE_UI_GET_CLASS(ui);
+	if(klass != NULL && klass->prefs_init != NULL) {
+		klass->prefs_init(ui);
+	}
+}
+
+void
+purple_ui_start(PurpleUi *ui) {
+	PurpleUiClass *klass = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	klass = PURPLE_UI_GET_CLASS(ui);
+	if(klass != NULL && klass->start != NULL) {
+		klass->start(ui);
+	}
+}
+
+void
+purple_ui_quit(PurpleUi *ui) {
+	PurpleUiClass *klass = NULL;
+
+	g_return_if_fail(PURPLE_IS_UI(ui));
+
+	klass = PURPLE_UI_GET_CLASS(ui);
+	if(klass != NULL && klass->quit != NULL) {
+		klass->quit(ui);
+	}
+}
+
+gpointer
+purple_ui_get_settings_backend(PurpleUi *ui) {
+	PurpleUiClass *klass = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_UI(ui), NULL);
+
+	klass = PURPLE_UI_GET_CLASS(ui);
+	if(klass != NULL && klass->get_settings_backend != NULL) {
+		return klass->get_settings_backend(ui);
+	}
+
+	return NULL;
+}
diff --git a/libpurple/purpleuiinfo.h b/libpurple/purpleuiinfo.h
deleted file mode 100644
--- a/libpurple/purpleuiinfo.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Purple - Internet Messaging Library
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Purple 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * 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, see <https://www.gnu.org/licenses/>.
- */
-
-#if !defined(PURPLE_GLOBAL_HEADER_INSIDE) && !defined(PURPLE_COMPILATION)
-# error "only <purple.h> may be included directly"
-#endif
-
-#ifndef PURPLE_UI_INFO_H
-#define PURPLE_UI_INFO_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-G_BEGIN_DECLS
-
-#define PURPLE_TYPE_UI_INFO (purple_ui_info_get_type())
-
-/**
- * PurpleUiInfo:
- *
- * #PurpleUiInfo keeps track of basic information about the user interface.
- */
-G_DECLARE_FINAL_TYPE(PurpleUiInfo, purple_ui_info, PURPLE, UI_INFO, GObject)
-
-/**
- * purple_ui_info_new:
- * @id: The identifier.
- * @name: The name, which will be displayed.
- * @version: The version.
- * @website: The website.
- * @support_website: The support website.
- * @client_type: The client type.
- *
- * Creates a new #PurpleUiInfo with the given values.  If you only want to set
- * a few of these, you should use g_object_new() directly.
- *
- * Returns: (transfer full): The newly created #PurpleUiInfo instance.
- *
- * Since: 3.0.0
- */
-PurpleUiInfo *purple_ui_info_new(const gchar *id,
-                                 const gchar *name,
-                                 const gchar *version,
-                                 const gchar *website,
-                                 const gchar *support_website,
-                                 const gchar *client_type);
-
-/**
- * purple_ui_info_get_id:
- * @info: The instance.
- *
- * Gets the identifier from @info.
- *
- * Returns: The identifier from @info.
- *
- * Since: 3.0.0
- */
-const gchar *purple_ui_info_get_id(PurpleUiInfo *info);
-
-/**
- * purple_ui_info_get_name:
- * @info: The #PurpleUiInfo instance.
- *
- * Gets the name from @info.
- *
- * Returns: The name from @info.
- *
- * Since: 3.0.0
- */
-const gchar *purple_ui_info_get_name(PurpleUiInfo *info);
-
-/**
- * purple_ui_info_get_version:
- * @info: The #PurpleUiInfo instance.
- *
- * Gets the version from @info.
- *
- * Returns: The version from @info.
- *
- * Since: 3.0.0
- */
-const gchar *purple_ui_info_get_version(PurpleUiInfo *info);
-
-/**
- * purple_ui_info_get_website:
- * @info: The #PurpleUiInfo instance.
- *
- * Gets the website from @info.
- *
- * Returns: The website for @info.
- *
- * Since: 3.0.0
- */
-const gchar *purple_ui_info_get_website(PurpleUiInfo *info);
-
-/**
- * purple_ui_info_get_support_website:
- * @info: The #PurpleUiInfo instance.
- *
- * Gets the support website from @info.
- *
- * Returns: The support website from @info.
- *
- * Since: 3.0.0
- */
-const gchar *purple_ui_info_get_support_website(PurpleUiInfo *info);
-
-/**
- * purple_ui_info_get_client_type:
- * @info: The #PurpleUiInfo instance.
- *
- * Gets the client type from @info.  For example: 'bot', 'console', 'mobile',
- * 'pc', 'web', etc.
- *
- * Returns: The client type of @info.
- *
- * Since: 3.0.0
- */
-const gchar *purple_ui_info_get_client_type(PurpleUiInfo *info);
-
-G_END_DECLS
-
-#endif /* PURPLE_UI_INFO_H */
-
diff --git a/libpurple/purpleuiinfo.c b/libpurple/purpleuiinfo.c
deleted file mode 100644
--- a/libpurple/purpleuiinfo.c
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * purple
- *
- * Purple 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * 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
- */
-
-#include "purpleuiinfo.h"
-
-struct _PurpleUiInfo {
-	GObject parent;
-
-	gchar *id;
-	gchar *name;
-	gchar *version;
-	gchar *website;
-	gchar *support_website;
-	gchar *client_type;
-};
-
-enum {
-	PROP_0,
-	PROP_ID,
-	PROP_NAME,
-	PROP_VERSION,
-	PROP_WEBSITE,
-	PROP_SUPPORT_WEBSITE,
-	PROP_CLIENT_TYPE,
-	N_PROPERTIES
-};
-static GParamSpec *properties[N_PROPERTIES] = { NULL, };
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-purple_ui_info_set_id(PurpleUiInfo *info, const gchar *id) {
-	g_free(info->id);
-	info->id = g_strdup(id);
-}
-
-static void
-purple_ui_info_set_name(PurpleUiInfo *info, const gchar *name) {
-	g_free(info->name);
-	info->name = g_strdup(name);
-}
-
-static void
-purple_ui_info_set_version(PurpleUiInfo *info, const gchar *version) {
-	g_free(info->version);
-	info->version = g_strdup(version);
-}
-
-static void
-purple_ui_info_set_website(PurpleUiInfo *info, const gchar *website) {
-	g_free(info->website);
-	info->website = g_strdup(website);
-}
-
-static void
-purple_ui_info_set_support_website(PurpleUiInfo *info,
-                                   const gchar *support_website)
-{
-	g_free(info->support_website);
-	info->support_website = g_strdup(support_website);
-}
-
-static void
-purple_ui_info_set_client_type(PurpleUiInfo *info, const gchar *client_type) {
-	g_free(info->client_type);
-	info->client_type = g_strdup(client_type);
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_TYPE(PurpleUiInfo, purple_ui_info, G_TYPE_OBJECT);
-
-static void
-purple_ui_info_get_property(GObject *obj, guint param_id, GValue *value,
-                            GParamSpec *pspec)
-{
-	PurpleUiInfo *info = PURPLE_UI_INFO(obj);
-
-	switch(param_id) {
-		case PROP_ID:
-			g_value_set_string(value, purple_ui_info_get_id(info));
-			break;
-		case PROP_NAME:
-			g_value_set_string(value, purple_ui_info_get_name(info));
-			break;
-		case PROP_VERSION:
-			g_value_set_string(value, purple_ui_info_get_version(info));
-			break;
-		case PROP_WEBSITE:
-			g_value_set_string(value, purple_ui_info_get_website(info));
-			break;
-		case PROP_SUPPORT_WEBSITE:
-			g_value_set_string(value, purple_ui_info_get_support_website(info));
-			break;
-		case PROP_CLIENT_TYPE:
-			g_value_set_string(value, purple_ui_info_get_client_type(info));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_ui_info_set_property(GObject *obj, guint param_id, const GValue *value,
-                            GParamSpec *pspec)
-{
-	PurpleUiInfo *info = PURPLE_UI_INFO(obj);
-
-	switch(param_id) {
-		case PROP_ID:
-			purple_ui_info_set_id(info, g_value_get_string(value));
-			break;
-		case PROP_NAME:
-			purple_ui_info_set_name(info, g_value_get_string(value));
-			break;
-		case PROP_VERSION:
-			purple_ui_info_set_version(info, g_value_get_string(value));
-			break;
-		case PROP_WEBSITE:
-			purple_ui_info_set_website(info, g_value_get_string(value));
-			break;
-		case PROP_SUPPORT_WEBSITE:
-			purple_ui_info_set_support_website(info,
-			                                   g_value_get_string(value));
-			break;
-		case PROP_CLIENT_TYPE:
-			purple_ui_info_set_client_type(info, g_value_get_string(value));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-purple_ui_info_init(PurpleUiInfo *info) {
-}
-
-static void
-purple_ui_info_finalize(GObject *obj) {
-	PurpleUiInfo *info = PURPLE_UI_INFO(obj);
-
-	g_clear_pointer(&info->id, g_free);
-	g_clear_pointer(&info->name, g_free);
-	g_clear_pointer(&info->version, g_free);
-	g_clear_pointer(&info->website, g_free);
-	g_clear_pointer(&info->support_website, g_free);
-	g_clear_pointer(&info->client_type, g_free);
-
-	G_OBJECT_CLASS(purple_ui_info_parent_class)->finalize(obj);
-}
-
-static void
-purple_ui_info_class_init(PurpleUiInfoClass *klass) {
-	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
-
-	obj_class->get_property = purple_ui_info_get_property;
-	obj_class->set_property = purple_ui_info_set_property;
-	obj_class->finalize = purple_ui_info_finalize;
-
-	/**
-	 * PurpleUiInfo:id:
-	 *
-	 * The identifier of the user interface.
-	 *
-	 * Since: 3.0.0
-	 */
-	properties[PROP_ID] =
-		g_param_spec_string("id", "id", "The identifier of the user interface",
-		                    NULL,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS);
-
-	/**
-	 * PurpleUiInfo:name:
-	 *
-	 * The name of the user interface.
-	 *
-	 * Since: 3.0.0
-	 */
-	properties[PROP_NAME] =
-		g_param_spec_string("name", "name", "The name of the user interface",
-		                    NULL,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS);
-
-	/**
-	 * PurpleUiInfo:version:
-	 *
-	 * The version of the user interface.
-	 *
-	 * Since: 3.0.0
-	 */
-	properties[PROP_VERSION] =
-		g_param_spec_string("version", "version",
-		                    "The version of the user interface",
-		                    NULL,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS);
-
-	/**
-	 * PurpleUiInfo:website:
-	 *
-	 * The website of the user interface.
-	 *
-	 * Since: 3.0.0
-	 */
-	properties[PROP_WEBSITE] =
-		g_param_spec_string("website", "website",
-		                    "The website of the user interface",
-		                    NULL,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS);
-
-	/**
-	 * PurpleUiInfo:support-website:
-	 *
-	 * The support website of the user interface.
-	 *
-	 * Since: 3.0.0
-	 */
-	properties[PROP_SUPPORT_WEBSITE] =
-		g_param_spec_string("support-website", "support-website",
-		                    "The support website of the user interface",
-		                    NULL,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS);
-
-	/**
-	 * PurpleUiInfo:client-type:
-	 *
-	 * The client type of the user interface.
-	 *
-	 * Since: 3.0.0
-	 */
-	properties[PROP_CLIENT_TYPE] =
-		g_param_spec_string("client-type", "client-type",
-		                    "The client type of the user interface",
-		                    NULL,
-		                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
-		                    G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-PurpleUiInfo *
-purple_ui_info_new(const gchar *id, const gchar *name,
-                   const gchar *version, const gchar *website,
-                   const gchar *support_website, const gchar *client_type)
-{
-	return g_object_new(PURPLE_TYPE_UI_INFO,
-	                    "id", id,
-	                    "name", name,
-	                    "version", version,
-	                    "website", website,
-	                    "support-website", support_website,
-	                    "client-type", client_type,
-	                    NULL);
-}
-
-const gchar *
-purple_ui_info_get_id(PurpleUiInfo *info) {
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(info), NULL);
-
-	return info->id;
-}
-
-const gchar *
-purple_ui_info_get_name(PurpleUiInfo *info) {
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(info), NULL);
-
-	return info->name;
-}
-
-const gchar *
-purple_ui_info_get_version(PurpleUiInfo *info) {
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(info), NULL);
-
-	return info->version;
-}
-
-const gchar *
-purple_ui_info_get_website(PurpleUiInfo *info) {
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(info), NULL);
-
-	return info->website;
-}
-
-const gchar *
-purple_ui_info_get_support_website(PurpleUiInfo *info) {
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(info), NULL);
-
-	return info->support_website;
-}
-
-const gchar *
-purple_ui_info_get_client_type(PurpleUiInfo *info) {
-	g_return_val_if_fail(PURPLE_IS_UI_INFO(info), NULL);
-
-	return info->client_type;
-}
diff --git a/libpurple/tests/test_ui.c b/libpurple/tests/test_ui.c
--- a/libpurple/tests/test_ui.c
+++ b/libpurple/tests/test_ui.c
@@ -58,21 +58,51 @@
 	.write_conv = test_write_conv
 };
 
+/******************************************************************************
+ * PurpleUi Implementation
+ *****************************************************************************/
+struct _TestPurpleUi {
+	PurpleUi parent;
+};
+
+G_DECLARE_FINAL_TYPE(TestPurpleUi, test_purple_ui, TEST_PURPLE, UI, PurpleUi)
+
+G_DEFINE_TYPE(TestPurpleUi, test_purple_ui, PURPLE_TYPE_UI)
+
 static void
-test_ui_init(void)
-{
+test_purple_ui_start(G_GNUC_UNUSED PurpleUi *ui) {
 	purple_conversations_set_ui_ops(&test_conv_uiops);
 }
 
 static gpointer
-test_ui_get_settings_backend(void) {
+test_purple_ui_get_settings_backend(G_GNUC_UNUSED PurpleUi *ui) {
 	return g_memory_settings_backend_new();
 }
 
-static PurpleCoreUiOps test_core_uiops = {
-	.ui_init = test_ui_init,
-	.get_settings_backend = test_ui_get_settings_backend,
-};
+static void
+test_purple_ui_init(G_GNUC_UNUSED TestPurpleUi *ui) {
+}
+
+static void
+test_purple_ui_class_init(TestPurpleUiClass *klass) {
+	PurpleUiClass *ui_class = PURPLE_UI_CLASS(klass);
+
+	ui_class->start = test_purple_ui_start;
+	ui_class->get_settings_backend = test_purple_ui_get_settings_backend;
+}
+
+static PurpleUi *
+test_purple_ui_new(void) {
+	return g_object_new(
+		test_purple_ui_get_type(),
+		"id", "test",
+		"name", "Test-UI",
+		"version", VERSION,
+		"website", PURPLE_WEBSITE,
+		"support-website", PURPLE_WEBSITE,
+		"client-type", "test",
+		NULL);
+}
 
 static gboolean
 test_ui_init_history(GError **error) {
@@ -101,7 +131,7 @@
 
 void
 test_ui_purple_init(void) {
-	PurpleUiInfo *ui_info = NULL;
+	PurpleUi *ui = NULL;
 	GError *error = NULL;
 
 #ifndef _WIN32
@@ -116,21 +146,12 @@
 	/* set the magic PURPLE_PLUGINS_SKIP environment variable */
 	g_setenv("PURPLE_PLUGINS_SKIP", "1", TRUE);
 
-	/* Set the core-uiops, which is used to
-	 * 	- initialize the ui specific preferences.
-	 * 	- initialize the debug ui.
-	 * 	- initialize the ui components for all the modules.
-	 * 	- uninitialize the ui components for all the modules when the core terminates.
-	 */
-	purple_core_set_ui_ops(&test_core_uiops);
-
-	ui_info = purple_ui_info_new("test", "Test-UI", VERSION, PURPLE_WEBSITE,
-	                             PURPLE_WEBSITE, "test");
+	ui = test_purple_ui_new();
 
 	/* Now that all the essential stuff has been set, let's try to init the core. It's
 	 * necessary to provide a non-NULL name for the current ui to the core. This name
 	 * is used by stuff that depends on this ui, for example the ui-specific plugins. */
-	if (!purple_core_init(ui_info)) {
+	if (!purple_core_init(ui)) {
 		/* Initializing the core failed. Terminate. */
 		fprintf(stderr,
 				"libpurple initialization failed. Dumping core.\n"
diff --git a/pidgin/gtkconv.h b/pidgin/gtkconv.h
--- a/pidgin/gtkconv.h
+++ b/pidgin/gtkconv.h
@@ -26,6 +26,8 @@
 #ifndef _PIDGIN_CONVERSATION_H_
 #define _PIDGIN_CONVERSATION_H_
 
+#include <gtk/gtk.h>
+
 typedef struct _PidginConversation PidginConversation;
 
 enum {
diff --git a/pidgin/libpidgin.c b/pidgin/libpidgin.c
--- a/pidgin/libpidgin.c
+++ b/pidgin/libpidgin.c
@@ -30,31 +30,12 @@
 #include <glib/gi18n-lib.h>
 #include <glib/gstdio.h>
 
-#define G_SETTINGS_ENABLE_BACKEND
-#include <gio/gsettingsbackend.h>
-
 #include <locale.h>
 
 #include <purple.h>
 
-#include "gtkaccount.h"
-#include "gtkblist.h"
-#include "gtkconn.h"
-#include "gtkxfer.h"
-#include "gtkidle.h"
-#include "gtkmedia.h"
-#include "gtknotify.h"
-#include "gtkprivacy.h"
-#include "gtkrequest.h"
-#include "gtkroomlist.h"
-#include "gtkutils.h"
-#include "gtkwhiteboard.h"
 #include "pidginapplication.h"
 #include "pidgincore.h"
-#include "pidgindebug.h"
-#include "pidginplugininfo.h"
-#include "pidginprefs.h"
-#include "pidginprivate.h"
 
 #ifndef _WIN32
 #include <signal.h>
@@ -154,181 +135,6 @@
 }
 #endif /* !_WIN32 */
 
-static void
-purple_ui_add_protocol_theme_paths(PurpleProtocol *protocol) {
-	GdkDisplay *display = NULL;
-	GtkIconTheme *theme = NULL;
-	const gchar *path = NULL;
-
-	display = gdk_display_get_default();
-
-	theme = gtk_icon_theme_get_for_display(display);
-
-	path = purple_protocol_get_icon_search_path(protocol);
-	if(path != NULL) {
-		gtk_icon_theme_add_search_path(theme, path);
-	}
-
-	path = purple_protocol_get_icon_resource_path(protocol);
-	if(path != NULL) {
-		gtk_icon_theme_add_resource_path(theme, path);
-	}
-}
-
-static void
-purple_ui_protocol_foreach_theme_cb(PurpleProtocol *protocol, gpointer data) {
-	purple_ui_add_protocol_theme_paths(protocol);
-}
-
-static void
-purple_ui_protocol_registered_cb(PurpleProtocolManager *manager,
-                                 PurpleProtocol *protocol)
-{
-	purple_ui_add_protocol_theme_paths(protocol);
-}
-
-static gboolean
-pidgin_history_init(GError **error) {
-	PurpleHistoryManager *manager = NULL;
-	PurpleHistoryAdapter *adapter = NULL;
-	gchar *filename = NULL;
-	const gchar *id = NULL;
-
-	manager = purple_history_manager_get_default();
-
-	/* Attempt to create the config_dir. We don't care about the result as the
-	 * logging adapter will fail with a better error than us failing to create
-	 * the directory.
-	 */
-	g_mkdir_with_parents(purple_config_dir(), 0700);
-
-	filename = g_build_filename(purple_config_dir(), "history.db", NULL);
-	adapter = purple_sqlite_history_adapter_new(filename);
-	g_free(filename);
-
-	id = purple_history_adapter_get_id(adapter);
-	if(!purple_history_manager_register(manager, adapter, error)) {
-		g_clear_object(&adapter);
-
-		return FALSE;
-	}
-
-	/* The manager adds a ref to the adapter on registration, so we can remove
-	 * our reference.
-	 */
-	g_clear_object(&adapter);
-
-	return purple_history_manager_set_active(manager, id, error);
-}
-
-static void
-pidgin_ui_init(void)
-{
-	PurpleProtocolManager *protocol_manager = NULL;
-	GdkDisplay *display = NULL;
-	GtkIconTheme *theme = NULL;
-	GError *error = NULL;
-	gchar *path;
-
-	pidgin_debug_init();
-
-	display = gdk_display_get_default();
-	theme = gtk_icon_theme_get_for_display(display);
-
-	path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons", NULL);
-	gtk_icon_theme_add_search_path(theme, path);
-	g_free(path);
-
-	/* Add a callback for when a protocol is registered to add its icon paths
-	 * if it was found after initial startup.
-	 */
-	protocol_manager = purple_protocol_manager_get_default();
-	g_signal_connect(protocol_manager, "registered",
-	                 G_CALLBACK(purple_ui_protocol_registered_cb), NULL);
-
-	/* Add the icon paths for all the protocols that libpurple found at start
-	 * up.
-	 */
-	purple_protocol_manager_foreach(protocol_manager,
-	                                purple_ui_protocol_foreach_theme_cb, NULL);
-
-	if(!pidgin_history_init(&error)) {
-		g_critical("failed to initialize the history api: %s",
-		           error != NULL ? error->message : "unknown");
-		g_clear_error(&error);
-	}
-
-	/* Set the UI operation structures. */
-	purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
-	purple_blist_set_ui(PIDGIN_TYPE_BUDDY_LIST);
-	purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
-	purple_request_set_ui_ops(pidgin_request_get_ui_ops());
-	purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
-	purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
-	purple_idle_set_ui(pidgin_idle_new());
-
-	pidgin_accounts_init();
-	pidgin_connection_init();
-	pidgin_request_init();
-	pidgin_blist_init();
-	pidgin_conversations_init();
-	pidgin_commands_init();
-	pidgin_privacy_init();
-	pidgin_xfers_init();
-	pidgin_roomlist_init();
-	pidgin_medias_init();
-	pidgin_notify_init();
-}
-
-static void
-pidgin_quit(void)
-{
-	/* Uninit */
-
-	/* Be sure to close all windows that are not attached to anything
-	 * (e.g., the debug window), or they may access things after they are
-	 * shut down. */
-	pidgin_notify_uninit();
-	pidgin_commands_uninit();
-	pidgin_conversations_uninit();
-	pidgin_blist_uninit();
-	pidgin_request_uninit();
-	pidgin_connection_uninit();
-	pidgin_accounts_uninit();
-	pidgin_xfers_uninit();
-	pidgin_debug_window_hide();
-	pidgin_debug_uninit();
-
-	/* and end it all... */
-	g_application_quit(g_application_get_default());
-}
-
-static gpointer
-pidgin_get_settings_backend(void) {
-	GSettingsBackend *backend = NULL;
-	char *config = NULL;
-
-	config = g_build_filename(purple_config_dir(), "pidgin3.ini", NULL);
-	backend = g_keyfile_settings_backend_new(config, "/", NULL);
-
-	g_free(config);
-
-	return backend;
-}
-
-static PurpleCoreUiOps core_ops = {
-	.ui_prefs_init = pidgin_prefs_init,
-	.ui_init = pidgin_ui_init,
-	.quit = pidgin_quit,
-	.get_settings_backend = pidgin_get_settings_backend,
-};
-
-PurpleCoreUiOps *
-pidgin_core_get_ui_ops(void)
-{
-	return &core_ops;
-}
-
 #ifndef _WIN32
 static void
 pidgin_setup_error_handler(void)
diff --git a/pidgin/meson.build b/pidgin/meson.build
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -58,6 +58,7 @@
 	'pidginstatusprimitivechooser.c',
 	'pidginstatusprimitivestore.c',
 	'pidgintalkatu.c',
+	'pidginui.c',
 	'prefs/pidginprefs.c',
 	'prefs/pidginawayprefs.c',
 	'prefs/pidginconversationprefs.c',
@@ -127,6 +128,7 @@
 	'pidginstatusprimitivechooser.h',
 	'pidginstatusprimitivestore.h',
 	'pidgintalkatu.h',
+	'pidginui.h',
 ]
 
 libpidgin_prefs_headers = [
diff --git a/pidgin/pidginapplication.c b/pidgin/pidginapplication.c
--- a/pidgin/pidginapplication.c
+++ b/pidgin/pidginapplication.c
@@ -52,9 +52,10 @@
 #include "pidginmooddialog.h"
 #include "pidginpluginsdialog.h"
 #include "pidginpluginsmenu.h"
+#include "pidginprefs.h"
 #include "pidginstatuseditor.h"
 #include "pidginstatusmanager.h"
-#include "pidginprefs.h"
+#include "pidginui.h"
 
 struct _PidginApplication {
 	GtkApplication parent;
@@ -735,7 +736,6 @@
 static void
 pidgin_application_startup(GApplication *application) {
 	PurpleAccountManager *manager = NULL;
-	PurpleUiInfo *ui_info = NULL;
 	GList *active_accounts = NULL;
 	gpointer handle = NULL;
 
@@ -770,13 +770,7 @@
 	winpidgin_init();
 #endif
 
-	purple_core_set_ui_ops(pidgin_core_get_ui_ops());
-
-	ui_info = purple_ui_info_new("pidgin3", PIDGIN_NAME, VERSION,
-	                             "https://pidgin.im",
-	                             "https://developer.pidgin.im", "pc");
-
-	if(!purple_core_init(ui_info)) {
+	if(!purple_core_init(pidgin_ui_new())) {
 		fprintf(stderr,
 				_("Initialization of the libpurple core failed. Aborting!\n"
 				  "Please report this!\n"));
diff --git a/pidgin/pidgincore.h b/pidgin/pidgincore.h
--- a/pidgin/pidgincore.h
+++ b/pidgin/pidgincore.h
@@ -61,17 +61,5 @@
  */
 int pidgin_start(int argc, char *argv[]);
 
-/**
- * pidgin_core_get_ui_ops:
- *
- * Gets the #PurpleCoreUiOps that Pidgin sets up. You probably don't want to
- * call this.
- *
- * Returns: The #PurpleCoreUiOps for Pidgin.
- *
- * Since: 3.0.0
- */
-PurpleCoreUiOps *pidgin_core_get_ui_ops(void);
-
 #endif /* PIDGIN_CORE_H */
 
diff --git a/pidgin/pidginui.h b/pidgin/pidginui.h
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginui.h
@@ -0,0 +1,60 @@
+/*
+ * 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
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PIDGIN_UI_H
+#define PIDGIN_UI_H
+
+#include <glib.h>
+
+#include <purple.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginUi:
+ *
+ * Is a subclass of [class@Purple.Ui] that identifies Pidgin to libpurple.
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_UI (pidgin_ui_get_type())
+G_DECLARE_FINAL_TYPE(PidginUi, pidgin_ui, PIDGIN, UI, PurpleUi)
+
+/**
+ * pidgin_ui_new:
+ *
+ * Creates the new [class@Pidgin.Ui] instance.
+ *
+ * Note: there's not much use for this outside of Pidgin's internal code.
+ *
+ * Returns: (transfer full): The new instance.
+ */
+PurpleUi *pidgin_ui_new(void);
+
+G_END_DECLS
+
+#endif /* PIDGIN_UI_H */
diff --git a/pidgin/pidginui.c b/pidgin/pidginui.c
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginui.c
@@ -0,0 +1,263 @@
+/*
+ * 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
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#define G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "pidginui.h"
+
+#include "gtkaccount.h"
+#include "gtkblist.h"
+#include "gtkconn.h"
+#include "gtkconv.h"
+#include "gtkidle.h"
+#include "gtkmedia.h"
+#include "gtknotify.h"
+#include "gtkprivacy.h"
+#include "gtkroomlist.h"
+#include "gtkrequest.h"
+#include "gtkwhiteboard.h"
+#include "gtkxfer.h"
+#include "pidgincore.h"
+#include "pidgindebug.h"
+#include "pidginprefs.h"
+#include "pidginprivate.h"
+
+struct _PidginUi {
+	PurpleUi parent;
+};
+
+G_DEFINE_TYPE(PidginUi, pidgin_ui, PURPLE_TYPE_UI)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static gboolean
+pidgin_history_init(GError **error) {
+	PurpleHistoryManager *manager = NULL;
+	PurpleHistoryAdapter *adapter = NULL;
+	gchar *filename = NULL;
+	const gchar *id = NULL;
+
+	manager = purple_history_manager_get_default();
+
+	/* Attempt to create the config_dir. We don't care about the result as the
+	 * logging adapter will fail with a better error than us failing to create
+	 * the directory.
+	 */
+	g_mkdir_with_parents(purple_config_dir(), 0700);
+
+	filename = g_build_filename(purple_config_dir(), "history.db", NULL);
+	adapter = purple_sqlite_history_adapter_new(filename);
+	g_free(filename);
+
+	id = purple_history_adapter_get_id(adapter);
+	if(!purple_history_manager_register(manager, adapter, error)) {
+		g_clear_object(&adapter);
+
+		return FALSE;
+	}
+
+	/* The manager adds a ref to the adapter on registration, so we can remove
+	 * our reference.
+	 */
+	g_clear_object(&adapter);
+
+	return purple_history_manager_set_active(manager, id, error);
+}
+
+static void
+pidgin_ui_add_protocol_theme_paths(PurpleProtocol *protocol) {
+	GdkDisplay *display = NULL;
+	GtkIconTheme *theme = NULL;
+	const gchar *path = NULL;
+
+	display = gdk_display_get_default();
+
+	theme = gtk_icon_theme_get_for_display(display);
+
+	path = purple_protocol_get_icon_search_path(protocol);
+	if(path != NULL) {
+		gtk_icon_theme_add_search_path(theme, path);
+	}
+
+	path = purple_protocol_get_icon_resource_path(protocol);
+	if(path != NULL) {
+		gtk_icon_theme_add_resource_path(theme, path);
+	}
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_ui_protocol_foreach_theme_cb(PurpleProtocol *protocol,
+                                    G_GNUC_UNUSED gpointer data)
+{
+	pidgin_ui_add_protocol_theme_paths(protocol);
+}
+
+static void
+pidgin_ui_protocol_registered_cb(G_GNUC_UNUSED PurpleProtocolManager *manager,
+                                 PurpleProtocol *protocol)
+{
+	pidgin_ui_add_protocol_theme_paths(protocol);
+}
+
+/******************************************************************************
+ * PurpleUi Implementation
+ *****************************************************************************/
+static void
+pidgin_ui_prefs_init(G_GNUC_UNUSED PurpleUi *ui) {
+	pidgin_prefs_init();
+}
+
+static void
+pidgin_ui_start(G_GNUC_UNUSED PurpleUi *ui) {
+	PurpleProtocolManager *protocol_manager = NULL;
+	GdkDisplay *display = NULL;
+	GtkIconTheme *theme = NULL;
+	GError *error = NULL;
+	gchar *path;
+
+	pidgin_debug_init();
+
+	display = gdk_display_get_default();
+	theme = gtk_icon_theme_get_for_display(display);
+
+	path = g_build_filename(PURPLE_DATADIR, "pidgin", "icons", NULL);
+	gtk_icon_theme_add_search_path(theme, path);
+	g_free(path);
+
+	/* Add a callback for when a protocol is registered to add its icon paths
+	 * if it was found after initial startup.
+	 */
+	protocol_manager = purple_protocol_manager_get_default();
+	g_signal_connect(protocol_manager, "registered",
+	                 G_CALLBACK(pidgin_ui_protocol_registered_cb), NULL);
+
+	/* Add the icon paths for all the protocols that libpurple found at start
+	 * up.
+	 */
+	purple_protocol_manager_foreach(protocol_manager,
+	                                pidgin_ui_protocol_foreach_theme_cb, NULL);
+
+	if(!pidgin_history_init(&error)) {
+		g_critical("failed to initialize the history api: %s",
+		           error != NULL ? error->message : "unknown");
+		g_clear_error(&error);
+	}
+
+	/* Set the UI operation structures. */
+	purple_xfers_set_ui_ops(pidgin_xfers_get_ui_ops());
+	purple_blist_set_ui(PIDGIN_TYPE_BUDDY_LIST);
+	purple_notify_set_ui_ops(pidgin_notify_get_ui_ops());
+	purple_request_set_ui_ops(pidgin_request_get_ui_ops());
+	purple_connections_set_ui_ops(pidgin_connections_get_ui_ops());
+	purple_whiteboard_set_ui_ops(pidgin_whiteboard_get_ui_ops());
+	purple_idle_set_ui(pidgin_idle_new());
+
+	pidgin_accounts_init();
+	pidgin_connection_init();
+	pidgin_request_init();
+	pidgin_blist_init();
+	pidgin_conversations_init();
+	pidgin_commands_init();
+	pidgin_privacy_init();
+	pidgin_xfers_init();
+	pidgin_roomlist_init();
+	pidgin_medias_init();
+	pidgin_notify_init();
+}
+
+static void
+pidgin_ui_quit(G_GNUC_UNUSED PurpleUi *ui) {
+	/* Be sure to close all windows that are not attached to anything
+	 * (e.g., the debug window), or they may access things after they are
+	 * shut down. */
+	pidgin_notify_uninit();
+	pidgin_commands_uninit();
+	pidgin_conversations_uninit();
+	pidgin_blist_uninit();
+	pidgin_request_uninit();
+	pidgin_connection_uninit();
+	pidgin_accounts_uninit();
+	pidgin_xfers_uninit();
+	pidgin_debug_window_hide();
+	pidgin_debug_uninit();
+
+	/* and end it all... */
+	g_application_quit(g_application_get_default());
+}
+
+static gpointer
+pidgin_ui_get_settings_backend(G_GNUC_UNUSED PurpleUi *ui) {
+	GSettingsBackend *backend = NULL;
+	char *config = NULL;
+
+	config = g_build_filename(purple_config_dir(), "pidgin3.ini", NULL);
+	backend = g_keyfile_settings_backend_new(config, "/", NULL);
+
+	g_free(config);
+
+	return backend;
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_ui_init(G_GNUC_UNUSED PidginUi *ui) {
+}
+
+static void
+pidgin_ui_class_init(PidginUiClass *klass) {
+	PurpleUiClass *ui_class = PURPLE_UI_CLASS(klass);
+
+	ui_class->prefs_init = pidgin_ui_prefs_init;
+	ui_class->start = pidgin_ui_start;
+	ui_class->quit = pidgin_ui_quit;
+	ui_class->get_settings_backend = pidgin_ui_get_settings_backend;
+}
+
+/******************************************************************************
+ * Public UI
+ *****************************************************************************/
+PurpleUi *
+pidgin_ui_new(void) {
+	return g_object_new(
+		PIDGIN_TYPE_UI,
+		"id", "pidgin3",
+		"name", PIDGIN_NAME,
+		"version", VERSION,
+		"website", "https://pidgin.im",
+		"support-website", "https://pidgin.im/contact/",
+		"client-type", "pc",
+		NULL);
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -243,7 +243,6 @@
 libpurple/purpleconversation.c
 libpurple/purpleconversationmanager.c
 libpurple/purpleconversationuiops.c
-libpurple/purplecoreuiops.c
 libpurple/purplecredentialmanager.c
 libpurple/purplecredentialprovider.c
 libpurple/purpledebugui.c
@@ -274,7 +273,7 @@
 libpurple/purpleprotocolserver.c
 libpurple/purpleprotocolwhiteboard.c
 libpurple/purplesqlitehistoryadapter.c
-libpurple/purpleuiinfo.c
+libpurple/purpleui.c
 libpurple/purplewhiteboard.c
 libpurple/purplewhiteboardmanager.c
 libpurple/purplewhiteboarduiops.c
