[rhythmbox] daap: add support for DACP (iTunes remote control) (bug #625214)
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] daap: add support for DACP (iTunes remote control) (bug #625214)
- Date: Mon, 18 Oct 2010 11:31:38 +0000 (UTC)
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">●</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">●</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"><b>Add Remote</b></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">●</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">●</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">●</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">●</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"><span foreground="red">Could not pair with this Remote.</span></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]