[rhythmbox] daap: add support for DACP (iTunes remote control) (bug #625214)



commit 25724cf3cec6fa5e195eca0cc301a70011f9c9b3
Author: Alexandre Rosenfeld <alexandre rosenfeld gmail com>
Date:   Mon Oct 18 21:30:05 2010 +1000

    daap: add support for DACP (iTunes remote control)  (bug #625214)

 plugins/daap/Makefile.am      |    6 +
 plugins/daap/daap-prefs.ui    |  288 +++++++++++++++---
 plugins/daap/rb-daap-plugin.c |  108 +++++++-
 plugins/daap/rb-daap-record.c |   32 ++-
 plugins/daap/rb-dacp-player.c |  360 ++++++++++++++++++++++
 plugins/daap/rb-dacp-player.h |   71 +++++
 plugins/daap/rb-dacp-source.c |  656 +++++++++++++++++++++++++++++++++++++++++
 plugins/daap/rb-dacp-source.h |   79 +++++
 plugins/daap/remote-icon.png  |  Bin 0 -> 4641 bytes
 po/POTFILES.in                |    1 +
 10 files changed, 1536 insertions(+), 65 deletions(-)
---
diff --git a/plugins/daap/Makefile.am b/plugins/daap/Makefile.am
index a22f866..8f9ea28 100644
--- a/plugins/daap/Makefile.am
+++ b/plugins/daap/Makefile.am
@@ -22,6 +22,10 @@ libdaap_la_SOURCES = \
 	rb-dmap-container-db-adapter.h		  \
 	rb-daap-dialog.c			  \
 	rb-daap-dialog.h			  \
+	rb-dacp-source.c			  \
+	rb-dacp-source.h			  \
+	rb-dacp-player.c			  \
+	rb-dacp-player.h			  \
 	rb-rhythmdb-dmap-db-adapter.c             \
 	rb-rhythmdb-dmap-db-adapter.h             \
 	rb-rhythmdb-query-model-dmap-db-adapter.c \
@@ -83,10 +87,12 @@ BUILT_SOURCES =							\
 
 plugin_DATA = 			\
 	$(BUILT_SOURCES)	\
+	$(top_srcdir)/plugins/daap/remote-icon.png		\
 	$(NULL)
 
 EXTRA_DIST = 			\
 	$(gtkbuilder_DATA)	\
+	$(top_srcdir)/plugins/daap/remote-icon.png		\
 	$(uixml_DATA)		\
 	$(plugin_in_files)	\
 	$(NULL)
diff --git a/plugins/daap/daap-prefs.ui b/plugins/daap/daap-prefs.ui
index 961f391..7cef165 100644
--- a/plugins/daap/daap-prefs.ui
+++ b/plugins/daap/daap-prefs.ui
@@ -5,12 +5,10 @@
   <object class="GtkVBox" id="daap_vbox">
     <property name="visible">True</property>
     <property name="border_width">12</property>
-    <property name="orientation">vertical</property>
     <property name="spacing">18</property>
     <child>
       <object class="GtkVBox" id="vbox9">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
         <property name="spacing">6</property>
         <child>
           <object class="GtkLabel" id="browser_views_label">
@@ -53,10 +51,27 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkVBox" id="vbox10">
+                  <object class="GtkTable" id="table1">
                     <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <property name="spacing">6</property>
+                    <property name="n_rows">4</property>
+                    <property name="n_columns">2</property>
+                    <property name="column_spacing">6</property>
+                    <property name="row_spacing">6</property>
+                    <child>
+                      <object class="GtkCheckButton" id="dacp_enable_check">
+                        <property name="label" translatable="yes">_Look for touch Remotes</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
                     <child>
                       <object class="GtkCheckButton" id="daap_enable_check">
                         <property name="label" translatable="yes">_Share my music</property>
@@ -67,99 +82,272 @@
                         <property name="draw_indicator">True</property>
                       </object>
                       <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">0</property>
+                        <property name="top_attach">2</property>
+                        <property name="bottom_attach">3</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="daap_name_entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">&#x25CF;</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label18">
+                        <property name="visible">True</property>
+                        <property name="xalign">0</property>
+                        <property name="label" translatable="yes">Library _name:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">daap_name_entry</property>
+                      </object>
+                      <packing>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="forget_remotes_button">
+                        <property name="label" translatable="yes">Forget known Remotes</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">1</property>
+                        <property name="bottom_attach">2</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkTable" id="daap_box">
+                      <object class="GtkCheckButton" id="daap_password_check">
+                        <property name="label" translatable="yes">Require _password:</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="top_attach">3</property>
+                        <property name="bottom_attach">4</property>
+                        <property name="x_options">GTK_FILL</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="daap_password_entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="visibility">False</property>
+                        <property name="invisible_char">&#x25CF;</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="right_attach">2</property>
+                        <property name="top_attach">3</property>
+                        <property name="bottom_attach">4</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkAlignment" id="passcode_widget">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkVBox" id="vbox98">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">&lt;b&gt;Add Remote&lt;/b&gt;</property>
+            <property name="use_markup">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkAlignment" id="pairing_widget">
+            <property name="visible">True</property>
+            <property name="xscale">0</property>
+            <property name="yscale">0</property>
+            <child>
+              <object class="GtkVBox" id="vbox99">
+                <property name="visible">True</property>
+                <property name="spacing">16</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Please enter the passcode displayed on your device.</property>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkAlignment" id="alignment3">
+                    <property name="visible">True</property>
+                    <property name="xscale">0</property>
+                    <property name="yscale">0</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox1">
                         <property name="visible">True</property>
-                        <property name="n_rows">2</property>
-                        <property name="n_columns">2</property>
-                        <property name="column_spacing">6</property>
-                        <property name="row_spacing">6</property>
+                        <property name="spacing">8</property>
                         <child>
-                          <object class="GtkCheckButton" id="daap_password_check">
-                            <property name="label" translatable="yes">Require _password:</property>
+                          <object class="GtkEntry" id="passcode_entry1">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="use_underline">True</property>
-                            <property name="draw_indicator">True</property>
+                            <property name="max_length">1</property>
+                            <property name="invisible_char">&#x25CF;</property>
+                            <property name="width_chars">1</property>
+                            <property name="xalign">0.5</property>
                           </object>
                           <packing>
-                            <property name="top_attach">1</property>
-                            <property name="bottom_attach">2</property>
-                            <property name="x_options">GTK_FILL</property>
-                            <property name="y_options"></property>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkEntry" id="daap_name_entry">
+                          <object class="GtkEntry" id="passcode_entry2">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
+                            <property name="max_length">1</property>
                             <property name="invisible_char">&#x25CF;</property>
+                            <property name="width_chars">1</property>
+                            <property name="xalign">0.5</property>
                           </object>
                           <packing>
-                            <property name="left_attach">1</property>
-                            <property name="right_attach">2</property>
-                            <property name="y_options"></property>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkLabel" id="label18">
+                          <object class="GtkEntry" id="passcode_entry3">
                             <property name="visible">True</property>
-                            <property name="label" translatable="yes">Shared music _name:</property>
-                            <property name="use_underline">True</property>
-                            <property name="mnemonic_widget">daap_name_entry</property>
+                            <property name="can_focus">True</property>
+                            <property name="max_length">1</property>
+                            <property name="invisible_char">&#x25CF;</property>
+                            <property name="width_chars">1</property>
+                            <property name="xalign">0.5</property>
                           </object>
                           <packing>
-                            <property name="x_options">GTK_FILL</property>
-                            <property name="y_options"></property>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">2</property>
                           </packing>
                         </child>
                         <child>
-                          <object class="GtkEntry" id="daap_password_entry">
+                          <object class="GtkEntry" id="passcode_entry4">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
-                            <property name="visibility">False</property>
+                            <property name="max_length">1</property>
                             <property name="invisible_char">&#x25CF;</property>
+                            <property name="width_chars">1</property>
+                            <property name="xalign">0.5</property>
                           </object>
                           <packing>
-                            <property name="left_attach">1</property>
-                            <property name="right_attach">2</property>
-                            <property name="top_attach">1</property>
-                            <property name="bottom_attach">2</property>
-                            <property name="y_options"></property>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">3</property>
                           </packing>
                         </child>
                       </object>
-                      <packing>
-                        <property name="position">1</property>
-                      </packing>
                     </child>
                   </object>
                   <packing>
                     <property name="position">1</property>
                   </packing>
                 </child>
+                <child>
+                  <object class="GtkLabel" id="pairing_status_widget">
+                    <property name="label" translatable="yes">&lt;span foreground="red"&gt;Could not pair with this Remote.&lt;/span&gt;</property>
+                    <property name="use_markup">True</property>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
               </object>
-              <packing>
-                <property name="position">1</property>
-              </packing>
             </child>
           </object>
           <packing>
             <property name="position">1</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkAlignment" id="finished_widget">
+            <property name="xscale">0</property>
+            <property name="yscale">0</property>
+            <child>
+              <object class="GtkHBox" id="finished_hbox">
+                <property name="visible">True</property>
+                <property name="spacing">16</property>
+                <child>
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">You can now control Rhythmbox through your Remote</property>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="close_pairing_button">
+                    <property name="label">gtk-close</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_stock">True</property>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
       </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">False</property>
-        <property name="position">0</property>
-      </packing>
     </child>
   </object>
+  <object class="GtkSizeGroup" id="daap_group">
+    <property name="mode">both</property>
+  </object>
 </interface>
diff --git a/plugins/daap/rb-daap-plugin.c b/plugins/daap/rb-daap-plugin.c
index c03afe0..c674c5f 100644
--- a/plugins/daap/rb-daap-plugin.c
+++ b/plugins/daap/rb-daap-plugin.c
@@ -51,6 +51,7 @@
 #include "rb-daap-source.h"
 #include "rb-daap-sharing.h"
 #include "rb-daap-src.h"
+#include "rb-dacp-source.h"
 #include "rb-uri-dialog.h"
 
 #include <libdmapsharing/dmap.h>
@@ -58,6 +59,7 @@
 /* preferences */
 #define CONF_DAAP_PREFIX  	CONF_PREFIX "/plugins/daap"
 #define CONF_ENABLE_BROWSING 	CONF_DAAP_PREFIX "/enable_browsing"
+#define CONF_ENABLE_REMOTE	CONF_DAAP_PREFIX "/enable_remote"
 
 #define DAAP_DBUS_PATH	"/org/gnome/Rhythmbox/DAAP"
 
@@ -76,9 +78,12 @@ struct RBDaapPluginPrivate
 
 	DMAPMdnsBrowser *mdns_browser;
 
+	DACPShare *dacp_share;
+
 	GHashTable *source_lookup;
 
 	guint enable_browsing_notify_id;
+	guint enable_remote_notify_id;
 
 	GdkPixbuf *daap_share_pixbuf;
 	GdkPixbuf *daap_share_locked_pixbuf;
@@ -87,7 +92,8 @@ struct RBDaapPluginPrivate
 enum
 {
 	PROP_0,
-	PROP_SHUTDOWN
+	PROP_SHUTDOWN,
+	PROP_SHELL
 };
 
 G_MODULE_EXPORT GType register_rb_plugin (GTypeModule *module);
@@ -113,6 +119,10 @@ static void enable_browsing_changed_cb (GConfClient *client,
 					guint cnxn_id,
 					GConfEntry *entry,
 					RBDaapPlugin *plugin);
+static void enable_remote_changed_cb (GConfClient *client,
+					guint cnxn_id,
+					GConfEntry *entry,
+					RBDaapPlugin *plugin);
 static void libdmapsharing_debug (const char *domain,
 				  GLogLevelFlags level,
 				  const char *message,
@@ -159,6 +169,14 @@ rb_daap_plugin_class_init (RBDaapPluginClass *klass)
 							       FALSE,
 							       G_PARAM_READABLE));
 
+	g_object_class_install_property (object_class,
+	                                 PROP_SHELL,
+	                                 g_param_spec_object ("shell",
+	                                                      "Shell",
+	                                                      "The Rhythmbox Shell",
+	                                                      RB_TYPE_SHELL,
+	                                                      G_PARAM_READABLE));
+
 	g_type_class_add_private (object_class, sizeof (RBDaapPluginPrivate));
 }
 
