Merged in rw_grim/pidgin (pull request #297)

Fri, 29 Dec 2017 03:57:44 +0000

author
Gary Kramlich <grim@reaperworld.com>
date
Fri, 29 Dec 2017 03:57:44 +0000
changeset 38849
20c80c3c31c5
parent 38817
6ac0c00dd6ad (current diff)
parent 38848
22e32372b906 (diff)
child 38851
2c1acd998186
child 38852
e80473941281
child 38854
086e15ec656d
child 38863
ec525e2a86e3

Merged in rw_grim/pidgin (pull request #297)

Moved PurpleProtocolXfer to xfer.[ch], started a testing ui

Approved-by: Eion Robb <eionrobb@gmail.com>

finch/gntblist.c file | annotate | diff | comparison | revisions
--- a/finch/gntblist.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/finch/gntblist.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1214,12 +1214,16 @@
 	add_custom_action(menu, _("Add Buddy Pounce"),
 			PURPLE_CALLBACK(finch_blist_pounce_node_cb), buddy);
 
-	if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, send))
+	if (PURPLE_IS_PROTOCOL_XFER(protocol))
 	{
-		if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive) ||
-			purple_protocol_xfer_iface_can_receive(protocol, gc, purple_buddy_get_name(buddy)))
+		if (purple_protocol_xfer_can_receive(
+			PURPLE_PROTOCOL_XFER(protocol),
+			gc,
+			purple_buddy_get_name(buddy))
+		) {
 			add_custom_action(menu, _("Send File"),
 					PURPLE_CALLBACK(finch_blist_menu_send_file_cb), buddy);
+		}
 	}
 
 	account = purple_buddy_get_account(buddy);
--- a/finch/gntconv.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/finch/gntconv.c	Fri Dec 29 03:57:44 2017 +0000
@@ -719,10 +719,13 @@
 		gnt_menu_add_item(GNT_MENU(sub), item);
 		gnt_menuitem_set_callback(item, add_pounce_cb, ggc);
 
-		if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, send) &&
-				(!PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive) ||
-					purple_protocol_xfer_iface_can_receive(protocol, gc,
-					purple_conversation_get_name(ggc->active_conv)))) {
+		if (PURPLE_IS_PROTOCOL_XFER(protocol) &&
+			purple_protocol_xfer_can_receive(
+				PURPLE_PROTOCOL_XFER(protocol),
+				gc,
+				purple_conversation_get_name(ggc->active_conv)
+			)
+		) {
 			item = gnt_menuitem_new(_("Send File"));
 			gnt_menu_add_item(GNT_MENU(sub), item);
 			gnt_menuitem_set_callback(item, send_file_cb, ggc);
--- a/libpurple/plugins.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/plugins.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1137,7 +1137,11 @@
 
 	gplugin_manager_add_default_paths();
 
-	purple_plugins_add_search_path(PURPLE_LIBDIR);
+	if(!g_getenv("PURPLE_PLUGINS_SKIP")) {
+		purple_plugins_add_search_path(PURPLE_LIBDIR);
+	} else {
+		purple_debug_info("plugins", "PURPLE_PLUGINS_SKIP environment variable set, skipping normal plugin paths");
+	}
 
 	g_signal_connect(gplugin_manager_get_instance(), "loading-plugin",
 	                 G_CALLBACK(plugin_loading_cb), NULL);
--- a/libpurple/protocol.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocol.c	Fri Dec 29 03:57:44 2017 +0000
@@ -800,63 +800,6 @@
 #undef DEFINE_PROTOCOL_FUNC
 
 /**************************************************************************
- * Protocol Xfer Interface API
- **************************************************************************/
-#define DEFINE_PROTOCOL_FUNC(protocol,funcname,...) \
-	PurpleProtocolXferIface *xfer_iface = \
-		PURPLE_PROTOCOL_GET_XFER_IFACE(protocol); \
-	if (xfer_iface && xfer_iface->funcname) \
-		xfer_iface->funcname(__VA_ARGS__);
-
-#define DEFINE_PROTOCOL_FUNC_WITH_RETURN(protocol,defaultreturn,funcname,...) \
-	PurpleProtocolXferIface *xfer_iface = \
-		PURPLE_PROTOCOL_GET_XFER_IFACE(protocol); \
-	if (xfer_iface && xfer_iface->funcname) \
-		return xfer_iface->funcname(__VA_ARGS__); \
-	else \
-		return defaultreturn;
-
-GType
-purple_protocol_xfer_iface_get_type(void)
-{
-	static GType type = 0;
-
-	if (G_UNLIKELY(type == 0)) {
-		static const GTypeInfo info = {
-			.class_size = sizeof(PurpleProtocolXferIface),
-		};
-
-		type = g_type_register_static(G_TYPE_INTERFACE,
-				"PurpleProtocolXferIface", &info, 0);
-	}
-	return type;
-}
-
-gboolean
-purple_protocol_xfer_iface_can_receive(PurpleProtocol *protocol,
-		PurpleConnection *gc, const char *who)
-{
-	DEFINE_PROTOCOL_FUNC_WITH_RETURN(protocol, FALSE, can_receive, gc, who);
-}
-
-void
-purple_protocol_xfer_iface_send(PurpleProtocol *protocol, PurpleConnection *gc,
-		const char *who, const char *filename)
-{
-	DEFINE_PROTOCOL_FUNC(protocol, send, gc, who, filename);
-}
-
-PurpleXfer *
-purple_protocol_xfer_iface_new_xfer(PurpleProtocol *protocol,
-		PurpleConnection *gc, const char *who)
-{
-	DEFINE_PROTOCOL_FUNC_WITH_RETURN(protocol, NULL, new_xfer, gc, who);
-}
-
-#undef DEFINE_PROTOCOL_FUNC_WITH_RETURN
-#undef DEFINE_PROTOCOL_FUNC
-
-/**************************************************************************
  * Protocol Roomlist Interface API
  **************************************************************************/
 #define DEFINE_PROTOCOL_FUNC(protocol,funcname,...) \
--- a/libpurple/protocol.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocol.h	Fri Dec 29 03:57:44 2017 +0000
@@ -556,35 +556,6 @@
 #define PURPLE_PROTOCOL_GET_PRIVACY_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE, \
                                                 PurpleProtocolPrivacyIface))
 
