[gnome-photos] dlna: Display item on remote DLNA renderer using dLeyna
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-photos] dlna: Display item on remote DLNA renderer using dLeyna
- Date: Thu, 27 Jun 2013 11:39:02 +0000 (UTC)
commit 8e0c3cc17c4562420ff306600b7845af4f917b59
Author: Emanuele Aina <emanuele aina collabora com>
Date: Mon May 20 11:19:07 2013 +0100
dlna: Display item on remote DLNA renderer using dLeyna
Let the user choose a DLNA renderer device from the list of DMRs
discovered on the network and display the current item there.
This adds a soft-dependency on the deyna-renderer DBus service: if the
service is not activatable the functionality will be automatically
disabled.
https://bugzilla.gnome.org/show_bug.cgi?id=701775
configure.ac | 2 +-
po/POTFILES.in | 1 +
src/Makefile.am | 50 ++
src/photos-application.c | 24 +
src/photos-dleyna-renderer-device.xml | 62 +++
src/photos-dleyna-renderer-manager.xml | 46 ++
src/photos-dleyna-renderer-push-host.xml | 36 ++
src/photos-dlna-renderer.c | 881 ++++++++++++++++++++++++++++++
src/photos-dlna-renderer.h | 138 +++++
src/photos-dlna-renderers-dialog.c | 290 ++++++++++
src/photos-dlna-renderers-dialog.h | 71 +++
src/photos-dlna-renderers-dialog.ui | 70 +++
src/photos-dlna-renderers-manager.c | 267 +++++++++
src/photos-dlna-renderers-manager.h | 75 +++
src/photos-main-toolbar.c | 10 +
src/photos-main.c | 4 +
src/photos-mpris-player.xml | 681 +++++++++++++++++++++++
src/photos-preview-menu.ui | 4 +
src/photos-remote-display-manager.c | 289 ++++++++++
src/photos-remote-display-manager.h | 86 +++
src/photos.gresource.xml | 1 +
21 files changed, 3087 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 11c29d2..1f0177b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,7 +26,7 @@ GLIB_GSETTINGS
EXEMPI_MIN_VERSION=1.99.5
GLIB_MIN_VERSION=2.35.1
-GTK_MIN_VERSION=3.8.0
+GTK_MIN_VERSION=3.9.4
LIBEXIF_MIN_VERSION=0.6.14
LIBRSVG_MIN_VERSION=2.26.0
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ec3b8d5..286d28d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ src/eog-uri-converter.c
src/eog-util.c
src/photos-application.c
[type: gettext/glade]src/photos-app-menu.ui
+[type: gettext/glade]src/photos-dlna-renderers-dialog.ui
src/photos-embed.c
src/photos-empty-results-box.c
src/photos-indexing-notification.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 0a8b13f..200afb5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,8 +8,16 @@ gnome_photos_built_sources = \
photos-about-data.h \
photos-enums.c \
photos-enums.h \
+ photos-dleyna-renderer-device.c \
+ photos-dleyna-renderer-device.h \
+ photos-dleyna-renderer-manager.c \
+ photos-dleyna-renderer-manager.h \
+ photos-dleyna-renderer-push-host.c \
+ photos-dleyna-renderer-push-host.h \
photos-marshalers.c \
photos-marshalers.h \
+ photos-mpris-player.c \
+ photos-mpris-player.h \
photos-resources.c \
photos-resources.h \
photos-tracker-resources.c \
@@ -66,6 +74,12 @@ gnome_photos_SOURCES = \
photos-create-collection-job.h \
photos-delete-item-job.c \
photos-delete-item-job.h \
+ photos-dlna-renderer.c \
+ photos-dlna-renderer.h \
+ photos-dlna-renderers-dialog.c \
+ photos-dlna-renderers-dialog.h \
+ photos-dlna-renderers-manager.c \
+ photos-dlna-renderers-manager.h \
photos-embed.c \
photos-embed.h \
photos-empty-results-box.c \
@@ -124,6 +138,8 @@ gnome_photos_SOURCES = \
photos-query.h \
photos-query-builder.h \
photos-query-builder.c \
+ photos-remote-display-manager.c \
+ photos-remote-display-manager.h \
photos-search-type.c \
photos-search-type.h \
photos-search-type-manager.c \
@@ -180,10 +196,15 @@ EXTRA_DIST = \
photos-generate-about \
photos.gresource.xml \
photos-app-menu.ui \
+ photos-dlna-renderers-dialog.ui \
photos-marshalers.list \
photos-preview-menu.ui \
photos-selection-menu.ui \
photos-tracker-resources.xml \
+ photos-dleyna-renderer-device.xml \
+ photos-dleyna-renderer-manager.xml \
+ photos-dleyna-renderer-push-host.xml \
+ photos-mpris-player.xml \
$(null)
AM_CPPFLAGS = \
@@ -363,6 +384,35 @@ photos-resources.h: photos.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --sou
--generate-header \
$<
+photos-dleyna-renderer-device.h photos-dleyna-renderer-device.c: photos-dleyna-renderer-device.xml
+ $(AM_V_GEN)gdbus-codegen \
+ --c-namespace Dleyna \
+ --generate-c-code photos-dleyna-renderer-device \
+ --interface-prefix com.intel.dLeynaRenderer. \
+ --annotate 'com.intel.dLeynaRenderer.RendererDevice.GetIcon()[Bytes]'
org.gtk.GDBus.C.ForceGVariant true \
+ $<
+
+photos-dleyna-renderer-manager.h photos-dleyna-renderer-manager.c: photos-dleyna-renderer-manager.xml
+ $(AM_V_GEN)gdbus-codegen \
+ --c-namespace Dleyna \
+ --generate-c-code photos-dleyna-renderer-manager \
+ --interface-prefix com.intel.dLeynaRenderer. \
+ $<
+
+photos-dleyna-renderer-push-host.h photos-dleyna-renderer-push-host.c: photos-dleyna-renderer-push-host.xml
+ $(AM_V_GEN)gdbus-codegen \
+ --c-namespace Dleyna \
+ --generate-c-code photos-dleyna-renderer-push-host \
+ --interface-prefix com.intel.dLeynaRenderer. \
+ $<
+
+photos-mpris-player.h photos-mpris-player.c: photos-mpris-player.xml
+ $(AM_V_GEN)gdbus-codegen \
+ --c-namespace Mpris \
+ --generate-c-code photos-mpris-player \
+ --interface-prefix org.mpris.MediaPlayer2. \
+ $<
+
photos-tracker-resources.h photos-tracker-resources.c: photos-tracker-resources.xml
$(AM_V_GEN)gdbus-codegen \
--c-namespace Tracker \
diff --git a/src/photos-application.c b/src/photos-application.c
index 1ee6c58..567966e 100644
--- a/src/photos-application.c
+++ b/src/photos-application.c
@@ -33,6 +33,7 @@
#include "eog-debug.h"
#include "photos-application.h"
+#include "photos-dlna-renderers-dialog.h"
#include "photos-item-manager.h"
#include "photos-main-window.h"
#include "photos-mode-controller.h"
@@ -52,6 +53,7 @@ struct _PhotosApplicationPrivate
GSimpleAction *sel_all_action;
GSimpleAction *sel_none_action;
GSimpleAction *set_bg_action;
+ GSimpleAction *remote_display_action;
GtkWidget *main_window;
PhotosBaseManager *item_mngr;
PhotosModeController *mode_cntrlr;
@@ -151,6 +153,24 @@ photos_application_properties (PhotosApplication *self)
static void
+photos_application_remote_display_current (PhotosApplication *self)
+{
+ PhotosApplicationPrivate *priv = self->priv;
+ PhotosBaseItem *item;
+ GtkWidget *dialog;
+ const gchar *urn;
+
+ item = PHOTOS_BASE_ITEM (photos_base_manager_get_active_object (priv->item_mngr));
+ if (item == NULL)
+ return;
+
+ urn = photos_base_item_get_id (item);
+ dialog = photos_dlna_renderers_dialog_new (GTK_WINDOW (priv->main_window), urn);
+ gtk_widget_show_all (dialog);
+}
+
+
+static void
photos_application_quit (PhotosApplication *self, GVariant *parameter)
{
gtk_widget_destroy (self->priv->main_window);
@@ -266,6 +286,10 @@ photos_application_startup (GApplication *application)
g_signal_connect_swapped (priv->properties_action, "activate", G_CALLBACK (photos_application_properties),
self);
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (priv->properties_action));
+ priv->remote_display_action = g_simple_action_new ("remote-display-current", NULL);
+ g_signal_connect_swapped (priv->remote_display_action, "activate", G_CALLBACK
(photos_application_remote_display_current), self);
+ g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (priv->remote_display_action));
+
action = g_simple_action_new ("quit", NULL);
g_signal_connect_swapped (action, "activate", G_CALLBACK (photos_application_quit), self);
g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action));
diff --git a/src/photos-dleyna-renderer-device.xml b/src/photos-dleyna-renderer-device.xml
new file mode 100644
index 0000000..e4a83a8
--- /dev/null
+++ b/src/photos-dleyna-renderer-device.xml
@@ -0,0 +1,62 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2013 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+<!-- GDBus 2.37.1 -->
+<node>
+ <interface name="com.intel.dLeynaRenderer.RendererDevice">
+ <method name="Cancel">
+ </method>
+ <method name="GetIcon">
+ <arg type="s" name="RequestedMimeType" direction="in">
+ </arg>
+ <arg type="s" name="Resolution" direction="in">
+ </arg>
+ <arg type="ay" name="Bytes" direction="out">
+ </arg>
+ <arg type="s" name="MimeType" direction="out">
+ </arg>
+ </method>
+ <property type="s" name="DeviceType" access="read">
+ </property>
+ <property type="s" name="UDN" access="read">
+ </property>
+ <property type="s" name="FriendlyName" access="read">
+ </property>
+ <property type="s" name="IconURL" access="read">
+ </property>
+ <property type="s" name="Manufacturer" access="read">
+ </property>
+ <property type="s" name="ManufacturerUrl" access="read">
+ </property>
+ <property type="s" name="ModelDescription" access="read">
+ </property>
+ <property type="s" name="ModelName" access="read">
+ </property>
+ <property type="s" name="ModelNumber" access="read">
+ </property>
+ <property type="s" name="SerialNumber" access="read">
+ </property>
+ <property type="s" name="PresentationURL" access="read">
+ </property>
+ <property type="s" name="ProtocolInfo" access="read">
+ </property>
+ </interface>
+</node>
diff --git a/src/photos-dleyna-renderer-manager.xml b/src/photos-dleyna-renderer-manager.xml
new file mode 100644
index 0000000..d651352
--- /dev/null
+++ b/src/photos-dleyna-renderer-manager.xml
@@ -0,0 +1,46 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2013 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+<!-- GDBus 2.37.1 -->
+<node>
+ <interface name="com.intel.dLeynaRenderer.Manager">
+ <method name="GetVersion">
+ <arg type="s" name="Version" direction="out">
+ </arg>
+ </method>
+ <method name="Release">
+ </method>
+ <method name="GetRenderers">
+ <arg type="as" name="Renderers" direction="out">
+ </arg>
+ </method>
+ <method name="Rescan">
+ </method>
+ <signal name="FoundRenderer">
+ <arg type="s" name="Path">
+ </arg>
+ </signal>
+ <signal name="LostRenderer">
+ <arg type="s" name="Path">
+ </arg>
+ </signal>
+ </interface>
+</node>
diff --git a/src/photos-dleyna-renderer-push-host.xml b/src/photos-dleyna-renderer-push-host.xml
new file mode 100644
index 0000000..f2e68bf
--- /dev/null
+++ b/src/photos-dleyna-renderer-push-host.xml
@@ -0,0 +1,36 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<!--
+ Photos - access, organize and share your photos on GNOME
+ Copyright © 2013 Intel Corporation. All rights reserved.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+-->
+<!-- GDBus 2.37.1 -->
+<node>
+ <interface name="com.intel.dLeynaRenderer.PushHost">
+ <method name="HostFile">
+ <arg type="s" name="Path" direction="in">
+ </arg>
+ <arg type="s" name="Uri" direction="out">
+ </arg>
+ </method>
+ <method name="RemoveFile">
+ <arg type="s" name="Path" direction="in">
+ </arg>
+ </method>
+ </interface>
+</node>
diff --git a/src/photos-dlna-renderer.c b/src/photos-dlna-renderer.c
new file mode 100644
index 0000000..8ded217
--- /dev/null
+++ b/src/photos-dlna-renderer.c
@@ -0,0 +1,881 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-dlna-renderer.h"
+
+
+typedef enum
+{
+ INIT_NOTHING = 0,
+ INIT_DEVICE = 1 << 0,
+ INIT_PUSH_HOST = 1 << 1,
+ INIT_PLAYER = 1 << 2,
+ INIT_READY = INIT_DEVICE | INIT_PUSH_HOST | INIT_PLAYER
+} InitStatus;
+
+
+struct _PhotosDlnaRendererPrivate
+{
+ GBusType bus_type;
+ GDBusProxyFlags flags;
+ gchar *object_path;
+ gchar *well_known_name;
+
+ GTask *init_task;
+ GError *init_error;
+
+ DleynaRendererDevice *device;
+ DleynaPushHost *push_host;
+ MprisPlayer *player;
+
+ InitStatus init_status;
+
+ GHashTable *urls_to_item;
+};
+
+
+enum
+{
+ PROP_0,
+ PROP_BUS_TYPE,
+ PROP_WELL_KNOWN_NAME,
+ PROP_FLAGS,
+ PROP_OBJECT_PATH,
+ PROP_SHARED_COUNT,
+};
+
+
+static void photos_dlna_renderer_async_initable_iface_init (GAsyncInitableIface *iface);
+
+
+G_DEFINE_TYPE_WITH_CODE (PhotosDlnaRenderer, photos_dlna_renderer, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
photos_dlna_renderer_async_initable_iface_init));
+
+
+static void
+photos_dlna_renderer_init_cleanup (PhotosDlnaRenderer *self)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ g_clear_error (&priv->init_error);
+ g_clear_object (&priv->init_task);
+}
+
+
+static void
+photos_dlna_renderer_dispose (GObject *object)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ g_clear_object (&priv->device);
+ g_clear_object (&priv->push_host);
+ g_clear_object (&priv->player);
+ g_clear_pointer (&priv->urls_to_item, g_hash_table_unref);
+
+ photos_dlna_renderer_init_cleanup (self);
+
+ G_OBJECT_CLASS (photos_dlna_renderer_parent_class)->dispose (object);
+}
+
+
+static void
+photos_dlna_renderer_finalize (GObject *object)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ g_free (priv->well_known_name);
+ g_free (priv->object_path);
+
+ G_OBJECT_CLASS (photos_dlna_renderer_parent_class)->finalize (object);
+}
+
+
+static void
+photos_dlna_renderer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+
+ switch (prop_id)
+ {
+ case PROP_SHARED_COUNT:
+ g_value_set_uint (value, photos_dlna_renderer_get_shared_count (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+photos_dlna_renderer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (object);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ switch (prop_id)
+ {
+ case PROP_FLAGS:
+ priv->flags = g_value_get_flags (value);
+ break;
+
+ case PROP_BUS_TYPE:
+ priv->bus_type = g_value_get_enum (value);
+ break;
+
+ case PROP_WELL_KNOWN_NAME:
+ priv->well_known_name = g_value_dup_string (value);
+ break;
+
+ case PROP_OBJECT_PATH:
+ priv->object_path = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+photos_dlna_renderer_init (PhotosDlnaRenderer *self)
+{
+ PhotosDlnaRendererPrivate *priv;
+
+ self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOS_TYPE_DLNA_RENDERER,
+ PhotosDlnaRendererPrivate);
+
+ priv->urls_to_item = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+
+static void
+photos_dlna_renderer_class_init (PhotosDlnaRendererClass *class)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+ gobject_class->dispose = photos_dlna_renderer_dispose;
+ gobject_class->finalize = photos_dlna_renderer_finalize;
+ gobject_class->get_property = photos_dlna_renderer_get_property;
+ gobject_class->set_property = photos_dlna_renderer_set_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_FLAGS,
+ g_param_spec_flags ("flags",
+ "Flags",
+ "Proxy flags",
+ G_TYPE_DBUS_PROXY_FLAGS,
+ G_DBUS_PROXY_FLAGS_NONE,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ g_object_class_install_property (gobject_class,
+ PROP_BUS_TYPE,
+ g_param_spec_enum ("bus-type",
+ "Bus Type",
+ "The bus to connect to, defaults to the session one",
+ G_TYPE_BUS_TYPE,
+ G_BUS_TYPE_SESSION,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ g_object_class_install_property (gobject_class,
+ PROP_WELL_KNOWN_NAME,
+ g_param_spec_string ("well-known-name",
+ "Well-Known Name",
+ "The well-known name of the service",
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ g_object_class_install_property (gobject_class,
+ PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "Object Path",
+ "The object path the proxy is for",
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ g_object_class_install_property (gobject_class,
+ PROP_SHARED_COUNT,
+ g_param_spec_uint ("shared-count",
+ "Shared Count",
+ "The number of shared items",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_BLURB |
+ G_PARAM_STATIC_NICK));
+
+ g_type_class_add_private (class, sizeof (PhotosDlnaRendererPrivate));
+}
+
+
+void
+photos_dlna_renderer_new_for_bus (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *well_known_name,
+ const gchar *object_path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (PHOTOS_TYPE_DLNA_RENDERER, G_PRIORITY_DEFAULT, cancellable,
+ callback, user_data, "flags", flags, "bus-type", bus_type,
+ "well-known-name", well_known_name,
+ "object-path", object_path, NULL);
+}
+
+
+/* photos_dlna_renderer_init_check_complete:
+ *
+ * Checks that all the async subtasks have completed and return the result.
+ *
+ * Note that if the caller does not take ownership of the object (eg. by not
+ * calling g_task_propagate_pointer() or in case of errors) this may lead to
+ * the object being disposed.
+ */
+static gboolean
+photos_dlna_renderer_init_check_complete (PhotosDlnaRenderer *self,
+ GError *error)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ g_return_val_if_fail (g_task_is_valid (priv->init_task, self), TRUE);
+
+ /* save the first error and then wait for all tasks to be completed
+ * (succeeding or failing) before returning it */
+ if (error != NULL && priv->init_error == NULL)
+ priv->init_error = g_error_copy (error);
+
+ if (priv->init_status != INIT_READY)
+ return FALSE;
+
+ /* make sure that `self` gets NULLified if g_object_unref() is
+ * called inside g_task_return_pointer() to prevent calling
+ * photos_dlna_renderer_init_cleanup() on an already freed object */
+ g_object_add_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+ if (priv->init_error != NULL)
+ {
+ g_task_return_error (priv->init_task, priv->init_error);
+ priv->init_error = NULL;
+ }
+ else
+ g_task_return_pointer (priv->init_task, self, g_object_unref);
+
+ if (self != NULL)
+ photos_dlna_renderer_init_cleanup (self);
+
+ g_object_remove_weak_pointer (G_OBJECT (self), (gpointer *)&self);
+
+ return TRUE;
+}
+
+
+static void
+photos_dlna_renderer_device_proxy_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (user_data);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GError *error = NULL;
+
+ priv->init_status |= INIT_DEVICE;
+ priv->device = dleyna_renderer_device_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ g_warning ("Unable to load the RendererDevice interface: %s", error->message);
+
+ photos_dlna_renderer_init_check_complete (self, error);
+
+ g_clear_error (&error);
+}
+
+
+static void
+photos_dlna_renderer_push_host_proxy_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (user_data);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GError *error = NULL;
+
+ priv->init_status |= INIT_PUSH_HOST;
+ priv->push_host = dleyna_push_host_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ g_warning ("Unable to load the PushHost interface: %s", error->message);
+
+ photos_dlna_renderer_init_check_complete (self, error);
+
+ g_clear_error (&error);
+}
+
+
+static void
+photos_dlna_renderer_player_proxy_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (user_data);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GError *error = NULL;
+
+ priv->init_status |= INIT_PLAYER;
+ priv->player = mpris_player_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ g_warning ("Unable to load the Player interface: %s", error->message);
+
+ photos_dlna_renderer_init_check_complete (self, error);
+
+ g_clear_error (&error);
+}
+
+
+static void
+photos_dlna_renderer_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (initable);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ priv->init_task = g_task_new (initable, cancellable, callback, user_data);
+ g_task_set_priority (priv->init_task, io_priority);
+
+ /* Load all three DBus interface proxies asynchronously and then complete
+ * the initialization when all of them are ready */
+ dleyna_renderer_device_proxy_new_for_bus (priv->bus_type, priv->flags, priv->well_known_name,
+ priv->object_path, cancellable,
+ photos_dlna_renderer_device_proxy_new_cb,
+ self);
+ dleyna_push_host_proxy_new_for_bus (priv->bus_type, priv->flags, priv->well_known_name,
+ priv->object_path, cancellable,
+ photos_dlna_renderer_push_host_proxy_new_cb,
+ self);
+ mpris_player_proxy_new_for_bus (priv->bus_type, priv->flags, priv->well_known_name,
+ priv->object_path, cancellable,
+ photos_dlna_renderer_player_proxy_new_cb,
+ self);
+}
+
+
+static gboolean
+photos_dlna_renderer_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (initable)), FALSE);
+
+ return g_task_propagate_pointer (G_TASK (result), error) != NULL;
+}
+
+
+PhotosDlnaRenderer*
+photos_dlna_renderer_new_for_bus_finish (GAsyncResult *result,
+ GError **error)
+{
+ GObject *object, *source_object;
+ GError *err = NULL;
+
+ source_object = g_async_result_get_source_object (result);
+ object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), result, &err);
+ g_object_unref (source_object);
+
+ if (err != NULL)
+ {
+ g_clear_object (&object);
+ g_propagate_error (error, err);
+ return NULL;
+ }
+
+ return PHOTOS_DLNA_RENDERER (object);
+}
+
+
+static void
+photos_dlna_renderer_async_initable_iface_init (GAsyncInitableIface *iface)
+{
+ iface->init_async = photos_dlna_renderer_init_async;
+ iface->init_finish = photos_dlna_renderer_init_finish;
+}
+
+
+const gchar *
+photos_dlna_renderer_get_object_path (PhotosDlnaRenderer *self)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ return priv->object_path;
+}
+
+
+DleynaRendererDevice *
+photos_dlna_renderer_get_device (PhotosDlnaRenderer *self)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ return priv->device;
+}
+
+
+static void
+photos_dlna_renderer_share_play_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ PhotosDlnaRenderer *self;
+ PhotosDlnaRendererPrivate *priv;
+ PhotosBaseItem *item;
+ GError *error = NULL;
+
+ self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+ priv = self->priv;
+
+ mpris_player_call_play_finish (MPRIS_PLAYER (source_object), res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Failed to call the Play method: %s", error->message);
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ item = g_object_get_data (G_OBJECT (task), "item");
+ g_task_return_pointer (task, g_object_ref (item), g_object_unref);
+
+out:
+ g_object_unref (task);
+}
+
+
+static void
+photos_dlna_renderer_share_open_uri_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ PhotosDlnaRenderer *self;
+ PhotosDlnaRendererPrivate *priv;
+ GError *error = NULL;
+
+ self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+ priv = self->priv;
+
+ mpris_player_call_open_uri_finish (MPRIS_PLAYER (source_object), res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Failed to call the OpenUri method: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* 3) Mpris.Player.Play() */
+ mpris_player_call_play (priv->player,
+ g_task_get_cancellable (task),
+ photos_dlna_renderer_share_play_cb,
+ task);
+}
+
+
+static void
+photos_dlna_renderer_share_host_file_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ PhotosDlnaRenderer *self;
+ PhotosDlnaRendererPrivate *priv;
+ PhotosBaseItem *item;
+ gchar *hosted_url;
+ GError *error = NULL;
+
+ self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+ priv = self->priv;
+
+ dleyna_push_host_call_host_file_finish (DLEYNA_PUSH_HOST (source_object), &hosted_url, res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Failed to call the HostFile method: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ item = g_object_get_data (G_OBJECT (task), "item");
+ g_hash_table_replace (priv->urls_to_item, hosted_url, g_object_ref (item));
+ g_object_notify (G_OBJECT (self), "shared-count");
+
+ /* 2) Mpris.Player.OpenUri(hosted_url) */
+ g_debug ("%s %s", __func__, hosted_url);
+ mpris_player_call_open_uri (priv->player, hosted_url,
+ g_task_get_cancellable (task),
+ photos_dlna_renderer_share_open_uri_cb,
+ task);
+}
+
+void
+photos_dlna_renderer_share (PhotosDlnaRenderer *self,
+ PhotosBaseItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GTask *task;
+ const gchar *uri;
+ gchar *filename;
+ GError *error = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_object_set_data_full (G_OBJECT (task), "item", g_object_ref (item), g_object_unref);
+
+ uri = photos_base_item_get_uri (item);
+ filename = g_filename_from_uri (uri, NULL, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to extract the local filename for the shared item: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* This will call a sequence of DBus methods to send the item to the DMR:
+ * 1) DleynaRenderer.PushHost.HostFile (filename)
+ * → returns hosted_url, the HTTP URL published by the local webserver
+ * 2) Mpris.Player.OpenUri(hosted_url)
+ * 3) Mpris.Player.Play()
+ * → not really needed as OpenUri should automatically switch to Play to
+ * avoid races, but with dleyna-renderer v0.0.1-22-g6981acf it is still
+ * needed, see https://github.com/01org/dleyna-renderer/issues/78
+ */
+
+ /* 1) DleynaRenderer.PushHost.HostFile() */
+ dleyna_push_host_call_host_file (priv->push_host, filename,
+ cancellable,
+ photos_dlna_renderer_share_host_file_cb,
+ task);
+ g_free (filename);
+}
+
+
+PhotosBaseItem *
+photos_dlna_renderer_share_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (res, G_OBJECT (self)), FALSE);
+
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+
+static gboolean
+photos_dlna_renderer_match_by_item_value (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ PhotosBaseItem *a = PHOTOS_BASE_ITEM (value);
+ PhotosBaseItem *b = PHOTOS_BASE_ITEM (user_data);
+
+ return g_strcmp0 (photos_base_item_get_id (a), photos_base_item_get_id (b)) == 0;
+}
+
+
+static void
+photos_dlna_renderer_unshare_remove_file_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ PhotosBaseItem *item;
+ gboolean success = TRUE;
+ GError *error = NULL;
+
+ item = g_object_get_data (G_OBJECT (task), "item");
+ g_hash_table_foreach_remove (priv->urls_to_item, photos_dlna_renderer_match_by_item_value, item);
+ g_object_notify (G_OBJECT (self), "shared-count");
+
+ dleyna_push_host_call_remove_file_finish (DLEYNA_PUSH_HOST (source_object), res, &error);
+ if (error != NULL)
+ {
+ /* Assume that ignoring RemoveFile() errors is safe, since they are
+ * likely caused by the file being already removed or the DBus service
+ * having been restarted. */
+ g_warning ("Failed to call the RemoveFile method: %s", error->message);
+ g_error_free (error);
+ success = FALSE;
+ }
+
+ g_task_return_boolean (task, success);
+
+out:
+ g_object_unref (task);
+}
+
+
+void
+photos_dlna_renderer_unshare (PhotosDlnaRenderer *self,
+ PhotosBaseItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GTask *task;
+ const gchar *uri;
+ gchar *filename;
+ GError *error = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_object_set_data_full (G_OBJECT (task), "item", g_object_ref (item), g_object_unref);
+
+ uri = photos_base_item_get_uri (item);
+ filename = g_filename_from_uri (uri, NULL, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to extract the local filename for the shared item: %s", error->message);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ dleyna_push_host_call_remove_file (priv->push_host, filename, cancellable,
+ photos_dlna_renderer_unshare_remove_file_cb, task);
+ g_free (filename);
+}
+
+
+void
+photos_dlna_renderer_unshare_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_if_fail (g_task_is_valid (res, G_OBJECT (self)));
+
+ g_task_propagate_boolean (G_TASK (res), error);
+}
+
+
+static void
+photos_dlna_renderer_unshare_all_unshare_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderer *self = PHOTOS_DLNA_RENDERER (source_object);
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GTask *task = G_TASK (user_data);
+ guint remaining;
+ GError *error = NULL;
+
+ /* decrement the remaining count */
+ remaining = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ g_task_set_task_data (task, GUINT_TO_POINTER (--remaining), NULL);
+
+ photos_dlna_renderer_unshare_finish (self, res, &error);
+
+ if (error != NULL)
+ g_warning ("Unable to unshare item: %s", error->message);
+
+ if (remaining == 0)
+ {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+}
+
+void
+photos_dlna_renderer_unshare_all (PhotosDlnaRenderer *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GTask *task;
+ GList *items, *item;
+ guint remaining;
+ GError *error = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ items = g_hash_table_get_values (priv->urls_to_item);
+
+ remaining = g_list_length (items);
+ g_task_set_task_data (task, GUINT_TO_POINTER (remaining), NULL);
+
+ for (item = items; item != NULL; item = g_list_next (item))
+ photos_dlna_renderer_unshare (self, PHOTOS_BASE_ITEM (item->data), cancellable,
+ photos_dlna_renderer_unshare_all_unshare_cb, task);
+
+ g_list_free (items);
+}
+
+
+void
+photos_dlna_renderer_unshare_all_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_if_fail (g_task_is_valid (res, G_OBJECT (self)));
+
+ g_task_propagate_boolean (G_TASK (res), error);
+}
+
+
+static void
+photos_dlna_renderer_device_get_icon_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = G_TASK (user_data);
+ PhotosDlnaRenderer *self;
+ GInputStream *icon_stream = NULL;
+ GdkPixbuf *pixbuf = NULL;
+ GVariant *icon_variant = NULL;
+ GBytes *icon_bytes = NULL;
+ const gchar *icon_data;
+ gssize icon_data_size;
+ gchar *mimetype = NULL;
+ GError *error = NULL;
+
+ self = PHOTOS_DLNA_RENDERER (g_task_get_source_object (task));
+
+ /* The icon data is forced to be a GVariant since the GDBus bindings assume
+ * bytestrings (type 'ay') to be nul-terminated and thus do not return the length
+ * of the buffer */
+ dleyna_renderer_device_call_get_icon_finish (DLEYNA_RENDERER_DEVICE (source_object),
+ &icon_variant, &mimetype, res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Failed to call the GetIcon method: %s", error->message);
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ /* We know that the serialization of variant containing just a byte array
+ * 'ay' is the byte array itself */
+ icon_bytes = g_variant_get_data_as_bytes (icon_variant);
+ icon_data = g_bytes_get_data (icon_bytes, &icon_data_size);
+ icon_stream = g_memory_input_stream_new_from_data (icon_data, icon_data_size, NULL);
+
+ pixbuf = gdk_pixbuf_new_from_stream (icon_stream, g_task_get_cancellable (task), &error);
+ if (error != NULL)
+ {
+ g_warning ("Failed to parse icon data: %s", error->message);
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ g_task_return_pointer (task, pixbuf, g_object_unref);
+
+out:
+ g_free (mimetype);
+ g_clear_pointer (&icon_variant, g_variant_unref);
+ g_clear_pointer (&icon_bytes, g_bytes_unref);
+ g_clear_object (&icon_stream);
+ g_object_unref (task);
+}
+
+
+
+const gchar *
+photos_dlna_renderer_get_friendly_name (PhotosDlnaRenderer *self)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ return dleyna_renderer_device_get_friendly_name (priv->device);
+}
+
+
+void
+photos_dlna_renderer_get_icon (PhotosDlnaRenderer *self,
+ const gchar *resolution,
+ const gchar *requested_mimetype,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+ GTask *task;
+ GError *error = NULL;
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ dleyna_renderer_device_call_get_icon (priv->device, requested_mimetype, resolution,
+ cancellable, photos_dlna_renderer_device_get_icon_cb,
+ task);
+}
+
+
+GdkPixbuf *
+photos_dlna_renderer_get_icon_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ g_return_if_fail (g_task_is_valid (res, G_OBJECT (self)));
+
+ return GDK_PIXBUF (g_task_propagate_pointer (G_TASK (res), error));
+}
+
+
+guint
+photos_dlna_renderer_get_shared_count (PhotosDlnaRenderer *self)
+{
+ PhotosDlnaRendererPrivate *priv = self->priv;
+
+ return g_hash_table_size (priv->urls_to_item);
+}
diff --git a/src/photos-dlna-renderer.h b/src/photos-dlna-renderer.h
new file mode 100644
index 0000000..302f4ba
--- /dev/null
+++ b/src/photos-dlna-renderer.h
@@ -0,0 +1,138 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_DLNA_RENDERER_H
+#define PHOTOS_DLNA_RENDERER_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "photos-base-item.h"
+#include "photos-dleyna-renderer-device.h"
+#include "photos-dleyna-renderer-push-host.h"
+#include "photos-mpris-player.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_DLNA_RENDERER (photos_dlna_renderer_get_type ())
+
+#define PHOTOS_DLNA_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERER, PhotosDlnaRenderer))
+
+#define PHOTOS_DLNA_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ PHOTOS_TYPE_DLNA_RENDERER, PhotosDlnaRendererClass))
+
+#define PHOTOS_IS_DLNA_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERER))
+
+#define PHOTOS_IS_DLNA_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ PHOTOS_TYPE_DLNA_RENDERER))
+
+#define PHOTOS_DLNA_RENDERER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERER, PhotosDlnaRendererClass))
+
+typedef struct _PhotosDlnaRenderer PhotosDlnaRenderer;
+typedef struct _PhotosDlnaRendererClass PhotosDlnaRendererClass;
+typedef struct _PhotosDlnaRendererPrivate PhotosDlnaRendererPrivate;
+
+struct _PhotosDlnaRenderer
+{
+ GObject parent_instance;
+ PhotosDlnaRendererPrivate *priv;
+};
+
+struct _PhotosDlnaRendererClass
+{
+ GObjectClass parent_class;
+};
+
+GType photos_dlna_renderer_get_type (void) G_GNUC_CONST;
+
+void photos_dlna_renderer_new_for_bus (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *name,
+ const gchar *object_path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+PhotosDlnaRenderer *photos_dlna_renderer_new_for_bus_finish (GAsyncResult *res,
+ GError **error);
+
+const gchar *photos_dlna_renderer_get_object_path (PhotosDlnaRenderer *renderer);
+
+DleynaRendererDevice *photos_dlna_renderer_get_device (PhotosDlnaRenderer *renderer);
+
+DleynaPushHost *photos_dlna_renderer_get_push_host (PhotosDlnaRenderer *renderer);
+
+MprisPlayer *photos_dlna_renderer_get_player (PhotosDlnaRenderer *renderer);
+
+void photos_dlna_renderer_share (PhotosDlnaRenderer *renderer,
+ PhotosBaseItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+PhotosBaseItem *photos_dlna_renderer_share_finish (PhotosDlnaRenderer *renderer,
+ GAsyncResult *res,
+ GError **error);
+
+void photos_dlna_renderer_unshare (PhotosDlnaRenderer *self,
+ PhotosBaseItem *item,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void photos_dlna_renderer_unshare_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error);
+
+void photos_dlna_renderer_unshare_all (PhotosDlnaRenderer *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+void photos_dlna_renderer_unshare_all_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error);
+
+const gchar *photos_dlna_renderer_get_friendly_name (PhotosDlnaRenderer *self);
+
+void photos_dlna_renderer_get_icon (PhotosDlnaRenderer *self,
+ const gchar *requested_mimetype,
+ const gchar *resolution,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+GdkPixbuf * photos_dlna_renderer_get_icon_finish (PhotosDlnaRenderer *self,
+ GAsyncResult *res,
+ GError **error);
+
+guint photos_dlna_renderer_get_shared_count (PhotosDlnaRenderer *self);
+
+G_END_DECLS
+
+#endif /* PHOTOS_DLNA_RENDERER_H */
diff --git a/src/photos-dlna-renderers-dialog.c b/src/photos-dlna-renderers-dialog.c
new file mode 100644
index 0000000..f18b6ae
--- /dev/null
+++ b/src/photos-dlna-renderers-dialog.c
@@ -0,0 +1,290 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-dlna-renderers-dialog.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "photos-dleyna-renderer-device.h"
+#include "photos-dleyna-renderer-push-host.h"
+#include "photos-dlna-renderer.h"
+#include "photos-dlna-renderers-manager.h"
+#include "photos-item-manager.h"
+#include "photos-local-item.h"
+#include "photos-mode-controller.h"
+#include "photos-remote-display-manager.h"
+
+
+struct _PhotosDlnaRenderersDialogPrivate
+{
+ PhotosBaseManager *item_mngr;
+ PhotosDlnaRenderersManager *renderers_mngr;
+ PhotosRemoteDisplayManager *remote_mngr;
+ PhotosModeController *mode_cntrlr;
+ GtkListBox *listbox;
+ gchar *urn;
+};
+
+
+enum
+{
+ PROP_0,
+ PROP_URN
+};
+
+
+G_DEFINE_TYPE (PhotosDlnaRenderersDialog, photos_dlna_renderers_dialog, GTK_TYPE_DIALOG);
+
+
+static void
+photos_dlna_renderers_separator_cb (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *separator;
+
+ separator = gtk_list_box_row_get_header (row);
+ if (separator == NULL && before != NULL)
+ {
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_list_box_row_set_header (row, separator);
+ gtk_widget_show (separator);
+ }
+}
+
+
+static void
+photos_dlna_renderers_dialog_row_activated_cb (PhotosDlnaRenderersDialog *self,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+ PhotosBaseItem *item;
+ PhotosDlnaRenderer *renderer;
+
+ renderer = g_object_get_data (G_OBJECT (row), "renderer");
+ item = PHOTOS_BASE_ITEM (photos_base_manager_get_object_by_id (priv->item_mngr,
+ priv->urn));
+ photos_remote_display_manager_set_renderer (priv->remote_mngr, renderer);
+ photos_remote_display_manager_render (priv->remote_mngr, item);
+
+ photos_mode_controller_set_window_mode (priv->mode_cntrlr, PHOTOS_WINDOW_MODE_OVERVIEW);
+
+ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
+}
+
+
+static void
+photos_dlna_renderers_dialog_dispose (GObject *object)
+{
+ PhotosDlnaRenderersDialog *self = PHOTOS_DLNA_RENDERERS_DIALOG (object);
+ PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+
+ g_clear_object (&priv->item_mngr);
+ g_clear_object (&priv->renderers_mngr);
+ g_clear_object (&priv->remote_mngr);
+ g_clear_object (&priv->mode_cntrlr);
+
+ G_OBJECT_CLASS (photos_dlna_renderers_dialog_parent_class)->dispose (object);
+}
+
+
+static void
+photos_dlna_renderers_dialog_finalize (GObject *object)
+{
+ PhotosDlnaRenderersDialog *self = PHOTOS_DLNA_RENDERERS_DIALOG (object);
+ PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+
+ g_free (priv->urn);
+
+ G_OBJECT_CLASS (photos_dlna_renderers_dialog_parent_class)->finalize (object);
+}
+
+
+static void
+photos_dlna_renderers_dialog_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PhotosDlnaRenderersDialog *self = PHOTOS_DLNA_RENDERERS_DIALOG (object);
+
+ switch (prop_id)
+ {
+ case PROP_URN:
+ self->priv->urn = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+photos_dlna_renderers_dialog_set_icon_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ GtkImage *image = GTK_IMAGE (user_data);
+ PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (source_object);
+ GError *error = NULL;
+
+ pixbuf = photos_dlna_renderer_get_icon_finish (renderer, res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to load renderer icon: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ gtk_image_set_from_pixbuf (image, pixbuf);
+
+out:
+ /* release the ref we took before the async call */
+ g_object_unref (image);
+}
+
+
+static void
+photos_dlna_renderers_dialog_add_renderer (PhotosDlnaRenderersDialog *self,
+ PhotosDlnaRenderer *renderer)
+{
+ PhotosDlnaRenderersDialogPrivate *priv = self->priv;
+ GIcon *icon;
+ GtkWidget *row;
+ GtkWidget *row_grid;
+ GtkWidget *image;
+ GtkWidget *label;
+ const gchar *name;
+ const gchar *icon_url;
+
+ row = gtk_list_box_row_new ();
+ row_grid = gtk_grid_new ();
+ gtk_container_set_border_width (GTK_CONTAINER (row_grid), 12);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (row_grid), GTK_ORIENTATION_HORIZONTAL);
+ gtk_grid_set_column_spacing (GTK_GRID (row_grid), 12);
+ gtk_container_add (GTK_CONTAINER (row), row_grid);
+ gtk_container_add (GTK_CONTAINER (priv->listbox), row);
+
+ g_object_set_data_full (G_OBJECT (row), "renderer", renderer, g_object_unref);
+
+ name = photos_dlna_renderer_get_friendly_name (renderer);
+
+ icon = g_themed_icon_new_with_default_fallbacks ("video-display-symbolic");
+ image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+
+ g_object_ref (image); /* keep a ref for the following async call and release it in the callback */
+ photos_dlna_renderer_get_icon (renderer, "", "", NULL,
+ photos_dlna_renderers_dialog_set_icon_cb, image);
+
+ gtk_container_add (GTK_CONTAINER (row_grid), image);
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_text (GTK_LABEL (label), name);
+ gtk_container_add (GTK_CONTAINER (row_grid), label);
+}
+
+
+void
+photos_dlna_renderers_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+
+static void
+photos_dlna_renderers_dialog_init (PhotosDlnaRenderersDialog *self)
+{
+ PhotosDlnaRenderersDialogPrivate *priv;
+ GList *renderers;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ PHOTOS_TYPE_DLNA_RENDERERS_DIALOG,
+ PhotosDlnaRenderersDialogPrivate);
+ priv = self->priv;
+ priv->item_mngr = photos_item_manager_new ();
+ priv->renderers_mngr = photos_dlna_renderers_manager_dup_singleton ();
+ priv->remote_mngr = photos_remote_display_manager_dup_singleton ();
+ priv->mode_cntrlr = photos_mode_controller_new ();
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_set_header_func (priv->listbox, photos_dlna_renderers_separator_cb, NULL, NULL);
+
+ renderers = photos_dlna_renderers_manager_dup_renderers (priv->renderers_mngr);
+
+ while (renderers != NULL)
+ {
+ PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (renderers->data);
+
+ photos_dlna_renderers_dialog_add_renderer (self, renderer);
+ renderers = g_list_delete_link (renderers, renderers);
+ }
+
+ g_signal_connect (self, "response", G_CALLBACK (photos_dlna_renderers_dialog_response_cb), NULL);
+}
+
+
+static void
+photos_dlna_renderers_dialog_class_init (PhotosDlnaRenderersDialogClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->dispose = photos_dlna_renderers_dialog_dispose;
+ object_class->finalize = photos_dlna_renderers_dialog_finalize;
+ object_class->set_property = photos_dlna_renderers_dialog_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_URN,
+ g_param_spec_string ("urn",
+ "Uniform Resource Name",
+ "An unique ID associated with this item",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/photos/dlna-renderers-dialog.ui");
+
+ gtk_widget_class_bind_child (widget_class, PhotosDlnaRenderersDialogPrivate, listbox);
+ gtk_widget_class_bind_callback (widget_class, photos_dlna_renderers_dialog_row_activated_cb);
+
+ g_type_class_add_private (class, sizeof (PhotosDlnaRenderersDialogPrivate));
+}
+
+
+GtkWidget *
+photos_dlna_renderers_dialog_new (GtkWindow *parent,
+ const gchar *urn)
+{
+ g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
+
+ return g_object_new (PHOTOS_TYPE_DLNA_RENDERERS_DIALOG,
+ "transient-for", parent, "urn", urn, NULL);
+}
diff --git a/src/photos-dlna-renderers-dialog.h b/src/photos-dlna-renderers-dialog.h
new file mode 100644
index 0000000..3858252
--- /dev/null
+++ b/src/photos-dlna-renderers-dialog.h
@@ -0,0 +1,71 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_DLNA_RENDERERS_DIALOG_H
+#define PHOTOS_DLNA_RENDERERS_DIALOG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_DLNA_RENDERERS_DIALOG (photos_dlna_renderers_dialog_get_type ())
+
+#define PHOTOS_DLNA_RENDERERS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERERS_DIALOG, PhotosDlnaRenderersDialog))
+
+#define PHOTOS_DLNA_RENDERERS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ PHOTOS_TYPE_DLNA_RENDERERS_DIALOG, PhotosDlnaRenderersDialogClass))
+
+#define PHOTOS_IS_DLNA_RENDERERS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERERS_DIALOG))
+
+#define PHOTOS_IS_DLNA_RENDERERS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ PHOTOS_TYPE_DLNA_RENDERERS_DIALOG))
+
+#define PHOTOS_DLNA_RENDERERS_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERERS_DIALOG, PhotosDlnaRenderersDialogClass))
+
+typedef struct _PhotosDlnaRenderersDialog PhotosDlnaRenderersDialog;
+typedef struct _PhotosDlnaRenderersDialogClass PhotosDlnaRenderersDialogClass;
+typedef struct _PhotosDlnaRenderersDialogPrivate PhotosDlnaRenderersDialogPrivate;
+
+struct _PhotosDlnaRenderersDialog
+{
+ GtkDialog parent_instance;
+ PhotosDlnaRenderersDialogPrivate *priv;
+};
+
+struct _PhotosDlnaRenderersDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+GType photos_dlna_renderers_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget *photos_dlna_renderers_dialog_new (GtkWindow *parent, const gchar *urn);
+
+G_END_DECLS
+
+#endif /* PHOTOS_DLNA_RENDERERS_DIALOG_H */
diff --git a/src/photos-dlna-renderers-dialog.ui b/src/photos-dlna-renderers-dialog.ui
new file mode 100644
index 0000000..dae7a15
--- /dev/null
+++ b/src/photos-dlna-renderers-dialog.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gnome-photos">
+ <!-- interface-requires gtk+ 3.10 -->
+ <template class="PhotosDlnaRenderersDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">DLNA Renderer Devices</property>
+ <property name="resizable">True</property>
+ <property name="modal">True</property>
+ <property name="default_width">400</property>
+ <property name="default_height">300</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">14</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action-area">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button-cancel">
+ <property name="label">gtk-cancel</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="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="can_focus">False</property>
+ <property name="border-width">6</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="visible">True</property>
+ <signal name="row-activated" object="PhotosDlnaRenderersDialog" swapped="yes"
+ handler="photos_dlna_renderers_dialog_row_activated_cb" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button-cancel</action-widget>
+ </action-widgets>
+ </template>
+</interface>
diff --git a/src/photos-dlna-renderers-manager.c b/src/photos-dlna-renderers-manager.c
new file mode 100644
index 0000000..888fbeb
--- /dev/null
+++ b/src/photos-dlna-renderers-manager.c
@@ -0,0 +1,267 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-dlna-renderers-manager.h"
+
+#include <gio/gio.h>
+
+#include "photos-dleyna-renderer-manager.h"
+#include "photos-dlna-renderer.h"
+
+struct _PhotosDlnaRenderersManagerPrivate
+{
+ DleynaManager *proxy;
+ GHashTable *renderers;
+ GError *error;
+};
+
+enum
+{
+ RENDERER_FOUND,
+ RENDERER_LOST,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static GObject *photos_dlna_renderers_manager_singleton = NULL;
+
+G_DEFINE_TYPE (PhotosDlnaRenderersManager, photos_dlna_renderers_manager, G_TYPE_OBJECT);
+
+static void
+photos_dlna_renderers_manager_dispose (GObject *object)
+{
+ PhotosDlnaRenderersManager *self = PHOTOS_DLNA_RENDERERS_MANAGER (object);
+ PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+
+ g_clear_object (&priv->proxy);
+ g_clear_pointer (&priv->renderers, g_hash_table_unref);
+ g_clear_error (&priv->error);
+
+ G_OBJECT_CLASS (photos_dlna_renderers_manager_parent_class)->dispose (object);
+}
+
+static void
+photos_dlna_renderers_manager_renderer_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderersManager *self = PHOTOS_DLNA_RENDERERS_MANAGER (user_data);
+ PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+ PhotosDlnaRenderer *renderer;
+ DleynaRendererDevice *device;
+ const gchar *object_path;
+ GError *error = NULL;
+
+ renderer = photos_dlna_renderer_new_for_bus_finish (res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to load renderer object: %s", error->message);
+ g_propagate_error (&priv->error, error);
+ return;
+ }
+
+ device = photos_dlna_renderer_get_device (renderer);
+ object_path = photos_dlna_renderer_get_object_path (renderer);
+ g_debug ("%s '%s' %s %s", __func__,
+ dleyna_renderer_device_get_friendly_name (device),
+ dleyna_renderer_device_get_udn (device),
+ object_path);
+ g_hash_table_insert (priv->renderers, (gpointer) object_path, renderer);
+ g_signal_emit (self, signals[RENDERER_FOUND], 0, renderer);
+}
+
+static void
+photos_dlna_renderers_manager_renderer_found_cb (PhotosDlnaRenderersManager *self,
+ const gchar *object_path,
+ gpointer *data)
+{
+ photos_dlna_renderer_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "com.intel.dleyna-renderer",
+ object_path,
+ NULL, /* GCancellable */
+ photos_dlna_renderers_manager_renderer_new_cb,
+ self);
+}
+
+static void
+photos_dlna_renderers_manager_renderer_lost_cb (PhotosDlnaRenderersManager *self,
+ const gchar *object_path,
+ gpointer *data)
+{
+ PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+ PhotosDlnaRenderer *renderer;
+ DleynaRendererDevice *device;
+
+ renderer = PHOTOS_DLNA_RENDERER (g_hash_table_lookup (priv->renderers, object_path));
+ g_return_if_fail (renderer != NULL);
+
+ g_hash_table_steal (priv->renderers, object_path);
+ device = photos_dlna_renderer_get_device (renderer);
+ g_debug ("%s '%s' %s %s", __func__,
+ dleyna_renderer_device_get_friendly_name (device),
+ dleyna_renderer_device_get_udn (device),
+ object_path);
+ g_signal_emit (self, signals[RENDERER_LOST], 0, renderer);
+ g_object_unref (renderer);
+}
+
+static void
+photos_dlna_renderers_manager_proxy_get_renderers_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderersManager *self = user_data;
+ PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+ gchar **object_paths, **path;
+ GError *error = NULL;
+
+ dleyna_manager_call_get_renderers_finish (priv->proxy, &object_paths, res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to fetch the list of available renderers: %s", error->message);
+ g_propagate_error (&priv->error, error);
+ return;
+ }
+
+ for (path = object_paths; *path != NULL; path++)
+ photos_dlna_renderers_manager_renderer_found_cb (self, *path, NULL);
+
+ g_strfreev (object_paths);
+}
+
+static void
+photos_dlna_renderers_manager_proxy_new_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderersManager *self = user_data;
+ PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+ GError *error = NULL;
+
+ priv->proxy = dleyna_manager_proxy_new_for_bus_finish (res, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to connect to the dLeynaRenderer.Manager DBus object: %s", error->message);
+ g_propagate_error (&priv->error, error);
+ return;
+ }
+
+ g_debug ("%s DLNA renderers manager initialized", __func__);
+
+ g_signal_connect_swapped (priv->proxy, "found-renderer",
+ G_CALLBACK (photos_dlna_renderers_manager_renderer_found_cb), self);
+ g_signal_connect_swapped (priv->proxy, "lost-renderer",
+ G_CALLBACK (photos_dlna_renderers_manager_renderer_lost_cb), self);
+
+ dleyna_manager_call_get_renderers (priv->proxy, NULL,
+ photos_dlna_renderers_manager_proxy_get_renderers_cb, self);
+}
+
+static GObject *
+photos_dlna_renderers_manager_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ if (photos_dlna_renderers_manager_singleton != NULL)
+ return g_object_ref (photos_dlna_renderers_manager_singleton);
+
+ photos_dlna_renderers_manager_singleton =
+ G_OBJECT_CLASS (photos_dlna_renderers_manager_parent_class)->constructor
+ (type, n_construct_params, construct_params);
+
+ g_object_add_weak_pointer (photos_dlna_renderers_manager_singleton, (gpointer)
&photos_dlna_renderers_manager_singleton);
+
+ return photos_dlna_renderers_manager_singleton;
+}
+
+static void
+photos_dlna_renderers_manager_init (PhotosDlnaRenderersManager *self)
+{
+ PhotosDlnaRenderersManagerPrivate *priv;
+ GError *error = NULL;
+
+ self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOS_TYPE_DLNA_RENDERERS_MANAGER,
+ PhotosDlnaRenderersManagerPrivate);
+
+ dleyna_manager_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "com.intel.dleyna-renderer",
+ "/com/intel/dLeynaRenderer",
+ NULL, /* GCancellable */
+ photos_dlna_renderers_manager_proxy_new_cb,
+ self);
+ priv->renderers = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
+}
+
+static void
+photos_dlna_renderers_manager_class_init (PhotosDlnaRenderersManagerClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->constructor = photos_dlna_renderers_manager_constructor;
+ object_class->dispose = photos_dlna_renderers_manager_dispose;
+
+ signals[RENDERER_FOUND] = g_signal_new ("renderer-found", G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, PHOTOS_TYPE_DLNA_RENDERER);
+
+ signals[RENDERER_LOST] = g_signal_new ("renderer-lost", G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, PHOTOS_TYPE_DLNA_RENDERER);
+
+ g_type_class_add_private (class, sizeof (PhotosDlnaRenderersManagerPrivate));
+}
+
+PhotosDlnaRenderersManager *
+photos_dlna_renderers_manager_dup_singleton (void)
+{
+ return g_object_new (PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, NULL);
+}
+
+GList *
+photos_dlna_renderers_manager_dup_renderers (PhotosDlnaRenderersManager *self)
+{
+ PhotosDlnaRenderersManagerPrivate *priv = self->priv;
+ GList *renderers;
+
+ renderers = g_hash_table_get_values (priv->renderers);
+ g_list_foreach (renderers, (GFunc) g_object_ref, NULL);
+
+ return renderers;
+}
+
+gboolean
+photos_dlna_renderers_manager_is_available (void)
+{
+ PhotosDlnaRenderersManager *self;
+
+ if (photos_dlna_renderers_manager_singleton == NULL)
+ return FALSE;
+
+ self = PHOTOS_DLNA_RENDERERS_MANAGER (photos_dlna_renderers_manager_singleton);
+
+ return self->priv->error == NULL;
+}
diff --git a/src/photos-dlna-renderers-manager.h b/src/photos-dlna-renderers-manager.h
new file mode 100644
index 0000000..5d523a1
--- /dev/null
+++ b/src/photos-dlna-renderers-manager.h
@@ -0,0 +1,75 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_DLNA_RENDERERS_MANAGER_H
+#define PHOTOS_DLNA_RENDERERS_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_DLNA_RENDERERS_MANAGER (photos_dlna_renderers_manager_get_type ())
+
+#define PHOTOS_DLNA_RENDERERS_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, PhotosDlnaRenderersManager))
+
+#define PHOTOS_DLNA_RENDERERS_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, PhotosDlnaRenderersManagerClass))
+
+#define PHOTOS_IS_DLNA_RENDERERS_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERERS_MANAGER))
+
+#define PHOTOS_IS_DLNA_RENDERERS_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ PHOTOS_TYPE_DLNA_RENDERERS_MANAGER))
+
+#define PHOTOS_DLNA_RENDERERS_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ PHOTOS_TYPE_DLNA_RENDERERS_MANAGER, PhotosDlnaRenderersManagerClass))
+
+typedef struct _PhotosDlnaRenderersManager PhotosDlnaRenderersManager;
+typedef struct _PhotosDlnaRenderersManagerClass PhotosDlnaRenderersManagerClass;
+typedef struct _PhotosDlnaRenderersManagerPrivate PhotosDlnaRenderersManagerPrivate;
+
+struct _PhotosDlnaRenderersManager
+{
+ GObject parent_instance;
+ PhotosDlnaRenderersManagerPrivate *priv;
+};
+
+struct _PhotosDlnaRenderersManagerClass
+{
+ GObjectClass parent_class;
+};
+
+GType photos_dlna_renderers_manager_get_type (void) G_GNUC_CONST;
+
+PhotosDlnaRenderersManager *photos_dlna_renderers_manager_dup_singleton (void);
+
+GList *photos_dlna_renderers_manager_dup_renderers (PhotosDlnaRenderersManager *self);
+
+gboolean photos_dlna_renderers_manager_is_available (void);
+
+G_END_DECLS
+
+#endif /* PHOTOS_DLNA_RENDERERS_MANAGER_H */
diff --git a/src/photos-main-toolbar.c b/src/photos-main-toolbar.c
index 1c7c509..75cbd14 100644
--- a/src/photos-main-toolbar.c
+++ b/src/photos-main-toolbar.c
@@ -365,6 +365,9 @@ photos_main_toolbar_populate_for_preview (PhotosMainToolbar *self)
GMenu *preview_menu;
GtkWidget *back_button;
GtkWidget *menu_button;
+ GtkApplication *app;
+ gboolean remote_display_available;
+ GAction *remote_display_action;
photos_header_bar_set_mode (PHOTOS_HEADER_BAR (priv->toolbar), PHOTOS_HEADER_BAR_MODE_STANDALONE);
@@ -379,6 +382,13 @@ photos_main_toolbar_populate_for_preview (PhotosMainToolbar *self)
gd_header_bar_pack_end (GD_HEADER_BAR (priv->toolbar), menu_button);
g_simple_action_set_enabled (priv->gear_menu, TRUE);
+
+ /* Disable the remote-display-current action if the dLeyna services are not
+ * available */
+ app = photos_application_new ();
+ remote_display_action = g_action_map_lookup_action (G_ACTION_MAP (app), "remote-display-current");
+ remote_display_available = photos_dlna_renderers_manager_is_available ();
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (remote_display_action), remote_display_available);
}
diff --git a/src/photos-main.c b/src/photos-main.c
index 0220931..61d8b3d 100644
--- a/src/photos-main.c
+++ b/src/photos-main.c
@@ -29,12 +29,14 @@
#include <glib/gi18n.h>
#include "photos-application.h"
+#include "photos-remote-display-manager.h"
gint
main (gint argc, gchar *argv[])
{
GtkApplication *app;
+ PhotosRemoteDisplayManager *remote_display_mngr;
gint exit_status;
bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
@@ -44,7 +46,9 @@ main (gint argc, gchar *argv[])
g_set_prgname (PACKAGE_TARNAME);
app = photos_application_new ();
+ remote_display_mngr = photos_remote_display_manager_dup_singleton ();
exit_status = g_application_run (G_APPLICATION (app), argc, argv);
+ g_object_unref (remote_display_mngr);
g_object_unref (app);
return exit_status;
diff --git a/src/photos-mpris-player.xml b/src/photos-mpris-player.xml
new file mode 100644
index 0000000..c956ff9
--- /dev/null
+++ b/src/photos-mpris-player.xml
@@ -0,0 +1,681 @@
+<?xml version="1.0" ?>
+<!--
+ Media Player Remote Interfacing Specification for D-Bus
+ http://cgit.freedesktop.org/xdg/mpris-spec/plain/spec/Player_Node.xml?id=v2.2
+-->
+<node name="/Player_Node" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.mpris.MediaPlayer2.Player">
+
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ This interface implements the methods for querying and providing basic
+ control over what is currently playing.
+ </p>
+ </tp:docstring>
+
+ <tp:enum name="Playback_Status" tp:name-for-bindings="Playback_Status" type="s">
+ <tp:enumvalue suffix="Playing" value="Playing">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A track is currently playing.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Paused" value="Paused">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A track is currently paused.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Stopped" value="Stopped">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>There is no track currently playing.</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A playback state.</p>
+ </tp:docstring>
+ </tp:enum>
+
+ <tp:enum name="Loop_Status" tp:name-for-bindings="Loop_Status" type="s">
+ <tp:enumvalue suffix="None" value="None">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The playback will stop when there are no more tracks to play</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Track" value="Track">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current track will start again from the begining once it has finished playing</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:enumvalue suffix="Playlist" value="Playlist">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The playback loops through a list of tracks</p>
+ </tp:docstring>
+ </tp:enumvalue>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A repeat / loop status</p>
+ </tp:docstring>
+ </tp:enum>
+
+ <tp:simple-type name="Track_Id" type="o" array-name="Track_Id_List">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Unique track identifier.</p>
+ <p>
+ If the media player implements the TrackList interface and allows
+ the same track to appear multiple times in the tracklist,
+ this must be unique within the scope of the tracklist.
+ </p>
+ <p>
+ Note that this should be a valid D-Bus object id, although clients
+ should not assume that any object is actually exported with any
+ interfaces at that path.
+ </p>
+ <p>
+ Media players may not use any paths starting with
+ <literal>/org/mpris</literal> unless explicitly allowed by this specification.
+ Such paths are intended to have special meaning, such as
+ <literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
+ to indicate "no track".
+ </p>
+ <tp:rationale>
+ <p>
+ This is a D-Bus object id as that is the definitive way to have
+ unique identifiers on D-Bus. It also allows for future optional
+ expansions to the specification where tracks are exported to D-Bus
+ with an interface similar to org.gnome.UPnP.MediaItem2.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Playback_Rate" type="d">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>A playback rate</p>
+ <p>
+ This is a multiplier, so a value of 0.5 indicates that playback is
+ happening at half speed, while 1.5 means that 1.5 seconds of "track time"
+ is consumed every second.
+ </p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Volume" type="d">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Audio volume level</p>
+ <ul>
+ <li>0.0 means mute.</li>
+ <li>1.0 is a sensible maximum volume level (ex: 0dB).</li>
+ </ul>
+ <p>
+ Note that the volume may be higher than 1.0, although generally
+ clients should not attempt to set it above 1.0.
+ </p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <tp:simple-type name="Time_In_Us" type="x">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Time in microseconds.</p>
+ </tp:docstring>
+ </tp:simple-type>
+
+ <method name="Next" tp:name-for-bindings="Next">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Skips to the next track in the tracklist.</p>
+ <p>
+ If there is no next track (and endless playback and track
+ repeat are both off), stop playback.
+ </p>
+ <p>If playback is paused or stopped, it remains that way.</p>
+ <p>
+ If <tp:member-ref>CanGoNext</tp:member-ref> is
+ <strong>false</strong>, attempting to call this method should have
+ no effect.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="Previous" tp:name-for-bindings="Previous">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Skips to the previous track in the tracklist.</p>
+ <p>
+ If there is no previous track (and endless playback and track
+ repeat are both off), stop playback.
+ </p>
+ <p>If playback is paused or stopped, it remains that way.</p>
+ <p>
+ If <tp:member-ref>CanGoPrevious</tp:member-ref> is
+ <strong>false</strong>, attempting to call this method should have
+ no effect.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="Pause" tp:name-for-bindings="Pause">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Pauses playback.</p>
+ <p>If playback is already paused, this has no effect.</p>
+ <p>
+ Calling Play after this should cause playback to start again
+ from the same position.
+ </p>
+ <p>
+ If <tp:member-ref>CanPause</tp:member-ref> is
+ <strong>false</strong>, attempting to call this method should have
+ no effect.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="PlayPause" tp:name-for-bindings="PlayPause">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Pauses playback.</p>
+ <p>If playback is already paused, resumes playback.</p>
+ <p>If playback is stopped, starts playback.</p>
+ <p>
+ If <tp:member-ref>CanPause</tp:member-ref> is
+ <strong>false</strong>, attempting to call this method should have
+ no effect and raise an error.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="Stop" tp:name-for-bindings="Stop">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Stops playback.</p>
+ <p>If playback is already stopped, this has no effect.</p>
+ <p>
+ Calling Play after this should cause playback to
+ start again from the beginning of the track.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, attempting to call this method should have
+ no effect and raise an error.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="Play" tp:name-for-bindings="Play">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Starts or resumes playback.</p>
+ <p>If already playing, this has no effect.</p>
+ <p>If there is no track to play, this has no effect.</p>
+ <p>
+ If <tp:member-ref>CanPlay</tp:member-ref> is
+ <strong>false</strong>, attempting to call this method should have
+ no effect.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="Seek" tp:name-for-bindings="Seek">
+ <arg direction="in" type="x" name="Offset" tp:type="Time_In_Us">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The number of microseconds to seek forward.</p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Seeks forward in the current track by the specified number
+ of microseconds.
+ </p>
+ <p>
+ A negative value seeks back. If this would mean seeking
+ back further than the start of the track, the position
+ is set to 0.
+ </p>
+ <p>
+ If the value passed in would mean seeking beyond the end
+ of the track, acts like a call to Next.
+ </p>
+ <p>
+ If the <tp:member-ref>CanSeek</tp:member-ref> property is false,
+ this has no effect.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <method name="SetPosition" tp:name-for-bindings="Set_Position">
+ <arg direction="in" type="o" tp:type="Track_Id" name="TrackId">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The currently playing track's identifier.</p>
+ <p>
+ If this does not match the id of the currently-playing track,
+ the call is ignored as "stale".
+ </p>
+ <p>
+ <literal>/org/mpris/MediaPlayer2/TrackList/NoTrack</literal>
+ is <em>not</em> a valid value for this argument.
+ </p>
+ </tp:docstring>
+ </arg>
+ <arg direction="in" type="x" tp:type="Time_In_Us" name="Position">
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Track position in microseconds.</p>
+ <p>This must be between 0 and <track_length>.</p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Sets the current track position in microseconds.</p>
+ <p>If the Position argument is less than 0, do nothing.</p>
+ <p>
+ If the Position argument is greater than the track length,
+ do nothing.
+ </p>
+ <p>
+ If the <tp:member-ref>CanSeek</tp:member-ref> property is false,
+ this has no effect.
+ </p>
+ <tp:rationale>
+ <p>
+ The reason for having this method, rather than making
+ <tp:member-ref>Position</tp:member-ref> writable, is to include
+ the TrackId argument to avoid race conditions where a client tries
+ to seek to a position when the track has already changed.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </method>
+
+ <method name="OpenUri" tp:name-for-bindings="Open_Uri">
+ <arg direction="in" type="s" tp:type="Uri" name="Uri">
+ <tp:docstring>
+ <p>
+ Uri of the track to load. Its uri scheme should be an element of the
+ <literal>org.mpris.MediaPlayer2.SupportedUriSchemes</literal>
+ property and the mime-type should match one of the elements of the
+ <literal>org.mpris.MediaPlayer2.SupportedMimeTypes</literal>.
+ </p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Opens the Uri given as an argument</p>
+ <p>If the playback is stopped, starts playing</p>
+ <p>
+ If the uri scheme or the mime-type of the uri to open is not supported,
+ this method does nothing and may raise an error. In particular, if the
+ list of available uri schemes is empty, this method may not be
+ implemented.
+ </p>
+ <p>Clients should not assume that the Uri has been opened as soon as this
+ method returns. They should wait until the mpris:trackid field in the
+ <tp:member-ref>Metadata</tp:member-ref> property changes.
+ </p>
+ <p>
+ If the media player implements the TrackList interface, then the
+ opened track should be made part of the tracklist, the
+ <literal>org.mpris.MediaPlayer2.TrackList.TrackAdded</literal> or
+ <literal>org.mpris.MediaPlayer2.TrackList.TrackListReplaced</literal>
+ signal should be fired, as well as the
+ <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
+ signal on the tracklist interface.
+ </p>
+ </tp:docstring>
+ </method>
+
+ <property name="PlaybackStatus" tp:name-for-bindings="Playback_Status" type="s"
tp:type="Playback_Status" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current playback status.</p>
+ <p>
+ May be "Playing", "Paused" or "Stopped".
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="LoopStatus" type="s" access="readwrite"
+ tp:name-for-bindings="Loop_Status" tp:type="Loop_Status">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current loop / repeat status</p>
+ <p>May be:
+ <ul>
+ <li>"None" if the playback will stop when there are no more tracks to play</li>
+ <li>"Track" if the current track will start again from the begining once it has finished
playing</li>
+ <li>"Playlist" if the playback loops through a list of tracks</li>
+ </ul>
+ </p>
+ <p>
+ This property is optional, and clients should deal with NotSupported
+ errors gracefully.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, attempting to set this property should have
+ no effect and raise an error.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="Rate" tp:name-for-bindings="Rate" type="d" tp:type="Playback_Rate" access="readwrite">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The current playback rate.</p>
+ <p>
+ The value must fall in the range described by
+ <tp:member-ref>MinimumRate</tp:member-ref> and
+ <tp:member-ref>MaximumRate</tp:member-ref>, and must not be 0.0. If
+ playback is paused, the <tp:member-ref>PlaybackStatus</tp:member-ref>
+ property should be used to indicate this. A value of 0.0 should not
+ be set by the client. If it is, the media player should act as
+ though <tp:member-ref>Pause</tp:member-ref> was called.
+ </p>
+ <p>
+ If the media player has no ability to play at speeds other than the
+ normal playback rate, this must still be implemented, and must
+ return 1.0. The <tp:member-ref>MinimumRate</tp:member-ref> and
+ <tp:member-ref>MaximumRate</tp:member-ref> properties must also be
+ set to 1.0.
+ </p>
+ <p>
+ Not all values may be accepted by the media player. It is left to
+ media player implementations to decide how to deal with values they
+ cannot use; they may either ignore them or pick a "best fit" value.
+ Clients are recommended to only use sensible fractions or multiples
+ of 1 (eg: 0.5, 0.25, 1.5, 2.0, etc).
+ </p>
+ <tp:rationale>
+ <p>
+ This allows clients to display (reasonably) accurate progress bars
+ without having to regularly query the media player for the current
+ position.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="Shuffle" tp:name-for-bindings="Shuffle" type="b" access="readwrite">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ A value of <strong>false</strong> indicates that playback is
+ progressing linearly through a playlist, while <strong>true</strong>
+ means playback is progressing through a playlist in some other order.
+ </p>
+ <p>
+ This property is optional, and clients should deal with NotSupported
+ errors gracefully.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, attempting to set this property should have
+ no effect and raise an error.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="Metadata" tp:name-for-bindings="Metadata" type="a{sv}" tp:type="Metadata_Map"
access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The metadata of the current element.</p>
+ <p>
+ If there is a current track, this must have a "mpris:trackid" entry
+ (of D-Bus type "o") at the very least, which contains a D-Bus path that
+ uniquely identifies this track.
+ </p>
+ <p>
+ See the type documentation for more details.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="Volume" type="d" tp:type="Volume" tp:name-for-bindings="Volume" access="readwrite">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" />
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>The volume level.</p>
+ <p>
+ When setting, if a negative value is passed, the volume
+ should be set to 0.0.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, attempting to set this property should have
+ no effect and raise an error.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="Position" type="x" tp:type="Time_In_Us" tp:name-for-bindings="Position" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The current track position in microseconds, between 0 and
+ the 'mpris:length' metadata entry (see Metadata).
+ </p>
+ <p>
+ Note: If the media player allows it, the current playback position
+ can be changed either the SetPosition method or the Seek method on
+ this interface. If this is not the case, the
+ <tp:member-ref>CanSeek</tp:member-ref> property is false, and
+ setting this property has no effect and can raise an error.
+ </p>
+ <p>
+ If the playback progresses in a way that is inconstistant with the
+ <tp:member-ref>Rate</tp:member-ref> property, the
+ <tp:member-ref>Seeked</tp:member-ref> signal is emited.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="MinimumRate" tp:name-for-bindings="Minimum_Rate" type="d" tp:type="Playback_Rate"
access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The minimum value which the <tp:member-ref>Rate</tp:member-ref>
+ property can take.
+ Clients should not attempt to set the
+ <tp:member-ref>Rate</tp:member-ref> property below this value.
+ </p>
+ <p>
+ Note that even if this value is 0.0 or negative, clients should
+ not attempt to set the <tp:member-ref>Rate</tp:member-ref> property
+ to 0.0.
+ </p>
+ <p>This value should always be 1.0 or less.</p>
+ </tp:docstring>
+ </property>
+
+ <property name="MaximumRate" tp:name-for-bindings="Maximum_Rate" type="d" tp:type="Playback_Rate"
access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ The maximum value which the <tp:member-ref>Rate</tp:member-ref>
+ property can take.
+ Clients should not attempt to set the
+ <tp:member-ref>Rate</tp:member-ref> property above this value.
+ </p>
+ <p>
+ This value should always be 1.0 or greater.
+ </p>
+ </tp:docstring>
+ </property>
+
+ <property name="CanGoNext" tp:name-for-bindings="Can_Go_Next" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Whether the client can call the <tp:member-ref>Next</tp:member-ref>
+ method on this interface and expect the current track to change.
+ </p>
+ <p>
+ If it is unknown whether a call to <tp:member-ref>Next</tp:member-ref> will
+ be successful (for example, when streaming tracks), this property should
+ be set to <strong>true</strong>.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, this property should also be
+ <strong>false</strong>.
+ </p>
+ <tp:rationale>
+ <p>
+ Even when playback can generally be controlled, there may not
+ always be a next track to move to.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="CanGoPrevious" tp:name-for-bindings="Can_Go_Previous" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Whether the client can call the
+ <tp:member-ref>Previous</tp:member-ref> method on this interface and
+ expect the current track to change.
+ </p>
+ <p>
+ If it is unknown whether a call to <tp:member-ref>Previous</tp:member-ref>
+ will be successful (for example, when streaming tracks), this property
+ should be set to <strong>true</strong>.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, this property should also be
+ <strong>false</strong>.
+ </p>
+ <tp:rationale>
+ <p>
+ Even when playback can generally be controlled, there may not
+ always be a next previous to move to.
+ </p>
+ </tp:rationale>
+
+ </tp:docstring>
+ </property>
+
+ <property name="CanPlay" tp:name-for-bindings="Can_Play" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Whether playback can be started using
+ <tp:member-ref>Play</tp:member-ref> or
+ <tp:member-ref>PlayPause</tp:member-ref>.
+ </p>
+ <p>
+ Note that this is related to whether there is a "current track": the
+ value should not depend on whether the track is currently paused or
+ playing. In fact, if a track is currently playing (and
+ <tp:member-ref>CanControl</tp:member-ref> is <strong>true</strong>),
+ this should be <strong>true</strong>.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, this property should also be
+ <strong>false</strong>.
+ </p>
+ <tp:rationale>
+ <p>
+ Even when playback can generally be controlled, it may not be
+ possible to enter a "playing" state, for example if there is no
+ "current track".
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="CanPause" tp:name-for-bindings="Can_Pause" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Whether playback can be paused using
+ <tp:member-ref>Pause</tp:member-ref> or
+ <tp:member-ref>PlayPause</tp:member-ref>.
+ </p>
+ <p>
+ Note that this is an intrinsic property of the current track: its
+ value should not depend on whether the track is currently paused or
+ playing. In fact, if playback is currently paused (and
+ <tp:member-ref>CanControl</tp:member-ref> is <strong>true</strong>),
+ this should be <strong>true</strong>.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, this property should also be
+ <strong>false</strong>.
+ </p>
+ <tp:rationale>
+ <p>
+ Not all media is pausable: it may not be possible to pause some
+ streamed media, for example.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="CanSeek" tp:name-for-bindings="Can_Seek" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Whether the client can control the playback position using
+ <tp:member-ref>Seek</tp:member-ref> and
+ <tp:member-ref>SetPosition</tp:member-ref>. This may be different for
+ different tracks.
+ </p>
+ <p>
+ If <tp:member-ref>CanControl</tp:member-ref> is
+ <strong>false</strong>, this property should also be
+ <strong>false</strong>.
+ </p>
+ <tp:rationale>
+ <p>
+ Not all media is seekable: it may not be possible to seek when
+ playing some streamed media, for example.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <property name="CanControl" tp:name-for-bindings="Can_Control" type="b" access="read">
+ <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>Whether the media player may be controlled over this interface.</p>
+ <p>
+ This property is not expected to change, as it describes an intrinsic
+ capability of the implementation.
+ </p>
+ <p>
+ If this is <strong>false</strong>, clients should assume that all
+ properties on this interface are read-only (and will raise errors
+ if writing to them is attempted), no methods are implemented
+ and all other properties starting with "Can" are also
+ <strong>false</strong>.
+ </p>
+ <tp:rationale>
+ <p>
+ This allows clients to determine whether to present and enable
+ controls to the user in advance of attempting to call methods
+ and write to properties.
+ </p>
+ </tp:rationale>
+ </tp:docstring>
+ </property>
+
+ <signal name="Seeked" tp:name-for-bindings="Seeked">
+ <arg name="Position" type="x" tp:type="Time_In_Us">
+ <tp:docstring>
+ <p>The new position, in microseconds.</p>
+ </tp:docstring>
+ </arg>
+ <tp:docstring xmlns="http://www.w3.org/1999/xhtml">
+ <p>
+ Indicates that the track position has changed in a way that is
+ inconsistant with the current playing state.
+ </p>
+ <p>When this signal is not received, clients should assume that:</p>
+ <ul>
+ <li>
+ When playing, the position progresses according to the rate property.
+ </li>
+ <li>When paused, it remains constant.</li>
+ </ul>
+ <p>
+ This signal does not need to be emitted when playback starts
+ or when the track changes, unless the track is starting at an
+ unexpected position. An expected position would be the last
+ known one when going from Paused to Playing, and 0 when going from
+ Stopped to Playing.
+ </p>
+ </tp:docstring>
+ </signal>
+
+ </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/src/photos-preview-menu.ui b/src/photos-preview-menu.ui
index ebd9cd9..78eb7bf 100644
--- a/src/photos-preview-menu.ui
+++ b/src/photos-preview-menu.ui
@@ -11,6 +11,10 @@
<attribute name="accel"><Primary>p</attribute>
</item>
<item>
+ <attribute name="action">app.remote-display-current</attribute>
+ <attribute name="label" translatable="yes">Display on…</attribute>
+ </item>
+ <item>
<attribute name="action">app.set-background</attribute>
<attribute name="label" translatable="yes">Set as Background</attribute>
</item>
diff --git a/src/photos-remote-display-manager.c b/src/photos-remote-display-manager.c
new file mode 100644
index 0000000..36b10d1
--- /dev/null
+++ b/src/photos-remote-display-manager.c
@@ -0,0 +1,289 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "photos-remote-display-manager.h"
+
+#include <gio/gio.h>
+
+#include "photos-dlna-renderers-manager.h"
+
+typedef struct {
+ PhotosRemoteDisplayManager *manager;
+ PhotosDlnaRenderer *renderer;
+ PhotosBaseItem *item;
+} Share;
+
+struct _PhotosRemoteDisplayManagerPrivate
+{
+ PhotosDlnaRenderersManager *renderers_mngr;
+ PhotosDlnaRenderer *renderer;
+};
+
+enum
+{
+ SHARE_BEGAN,
+ SHARE_ENDED,
+ SHARE_ERROR,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static GObject *remote_display_manager_singleton = NULL;
+
+G_DEFINE_TYPE (PhotosRemoteDisplayManager, photos_remote_display_manager, G_TYPE_OBJECT);
+
+
+static Share *
+photos_remote_display_manager_share_new (PhotosRemoteDisplayManager *manager,
+ PhotosDlnaRenderer *renderer,
+ PhotosBaseItem *item)
+{
+ Share *share;
+
+ share = g_new (Share, 1);
+ share->manager = manager;
+ share->renderer = g_object_ref (renderer);
+ share->item = g_object_ref (item);
+
+ return share;
+}
+
+
+static void
+photos_remote_display_manager_share_destroy (Share *share)
+{
+ g_object_unref (share->renderer);
+ g_object_unref (share->item);
+ g_free (share);
+}
+
+static void
+photos_remote_display_manager_dispose (GObject *object)
+{
+ PhotosRemoteDisplayManager *self = PHOTOS_REMOTE_DISPLAY_MANAGER (object);
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+ g_clear_object (&priv->renderers_mngr);
+ g_clear_object (&priv->renderer);
+
+ G_OBJECT_CLASS (photos_remote_display_manager_parent_class)->dispose (object);
+}
+
+
+static GObject *
+photos_remote_display_manager_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ if (remote_display_manager_singleton != NULL)
+ return g_object_ref (remote_display_manager_singleton);
+
+ remote_display_manager_singleton =
+ G_OBJECT_CLASS (photos_remote_display_manager_parent_class)->constructor
+ (type, n_construct_params, construct_params);
+
+ g_object_add_weak_pointer (remote_display_manager_singleton, (gpointer) &remote_display_manager_singleton);
+
+ return remote_display_manager_singleton;
+}
+
+static void
+photos_remote_display_manager_renderer_lost_cb (PhotosRemoteDisplayManager *self,
+ PhotosDlnaRenderer *renderer,
+ gpointer user_data)
+{
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+ if (renderer == priv->renderer)
+ photos_remote_display_manager_stop (self);
+}
+
+static void
+photos_remote_display_manager_init (PhotosRemoteDisplayManager *self)
+{
+ PhotosRemoteDisplayManagerPrivate *priv;
+
+ self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER,
+ PhotosRemoteDisplayManagerPrivate);
+
+ /* Keep a connection to the renderers manager alive to keep the list of
+ * renderers up-to-date */
+ priv->renderers_mngr = photos_dlna_renderers_manager_dup_singleton ();
+
+ g_signal_connect_object (priv->renderers_mngr, "renderer-lost",
+ G_CALLBACK (photos_remote_display_manager_renderer_lost_cb), self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+photos_remote_display_manager_class_init (PhotosRemoteDisplayManagerClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->constructor = photos_remote_display_manager_constructor;
+ object_class->dispose = photos_remote_display_manager_dispose;
+
+ signals[SHARE_BEGAN] = g_signal_new ("share-began", G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ PHOTOS_TYPE_DLNA_RENDERER,
+ PHOTOS_TYPE_BASE_ITEM);
+
+ signals[SHARE_ENDED] = g_signal_new ("share-ended", G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ PHOTOS_TYPE_DLNA_RENDERER);
+
+ signals[SHARE_ERROR] = g_signal_new ("share-error", G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 3,
+ PHOTOS_TYPE_DLNA_RENDERER,
+ PHOTOS_TYPE_BASE_ITEM,
+ G_TYPE_ERROR);
+
+ g_type_class_add_private (class, sizeof (PhotosRemoteDisplayManagerPrivate));
+}
+
+
+static void
+photos_remote_display_manager_share_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ Share *share = user_data;
+ PhotosRemoteDisplayManager *self = share->manager;
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+ PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (source_object);
+ PhotosBaseItem *item;
+ GError *error = NULL;
+
+ item = photos_dlna_renderer_share_finish (priv->renderer, res, &error);
+ g_object_unref (item); /* We already hold a ref to the item to be shared */
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to remotely display item '%s': %s",
+ item != NULL ? photos_base_item_get_id (item) : "(none)",
+ error->message);
+ g_signal_emit (share->manager, signals[SHARE_ERROR], 0, share->renderer, share->item, error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_signal_emit (share->manager, signals[SHARE_BEGAN], 0, share->renderer, share->item);
+
+out:
+ photos_remote_display_manager_share_destroy (share);
+}
+
+
+static void
+photos_remote_display_manager_unshare_all_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ PhotosDlnaRenderer *renderer = PHOTOS_DLNA_RENDERER (source_object);
+ PhotosRemoteDisplayManager *self = PHOTOS_REMOTE_DISPLAY_MANAGER (user_data);
+ GError *error = NULL;
+
+ photos_dlna_renderer_unshare_all_finish (renderer, res, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Error while unsharing: %s", error->message);
+ g_error_free (error);
+ }
+
+ /* Avoid firing ::share-ended if any other item has been shared between the
+ * _unshare_all() call and this callback */
+ if (!photos_remote_display_manager_is_active (self))
+ g_signal_emit (self, signals[SHARE_ENDED], 0, renderer);
+}
+
+
+PhotosRemoteDisplayManager *
+photos_remote_display_manager_dup_singleton (void)
+{
+ return g_object_new (PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, NULL);
+}
+
+
+void
+photos_remote_display_manager_set_renderer (PhotosRemoteDisplayManager *self,
+ PhotosDlnaRenderer *renderer)
+{
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+ g_clear_object (&priv->renderer);
+
+ if (renderer)
+ priv->renderer = g_object_ref (renderer);
+}
+
+
+PhotosDlnaRenderer *
+photos_remote_display_manager_get_renderer (PhotosRemoteDisplayManager *self)
+{
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+ return priv->renderer;
+}
+
+
+void
+photos_remote_display_manager_render (PhotosRemoteDisplayManager *self,
+ PhotosBaseItem *item)
+{
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+ Share *share;
+
+ g_return_if_fail (priv->renderer != NULL);
+
+ share = photos_remote_display_manager_share_new (self, priv->renderer, item);
+ photos_dlna_renderer_share (share->renderer, share->item, NULL,
+ photos_remote_display_manager_share_cb, share);
+}
+
+
+void
+photos_remote_display_manager_stop (PhotosRemoteDisplayManager *self)
+{
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+ g_return_if_fail (priv->renderer != NULL);
+
+ photos_dlna_renderer_unshare_all (priv->renderer, NULL,
+ photos_remote_display_manager_unshare_all_cb, self);
+}
+
+
+gboolean
+photos_remote_display_manager_is_active (PhotosRemoteDisplayManager *self)
+{
+ PhotosRemoteDisplayManagerPrivate *priv = self->priv;
+
+ if (priv->renderer == NULL)
+ return FALSE;
+
+ return photos_dlna_renderer_get_shared_count (priv->renderer) > 0;
+}
diff --git a/src/photos-remote-display-manager.h b/src/photos-remote-display-manager.h
new file mode 100644
index 0000000..0dfbcfa
--- /dev/null
+++ b/src/photos-remote-display-manager.h
@@ -0,0 +1,86 @@
+/*
+ * Photos - access, organize and share your photos on GNOME
+ * Copyright © 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef PHOTOS_REMOTE_DISPLAY_MANAGER_H
+#define PHOTOS_REMOTE_DISPLAY_MANAGER_H
+
+#include <glib-object.h>
+
+#include "photos-base-item.h"
+#include "photos-dlna-renderer.h"
+
+G_BEGIN_DECLS
+
+#define PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER (photos_remote_display_manager_get_type ())
+
+#define PHOTOS_REMOTE_DISPLAY_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, PhotosRemoteDisplayManager))
+
+#define PHOTOS_REMOTE_DISPLAY_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, PhotosRemoteDisplayManagerClass))
+
+#define PHOTOS_IS_REMOTE_DISPLAY_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER))
+
+#define PHOTOS_IS_REMOTE_DISPLAY_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER))
+
+#define PHOTOS_REMOTE_DISPLAY_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ PHOTOS_TYPE_REMOTE_DISPLAY_MANAGER, PhotosRemoteDisplayManagerClass))
+
+typedef struct _PhotosRemoteDisplayManager PhotosRemoteDisplayManager;
+typedef struct _PhotosRemoteDisplayManagerClass PhotosRemoteDisplayManagerClass;
+typedef struct _PhotosRemoteDisplayManagerPrivate PhotosRemoteDisplayManagerPrivate;
+
+struct _PhotosRemoteDisplayManager
+{
+ GObject parent_instance;
+ PhotosRemoteDisplayManagerPrivate *priv;
+};
+
+struct _PhotosRemoteDisplayManagerClass
+{
+ GObjectClass parent_class;
+};
+
+GType photos_remote_display_manager_get_type (void) G_GNUC_CONST;
+
+PhotosRemoteDisplayManager *photos_remote_display_manager_dup_singleton (void);
+
+void photos_remote_display_manager_set_renderer (PhotosRemoteDisplayManager *self,
+ PhotosDlnaRenderer
*renderer);
+
+PhotosDlnaRenderer *photos_remote_display_manager_get_renderer (PhotosRemoteDisplayManager *self);
+
+void photos_remote_display_manager_render (PhotosRemoteDisplayManager *self,
+ PhotosBaseItem *item);
+
+void photos_remote_display_manager_stop (PhotosRemoteDisplayManager *self);
+
+gboolean photos_remote_display_manager_is_active (PhotosRemoteDisplayManager *self);
+
+G_END_DECLS
+
+#endif /* PHOTOS_REMOTE_DISPLAY_MANAGER_H */
diff --git a/src/photos.gresource.xml b/src/photos.gresource.xml
index 458373e..2414660 100644
--- a/src/photos.gresource.xml
+++ b/src/photos.gresource.xml
@@ -2,6 +2,7 @@
<gresources>
<gresource prefix="/org/gnome/photos">
<file alias="app-menu.ui" preprocess="xml-stripblanks">photos-app-menu.ui</file>
+ <file alias="dlna-renderers-dialog.ui" preprocess="xml-stripblanks"
compressed="true">photos-dlna-renderers-dialog.ui</file>
<file alias="preview-menu.ui" preprocess="xml-stripblanks">photos-preview-menu.ui</file>
<file alias="selection-menu.ui" preprocess="xml-stripblanks">photos-selection-menu.ui</file>
</gresource>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]