@@ -204,6 +222,9 @@ rb_daap_plugin_get_property (GObject *object,
 	case PROP_SHUTDOWN:
 		g_value_set_boolean (value, plugin->priv->shutdown);
 		break;
+	case PROP_SHELL:
+		g_value_take_object (value, plugin->priv->shell);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -217,6 +238,7 @@ impl_activate (RBPlugin *bplugin,
 	RBDaapPlugin *plugin = RB_DAAP_PLUGIN (bplugin);
 	gboolean no_registration;
 	gboolean enabled = TRUE;
+	gboolean remote_enabled = TRUE;
 	GConfValue *value;
 	GConfClient *client = eel_gconf_client_get_global ();
 	GtkUIManager *uimanager = NULL;
@@ -248,6 +270,24 @@ impl_activate (RBPlugin *bplugin,
 					    (GConfClientNotifyFunc) enable_browsing_changed_cb,
 					    plugin);
 
+	/* Check if Remote (DACP) lookup is enabled */
+	value = gconf_client_get_without_default (client,
+						  CONF_ENABLE_REMOTE, NULL);
+	if (value != NULL) {
+		remote_enabled = gconf_value_get_bool (value);
+		gconf_value_free (value);
+	}
+
+	plugin->priv->dacp_share = rb_daap_create_dacp_share (RB_PLUGIN (plugin));
+	if (remote_enabled) {
+		dacp_share_start_lookup (plugin->priv->dacp_share);
+	}
+
+	plugin->priv->enable_remote_notify_id =
+		eel_gconf_notification_add (CONF_ENABLE_REMOTE,
+					    (GConfClientNotifyFunc) enable_remote_changed_cb,
+					    plugin);
+
 	create_pixbufs (plugin);
 
 	g_object_get (shell,
@@ -328,6 +368,13 @@ impl_deactivate	(RBPlugin *bplugin,
 		plugin->priv->enable_browsing_notify_id = EEL_GCONF_UNDEFINED_CONNECTION;
 	}
 
+	g_object_unref (plugin->priv->dacp_share);
+
+	if (plugin->priv->enable_remote_notify_id != EEL_GCONF_UNDEFINED_CONNECTION) {
+		eel_gconf_notification_remove (plugin->priv->enable_remote_notify_id);
+		plugin->priv->enable_remote_notify_id = EEL_GCONF_UNDEFINED_CONNECTION;
+	}
+
 	g_object_get (shell,
 		      "ui-manager", &uimanager,
 		      NULL);
@@ -616,6 +663,21 @@ enable_browsing_changed_cb (GConfClient *client,
 }
 
 static void
+enable_remote_changed_cb (GConfClient *client,
+			    guint cnxn_id,
+			    GConfEntry *entry,
+			    RBDaapPlugin *plugin)
+{
+	gboolean enabled = eel_gconf_get_boolean (CONF_ENABLE_REMOTE);
+
+	if (enabled) {
+		dacp_share_start_lookup (plugin->priv->dacp_share);
+	} else {
+		dacp_share_stop_lookup (plugin->priv->dacp_share);
+	}
+}
+
+static void
 libdmapsharing_debug (const char *domain,
 		      GLogLevelFlags level,
 		      const char *message,
@@ -740,15 +802,32 @@ preferences_response_cb (GtkWidget *dialog, gint response, RBPlugin *plugin)
 
 static void
 share_check_button_toggled_cb (GtkToggleButton *button,
-			       GtkWidget *widget)
+			       GtkBuilder *builder)
 {
 	gboolean b;
+	GtkToggleButton *password_check;
+	GtkWidget *password_entry;
 
 	b = gtk_toggle_button_get_active (button);
 
 	eel_gconf_set_boolean (CONF_DAAP_ENABLE_SHARING, b);
 
-	gtk_widget_set_sensitive (widget, b);
+	password_check = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, "daap_password_check"));
+	password_entry = GTK_WIDGET (gtk_builder_get_object (builder, "daap_password_entry"));
+
+	gtk_widget_set_sensitive (password_entry, b && gtk_toggle_button_get_active (password_check));
+	gtk_widget_set_sensitive (GTK_WIDGET (password_check), b);
+}
+
+static void
+remote_check_button_toggled_cb (GtkToggleButton *button,
+			       gpointer data)
+{
+	gboolean b;
+
+	b = gtk_toggle_button_get_active (button);
+
+	eel_gconf_set_boolean (CONF_ENABLE_REMOTE, b);
 }
 
 static void
@@ -764,6 +843,13 @@ password_check_button_toggled_cb (GtkToggleButton *button,
 	gtk_widget_set_sensitive (widget, b);
 }
 
+static void
+forget_remotes_button_toggled_cb (GtkToggleButton *button,
+				  gpointer data)
+{
+	eel_gconf_unset (CONF_KNOWN_REMOTES);
+}
+
 static gboolean
 share_name_entry_focus_out_event_cb (GtkEntry *entry,
 				     GdkEventFocus *event,
@@ -828,29 +914,38 @@ static void
 update_config_widget (RBDaapPlugin *plugin)
 {
 	GtkWidget *check;
+	GtkWidget *remote_check;
 	GtkWidget *name_entry;
 	GtkWidget *password_entry;
 	GtkWidget *password_check;
-	GtkWidget *box;
+	GtkWidget *forget_remotes_button;
 	gboolean sharing_enabled;
+	gboolean remote_enabled;
 	gboolean require_password;
 	char *name;
 	char *password;
 
 	check = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "daap_enable_check"));
+	remote_check = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "dacp_enable_check"));
 	password_check = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "daap_password_check"));
 	name_entry = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "daap_name_entry"));
 	password_entry = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "daap_password_entry"));