-#define PURPLE_TYPE_PROTOCOL_XFER_IFACE     (purple_protocol_xfer_iface_get_type())
-
-typedef struct _PurpleProtocolXferIface PurpleProtocolXferIface;
-
-/**
- * PurpleProtocolXferIface:
- *
- * The protocol file transfer interface.
- *
- * This interface provides file transfer callbacks for the protocol.
- */
-struct _PurpleProtocolXferIface
-{
-	/*< private >*/
-	GTypeInterface parent_iface;
-
-	/*< public >*/
-	gboolean (*can_receive)(PurpleConnection *connection, const char *who);
-
-	void (*send)(PurpleConnection *connection, const char *who,
-					  const char *filename);
-
-	PurpleXfer *(*new_xfer)(PurpleConnection *connection, const char *who);
-};
-
-#define PURPLE_PROTOCOL_HAS_XFER_IFACE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_PROTOCOL_XFER_IFACE))
-#define PURPLE_PROTOCOL_GET_XFER_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), PURPLE_TYPE_PROTOCOL_XFER_IFACE, \
-                                             PurpleProtocolXferIface))
-
 #define PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE     (purple_protocol_roomlist_iface_get_type())
 
 typedef struct _PurpleProtocolRoomlistIface PurpleProtocolRoomlistIface;
@@ -1084,26 +1055,6 @@
 		PurpleConnection *connection);
 
 /**************************************************************************/
-/* Protocol Xfer Interface API                                            */
-/**************************************************************************/
-
-/**
- * purple_protocol_xfer_iface_get_type:
- *
- * Returns: The #GType for the protocol xfer interface.
- */
-GType purple_protocol_xfer_iface_get_type(void);
-
-gboolean purple_protocol_xfer_iface_can_receive(PurpleProtocol *protocol,
-		PurpleConnection *connection, const char *who);
-
-void purple_protocol_xfer_iface_send(PurpleProtocol *protocol, PurpleConnection *connection,
-		const char *who, const char *filename);
-
-PurpleXfer *purple_protocol_xfer_iface_new_xfer(PurpleProtocol *protocol,
-		PurpleConnection *connection, const char *who);
-
-/**************************************************************************/
 /* Protocol Roomlist Interface API                                        */
 /**************************************************************************/
 
--- a/libpurple/protocols/bonjour/bonjour.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Fri Dec 29 03:57:44 2017 +0000
@@ -466,7 +466,7 @@
 }
 
 static gboolean
-bonjour_can_receive_file(PurpleConnection *connection, const char *who)
+bonjour_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *connection, const char *who)
 {
 	PurpleBuddy *buddy = purple_blist_find_buddy(purple_connection_get_account(connection), who);
 
@@ -699,7 +699,7 @@
 }
 
 static void
-bonjour_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+bonjour_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
 	xfer_iface->can_receive = bonjour_can_receive_file;
 	xfer_iface->send        = bonjour_send_file;
@@ -718,7 +718,7 @@
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_IM_IFACE,
 	                                  bonjour_protocol_im_iface_init)
 
-	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
 	                                  bonjour_protocol_xfer_iface_init)
 );
 
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Fri Dec 29 03:57:44 2017 +0000
@@ -339,7 +339,7 @@
 }
 
 PurpleXfer *
-bonjour_new_xfer(PurpleConnection *gc, const char *who)
+bonjour_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
 {
 	PurpleXfer *xfer;
 	XepXfer *xep_xfer;
@@ -376,7 +376,7 @@
 }
 
 void
-bonjour_send_file(PurpleConnection *gc, const char *who, const char *file)
+bonjour_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file)
 {
 	PurpleXfer *xfer;
 
@@ -385,7 +385,7 @@
 
 	purple_debug_info("bonjour", "Bonjour-send-file to=%s.\n", who);
 
-	xfer = bonjour_new_xfer(gc, who);
+	xfer = bonjour_new_xfer(prplxfer, gc, who);
 
 	if (file)
 		purple_xfer_request_accepted(xfer, file);
--- a/libpurple/protocols/bonjour/bonjour_ft.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.h	Fri Dec 29 03:57:44 2017 +0000
@@ -60,7 +60,7 @@
  * @param gc The PurpleConnection handle.
  * @param who Who will we be sending it to?
  */
-PurpleXfer *bonjour_new_xfer(PurpleConnection *gc, const char *who);
+PurpleXfer *bonjour_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
 
 /**
  * Send a file.
@@ -69,7 +69,7 @@
  * @param who Who are we sending it to?
  * @param file What file? If NULL, user will choose after this call.
  */
-void bonjour_send_file(PurpleConnection *gc, const char *who, const char *file);
+void bonjour_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file);
 
 void xep_si_parse(PurpleConnection *pc, PurpleXmlNode *packet, PurpleBuddy *pb);
 void
--- a/libpurple/protocols/gg/edisc.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/gg/edisc.c	Fri Dec 29 03:57:44 2017 +0000
@@ -383,7 +383,7 @@
 static void ggp_edisc_xfer_send_done(PurpleHttpConnection *hc,
 	PurpleHttpResponse *response, gpointer _xfer);
 
-gboolean ggp_edisc_xfer_can_receive_file(PurpleConnection *gc,
+gboolean ggp_edisc_xfer_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc,
 	const char *who)
 {
 	PurpleBuddy *buddy;
@@ -664,7 +664,7 @@
 		ggp_edisc_xfer_error(xfer, _("Error while sending a file"));
 }
 
