diff options
Diffstat (limited to 'src/dlna.c')
-rw-r--r-- | src/dlna.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/src/dlna.c b/src/dlna.c new file mode 100644 index 0000000..20ed99a --- /dev/null +++ b/src/dlna.c @@ -0,0 +1,580 @@ +/* + * snappy - 0.1 beta + * + * Copyright (C) 2011 Collabora Multimedia Ltd. + * <luis.debethencourt@collabora.co.uk> + * + * 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, 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 <gio/gio.h> +#include <stdlib.h> + +#include "dlna.h" + +const char *mpris_introspection_xml = + "<node>" + " <interface name='org.mpris.MediaPlayer2'>" + " <method name='Raise'/>" + " <method name='Quit'/>" + " <property name='CanQuit' type='b' access='read'/>" + " <property name='CanRaise' type='b' access='read'/>" + " <property name='HasTrackList' type='b' access='read'/>" + " <property name='Identity' type='s' access='read'/>" + " <property name='DesktopEntry' type='s' access='read'/>" + " <property name='SupportedUriSchemes' type='as' access='read'/>" + " <property name='SupportedMimeTypes' type='as' access='read'/>" + " </interface>" + " <interface name='org.mpris.MediaPlayer2.Player'>" + " <method name='Next'/>" + " <method name='Previous'/>" + " <method name='Pause'/>" + " <method name='PlayPause'/>" + " <method name='Stop'/>" + " <method name='Play'/>" + " <method name='Seek'>" + " <arg direction='in' name='Offset' type='x'/>" + " </method>" + " <method name='SetPosition'>" + " <arg direction='in' name='TrackId' type='o'/>" + " <arg direction='in' name='Position' type='x'/>" + " </method>" + " <method name='OpenUri'>" + " <arg direction='in' name='Uri' type='s'/>" + " </method>" + " <signal name='Seeked'>" + " <arg name='Position' type='x'/>" + " </signal>" + " <property name='PlaybackStatus' type='s' access='read'/>" + " <property name='LoopStatus' type='s' access='readwrite'/>" + " <property name='Rate' type='d' access='readwrite'/>" + " <property name='Shuffle' type='b' access='readwrite'/>" + " <property name='Metadata' type='a{sv}' access='read'/>" + " <property name='Volume' type='d' access='readwrite'/>" + " <property name='Position' type='x' access='read'/>" + " <property name='MinimumRate' type='d' access='read'/>" + " <property name='MaximumRate' type='d' access='read'/>" + " <property name='CanGoNext' type='b' access='read'/>" + " <property name='CanGoPrevious' type='b' access='read'/>" + " <property name='CanPlay' type='b' access='read'/>" + " <property name='CanPause' type='b' access='read'/>" + " <property name='CanSeek' type='b' access='read'/>" + " <property name='CanControl' type='b' access='read'/>" + " </interface>" + " <interface name='org.mpris.MediaPlayer2.TrackList'>" + " <method name='GetTracksMetadata'>" + " <arg direction='in' name='TrackIds' type='ao'/>" + " <arg direction='out' name='Metadata' type='aa{sv}'/>" + " </method>" + " <method name='AddTrack'>" + " <arg direction='in' name='Uri' type='s'/>" + " <arg direction='in' name='AfterTrack' type='o'/>" + " <arg direction='in' name='SetAsCurrent' type='b'/>" + " </method>" + " <method name='RemoveTrack'>" + " <arg direction='in' name='TrackId' type='o'/>" + " </method>" + " <method name='GoTo'>" + " <arg direction='in' name='TrackId' type='o'/>" + " </method>" + " <signal name='TrackListReplaced'>" + " <arg name='Tracks' type='ao'/>" + " <arg name='CurrentTrack' type='o'/>" + " </signal>" + " <signal name='TrackAdded'>" + " <arg name='Metadata' type='a{sv}'/>" + " <arg name='AfterTrack' type='o'/>" + " </signal>" + " <signal name='TrackRemoved'>" + " <arg name='TrackId' type='o'/>" + " </signal>" + " <signal name='TrackMetadataChanged'>" + " <arg name='TrackId' type='o'/>" + " <arg name='Metadata' type='a{sv}'/>" + " </signal>" + " <property name='Tracks' type='ao' access='read'/>" + " <property name='CanEditTracks' type='b' access='read'/>" + " </interface>" + " <interface name='org.mpris.MediaPlayer2.Playlists'>" + " <method name='ActivatePlaylist'>" + " <arg direction='in' name='PlaylistId' type='o'/>" + " </method>" + " <method name='GetPlaylists'>" + " <arg direction='in' name='Index' type='u'/>" + " <arg direction='in' name='MaxCount' type='u'/>" + " <arg direction='in' name='Order' type='s'/>" + " <arg direction='in' name='ReverseOrder' type='b'/>" + " <arg direction='out' type='a(oss)'/>" + " </method>" + " <property name='PlaylistCount' type='u' access='read'/>" + " <property name='Orderings' type='as' access='read'/>" + " <property name='ActivePlaylist' type='(b(oss))' access='read'/>" + " </interface>" + "</node>"; + +/* for now */ +static const GDBusInterfaceVTable interface_vtable = { + (GDBusInterfaceMethodCallFunc) handle_method_call, + (GDBusInterfaceGetPropertyFunc) handle_get_property, + (GDBusInterfaceSetPropertyFunc) handle_set_property +}; + +static const GDBusInterfaceVTable root_vtable = { + (GDBusInterfaceMethodCallFunc) handle_root_method_call, + (GDBusInterfaceGetPropertyFunc) get_root_property, + NULL +}; + +static GDBusNodeInfo *introspection_data = NULL; + +G_DEFINE_TYPE (SnappyMP, my_object, G_TYPE_OBJECT); + +static void +my_object_init (SnappyMP * object) +{ + // g_print ("my_object_init\n"); + + object->name = "snappy"; +} + +static void +my_object_finalize (GObject * object) +{ + SnappyMP *myobj = (SnappyMP *) object; + + g_free (myobj->name); + + G_OBJECT_CLASS (my_object_parent_class)->finalize (object); +} + +static void +my_object_get_property (GObject * object, + guint prop_id, + GValue * value, + GParamSpec * pspec) +{ + SnappyMP *myobj = (SnappyMP *) object; + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, myobj->name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +my_object_set_property (GObject * object, + guint prop_id, + const GValue * value, + GParamSpec * pspec) +{ + SnappyMP *myobj = (SnappyMP *) object; + + switch (prop_id) { + case PROP_NAME: + g_free (myobj->name); + myobj->name = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +my_object_class_init (SnappyMPClass * class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->finalize = my_object_finalize; + gobject_class->set_property = my_object_set_property; + gobject_class->get_property = my_object_get_property; + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", "Name", "Name", NULL, G_PARAM_READWRITE)); + + mediaplayer_signals[OPEN_URI] = + g_signal_newv ("open-uri", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + NULL, + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0, + NULL); +} + +void +my_object_change_uri (SnappyMP * myobj, gchar * uri) +{ + if (myobj != NULL) + myobj->uri = uri; + + // g_print ("changing uri: %s\n", uri); + g_object_set (G_OBJECT (myobj), "uri", uri, NULL); + + engine_open_uri (myobj->engine, uri); +} + +static void +handle_result (GDBusMethodInvocation * invocation, + gboolean ret, + GError * error) +{ + if (ret) { + g_dbus_method_invocation_return_value (invocation, NULL); + } else { + if (error != NULL) { + g_print ("DLNA returning error: %s", error->message); + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } else { + g_print ("DLNA returning unknown error"); + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Unknown error"); + } + } +} + +void +handle_method_call (GDBusConnection * connection, + const gchar * sender, + const gchar * object_path, + const gchar * interface_name, + const gchar * method_name, + GVariant * parameters, + GDBusMethodInvocation * invocation, + SnappyMP * myobj) +{ + gboolean ret = TRUE; + GError *error = NULL; + + // g_print ("handle_method_call: %s\n", method_name); + + if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 || + g_strcmp0 (interface_name, MPRIS_PLAYER_INTERFACE) != 0) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_NOT_SUPPORTED, + "Method %s.%s not supported", + interface_name, + method_name); + return; + } + + if (g_strcmp0 (method_name, "OpenUri") == 0) { + gchar *uri; + + g_variant_get (parameters, "(s)", &uri); + my_object_change_uri (myobj, uri); + + g_dbus_method_invocation_return_value (invocation, NULL); + + } else if (g_strcmp0 (method_name, "Next") == 0) { + /// ToDo: next track call + + handle_result (invocation, ret, error); + } else if (g_strcmp0 (method_name, "Play") == 0) { + engine_play (myobj->engine); + + handle_result (invocation, ret, error); + } else if (g_strcmp0 (method_name, "Stop") == 0) { + engine_stop (myobj->engine); + + handle_result (invocation, ret, error); + } + +} + +GVariant * +handle_get_property (GDBusConnection * connection, + const gchar * sender, + const gchar * object_path, + const gchar * interface_name, + const gchar * property_name, GError ** error, gpointer user_data) +{ + GVariant *ret; + SnappyMP *myobj = user_data; + + // g_print ("handle_get_property: %s\n", property_name); + + ret = NULL; + if (g_strcmp0 (property_name, "Name") == 0) { + //ret = g_variant_new_string (myobj->name ? myobj->name : ""); + ret = g_variant_new_string ("snappy"); + + } else if (g_strcmp0 (property_name, "PlaybackStatus") == 0) { + ret = g_variant_new_string ("Paused"); + } else if (g_strcmp0 (property_name, "LoopStatus") == 0) { + ret = g_variant_new_string ("Paused"); + } else if (g_strcmp0 (property_name, "Rate") == 0) { + ret = g_variant_new_double (0); + } else if (g_strcmp0 (property_name, "Shuffle") == 0) { + ret = g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "Metadata") == 0) { + const char *strv[] = { "", NULL }; + ret = g_variant_new_strv (strv, -1); + } else if (g_strcmp0 (property_name, "Volume") == 0) { + ret = g_variant_new_double (0); + } else if (g_strcmp0 (property_name, "Position") == 0) { + ret = g_variant_new_double (0); + } else if (g_strcmp0 (property_name, "MinimumRate") == 0) { + ret = g_variant_new_double (0); + } else if (g_strcmp0 (property_name, "MaximumRate") == 0) { + ret = g_variant_new_double (0); + } else if (g_strcmp0 (property_name, "CanGoNext") == 0) { + ret = g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "CanGoPrevious") == 0) { + ret = g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "CanPlay") == 0) { + ret = g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "CanPause") == 0) { + ret = g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "CanSeek") == 0) { + ret = g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "CanControl") == 0) { + ret = g_variant_new_boolean (FALSE); + + } else if (g_strcmp0 (property_name, "Identity") == 0) { + return g_variant_new_string ("snappy"); + } else if (g_strcmp0 (property_name, "SupportedUriSchemes") == 0) { + /* not planning to support this seriously */ + const char *fake_supported_schemes[] = { + "file", "http", "cdda", "smb", "sftp", NULL + }; + return g_variant_new_strv (fake_supported_schemes, -1); + } else if (g_strcmp0 (property_name, "SupportedMimeTypes") == 0) { + /* nor this */ + const char *fake_supported_mimetypes[] = { + "application/ogg", "audio/x-vorbis+ogg", "audio/x-flac", "audio/mpeg", + NULL + }; + return g_variant_new_strv (fake_supported_mimetypes, -1); + } + + return ret; +} + +gboolean +handle_set_property (GDBusConnection * connection, + const gchar * sender, + const gchar * object_path, + const gchar * interface_name, + const gchar * property_name, + GVariant * value, GError ** error, gpointer user_data) +{ + SnappyMP *myobj = user_data; + + if (g_strcmp0 (property_name, "Name") == 0) { + g_object_set (myobj, "name", g_variant_get_string (value, NULL), NULL); + } + + return TRUE; +} + +void +handle_root_method_call (GDBusConnection * connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant * parameters, + GDBusMethodInvocation * invocation, SnappyMP * mp) +{ + if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 || + g_strcmp0 (interface_name, MPRIS_ROOT_INTERFACE) != 0) { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_NOT_SUPPORTED, + "Method %s.%s not supported", interface_name, method_name); + return; + } + + if (g_strcmp0 (method_name, "Raise") == 0) { + g_dbus_method_invocation_return_value (invocation, NULL); + } else if (g_strcmp0 (method_name, "Quit") == 0) { + g_dbus_method_invocation_return_value (invocation, NULL); + } else { + g_dbus_method_invocation_return_error (invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_NOT_SUPPORTED, + "Method %s.%s not supported", interface_name, method_name); + } +} + +GVariant * +get_root_property (GDBusConnection * connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *property_name, GError ** error, SnappyMP * mp) +{ + // g_print ("get_root_property: %s\n", property_name); + + if (g_strcmp0 (object_path, MPRIS_OBJECT_NAME) != 0 || + g_strcmp0 (interface_name, MPRIS_ROOT_INTERFACE) != 0) { + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_NOT_SUPPORTED, + "Property %s.%s not supported", interface_name, property_name); + return NULL; + } + + if (g_strcmp0 (property_name, "CanQuit") == 0) { + return g_variant_new_boolean (TRUE); + } else if (g_strcmp0 (property_name, "CanRaise") == 0) { + return g_variant_new_boolean (TRUE); + } else if (g_strcmp0 (property_name, "HasTrackList") == 0) { + return g_variant_new_boolean (FALSE); + } else if (g_strcmp0 (property_name, "Identity") == 0) { + return g_variant_new_string ("snappy"); + } else if (g_strcmp0 (property_name, "DesktopEntry") == 0) { + GVariant *v = NULL; + char *path; + return v; + } else if (g_strcmp0 (property_name, "SupportedUriSchemes") == 0) { + /* not planning to support this seriously */ + const char *fake_supported_schemes[] = { + "file", "http", "cdda", "smb", "sftp", NULL + }; + return g_variant_new_strv (fake_supported_schemes, -1); + } else if (g_strcmp0 (property_name, "SupportedMimeTypes") == 0) { + /* nor this */ + const char *fake_supported_mimetypes[] = { + "application/ogg", "audio/x-vorbis+ogg", "audio/x-flac", "audio/mpeg", + NULL + }; + return g_variant_new_strv (fake_supported_mimetypes, -1); + } + + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_NOT_SUPPORTED, + "Property %s.%s not supported", interface_name, property_name); + return NULL; +} + +static void +send_property_change (GObject * obj, + GParamSpec * pspec, + GDBusConnection * connection) +{ + + GVariantBuilder *builder; + GVariantBuilder *invalidated_builder; + SnappyMP *myobj = (SnappyMP *) obj; + + builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); + + if (g_strcmp0 (pspec->name, "name") == 0) + g_variant_builder_add (builder, + "{sv}", "Name", g_variant_new_string (myobj->name ? myobj->name : "")); + + // g_print ("some property changed %s\n", pspec->name); + + g_dbus_connection_emit_signal (connection, + NULL, + "org/mpris/MediaPlayer2", + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + g_variant_new ("(sa{sv}as)", + "org.mpris.MediaPlayer2", builder, invalidated_builder), NULL); +} + +static void +on_name_acquired (GDBusConnection * connection, + const gchar * name, + gpointer user_data) +{ + // g_print ("DLNA MediaPlayer name acquired.\n"); +} + +static void +on_name_lost (GDBusConnection * connection, + const gchar * name, + gpointer user_data) +{ + exit (1); +} + +gboolean +load_dlna (SnappyMP *mp) +{ + guint owner_id, player_id, root_id; + GError *error = NULL; + GDBusInterfaceInfo *ifaceinfo; + GDBusConnection *connection; + + g_type_init (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + + /* Build the introspection data structures from the XML */ + introspection_data = + g_dbus_node_info_new_for_xml (mpris_introspection_xml, NULL); + g_assert (introspection_data != NULL); + + /* register media player interface */ + ifaceinfo = + g_dbus_node_info_lookup_interface (introspection_data, + MPRIS_PLAYER_INTERFACE); + mp->player_id = + g_dbus_connection_register_object (connection, MPRIS_OBJECT_NAME, + ifaceinfo, &interface_vtable, mp, NULL, &error); + + /* register root interface */ + ifaceinfo = + g_dbus_node_info_lookup_interface (introspection_data, + MPRIS_ROOT_INTERFACE); + mp->root_id = + g_dbus_connection_register_object (connection, MPRIS_OBJECT_NAME, + ifaceinfo, &root_vtable, NULL, NULL, &error); + if (error != NULL) { + g_warning ("unable to register MPRIS root interface: %s", error->message); + g_error_free (error); + } + + mp->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.mpris.MediaPlayer2.snappy", + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, + (GBusNameAcquiredCallback) on_name_acquired, + (GBusNameLostCallback) on_name_lost, + g_object_ref (mp), + g_object_unref); + g_assert (mp->owner_id > 0); + + return TRUE; +} + +gboolean +close_dlna (SnappyMP *mp) +{ + g_bus_unown_name (mp->owner_id); + + g_dbus_node_info_unref (introspection_data); + + g_object_unref (mp); + + return TRUE; +} |