-	box = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "daap_box"));
+	forget_remotes_button = GTK_WIDGET (gtk_builder_get_object (plugin->priv->builder, "forget_remotes_button"));
 
 	sharing_enabled = eel_gconf_get_boolean (CONF_DAAP_ENABLE_SHARING);
 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), sharing_enabled);
-	g_signal_connect (check, "toggled", G_CALLBACK (share_check_button_toggled_cb), box);
+	g_signal_connect (check, "toggled", G_CALLBACK (share_check_button_toggled_cb), plugin->priv->builder);
+
+	remote_enabled = eel_gconf_get_boolean (CONF_ENABLE_REMOTE);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (remote_check), remote_enabled);
+	g_signal_connect (remote_check, "toggled", G_CALLBACK (remote_check_button_toggled_cb), plugin->priv->builder);
 
 	require_password = eel_gconf_get_boolean (CONF_DAAP_REQUIRE_PASSWORD);
 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (password_check), require_password);
 	g_signal_connect (password_check, "toggled", G_CALLBACK (password_check_button_toggled_cb), password_entry);
 
+	g_signal_connect (forget_remotes_button, "clicked", G_CALLBACK (forget_remotes_button_toggled_cb), NULL);
+
 	name = eel_gconf_get_string (CONF_DAAP_SHARE_NAME);
 	if (name == NULL || name[0] == '\0')
 		name = rb_daap_sharing_default_share_name ();
@@ -867,7 +962,6 @@ update_config_widget (RBDaapPlugin *plugin)
 	g_signal_connect (password_entry, "focus-out-event",
 			  G_CALLBACK (share_password_entry_focus_out_event_cb), NULL);
 
-	gtk_widget_set_sensitive (box, sharing_enabled);
 	gtk_widget_set_sensitive (password_entry, require_password);
 }
 