-PurpleXfer * ggp_edisc_xfer_send_new(PurpleConnection *gc, const char *who)
+PurpleXfer * ggp_edisc_xfer_send_new(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
 {
 	PurpleXfer *xfer;
 	ggp_edisc_xfer *edisc_xfer;
@@ -686,7 +686,7 @@
 	return xfer;
 }
 
-void ggp_edisc_xfer_send_file(PurpleConnection *gc, const char *who,
+void ggp_edisc_xfer_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who,
 	const char *filename)
 {
 	PurpleXfer *xfer;
@@ -697,7 +697,7 @@
 	/* Nothing interesting here, this code is common among protocols.
 	 * See ggp_edisc_xfer_send_new. */
 
-	xfer = ggp_edisc_xfer_send_new(gc, who);
+	xfer = ggp_edisc_xfer_send_new(prplxfer, gc, who);
 	if (filename)
 		purple_xfer_request_accepted(xfer, filename);
 	else
--- a/libpurple/protocols/gg/edisc.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/gg/edisc.h	Fri Dec 29 03:57:44 2017 +0000
@@ -41,9 +41,9 @@
 	const char *data);
 
 /* Sending a file. */
-gboolean ggp_edisc_xfer_can_receive_file(PurpleConnection *gc, const char *who);
-void ggp_edisc_xfer_send_file(PurpleConnection *gc, const char *who,
+gboolean ggp_edisc_xfer_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
+void ggp_edisc_xfer_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who,
 	const char *filename);
-PurpleXfer * ggp_edisc_xfer_send_new(PurpleConnection *gc, const char *who);
+PurpleXfer * ggp_edisc_xfer_send_new(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
 
 #endif /* _GGP_EDISC_H */
--- a/libpurple/protocols/gg/gg.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/gg/gg.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1071,7 +1071,7 @@
 }
 
 static void
-ggp_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+ggp_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
 	xfer_iface->can_receive = ggp_edisc_xfer_can_receive_file;
 	xfer_iface->send        = ggp_edisc_xfer_send_file;
@@ -1099,7 +1099,7 @@
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
 	                                  ggp_protocol_privacy_iface_init)
 
-	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
 	                                  ggp_protocol_xfer_iface_init)
 );
 
--- a/libpurple/protocols/irc/dcc_send.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/irc/dcc_send.c	Fri Dec 29 03:57:44 2017 +0000
@@ -359,7 +359,7 @@
 
 }
 
-PurpleXfer *irc_dccsend_new_xfer(PurpleConnection *gc, const char *who) {
+PurpleXfer *irc_dccsend_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who) {
 	PurpleXfer *xfer;
 	struct irc_xfer_send_data *xd;
 
@@ -387,8 +387,8 @@
  * buddy menu
  * It sets up the PurpleXfer struct and tells Purple to go ahead
  */
-void irc_dccsend_send_file(PurpleConnection *gc, const char *who, const char *file) {
-	PurpleXfer *xfer = irc_dccsend_new_xfer(gc, who);
+void irc_dccsend_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file) {
+	PurpleXfer *xfer = irc_dccsend_new_xfer(prplxfer, gc, who);
 
 	/* Perform the request */
 	if (file)
--- a/libpurple/protocols/irc/irc.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/irc/irc.c	Fri Dec 29 03:57:44 2017 +0000
@@ -916,7 +916,7 @@
 }
 
 static void
-irc_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+irc_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
 	xfer_iface->send     = irc_dccsend_send_file;
 	xfer_iface->new_xfer = irc_dccsend_new_xfer;
@@ -940,7 +940,7 @@
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_ROOMLIST_IFACE,
 	                                  irc_protocol_roomlist_iface_init)
 
-	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
 	                                  irc_protocol_xfer_iface_init)
 );
 
--- a/libpurple/protocols/irc/irc.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/irc/irc.h	Fri Dec 29 03:57:44 2017 +0000
@@ -237,7 +237,7 @@
 int irc_cmd_whois(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 int irc_cmd_whowas(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
 
-PurpleXfer *irc_dccsend_new_xfer(PurpleConnection *gc, const char *who);
-void irc_dccsend_send_file(PurpleConnection *gc, const char *who, const char *file);
+PurpleXfer *irc_dccsend_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
+void irc_dccsend_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file);
 void irc_dccsend_recv(struct irc_conn *irc, const char *from, const char *msg);
 #endif /* _PURPLE_IRC_H */
--- a/libpurple/protocols/jabber/jabber.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Fri Dec 29 03:57:44 2017 +0000
@@ -3540,7 +3540,7 @@
 #endif
 }
 
-gboolean jabber_can_receive_file(PurpleConnection *gc, const char *who)
+gboolean jabber_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
 {
 	JabberStream *js = purple_connection_get_protocol_data(gc);
 
@@ -4263,7 +4263,7 @@
 }
 
 static void
-jabber_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+jabber_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
 	xfer_iface->can_receive = jabber_can_receive_file;
 	xfer_iface->send        = jabber_si_xfer_send;
@@ -4297,7 +4297,7 @@
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_MEDIA_IFACE,
 	                                  jabber_protocol_media_iface_init)
 
-	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
 	                                  jabber_protocol_xfer_iface_init)
 );
 
--- a/libpurple/protocols/jabber/jabber.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Fri Dec 29 03:57:44 2017 +0000
@@ -431,6 +431,6 @@
 gboolean jabber_initiate_media(PurpleAccount *account, const char *who,
 		PurpleMediaSessionType type);
 PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
-gboolean jabber_can_receive_file(PurpleConnection *gc, const gchar *who);
+gboolean jabber_can_receive_file(PurpleProtocolXfer *xfer, PurpleConnection *gc, const gchar *who);
 
 #endif /* PURPLE_JABBER_H_ */
--- a/libpurple/protocols/jabber/si.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/jabber/si.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1618,7 +1618,7 @@
 	}
 }
 
-PurpleXfer *jabber_si_new_xfer(PurpleConnection *gc, const char *who)
+PurpleXfer *jabber_si_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
 {
 	JabberStream *js;
 
@@ -1647,11 +1647,11 @@
 	return xfer;
 }
 
-void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file)
+void jabber_si_xfer_send(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file)
 {
 	PurpleXfer *xfer;
 
-	xfer = jabber_si_new_xfer(gc, who);
+	xfer = jabber_si_new_xfer(prplxfer, gc, who);
 
 	if (file)
 		purple_xfer_request_accepted(xfer, file);
--- a/libpurple/protocols/jabber/si.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/jabber/si.h	Fri Dec 29 03:57:44 2017 +0000
@@ -32,8 +32,8 @@
                               JabberIqType type, const char *id, PurpleXmlNode *query);
 void jabber_si_parse(JabberStream *js, const char *from, JabberIqType type,
                      const char *id, PurpleXmlNode *si);
-PurpleXfer *jabber_si_new_xfer(PurpleConnection *gc, const char *who);
-void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file);
+PurpleXfer *jabber_si_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
+void jabber_si_xfer_send(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file);
 void jabber_si_init(void);
 void jabber_si_uninit(void);
 
--- a/libpurple/protocols/oscar/oscar.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Fri Dec 29 03:57:44 2017 +0000
@@ -5257,7 +5257,7 @@
  * allowed to send a file to this user.
  */
 gboolean
-oscar_can_receive_file(PurpleConnection *gc, const char *who)
+oscar_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
 {
 	OscarData *od;
 	PurpleAccount *account;
@@ -5286,7 +5286,7 @@
 }
 
 PurpleXfer *
-oscar_new_xfer(PurpleConnection *gc, const char *who)
+oscar_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who)
 {
 	PurpleXfer *xfer;
 	OscarData *od;
@@ -5320,11 +5320,11 @@
  * file is to be sent to a special someone.
  */
 void
-oscar_send_file(PurpleConnection *gc, const char *who, const char *file)
+oscar_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file)
 {
 	PurpleXfer *xfer;
 
-	xfer = oscar_new_xfer(gc, who);
+	xfer = oscar_new_xfer(prplxfer, gc, who);
 
 	if (file != NULL)
 		purple_xfer_request_accepted(xfer, file);
@@ -5760,7 +5760,7 @@
 }
 
 static void
-oscar_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+oscar_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
 	xfer_iface->can_receive = oscar_can_receive_file;
 	xfer_iface->send        = oscar_send_file;
@@ -5785,7 +5785,7 @@
 	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
 	                                  oscar_protocol_privacy_iface_init)
 
-	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+	PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
 	                                  oscar_protocol_xfer_iface_init)
 );
 
--- a/libpurple/protocols/oscar/oscarcommon.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/oscar/oscarcommon.h	Fri Dec 29 03:57:44 2017 +0000
@@ -116,9 +116,9 @@
 const char *oscar_normalize(const PurpleAccount *account, const char *str);
 void oscar_set_icon(PurpleConnection *gc, PurpleImage *img);
 void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group);
-gboolean oscar_can_receive_file(PurpleConnection *gc, const char *who);
-void oscar_send_file(PurpleConnection *gc, const char *who, const char *file);
-PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who);
+gboolean oscar_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
+void oscar_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who, const char *file);
+PurpleXfer *oscar_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who);
 gboolean oscar_offline_message(const PurpleBuddy *buddy);
 gssize oscar_get_max_message_size(PurpleConversation *conv);
 GList *oscar_get_actions(PurpleConnection *gc);
--- a/libpurple/protocols/sametime/sametime.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Fri Dec 29 03:57:44 2017 +0000
@@ -4941,7 +4941,8 @@
 }
 
 
-static gboolean mw_protocol_can_receive_file(PurpleConnection *gc,
+static gboolean mw_protocol_can_receive_file(PurpleProtocolXfer *prplxfer,
+					 PurpleConnection *gc,
 					 const char *who) {
   struct mwPurpleProtocolData *pd;
   struct mwServiceAware *srvc;
@@ -5030,7 +5031,7 @@
 }
 
 
-static PurpleXfer *mw_protocol_new_xfer(PurpleConnection *gc, const char *who) {
+static PurpleXfer *mw_protocol_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *who) {
   PurpleAccount *acct;
   PurpleXfer *xfer;
 
@@ -5046,10 +5047,11 @@
   return xfer;
 }
 
-static void mw_protocol_send_file(PurpleConnection *gc,
+static void mw_protocol_send_file(PurpleProtocolXfer *prplxfer,
+			      PurpleConnection *gc,
 			      const char *who, const char *file) {
 
-  PurpleXfer *xfer = mw_protocol_new_xfer(gc, who);
+  PurpleXfer *xfer = mw_protocol_new_xfer(prplxfer, gc, who);
 
   if(file) {
     DEBUG_INFO("file != NULL\n");
@@ -5701,7 +5703,7 @@
 
 
 static void
-mw_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+mw_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
   xfer_iface->can_receive = mw_protocol_can_receive_file;
   xfer_iface->send        = mw_protocol_send_file;
@@ -5727,7 +5729,7 @@
   PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_PRIVACY_IFACE,
                                     mw_protocol_privacy_iface_init)
 
-  PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER_IFACE,
+  PURPLE_IMPLEMENT_INTERFACE_STATIC(PURPLE_TYPE_PROTOCOL_XFER,
                                     mw_protocol_xfer_iface_init)
 );
 
--- a/libpurple/protocols/silc/ft.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/silc/ft.c	Fri Dec 29 03:57:44 2017 +0000
@@ -441,7 +441,7 @@
 	g_free(context);
 }
 
-PurpleXfer *silcpurple_ftp_new_xfer(PurpleConnection *gc, const char *name)
+PurpleXfer *silcpurple_ftp_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *name)
 {
 	SilcPurple sg = purple_connection_get_protocol_data(gc);
 	SilcClient client = sg->client;
@@ -482,7 +482,7 @@
 	return xfer->xfer;
 }
 