diff --git a/plugins/daap/rb-daap-record.c b/plugins/daap/rb-daap-record.c
index 1de003e..fee6064 100644
--- a/plugins/daap/rb-daap-record.c
+++ b/plugins/daap/rb-daap-record.c
@@ -38,7 +38,6 @@
 struct RBDAAPRecordPrivate {
 	guint64 filesize;
 	char *location;
-	int mediakind;
 	char *format;	 /* Format, possibly after transcoding. */
 	char *real_format;
 	char *title;
@@ -46,6 +45,7 @@ struct RBDAAPRecordPrivate {
 	char *artist;
 	char *genre;
 	gboolean has_video;
+	gint mediakind;
 	gint rating;
 	int duration;
 	int track;
@@ -56,6 +56,7 @@ struct RBDAAPRecordPrivate {
 	int bitrate;
 	char *sort_artist;
 	char *sort_album;
+	gint64 albumid;
 };
 
 enum {
@@ -79,7 +80,8 @@ enum {
 	PROP_HAS_VIDEO,
 	PROP_REAL_FORMAT,
 	PROP_ARTIST_SORT_NAME,
-	PROP_ALBUM_SORT_NAME
+	PROP_ALBUM_SORT_NAME,
+	PROP_ALBUM_ID
 };
 
 static void rb_daap_record_finalize (GObject *object);
@@ -105,6 +107,9 @@ rb_daap_record_set_property (GObject *object,
 			g_free (record->priv->album);
 			record->priv->album = g_value_dup_string (value);
 			break;
+		case PROP_ALBUM_ID:
+			record->priv->albumid = g_value_get_int64 (value);
+			break;
 		case PROP_ARTIST:
 			g_free (record->priv->artist);
 			record->priv->artist = g_value_dup_string (value);
@@ -186,6 +191,9 @@ rb_daap_record_get_property (GObject *object,
 		case PROP_ALBUM:
 			g_value_set_string (value, record->priv->album);
 			break;
+		case PROP_ALBUM_ID:
+			g_value_set_int64 (value, record->priv->albumid);
+			break;
 		case PROP_ARTIST:
 			g_value_set_string (value, record->priv->artist);
 			break;
@@ -272,7 +280,7 @@ static void
 rb_daap_record_init (RBDAAPRecord *record)
 {
 	record->priv = RB_DAAP_RECORD_GET_PRIVATE (record);
-	
+
         record->priv->location		= NULL;
         record->priv->format		= NULL;
         record->priv->real_format	= NULL;
@@ -302,9 +310,9 @@ rb_daap_record_class_init (RBDAAPRecordClass *klass)
 
 	g_type_class_add_private (klass, sizeof (RBDAAPRecordPrivate));
 
-	gobject_class->set_property = rb_daap_record_set_property;	
-	gobject_class->get_property = rb_daap_record_get_property;	
-	gobject_class->finalize     = rb_daap_record_finalize;	
+	gobject_class->set_property = rb_daap_record_set_property;
+	gobject_class->get_property = rb_daap_record_get_property;
+	gobject_class->finalize     = rb_daap_record_finalize;
 
 	g_object_class_override_property (gobject_class, PROP_LOCATION, "location");
 	g_object_class_override_property (gobject_class, PROP_TITLE, "title");
@@ -325,6 +333,7 @@ rb_daap_record_class_init (RBDAAPRecordClass *klass)
 	g_object_class_override_property (gobject_class, PROP_HAS_VIDEO, "has-video");
 	g_object_class_override_property (gobject_class, PROP_ARTIST_SORT_NAME, "sort_artist");
 	g_object_class_override_property (gobject_class, PROP_ALBUM_SORT_NAME, "sort_album");
+	g_object_class_override_property (gobject_class, PROP_ALBUM_ID, "songalbumid");
 
 	g_object_class_install_property (gobject_class, PROP_REAL_FORMAT,
 	                        g_param_spec_string ("real-format",
@@ -353,7 +362,7 @@ rb_daap_record_dmap_iface_init (gpointer iface, gpointer data)
 	g_assert (G_TYPE_FROM_INTERFACE (dmap_record) == TYPE_DMAP_RECORD);
 }
 
-G_DEFINE_TYPE_WITH_CODE (RBDAAPRecord, rb_daap_record, G_TYPE_OBJECT, 
+G_DEFINE_TYPE_WITH_CODE (RBDAAPRecord, rb_daap_record, G_TYPE_OBJECT,
 			 G_IMPLEMENT_INTERFACE (TYPE_DAAP_RECORD, rb_daap_record_daap_iface_init)
 			 G_IMPLEMENT_INTERFACE (TYPE_DMAP_RECORD, rb_daap_record_dmap_iface_init))
 
@@ -384,7 +393,7 @@ RBDAAPRecord *rb_daap_record_new (RhythmDBEntry *entry)
 	if (entry) {
 		gchar *ext;
 
-		record->priv->filesize = rhythmdb_entry_get_uint64 
+		record->priv->filesize = rhythmdb_entry_get_uint64
 						(entry, RHYTHMDB_PROP_FILE_SIZE);
 
 		record->priv->location = rhythmdb_entry_dup_string
@@ -399,6 +408,10 @@ RBDAAPRecord *rb_daap_record_new (RhythmDBEntry *entry)
 		record->priv->album    = rhythmdb_entry_dup_string
 						(entry, RHYTHMDB_PROP_ALBUM);
 
+		/* Since we don't support album id's on Rhythmbox, "emulate" it */
+		record->priv->albumid  = (gint64) rhythmdb_entry_get_refstring
+						(entry, RHYTHMDB_PROP_ALBUM);
+
 		record->priv->genre    = rhythmdb_entry_dup_string
 						(entry, RHYTHMDB_PROP_GENRE);
 
@@ -414,6 +427,9 @@ RBDAAPRecord *rb_daap_record_new (RhythmDBEntry *entry)
 		record->priv->real_format = g_strdup (ext);
 		record->priv->format = g_strdup (record->priv->real_format);
 
+		/* Only support songs */
+		record->priv->mediakind = 1;
+
 		record->priv->track    = rhythmdb_entry_get_ulong
 						(entry, RHYTHMDB_PROP_TRACK_NUMBER);
 
diff --git a/plugins/daap/rb-dacp-player.c b/plugins/daap/rb-dacp-player.c
new file mode 100644
index 0000000..f5d4875
--- /dev/null
+++ b/plugins/daap/rb-dacp-player.c
@@ -0,0 +1,360 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * rhythmbox
+ * Copyright (C) Alexandre Rosenfeld 2010 <alexandre rosenfeld gmail com>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "rhythmdb.h"
+#include "rb-shell.h"
+#include "rb-shell-player.h"
+#include "rb-daap-record.h"
+#include "rb-playlist-manager.h"
+#include "rb-play-queue-source.h"
+
+#include <libdmapsharing/dmap.h>
+
+#include "rb-dacp-player.h"
+
+struct _RBDACPPlayerPrivate {
+	RBShell *shell;
+	RBShellPlayer *shell_player;
+};
+
+static void rb_dacp_player_get_property (GObject *object, guint prop_id,
+                                         GValue *value, GParamSpec *pspec);
+static void rb_dacp_player_set_property (GObject *object, guint prop_id,
+                                         const GValue *value, GParamSpec *pspec);
+
+static void playing_song_changed (RBShellPlayer *shell_player, RhythmDBEntry *entry, RBDACPPlayer *player);
+static void elapsed_changed (RBShellPlayer *shell_player, guint elapsed, RBDACPPlayer *player);
+
+static DAAPRecord *rb_dacp_player_now_playing_record  (DACPPlayer *player);
+static gchar *rb_dacp_player_now_playing_artwork (DACPPlayer *player, guint width, guint height);
+static void rb_dacp_player_play_pause          (DACPPlayer *player);
+static void rb_dacp_player_pause               (DACPPlayer *player);
+static void rb_dacp_player_next_item           (DACPPlayer *player);
+static void rb_dacp_player_prev_item           (DACPPlayer *player);
+
+static void rb_dacp_player_cue_clear           (DACPPlayer *player);
+static void rb_dacp_player_cue_play            (DACPPlayer *player, GList *records, guint index);
+
+enum {
+	PROP_0,
+	PROP_PLAYING_TIME,
+	PROP_SHUFFLE_STATE,
+	PROP_REPEAT_STATE,
+	PROP_PLAY_STATE,
+	PROP_VOLUME
+};
+
+enum {
+	PLAYER_UPDATED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+rb_dacp_player_iface_init (gpointer iface, gpointer data)
+{
+	DACPPlayerInterface *dacp_player = iface;
+
+	g_assert (G_TYPE_FROM_INTERFACE (dacp_player) == TYPE_DACP_PLAYER);
+
+	dacp_player->now_playing_record  = rb_dacp_player_now_playing_record;
+	dacp_player->now_playing_artwork = rb_dacp_player_now_playing_artwork;
+	dacp_player->play_pause          = rb_dacp_player_play_pause;
+	dacp_player->pause               = rb_dacp_player_pause;
+	dacp_player->next_item           = rb_dacp_player_next_item;
+	dacp_player->prev_item           = rb_dacp_player_prev_item;
+
+	dacp_player->cue_clear           = rb_dacp_player_cue_clear;
+	dacp_player->cue_play            = rb_dacp_player_cue_play;
+}
+
+G_DEFINE_TYPE_WITH_CODE (RBDACPPlayer, rb_dacp_player, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (TYPE_DACP_PLAYER, rb_dacp_player_iface_init))
+
+static void
+rb_dacp_player_init (RBDACPPlayer *object)
+{
+	object->priv = RB_DACP_PLAYER_GET_PRIVATE (object);
+}
+
+static void
+rb_dacp_player_finalize (GObject *object)
+{
+	RBDACPPlayer *player = RB_DACP_PLAYER (object);
+
+	g_signal_handlers_disconnect_by_func (player->priv->shell_player, playing_song_changed, player);
+
+	g_object_unref (player->priv->shell);
+	g_object_unref (player->priv->shell_player);
+
+	G_OBJECT_CLASS (rb_dacp_player_parent_class)->finalize (object);
+}
+
+static void
+rb_dacp_player_class_init (RBDACPPlayerClass *klass)
+{
+	GObjectClass* object_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (RBDACPPlayerPrivate));
+
+	object_class->set_property = rb_dacp_player_set_property;
+	object_class->get_property = rb_dacp_player_get_property;
+	object_class->finalize     = rb_dacp_player_finalize;
+
+	g_object_class_override_property (object_class, PROP_PLAYING_TIME, "playing-time");
+	g_object_class_override_property (object_class, PROP_SHUFFLE_STATE, "shuffle-state");
+	g_object_class_override_property (object_class, PROP_REPEAT_STATE, "repeat-state");
+	g_object_class_override_property (object_class, PROP_PLAY_STATE, "play-state");
+	g_object_class_override_property (object_class, PROP_VOLUME, "volume");
+
+	signals[PLAYER_UPDATED] =
+		g_signal_new ("player_updated",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBDACPPlayerClass, player_updated),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+
+	object_class->finalize = rb_dacp_player_finalize;
+}
+
+static void
+rb_dacp_player_get_property (GObject *object,
+                             guint prop_id,
+                             GValue *value,
+                             GParamSpec *pspec)
+{
+	RBDACPPlayer *player = RB_DACP_PLAYER (object);
+
+	gboolean repeat;
+	gboolean shuffle;
+	guint playing_time;
+	gboolean playing;
+	gdouble volume;
+	RhythmDBEntry *entry;
+
+	switch (prop_id) {
+		case PROP_PLAYING_TIME:
+			rb_shell_player_get_playing_time (player->priv->shell_player, &playing_time, NULL);
+			g_value_set_ulong (value, playing_time * 1000);
+			break;
+		case PROP_SHUFFLE_STATE:
+			rb_shell_player_get_playback_state (player->priv->shell_player, &shuffle, &repeat);
+			g_value_set_boolean (value, shuffle);
+			break;
+		case PROP_REPEAT_STATE:
+			rb_shell_player_get_playback_state (player->priv->shell_player, &shuffle, &repeat);
+			g_value_set_enum (value, repeat ? REPEAT_ALL : REPEAT_NONE);
+			break;
+		case PROP_PLAY_STATE:
+			entry = rb_shell_player_get_playing_entry (player->priv->shell_player);
+			if (entry) {
+				g_object_get (player->priv->shell_player, "playing", &playing, NULL);
+				g_value_set_enum (value, playing ? PLAY_PLAYING : PLAY_PAUSED);
+				rhythmdb_entry_unref (entry);
+			} else {
+				g_value_set_enum (value, PLAY_STOPPED);
+			}
+			break;
+		case PROP_VOLUME:
+			rb_shell_player_get_volume (player->priv->shell_player, &volume, NULL);
+			g_value_set_ulong (value, (gulong) ceil (volume * 100.0));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+rb_dacp_player_set_property (GObject *object,
+                             guint prop_id,
+                             const GValue *value,
+                             GParamSpec *pspec)
+{
+	RBDACPPlayer *player = RB_DACP_PLAYER (object);
+
+	gboolean shuffle;
+	gboolean repeat;
+	ulong playing_time;
+	gdouble volume;
+
+	switch (prop_id) {
+		case PROP_PLAYING_TIME:
+			playing_time = g_value_get_ulong (value);
+			rb_shell_player_set_playing_time (player->priv->shell_player, (ulong) ceil (playing_time / 1000), NULL);
+			break;
+		case PROP_SHUFFLE_STATE:
+			rb_shell_player_get_playback_state (player->priv->shell_player, &shuffle, &repeat);
+			rb_shell_player_set_playback_state (player->priv->shell_player, g_value_get_boolean (value), repeat);
+			break;
+		case PROP_REPEAT_STATE:
+			rb_shell_player_get_playback_state (player->priv->shell_player, &shuffle, &repeat);
+			rb_shell_player_set_playback_state (player->priv->shell_player, shuffle, g_value_get_enum (value) != REPEAT_NONE);
+			break;
+		case PROP_VOLUME:
+			volume = ((double) g_value_get_ulong (value))  / 100.0;
+			rb_shell_player_set_volume (player->priv->shell_player, volume, NULL);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+playing_song_changed (RBShellPlayer *shell_player,
+                      RhythmDBEntry *entry,
+                      RBDACPPlayer *player)
+{
+	g_signal_emit (player, signals [PLAYER_UPDATED], 0);
+}
+
+static void
+elapsed_changed (RBShellPlayer *shell_player,
+                 guint elapsed,
+                 RBDACPPlayer *player)
+{
+	g_signal_emit (player, signals [PLAYER_UPDATED], 0);
+}
+
+RBDACPPlayer *
+rb_dacp_player_new (RBShell *shell)
+{
+	RBDACPPlayer *player;
+
+	player = RB_DACP_PLAYER (g_object_new (RB_TYPE_DACP_PLAYER, NULL));
+
+	player->priv->shell = g_object_ref (shell);
+	player->priv->shell_player = g_object_ref (rb_shell_get_player (shell));
+	g_signal_connect_object (player->priv->shell_player,
+	                         "playing-song-changed",
+	                         G_CALLBACK (playing_song_changed),
+	                         player,
+	                         0);
+	g_signal_connect_object (player->priv->shell_player,
+	                         "elapsed-changed",
+	                         G_CALLBACK (elapsed_changed),
+	                         player,
+	                         0);
+
+	return player;
+}
+
+static DAAPRecord *
+rb_dacp_player_now_playing_record (DACPPlayer *player)
+{
+	RhythmDBEntry *entry;
+	DAAPRecord *record;
+
+	entry = rb_shell_player_get_playing_entry (RB_DACP_PLAYER (player)->priv->shell_player);
+	if (entry == NULL) {
+		return NULL;
+	} else {
+		record = DAAP_RECORD (rb_daap_record_new (entry));
+		rhythmdb_entry_unref (entry);
+		return record;
+	}
+}
+
+static gchar *
+rb_dacp_player_now_playing_artwork (DACPPlayer *player, guint width, guint height)
+{
+	return NULL;
+}
+
+static void
+rb_dacp_player_play_pause (DACPPlayer *player)
+{
+	rb_shell_player_playpause (RB_DACP_PLAYER (player)->priv->shell_player, FALSE, NULL);
+}
+
+static void
+rb_dacp_player_pause (DACPPlayer *player)
+{
+	rb_shell_player_pause (RB_DACP_PLAYER (player)->priv->shell_player, NULL);
+}
+
+static void
+rb_dacp_player_next_item (DACPPlayer *player)
+{
+	rb_shell_player_do_next (RB_DACP_PLAYER (player)->priv->shell_player, NULL);
+}
+
+static void
+rb_dacp_player_prev_item (DACPPlayer *player)
+{
+	rb_shell_player_do_previous (RB_DACP_PLAYER (player)->priv->shell_player, NULL);
+}
+
+static void
+rb_dacp_player_cue_clear (DACPPlayer *player)
+{
+	rb_shell_clear_queue (RB_DACP_PLAYER (player)->priv->shell, NULL);
+}
+
+static void
+rb_dacp_player_cue_play (DACPPlayer *player, GList *records, guint index)
+{
+	GList *record;
+	gint current = 0;
+
+	for (record = records; record; record = record->next) {
+		gchar *location;
+
+		g_object_get (G_OBJECT (record->data), "location", &location, NULL);
+		rb_shell_add_to_queue (RB_DACP_PLAYER (player)->priv->shell, location, NULL);
+
+		if (current == index) {
+			RhythmDB *db;
+			RhythmDBEntry *entry;
+			RBPlayQueueSource *queue;
+			g_object_get (RB_DACP_PLAYER (player)->priv->shell,
+			              "db", &db,
+			              "queue-source", &queue,
+			              NULL);
+			entry = rhythmdb_entry_lookup_by_location (db, location);
+			if (entry)
+				rb_shell_player_play_entry (RB_DACP_PLAYER (player)->priv->shell_player, entry, RB_SOURCE (queue));
+			g_object_unref (db);
+			g_object_unref (queue);
+		}
+
+		g_free (location);
+		current++;
+	}
+}
diff --git a/plugins/daap/rb-dacp-player.h b/plugins/daap/rb-dacp-player.h
new file mode 100644
index 0000000..3063dce
--- /dev/null
+++ b/plugins/daap/rb-dacp-player.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * rhythmbox
+ * Copyright (C) Alexandre Rosenfeld 2010 <alexandre rosenfeld gmail com>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef _RB_DACP_PLAYER_H_
+#define _RB_DACP_PLAYER_H_
+
+#include <glib-object.h>
+#include <libdmapsharing/dmap.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DACP_PLAYER             (rb_dacp_player_get_type ())
+#define RB_DACP_PLAYER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), RB_TYPE_DACP_PLAYER, RBDACPPlayer))
+#define RB_DACP_PLAYER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), RB_TYPE_DACP_PLAYER, RBDACPPlayerClass))
+#define RB_IS_DACP_PLAYER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RB_TYPE_DACP_PLAYER))
+#define RB_IS_DACP_PLAYER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), RB_TYPE_DACP_PLAYER))
+#define RB_DACP_PLAYER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), RB_TYPE_DACP_PLAYER, RBDACPPlayerClass))
+#define RB_DACP_PLAYER_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_DACP_PLAYER, RBDACPPlayerPrivate))
+
+typedef struct _RBDACPPlayerClass RBDACPPlayerClass;
+typedef struct _RBDACPPlayer RBDACPPlayer;
+
+typedef struct _RBDACPPlayerPrivate RBDACPPlayerPrivate;
+
+struct _RBDACPPlayerClass
+{
+	GObjectClass parent_class;
+
+	void (*player_updated) (DACPPlayer *player);
+};
+
+struct _RBDACPPlayer
+{
+	GObject parent_instance;
+
+	RBDACPPlayerPrivate *priv;
+};
+
+GType rb_dacp_player_get_type (void) G_GNUC_CONST;
+
+RBDACPPlayer *rb_dacp_player_new (RBShell *shell);
+
+G_END_DECLS
+
+#endif /* _RB_DACP_PLAYER_H_ */
diff --git a/plugins/daap/rb-dacp-source.c b/plugins/daap/rb-dacp-source.c
new file mode 100644
index 0000000..e9b3c0b
--- /dev/null
+++ b/plugins/daap/rb-dacp-source.c
@@ -0,0 +1,656 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Implementation of DACP (iTunes Remote) source object
+ *
+ *  Copyright (C) 2010 Alexandre Rosenfeld <alexandre rosenfeld gmail com>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "rhythmdb.h"
+#include "rb-shell.h"
+#include "rb-source-group.h"
+#include "eel-gconf-extensions.h"
+#include "rb-stock-icons.h"
+#include "rb-debug.h"
+#include "rb-util.h"
+#include "rb-file-helpers.h"
+#include "rb-builder-helpers.h"
+#include "rb-dialog.h"
+#include "rb-preferences.h"
+#include "rb-playlist-manager.h"
+#include "rb-shell-player.h"
+#include "rb-sourcelist-model.h"
+#include "rb-rhythmdb-dmap-db-adapter.h"
+#include "rb-dmap-container-db-adapter.h"
+
+#include "rb-daap-plugin.h"
+#include "rb-daap-sharing.h"
+#include "rb-dacp-player.h"
+
+#include <libdmapsharing/dmap.h>
+
+#include "rb-dacp-source.h"
+
+static void rb_dacp_source_dispose (GObject *object);
+static void rb_dacp_source_set_property  (GObject *object,
+					  guint prop_id,
+					  const GValue *value,
+					  GParamSpec *pspec);
+static void rb_dacp_source_get_property  (GObject *object,
+					  guint prop_id,
+					  GValue *value,
+					  GParamSpec *pspec);
+
+static void rb_dacp_source_connecting (RBDACPSource *source, gboolean connecting);
+static gboolean entry_insert_text_cb (GtkWidget *entry, gchar *text, gint len, gint *position, RBDACPSource *rb_dacp_source);
+static gboolean entry_backspace_cb (GtkWidget *entry, RBDACPSource *rb_dacp_source);
+static void rb_dacp_source_create_ui (RBDACPSource *source, RBPlugin *plugin);
+static void remote_paired_cb (DACPShare *share, gchar *service_name, gboolean connected, RBDACPSource *source);
+
+static void dacp_remote_added (DACPShare *share, gchar *service_name, gchar *display_name, RBDaapPlugin *plugin);
+static void dacp_remote_removed (DACPShare *share, gchar *service_name, RBDaapPlugin *plugin);
+
+/* DACPShare signals */
+static gboolean dacp_lookup_guid (DACPShare *share, gchar *guid);
+static void     dacp_add_guid    (DACPShare *share, gchar *guid);
+
+static void dacp_player_updated (RBDACPPlayer *player, DACPShare *share);
+
+struct RBDACPSourcePrivate
+{
+	char *service_name;
+
+	gboolean done_pairing;
+
+	DACPShare *dacp_share;
+
+	GtkBuilder *builder;
+	GtkWidget *entries[4];
+	GtkWidget *finished_widget;
+	GtkWidget *pairing_widget;
+	GtkWidget *pairing_status_widget;
+};
+
+enum {
+	PROP_0,
+	PROP_SERVICE_NAME
+};
+
+G_DEFINE_TYPE (RBDACPSource, rb_dacp_source, RB_TYPE_SOURCE)
+
+static gboolean
+entry_insert_text_cb (GtkWidget *entry, gchar *text, gint len, gint *position, RBDACPSource *source)
+{
+	gchar new_char = text[*position];
+	gint entry_pos = 0;
+	gchar passcode[4];
+	int i;
+
+	/* Find out which entry the user just entered text */
+	for (entry_pos = 0; entry_pos < 4; entry_pos++) {
+		if (entry == source->priv->entries[entry_pos]) {
+			break;
+		}
+	}
+
+	if (!isdigit (new_char)) {
+		/* is this a number? If not, don't let it in */
+		g_signal_stop_emission_by_name(entry, "insert-text");
+		return TRUE;
+	}
+	if (entry_pos < 3) {
+		/* Focus the next entry */
+		gtk_widget_grab_focus(source->priv->entries[entry_pos + 1]);
+	} else if (entry_pos == 3) {
+		/* The user entered all 4 characters of the passcode, so let's pair */
+		for (i = 0; i < 3; i++) {
+			const gchar *text = gtk_entry_get_text (GTK_ENTRY (source->priv->entries[i]));
+			passcode[i] = text[0];
+		}
+		/* The last character is still not in the entry */
+		passcode[3] = new_char;
+		rb_dacp_source_connecting (source, TRUE);
+		/* Let DACPShare do the heavy-lifting */
+		dacp_share_pair(source->priv->dacp_share,
+		                source->priv->service_name,
+		                passcode);
+	}
+	/* let the default handler display the number */
+	return FALSE;
+}
+
+static gboolean
+entry_backspace_cb (GtkWidget *entry, RBDACPSource *rb_dacp_source)
+{
+	gint entry_pos = 0;
+
+	/* Find out which entry the user just entered text */
+	for (entry_pos = 0; entry_pos < 4; entry_pos++) {
+		if (entry == rb_dacp_source->priv->entries[entry_pos]) {
+			break;
+		}
+	}
+
+	if (entry_pos > 0) {
+		gtk_entry_set_text (GTK_ENTRY (rb_dacp_source->priv->entries[entry_pos]), "");
+		/* Focus the previous entry */
+		gtk_widget_grab_focus (rb_dacp_source->priv->entries[entry_pos - 1]);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+close_pairing_clicked_cb (GtkButton *button, RBDACPSource *source)
+{
+	rb_source_delete_thyself (RB_SOURCE (source));
+
+	return FALSE;
+}
+
+static void
+rb_dacp_source_dispose (GObject *object)
+{
+	G_OBJECT_CLASS (rb_dacp_source_parent_class)->dispose (object);
+}
+
+static void
+rb_dacp_source_finalize (GObject *object)
+{
+	RBDACPSource *source = RB_DACP_SOURCE (object);
+
+	g_free (source->priv->service_name);
+	g_object_unref (source->priv->builder);
+	g_object_unref (source->priv->dacp_share);
+
+	G_OBJECT_CLASS (rb_dacp_source_parent_class)->finalize (object);
+}
+
+static void
+rb_dacp_source_class_init (RBDACPSourceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+
+	object_class->dispose      = rb_dacp_source_dispose;
+	object_class->finalize     = rb_dacp_source_finalize;
+	object_class->get_property = rb_dacp_source_get_property;
+	object_class->set_property = rb_dacp_source_set_property;
+
+	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
+	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
+
+	g_object_class_install_property (object_class,
+					 PROP_SERVICE_NAME,
+					 g_param_spec_string ("service-name",
+							      "Service name",
+							      "mDNS/DNS-SD service name of the share",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (klass, sizeof (RBDACPSourcePrivate));
+}
+
+static void
+rb_dacp_source_init (RBDACPSource *source)
+{
+	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source,
+						    RB_TYPE_DACP_SOURCE,
+						    RBDACPSourcePrivate);
+}
+
+static void
+rb_dacp_source_set_property (GObject *object,
+			    guint prop_id,
+			    const GValue *value,
+			    GParamSpec *pspec)
+{
+	RBDACPSource *source = RB_DACP_SOURCE (object);
+
+	switch (prop_id) {
+		case PROP_SERVICE_NAME:
+			source->priv->service_name = g_value_dup_string (value);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+static void
+rb_dacp_source_get_property (GObject *object,
+			    guint prop_id,
+			    GValue *value,
+			    GParamSpec *pspec)
+{
+	RBDACPSource *source = RB_DACP_SOURCE (object);
+
+	switch (prop_id) {
+		case PROP_SERVICE_NAME:
+			g_value_set_string (value, source->priv->service_name);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+			break;
+	}
+}
+
+RBDACPSource *
+rb_dacp_source_new (RBPlugin *plugin,
+                    RBShell *shell,
+                    DACPShare *dacp_share,
+                    const char *display_name,
+                    const char *service_name)
+{
+	RBDACPSource *source;
+	RhythmDB *db;
+	RhythmDBQueryModel *query_model;
+	RBSourceGroup *source_group;
+
+	/* Source icon data */
+	gchar *icon_filename;
+	gint icon_size;
+	GdkPixbuf *icon_pixbuf;
+
+	icon_filename = rb_plugin_find_file (plugin, "remote-icon.png");
+	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &icon_size, NULL);
+	icon_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_filename, icon_size, icon_size, NULL);
+
+	/* Remotes category */
+	source_group = rb_source_group_get_by_name ("remotes");
+	if (source_group == NULL) {
+		source_group = rb_source_group_register ("remotes",
+		                                         _("Remotes"),
+		                                         RB_SOURCE_GROUP_CATEGORY_TRANSIENT);
+	}
+
+	/* This stops some assertions failing due to no query-model */
+	g_object_get (shell, "db", &db, NULL);
+	query_model = rhythmdb_query_model_new_empty (db);
+
+	source = RB_DACP_SOURCE (g_object_new (RB_TYPE_DACP_SOURCE,
+					  "name", display_name,
+					  "service-name", service_name,
+					  "icon", icon_pixbuf,
+					  "shell", shell,
+					  "visibility", TRUE,
+					  "entry-type", RHYTHMDB_ENTRY_TYPE_IGNORE,
+					  "source-group", source_group,
+					  "plugin", plugin,
+					  "query-model", query_model,
+					  NULL));
+
+	g_object_ref (dacp_share);
+	source->priv->dacp_share = dacp_share;
+	/* Retrieve notifications when the remote is finished pairing */
+	g_signal_connect_object (dacp_share, "remote-paired", G_CALLBACK (remote_paired_cb), source, 0);
+
+	g_free (icon_filename);
+	g_object_unref (icon_pixbuf);
+
+	g_object_unref (db);
+	g_object_unref (query_model);
+
+	rb_dacp_source_create_ui (source, plugin);
+
+	return source;
+}
+
+static void
+rb_dacp_source_create_ui (RBDACPSource *source, RBPlugin *plugin)
+{
+	gchar *builder_filename;
+	GtkButton *close_pairing_button;
+	PangoFontDescription *font;
+	int i;
+
+	builder_filename = rb_plugin_find_file (RB_PLUGIN (plugin), "daap-prefs.ui");
+	g_return_if_fail (builder_filename != NULL);
+
+	source->priv->builder = rb_builder_load (builder_filename, NULL);
+	g_free (builder_filename);
+
+	GtkWidget *passcode_widget = GTK_WIDGET (gtk_builder_get_object (source->priv->builder, "passcode_widget"));
+	gtk_container_add(GTK_CONTAINER(source), passcode_widget);
+
+	close_pairing_button = GTK_BUTTON (gtk_builder_get_object (source->priv->builder, "close_pairing_button"));
+	g_signal_connect_object (close_pairing_button, "clicked", G_CALLBACK (close_pairing_clicked_cb), source, 0);
+
+	source->priv->finished_widget = GTK_WIDGET (gtk_builder_get_object (source->priv->builder, "finished_widget"));
+	source->priv->pairing_widget = GTK_WIDGET (gtk_builder_get_object (source->priv->builder, "pairing_widget"));
+	source->priv->pairing_status_widget = GTK_WIDGET (gtk_builder_get_object (source->priv->builder, "pairing_status_widget"));
+
+	font = pango_font_description_from_string ("normal 28");
+
+	for (i = 0; i < 4; i++) {
+		gchar *entry_name = g_strdup_printf ("passcode_entry%d", i + 1);
+		source->priv->entries[i] = GTK_WIDGET (gtk_builder_get_object (source->priv->builder, entry_name));
+		gtk_widget_modify_font (source->priv->entries[i], font);
+		g_signal_connect_object (source->priv->entries[i],
+		                         "insert-text",
+		                         G_CALLBACK (entry_insert_text_cb),
+		                         source,
+		                         0);
+		g_signal_connect_object (source->priv->entries[i],
+		                         "backspace",
+		                         G_CALLBACK (entry_backspace_cb),
+		                         source,
+		                         0);
+		g_free(entry_name);
+	}
+
+	pango_font_description_free (font);
+
+	gtk_widget_show(passcode_widget);
+}
+
+
+static void
+rb_dacp_source_reset_passcode (RBDACPSource *source)
+{
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		gtk_entry_set_text (GTK_ENTRY (source->priv->entries[i]), "");
+	}
+	gtk_widget_grab_focus (source->priv->entries [0]);
+}
+
+void
+rb_dacp_source_remote_found (RBDACPSource *source)
+{
+	if (source->priv->done_pairing) {
+		rb_dacp_source_reset_passcode (source);
+		gtk_widget_show (source->priv->pairing_widget);
+		gtk_widget_hide (source->priv->pairing_status_widget);
+		gtk_widget_hide (source->priv->finished_widget);
+		source->priv->done_pairing = FALSE;
+	}
+}
+
+void
+rb_dacp_source_remote_lost (RBDACPSource *source)
+{
+	if (!source->priv->done_pairing) {
+		rb_source_delete_thyself (RB_SOURCE (source));
+	}
+}
+
+static void
+rb_dacp_source_connecting (RBDACPSource *source, gboolean connecting) {
+	int i;
+
+	if (connecting) {
+		gtk_widget_show (source->priv->pairing_status_widget);
+		gtk_label_set_markup (GTK_LABEL (source->priv->pairing_status_widget), _("Connecting..."));
+	} else {
+		gtk_label_set_markup (GTK_LABEL (source->priv->pairing_status_widget), _("Could not pair with this Remote."));
+	}
+
+	for (i = 0; i < 4; i++) {
+		gtk_widget_set_sensitive (source->priv->entries [i], !connecting);
+	}
+}
+
+static void
+remote_paired_cb (DACPShare *share, gchar *service_name, gboolean connected, RBDACPSource *source)
+{
+	/* Check if this remote is the remote paired */
+	if (g_strcmp0(service_name, source->priv->service_name) != 0)
+		return;
+	rb_dacp_source_connecting (source, FALSE);
+	if (connected) {
+		gtk_widget_hide (source->priv->pairing_widget);
+		gtk_widget_show (source->priv->finished_widget);
+		source->priv->done_pairing = TRUE;
+	} else {
+		gtk_widget_show (source->priv->pairing_status_widget);
+		rb_dacp_source_reset_passcode (source);
+	}
+}
+
+DACPShare *
+rb_daap_create_dacp_share (RBPlugin *plugin)
+{
+	DACPShare *share;
+	DACPPlayer *player;
+	RhythmDB *rdb;
+	DMAPDb *db;
+	DMAPContainerDb *container_db;
+	RBPlaylistManager *playlist_manager;
+	RBShell *shell;
+	gchar *name;
+
+	g_object_get (plugin, "shell", &shell, NULL);
+
+	g_object_get (shell,
+	              "db", &rdb,
+	              "playlist-manager", &playlist_manager,
+	              NULL);
+	db = DMAP_DB (rb_rhythmdb_dmap_db_adapter_new (rdb, RHYTHMDB_ENTRY_TYPE_SONG));
+	container_db = DMAP_CONTAINER_DB (rb_dmap_container_db_adapter_new (playlist_manager));
+
+	player = DACP_PLAYER (rb_dacp_player_new (shell));
+
+	name = eel_gconf_get_string (CONF_DAAP_SHARE_NAME);
+	if (name == NULL || *name == '\0') {
+		g_free (name);
+		name = rb_daap_sharing_default_share_name ();
+	}
+
+	share = dacp_share_new (name, player, db, container_db);
+
+	g_signal_connect_object (share,
+				 "add-guid",
+				 G_CALLBACK (dacp_add_guid),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+	g_signal_connect_object (share,
+				 "lookup-guid",
+				 G_CALLBACK (dacp_lookup_guid),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+
+	g_signal_connect_object (share,
+				 "remote-found",
+				 G_CALLBACK (dacp_remote_added),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+	g_signal_connect_object (share,
+				 "remote-lost",
+				 G_CALLBACK (dacp_remote_removed),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+
+	g_signal_connect_object (player,
+	                         "player-updated",
+	                         G_CALLBACK (dacp_player_updated),
+	                         share,
+	                         0);
+
+	g_object_unref (db);
+	g_object_unref (container_db);
+	g_object_unref (rdb);
+	g_object_unref (playlist_manager);
+	g_object_unref (player);
+
+	return share;
+}
+
+static void
+dacp_player_updated (RBDACPPlayer *player,
+                     DACPShare *share)
+{
+	dacp_share_player_updated (share);
+}
+
+static void
+dacp_add_guid (DACPShare *share,
+               gchar *guid)
+{
+	GSList *known_guids;
+
+	known_guids = eel_gconf_get_string_list (CONF_KNOWN_REMOTES);
+	if (g_slist_find_custom (known_guids, guid, (GCompareFunc) g_strcmp0)) {
+		g_slist_free (known_guids);
+		return;
+	}
+	known_guids = g_slist_insert_sorted (known_guids, guid, (GCompareFunc) g_strcmp0);
+	eel_gconf_set_string_list (CONF_KNOWN_REMOTES, known_guids);
+
+	g_slist_free (known_guids);
+}
+
+static gboolean
+dacp_lookup_guid (DACPShare *share,
+                  gchar *guid)
+{
+	GSList *known_guids;
+	int found;
+
+	known_guids = eel_gconf_get_string_list (CONF_KNOWN_REMOTES);
+	found = g_slist_find_custom (known_guids, guid, (GCompareFunc) g_strcmp0) != NULL;
+
+	g_slist_free (known_guids);
+
+	return found;
+}
+
+/* Remote sources */
+typedef struct {
+	const gchar *name;
+	gboolean    found;
+	RBDACPSource *source;
+} FindSource;
+
+static gboolean
+sourcelist_find_dacp_source_foreach (GtkTreeModel *model,
+                                     GtkTreePath  *path,
+                                     GtkTreeIter  *iter,
+                                     FindSource   *fg)
+{
+	gchar *name;
+	RBSource *source;
+
+	gtk_tree_model_get (model, iter,
+	                    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+	                    -1);
+	if (source && RB_IS_DACP_SOURCE (source)) {
+		g_object_get (source, "service-name", &name, NULL);
+		if (strcmp (name, fg->name) == 0) {
+			fg->found = TRUE;
+			fg->source = RB_DACP_SOURCE (source);
+		}
+		g_free (name);
+	}
+
+	return fg->found;
+}
+
+static RBDACPSource *
+find_dacp_source (RBShell *shell, const gchar *service_name)
+{
+	RBSourceListModel *source_list;
+	FindSource find_group;
+
+	find_group.found = FALSE;
+	find_group.name = service_name;
+
+	g_object_get (shell, "sourcelist-model", &source_list, NULL);
+
+	gtk_tree_model_foreach (GTK_TREE_MODEL (source_list),
+	                        (GtkTreeModelForeachFunc) sourcelist_find_dacp_source_foreach,
+	                        &find_group);
+
+	if (find_group.found) {
+		g_assert (find_group.source != NULL);
+		return find_group.source;
+	} else {
+		return NULL;
+	}
+}
+
+static void
+dacp_remote_added (DACPShare    *share,
+                   gchar        *service_name,
+                   gchar        *display_name,
+                   RBDaapPlugin *plugin)
+{
+	RBDACPSource *source;
+	RBShell *shell;
+
+	rb_debug ("Remote %s (%s) found", service_name, display_name);
+
+	g_object_get (plugin, "shell", &shell, NULL);
+
+	GDK_THREADS_ENTER ();
+
+	source = find_dacp_source (shell, service_name);
+	if (source == NULL) {
+		source = rb_dacp_source_new (RB_PLUGIN (plugin),
+		                             shell,
+		                             share,
+		                             display_name,
+		                             service_name);
+		rb_shell_append_source (shell, RB_SOURCE (source), NULL);
+	} else {
+		rb_dacp_source_remote_found (source);
+	}
+
+	GDK_THREADS_LEAVE ();
+}
+
+static void
+dacp_remote_removed (DACPShare       *share,
+                     gchar           *service_name,
+                     RBDaapPlugin    *plugin)
+{
+	RBDACPSource *source;
+	RBShell *shell;
+
+	rb_debug ("Remote '%s' went away", service_name);
+
+	g_object_get (plugin, "shell", &shell, NULL);
+
+	GDK_THREADS_ENTER ();
+
+	source = find_dacp_source (shell, service_name);
+
+	if (source != NULL) {
+		rb_dacp_source_remote_lost (source);
+	}
+
+	GDK_THREADS_LEAVE ();
+}
diff --git a/plugins/daap/rb-dacp-source.h b/plugins/daap/rb-dacp-source.h
new file mode 100644
index 0000000..581d6cf
--- /dev/null
+++ b/plugins/daap/rb-dacp-source.h
@@ -0,0 +1,79 @@
+/*
+ *  Header for DACP (iTunes Remote) source object
+ *
+ *  Copyright (C) 2010 Alexandre Rosenfeld <alexandre rosenfeld gmail com>
+ *
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_DACP_SOURCE_H
+#define __RB_DACP_SOURCE_H
+
+#include "rb-shell.h"
+#include "rb-source.h"
+#include "rb-plugin.h"
+
+#include <libdmapsharing/dmap.h>
+
+G_BEGIN_DECLS
+
+/* preferences */
+#define CONF_DACP_PREFIX  	CONF_PREFIX "/plugins/daap"
+#define CONF_KNOWN_REMOTES 	CONF_DACP_PREFIX "/known_remotes"
+
+#define RB_TYPE_DACP_SOURCE         (rb_dacp_source_get_type ())
+#define RB_DACP_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DACP_SOURCE, RBDACPSource))
+#define RB_DACP_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DACP_SOURCE, RBDACPSourceClass))
+#define RB_IS_DACP_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DACP_SOURCE))
+#define RB_IS_DACP_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DACP_SOURCE))
+#define RB_DACP_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DACP_SOURCE, RBDACPSourceClass))
+
+typedef struct RBDACPSourcePrivate RBDACPSourcePrivate;
+
+typedef struct {
+	RBSource parent;
+
+	RBDACPSourcePrivate *priv;
+} RBDACPSource;
+
+typedef struct {
+	RBSourceClass parent;
+} RBDACPSourceClass;
+
+GType 		rb_dacp_source_get_type 	(void);
+
+RBDACPSource* rb_dacp_source_new 		(RBPlugin *plugin,
+						 RBShell *shell,
+						 DACPShare *dacp_share,
+						 const char *display_name,
+						 const char *service_name);
+
+void           rb_dacp_source_remote_found     (RBDACPSource *source);
+void           rb_dacp_source_remote_lost      (RBDACPSource *source);
+
+DACPShare     *rb_daap_create_dacp_share       (RBPlugin *plugin);
+
+G_END_DECLS
+
+#endif /* __RB_DACP_SOURCE_H */
diff --git a/plugins/daap/remote-icon.png b/plugins/daap/remote-icon.png
new file mode 100644
index 0000000..3ff94a8
Binary files /dev/null and b/plugins/daap/remote-icon.png differ
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 041eb51..fcc548a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -77,6 +77,7 @@ plugins/daap/rb-daap-dialog.c
 plugins/daap/rb-daap-plugin.c
 plugins/daap/rb-daap-sharing.c
 plugins/daap/rb-daap-source.c
+plugins/daap/rb-dacp-source.c
 plugins/daap/rb-rhythmdb-dmap-db-adapter.c
 [type: gettext/ini]plugins/dbus-media-server/dbus-media-server.rb-plugin.in
 [type: gettext/ini]plugins/fmradio/fmradio.rb-plugin.in



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]