-void silcpurple_ftp_send_file(PurpleConnection *gc, const char *name, const char *file)
+void silcpurple_ftp_send_file(PurpleProtocolXfer *prplxfer, PurpleConnection *gc, const char *name, const char *file)
 {
 	PurpleXfer *xfer = silcpurple_ftp_new_xfer(gc, name);
 
--- a/libpurple/protocols/silc/silc.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/protocols/silc/silc.c	Fri Dec 29 03:57:44 2017 +0000
@@ -2283,7 +2283,7 @@
 }
 
 static void
-silcpurple_protocol_xfer_iface_init(PurpleProtocolXferIface *xfer_iface)
+silcpurple_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface)
 {
 	xfer_iface->send     = silcpurple_ftp_send_file;
 	xfer_iface->new_xfer = silcpurple_ftp_new_xfer;
--- a/libpurple/server.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/server.c	Fri Dec 29 03:57:44 2017 +0000
@@ -894,9 +894,12 @@
 	if (gc) {
 		protocol = purple_connection_get_protocol(gc);
 
-		if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive) ||
-				purple_protocol_xfer_iface_can_receive(protocol, gc, who))
+		if(PURPLE_IS_PROTOCOL_XFER(protocol)) {
+			PurpleProtocolXfer *xfer = PURPLE_PROTOCOL_XFER(protocol);
 
-			purple_protocol_xfer_iface_send(protocol, gc, who, file);
+			if(purple_protocol_xfer_can_receive(xfer, gc, who)) {
+				purple_protocol_xfer_send(xfer, gc, who, file);
+			}
+		}
 	}
 }
--- a/libpurple/tests/meson.build	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/tests/meson.build	Fri Dec 29 03:57:44 2017 +0000
@@ -1,16 +1,30 @@
 PROGS = [
     'image',
+    'protocol_xfer',
     'smiley',
     'smiley_list',
     'trie',
     'util',
     'xmlnode'
 ]
+
+test_ui = static_library(
+    'test-ui',
+    'test_ui.c',
+    'test_ui.h',
+    c_args: [
+        '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir())
+    ],
+    dependencies: [libpurple_dep, glib, dbus, dbus_glib]
+)
+
 foreach prog : PROGS
-	e = executable('test_' + prog, 'test_@0@.c'.format(prog),
-	               c_args : [
-	                   '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir())
-	               ],
-	               dependencies : [libpurple_dep, glib])
-	test(prog, e)
+    e = executable('test_' + prog, 'test_@0@.c'.format(prog),
+                   c_args : [
+                       '-DTEST_DATA_DIR="@0@/data"'.format(meson.current_source_dir())
+                   ],
+                   dependencies : [libpurple_dep, glib, dbus, dbus_glib],
+                   link_with: test_ui,
+    )
+    test(prog, e)
 endforeach
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_protocol_xfer.c	Fri Dec 29 03:57:44 2017 +0000
@@ -0,0 +1,185 @@
+/*
+ * 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 <glib.h>
+#include <string.h>
+
+#include <purple.h>
+
+#include "dbus-server.h"
+
+#include "test_ui.h"
+
+/******************************************************************************
+ * PurpleProtcolXfer Implementations
+ *****************************************************************************/
+static GType test_purple_protocol_xfer_get_type(void);
+
+typedef struct {
+	PurpleProtocol parent;
+
+	gboolean can_send;
+	gboolean new_xfer_called;
+	gboolean send_called;
+} TestPurpleProtocolXfer;
+
+typedef struct {
+	PurpleProtocolClass parent;
+} TestPurpleProtocolXferClass;
+
+
+static gboolean
+test_purple_protocol_xfer_can_receive(PurpleProtocolXfer *prplxfer, PurpleConnection *c, const gchar *who) {
+	TestPurpleProtocolXfer *test_xfer = (TestPurpleProtocolXfer *)prplxfer;
+
+	return test_xfer->can_send;
+}
+
+static void
+test_purple_protocol_xfer_send(PurpleProtocolXfer *prplxfer, PurpleConnection *c, const gchar *who, const gchar *filename) {
+	TestPurpleProtocolXfer *test_xfer = (TestPurpleProtocolXfer *)prplxfer;
+
+	test_xfer->send_called = TRUE;
+}
+
+static PurpleXfer *
+test_purple_protocol_xfer_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *c, const gchar *who) {
+	TestPurpleProtocolXfer *test_xfer = (TestPurpleProtocolXfer *)prplxfer;
+	PurpleAccount *a = purple_connection_get_account(c);
+
+	test_xfer->new_xfer_called = TRUE;
+
+	return purple_xfer_new(a, PURPLE_XFER_TYPE_SEND, who);
+}
+
+static void
+test_purple_protocol_xfer_iface_init(PurpleProtocolXferInterface *iface) {
+	iface->can_receive = test_purple_protocol_xfer_can_receive;
+	iface->send = test_purple_protocol_xfer_send;
+	iface->new_xfer = test_purple_protocol_xfer_new_xfer;
+}
+
+G_DEFINE_TYPE_WITH_CODE(
+	TestPurpleProtocolXfer,
+	test_purple_protocol_xfer,
+	PURPLE_TYPE_PROTOCOL,
+	G_IMPLEMENT_INTERFACE(
+		PURPLE_TYPE_PROTOCOL_XFER,
+		test_purple_protocol_xfer_iface_init
+	)
+);
+
+static void
+test_purple_protocol_xfer_init(TestPurpleProtocolXfer *prplxfer) {
+	PurpleProtocol *prpl = PURPLE_PROTOCOL(prplxfer);
+
+	prpl->id = "prpl-xfer";
+}
+
+static void
+test_purple_protocol_xfer_class_init(TestPurpleProtocolXferClass *klass) {
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_protocol_xfer_can_receive_func(void) {
+	TestPurpleProtocolXfer *xfer = g_object_new(test_purple_protocol_xfer_get_type(), NULL);
+	PurpleAccount *a = purple_account_new("prpl-xfer-can-receive", "prpl-xfer");
+	PurpleConnection *c = g_object_new(PURPLE_TYPE_CONNECTION, "account", a, NULL);
+	gboolean actual = FALSE;
+
+	g_assert_true(PURPLE_IS_PROTOCOL_XFER(xfer));
+
+	xfer->can_send = FALSE;
+	actual = purple_protocol_xfer_can_receive(
+		PURPLE_PROTOCOL_XFER(xfer),
+		c, 
+		"foo"
+	);
+	g_assert_false(actual);
+
+	xfer->can_send = TRUE;
+	actual = purple_protocol_xfer_can_receive(
+		PURPLE_PROTOCOL_XFER(xfer),
+		c,
+		"foo"
+	);
+	g_assert_true(actual);
+}
+
+static void
+test_purple_protocol_xfer_send_func(void) {
+	TestPurpleProtocolXfer *prplxfer = g_object_new(test_purple_protocol_xfer_get_type(), NULL);
+	PurpleAccount *a = purple_account_new("prpl-xfer-send", "prpl-xfer");
+	PurpleConnection *c = g_object_new(PURPLE_TYPE_CONNECTION, "account", a, NULL);
+
+	purple_protocol_xfer_send(PURPLE_PROTOCOL_XFER(prplxfer), c, "foo", "somefile");
+	g_assert_true(prplxfer->send_called);
+}
+
+static void
+test_purple_protocol_xfer_new_func(void) {
+	TestPurpleProtocolXfer *prplxfer = g_object_new(test_purple_protocol_xfer_get_type(), NULL);
+	PurpleAccount *a = purple_account_new("prpl-xfer-new-xfer", "prpl-xfer");
+	PurpleConnection *c = g_object_new(PURPLE_TYPE_CONNECTION, "account", a, NULL);
+	PurpleXfer *xfer = NULL;
+
+	xfer = purple_protocol_xfer_new_xfer(PURPLE_PROTOCOL_XFER(prplxfer), c, "foo");
+	g_assert_true(PURPLE_IS_XFER(xfer));
+	g_assert_cmpstr("foo", ==, purple_xfer_get_remote_user(xfer));
+	g_assert_true(prplxfer->new_xfer_called);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar **argv) {
+	gint res = 0;
+
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_set_nonfatal_assertions();
+
+	test_ui_purple_init();
+
+	g_test_add_func(
+		"/protocol-xfer/can-receive",
+		test_purple_protocol_xfer_can_receive_func
+	);
+
+	g_test_add_func(
+		"/protocol-xfer/send",
+		test_purple_protocol_xfer_send_func
+	);
+
+	g_test_add_func(
+		"/protocol-xfer/new",
+		test_purple_protocol_xfer_new_func
+	);
+
+	res = g_test_run();
+
+	return res;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_ui.c	Fri Dec 29 03:57:44 2017 +0000
@@ -0,0 +1,117 @@
+/*
+ * pidgin
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+
+#include "purple.h"
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include <signal.h>
+#include <string.h>
+#ifdef _WIN32
+#  include <conio.h>
+#else
+#  include <unistd.h>
+#endif
+
+#include "test_ui.h"
+
+/*** Conversation uiops ***/
+static void
+test_write_conv(PurpleConversation *conv, PurpleMessage *msg)
+{
+	time_t mtime = purple_message_get_time(msg);
+
+	printf("(%s) %s %s: %s\n",
+		purple_conversation_get_name(conv),
+		purple_utf8_strftime("(%H:%M:%S)", localtime(&mtime)),
+		purple_message_get_author_alias(msg),
+		purple_message_get_contents(msg));
+}
+
+static PurpleConversationUiOps test_conv_uiops = {
+	.write_conv = test_write_conv
+};
+
+static void
+test_ui_init(void)
+{
+	purple_conversations_set_ui_ops(&test_conv_uiops);
+}
+
+static PurpleCoreUiOps test_core_uiops = {
+	.ui_init = test_ui_init
+};
+
+void
+test_ui_purple_init(void) {
+#ifndef _WIN32
+	/* libpurple's built-in DNS resolution forks processes to perform
+	 * blocking lookups without blocking the main process.  It does not
+	 * handle SIGCHLD itself, so if the UI does not you quickly get an army
+	 * of zombie subprocesses marching around.
+	 */
+	signal(SIGCHLD, SIG_IGN);
+#endif
+
+	/* set the magic PURPLE_PLUGINS_SKIP environment variable */
+	g_setenv("PURPLE_PLUGINS_SKIP", "1", TRUE);
+
+	/* Set a custom user directory (optional) */
+	purple_util_set_user_dir(TEST_DATA_DIR);
+
+	/* We do not want any debugging for now to keep the noise to a minimum. */
+	purple_debug_set_enabled(FALSE);
+
+	/* 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);
+
+	/* 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("test-ui")) {
+		/* Initializing the core failed. Terminate. */
+		fprintf(stderr,
+				"libpurple initialization failed. Dumping core.\n"
+				"Please report this!\n");
+		abort();
+	}
+
+	/* Set path to search for plugins. The core (libpurple) takes care of loading the
+	 * core-plugins, which includes the in-tree protocols. So it is not essential to add
+	 * any path here, but it might be desired, especially for ui-specific plugins. */
+	purple_plugins_add_search_path(TEST_DATA_DIR);
+	purple_plugins_refresh();
+
+	/* Load the preferences. */
+	purple_prefs_load();
+
+	/* Load the desired plugins. The client should save the list of loaded plugins in
+	 * the preferences using purple_plugins_save_loaded(PLUGIN_SAVE_PREF) */
+	purple_plugins_load_saved(TEST_DATA_DIR);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_ui.h	Fri Dec 29 03:57:44 2017 +0000
@@ -0,0 +1,33 @@
+/* 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
+ */
+
+#ifndef PURPLE_TEST_UI_H
+#define PURPLE_TEST_UI_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void test_ui_purple_init(void);
+
+G_END_DECLS
+
+#endif /* PURPLE_TEST_UI_H */
--- a/libpurple/xfer.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/xfer.c	Fri Dec 29 03:57:44 2017 +0000
@@ -2360,21 +2360,23 @@
 	g_return_val_if_fail(who != NULL, NULL);
 
 	protocol = purple_protocols_find(purple_account_get_protocol_id(account));
-
-	g_return_val_if_fail(PURPLE_IS_PROTOCOL(protocol), NULL);
-
-	if (PURPLE_PROTOCOL_IMPLEMENTS(protocol, FACTORY_IFACE, xfer_new))
-		xfer = purple_protocol_factory_iface_xfer_new(protocol, account, type,
-				who);
-	else
+	if (PURPLE_IS_PROTOCOL_XFER(protocol)) {
+		PurpleConnection *connection = purple_account_get_connection(account);
+
+		xfer = purple_protocol_xfer_new_xfer(
+			PURPLE_PROTOCOL_XFER(protocol),
+			connection,
+			/* TODO: this should support the type */
+			who
+		);
+	} else {
 		xfer = g_object_new(PURPLE_TYPE_XFER,
 			"account",     account,
 			"type",        type,
 			"remote-user", who,
 			NULL
 		);
-
-	g_return_val_if_fail(xfer != NULL, NULL);
+	}
 
 	return xfer;
 }
@@ -2477,3 +2479,66 @@
 
 	return type;
 }
+
+/**************************************************************************
+ * PurpleXferProtocolInterface
+ **************************************************************************/
+G_DEFINE_INTERFACE(PurpleProtocolXfer, purple_protocol_xfer, G_TYPE_INVALID);
+
+static void
+purple_protocol_xfer_default_init(PurpleProtocolXferInterface *face) {
+}
+
+gboolean
+purple_protocol_xfer_can_receive(PurpleProtocolXfer *prplxfer,
+                                 PurpleConnection *connection,
+                                 const gchar *who
+) {
+	PurpleProtocolXferInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_XFER(prplxfer), FALSE);
+	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), FALSE);
+	g_return_val_if_fail(who, FALSE);
+
+	iface = PURPLE_PROTOCOL_XFER_GET_IFACE(prplxfer);
+	if(iface &&  iface->can_receive)
+		return iface->can_receive(prplxfer, connection, who);
+
+	return FALSE;
+}
+
+void
+purple_protocol_xfer_send(PurpleProtocolXfer *prplxfer,
+                          PurpleConnection *connection,
+                          const gchar *who,
+                          const gchar *filename
+) {
+	PurpleProtocolXferInterface *iface = NULL;
+
+	g_return_if_fail(PURPLE_IS_PROTOCOL_XFER(prplxfer));
+	g_return_if_fail(PURPLE_IS_CONNECTION(connection));
+	g_return_if_fail(who);
+	g_return_if_fail(filename);
+
+	iface = PURPLE_PROTOCOL_XFER_GET_IFACE(prplxfer);
+	if(iface && iface->send)
+		iface->send(prplxfer, connection, who, filename);
+}
+
+PurpleXfer *
+purple_protocol_xfer_new_xfer(PurpleProtocolXfer *prplxfer,
+                              PurpleConnection *connection,
+                              const gchar *who
+) {
+	PurpleProtocolXferInterface *iface = NULL;
+
+	g_return_val_if_fail(PURPLE_IS_PROTOCOL_XFER(prplxfer), FALSE);
+	g_return_val_if_fail(PURPLE_IS_CONNECTION(connection), FALSE);
+	g_return_val_if_fail(who, FALSE);
+
+	iface = PURPLE_PROTOCOL_XFER_GET_IFACE(prplxfer);
+	if(iface && iface->new_xfer)
+		return iface->new_xfer(prplxfer, connection, who);
+
+	return NULL;
+}
--- a/libpurple/xfer.h	Tue Dec 26 05:56:57 2017 +0000
+++ b/libpurple/xfer.h	Fri Dec 29 03:57:44 2017 +0000
@@ -38,6 +38,11 @@
 
 #define PURPLE_TYPE_XFER_UI_OPS      (purple_xfer_ui_ops_get_type())
 
+#define PURPLE_TYPE_PROTOCOL_XFER           (purple_protocol_xfer_get_type())
+#define PURPLE_PROTOCOL_XFER(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), PURPLE_TYPE_PROTOCOL_XFER, PurpleProtocolXfer))
+#define PURPLE_IS_PROTOCOL_XFER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), PURPLE_TYPE_PROTOCOL_XFER))
+#define PURPLE_PROTOCOL_XFER_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE((obj), PURPLE_TYPE_PROTOCOL_XFER, PurpleProtocolXferInterface))
+
 /**************************************************************************/
 /** Data Structures                                                       */
 /**************************************************************************/
@@ -46,10 +51,14 @@
 
 typedef struct _PurpleXferUiOps PurpleXferUiOps;
 
+typedef struct _PurpleProtocolXfer PurpleProtocolXfer;
+typedef struct _PurpleProtocolXferInterface PurpleProtocolXferInterface;
+
 #include <glib.h>
 #include <stdio.h>
 
 #include "account.h"
+#include "connection.h"
 
 /**
  * PurpleXferType:
@@ -182,6 +191,26 @@
 	void (*_purple_reserved4)(void);
 };
 
+/**
+ * PurpleProtocolXferInterface:
+ *
+ * The protocol file transfer interface.
+ *
+ * This interface provides file transfer callbacks for the protocol.
+ */
+struct _PurpleProtocolXferInterface
+{
+	/*< private >*/
+	GTypeInterface parent_iface;
+
+	/*< public >*/
+	gboolean (*can_receive)(PurpleProtocolXfer *prplxfer, PurpleConnection *c, const gchar *who);
+
+	void (*send)(PurpleProtocolXfer *prplxfer, PurpleConnection *c, const gchar *who, const gchar *filename);
+
+	PurpleXfer *(*new_xfer)(PurpleProtocolXfer *prplxfer, PurpleConnection *c, const gchar *who);
+};
+
 G_BEGIN_DECLS
 
 /**************************************************************************/
@@ -946,6 +975,52 @@
  */
 PurpleXferUiOps *purple_xfers_get_ui_ops(void);
 
+/******************************************************************************
+ * Protocol Interface
+ *****************************************************************************/
+
+/**
+ * purple_protocol_xfer_get_type:
+ *
+ * Returns: The #GType for the protocol xfer interface.
+ */
+GType purple_protocol_xfer_get_type(void);
+
+/**
+ * purple_protocol_xfer_can_receive:
+ * @prplxfer: The #PurpleProtocolXfer implementer instance
+ * @connection: The #PurpleConnection that we're checking
+ * @who: The user that we want to send a file transfer to.
+ *
+ * Checks whether or not we can transfer a file to @who.
+ *
+ * Returns: TRUE on success, FALSE otherwise.
+ */
+gboolean purple_protocol_xfer_can_receive(PurpleProtocolXfer *prplxfer, PurpleConnection *connection, const gchar *who);
+
+/**
+ * purple_protocol_xfer_send:
+ * @prplxfer: The #PurpleProtocolXfer implementer instance
+ * @connection: The #PurpleConnection that we're checking
+ * @who: The user that we want to set a file transfer to.
+ * @filename: The name of the file to send.
+ *
+ * Sends @filename to @who.
+ */
+void purple_protocol_xfer_send(PurpleProtocolXfer *prplxfer, PurpleConnection *connection, const gchar *who, const gchar *filename);
+
+/**
+ * purple_protocol_xfer_send:
+ * @prplxfer: The #PurpleProtocolXfer implementer instance
+ * @connection: The #PurpleConnection that we're checking
+ * @who: The user that we want to send a file transfer to.
+ *
+ * Creates a new #PurpleXfer to @who.
+ *
+ * Returns: A new #PurpleXfer instance.
+ */
+PurpleXfer *purple_protocol_xfer_new_xfer(PurpleProtocolXfer *prplxfer, PurpleConnection *connection, const gchar *who);
+
 G_END_DECLS
 
 #endif /* _PURPLE_XFER_H_ */
--- a/pidgin/gtkblist.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/pidgin/gtkblist.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1525,11 +1525,11 @@
 
 #endif
 
-	if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, send)) {
-		if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive) ||
-			purple_protocol_xfer_iface_can_receive(protocol,
-			purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy)))
-		{
+	if (protocol && PURPLE_IS_PROTOCOL_XFER(protocol)) {
+		if (purple_protocol_xfer_can_receive(
+			PURPLE_PROTOCOL_XFER(protocol),
+			purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy)
+		)) {
 			pidgin_new_menu_item(menu, _("_Send File..."),
                                         PIDGIN_STOCK_TOOLBAR_SEND_FILE,
                                         G_CALLBACK(gtk_blist_menu_send_file_cb),
--- a/pidgin/gtkconv.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/pidgin/gtkconv.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1680,7 +1680,7 @@
 			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 
 
-		if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, send))
+		if (protocol && PURPLE_IS_PROTOCOL_XFER(protocol))
 		{
 			gboolean can_receive_file = TRUE;
 
@@ -1694,9 +1694,11 @@
 				gchar *real_who = NULL;
 				real_who = purple_protocol_chat_iface_get_user_real_name(protocol, gc,
 					purple_chat_conversation_get_id(chat), who);
-				if (!(!PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive) ||
-						purple_protocol_xfer_iface_can_receive(protocol, gc, real_who ? real_who : who)))
+
+				if (!purple_protocol_xfer_can_receive(protocol, gc, real_who ? real_who : who)) {
 					can_receive_file = FALSE;
+				}
+
 				g_free(real_who);
 			}
 
@@ -7418,12 +7420,18 @@
 
 		if (PURPLE_IS_IM_CONVERSATION(conv))
 		{
+			gboolean can_send_file = FALSE;
+			const gchar *name = purple_conversation_get_name(conv);
+
+			if (PURPLE_IS_PROTOCOL_XFER(protocol) &&
+			    purple_protocol_xfer_can_receive(PURPLE_PROTOCOL_XFER(protocol), gc, name)
+			) {
+				can_send_file = TRUE;
+			}
+
 			gtk_action_set_sensitive(win->menu->add, (PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER_IFACE, add_buddy)));
 			gtk_action_set_sensitive(win->menu->remove, (PURPLE_PROTOCOL_IMPLEMENTS(protocol, SERVER_IFACE, remove_buddy)));
-			gtk_action_set_sensitive(win->menu->send_file,
-									 (PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, send) &&
-									 (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive) ||
-									  purple_protocol_xfer_iface_can_receive(protocol, gc, purple_conversation_get_name(conv)))));
+			gtk_action_set_sensitive(win->menu->send_file, can_send_file);
 			gtk_action_set_sensitive(win->menu->get_attention, (PURPLE_PROTOCOL_IMPLEMENTS(protocol, ATTENTION_IFACE, send)));
 			gtk_action_set_sensitive(win->menu->alias,
 									 (account != NULL) &&
--- a/pidgin/gtkutils.c	Tue Dec 26 05:56:57 2017 +0000
+++ b/pidgin/gtkutils.c	Fri Dec 29 03:57:44 2017 +0000
@@ -1469,10 +1469,15 @@
 	if (!(purple_connection_get_flags(gc) & PURPLE_CONNECTION_FLAG_NO_IMAGES))
 		im = TRUE;
 
-	if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, can_receive))
-		ft = purple_protocol_xfer_iface_can_receive(protocol, gc, who);
-	else if (protocol && PURPLE_PROTOCOL_IMPLEMENTS(protocol, XFER_IFACE, send))
-		ft = TRUE;
+	if (protocol && PURPLE_IS_PROTOCOL_XFER(protocol)) {
+		PurpleProtocolXferInterface *iface = PURPLE_PROTOCOL_XFER(protocol);
+
+		if(iface->can_receive) {
+			ft = purple_protocol_xfer_can_receive(protocol, gc, who);
+		} else {
+			ft = (iface->send) ? TRUE : FALSE;
+		}
+	}
 
 	if (im && ft) {
 		purple_request_choice(NULL, NULL,

mercurial