diff options
author | Luis de Bethencourt <luis.debethencourt@collabora.co.uk> | 2011-03-21 19:49:05 +0100 |
---|---|---|
committer | Luis de Bethencourt <luis@debethencourt.com> | 2011-05-19 11:33:22 +0200 |
commit | a5b292d85c837bff3890b831d9c9b29364b1fdbc (patch) | |
tree | 39defe06c045ad972aa4482c36ee24e893b9c786 | |
parent | 0490c68af92cd4a75e9012a422277b85699d581b (diff) |
dlna: created proto code for mpris
proto code that works stand-alone.
launch it, start rygel, and then gupnp-av-cp to send it a uri
to handle.
-rw-r--r-- | src/mpris.c | 454 | ||||
-rw-r--r-- | src/mpris.h | 107 |
2 files changed, 561 insertions, 0 deletions
diff --git a/src/mpris.c b/src/mpris.c new file mode 100644 index 0000000..faebf2b --- /dev/null +++ b/src/mpris.c @@ -0,0 +1,454 @@ +// gcc `pkg-config --libs --cflags gio-2.0` mpris.c -o mpris + +#include <gio/gio.h> +#include <stdlib.h> +#include "mpris.h" + + +/* ---------------------------------------------------------------------------------------------------- */ + +/* The object we want to export */ +typedef struct _MediaPlayer2Class MediaPlayer2Class; +typedef struct _MediaPlayer2 MediaPlayer2; + +struct _MediaPlayer2Class +{ + GObjectClass parent_class; +}; + +struct _MediaPlayer2 +{ + GObject parent_instance; + + gint count; + gchar *name; + + GDBusConnection *connection; + GDBusNodeInfo *node_info; + guint name_own_id; + guint root_id; + guint player_id; + guint playlists_id; + + int playlist_count; + + GHashTable *player_property_changes; + GHashTable *playlist_property_changes; + guint property_emit_id; + + gint64 last_elapsed; +}; + +enum +{ + PROP_0, + PROP_COUNT, + PROP_NAME +}; + +G_DEFINE_TYPE (MediaPlayer2, my_object, G_TYPE_OBJECT); + +static void +my_object_finalize (GObject * object) +{ + MediaPlayer2 *myobj = (MediaPlayer2 *) object; + + g_free (myobj->name); + + G_OBJECT_CLASS (my_object_parent_class)->finalize (object); +} + +static void +my_object_init (MediaPlayer2 * object) +{ + object->count = 0; + object->name = NULL; +} + +static void +my_object_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + MediaPlayer2 *myobj = (MediaPlayer2 *) object; + + switch (prop_id) { + case PROP_COUNT: + g_value_set_int (value, myobj->count); + break; + + 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) +{ + MediaPlayer2 *myobj = (MediaPlayer2 *) object; + + switch (prop_id) { + case PROP_COUNT: + myobj->count = g_value_get_int (value); + break; + + 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 (MediaPlayer2Class * 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_COUNT, + g_param_spec_int ("count", + "Count", "Count", 0, 99999, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", "Name", "Name", NULL, G_PARAM_READWRITE)); +} + +/* A method that we want to export */ +void +my_object_change_count (MediaPlayer2 * myobj, gint change) +{ + myobj->count = 2 * myobj->count + change; + + g_object_notify (G_OBJECT (myobj), "count"); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static GDBusNodeInfo *introspection_data = NULL; + +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 ("returning error: %s", error->message); + g_dbus_method_invocation_return_gerror (invocation, error); + g_error_free (error); + } else { + g_print ("returning unknown error"); + g_dbus_method_invocation_return_error_literal (invocation, + G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Unknown error"); + } + } +} + +static 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, gpointer user_data) +{ + MediaPlayer2 *myobj = user_data; + gboolean ret = TRUE; + GError *error = NULL; + + g_print ("handle_method_call\n"); + + if (g_strcmp0 (method_name, "ChangeCount") == 0) { + gint change; + g_variant_get (parameters, "(i)", &change); + + my_object_change_count (myobj, change); + + g_dbus_method_invocation_return_value (invocation, NULL); + } else if (g_strcmp0 (method_name, "Next") == 0) { + g_print ("next\n"); + /// ToDo: next track call + handle_result (invocation, ret, error); + } else if (g_strcmp0 (method_name, "OpenUri") == 0) { + const gchar *uri; + + g_variant_get (parameters, "(&s)", &uri); + g_print ("set uri: %s\n", uri); + handle_result (invocation, ret, error); + } +} + +static 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; + MediaPlayer2 *myobj = user_data; + + g_print ("handle_get_property: %s\n", property_name); + + ret = NULL; + if (g_strcmp0 (property_name, "Count") == 0) { + ret = g_variant_new_int32 (myobj->count); + } else 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, "Volume") == 0) { + ret = g_variant_new_double (0); + } else if (g_strcmp0 (property_name, "PlaybackStatus") == 0) { + ret = g_variant_new_string ("Paused"); + } 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, "Position") == 0) { + ret = g_variant_new_double (0); + } + return ret; +} + +static 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) +{ + MediaPlayer2 *myobj = user_data; + + g_print ("handle_set_property\n"); + + if (g_strcmp0 (property_name, "Count") == 0) { + g_object_set (myobj, "count", g_variant_get_int32 (value), NULL); + } else if (g_strcmp0 (property_name, "Name") == 0) { + g_object_set (myobj, "name", g_variant_get_string (value, NULL), NULL); + } + + return TRUE; +} + +static 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, MediaPlayer2 * 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); + } +} + +static GVariant * +get_root_property (GDBusConnection * connection, + const char *sender, + const char *object_path, + const char *interface_name, + const char *property_name, GError ** error, MediaPlayer2 * 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; +} + +/* 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 void +send_property_change (GObject * obj, + GParamSpec * pspec, GDBusConnection * connection) +{ + GVariantBuilder *builder; + GVariantBuilder *invalidated_builder; + MediaPlayer2 *myobj = (MediaPlayer2 *) 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, "count") == 0) + g_variant_builder_add (builder, + "{sv}", "Count", g_variant_new_int32 (myobj->count)); + else if (g_strcmp0 (pspec->name, "name") == 0) + g_variant_builder_add (builder, + "{sv}", "Name", g_variant_new_string (myobj->name ? myobj->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_bus_acquired (GDBusConnection * connection, + const gchar * name, gpointer user_data) +{ + MediaPlayer2 *myobj = user_data; + guint registration_id; + + g_print ("on bus acquired\n"); + + g_signal_connect (myobj, "notify", + G_CALLBACK (send_property_change), connection); + registration_id = g_dbus_connection_register_object (connection, "/org/mpris/MediaPlayer2", introspection_data->interfaces[0], &interface_vtable, myobj, NULL, /* user_data_free_func */ + NULL); /* GError** */ + // g_assert (registration_id > 0); +} + +static void +on_name_acquired (GDBusConnection * connection, + const gchar * name, gpointer user_data) +{ +} + +static void +on_name_lost (GDBusConnection * connection, + const gchar * name, gpointer user_data) +{ + exit (1); +} + +int +main (int argc, char *argv[]) +{ + guint owner_id, player_id, root_id; + GMainLoop *loop; + MediaPlayer2 *myobj; + + 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); + + myobj = g_object_new (my_object_get_type (), NULL); + + /* register root interface */ + ifaceinfo = + g_dbus_node_info_lookup_interface (introspection_data, + MPRIS_ROOT_INTERFACE); + 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); + } + + /* register player interface */ + ifaceinfo = + g_dbus_node_info_lookup_interface (introspection_data, + MPRIS_PLAYER_INTERFACE); + player_id = + g_dbus_connection_register_object (connection, MPRIS_OBJECT_NAME, + ifaceinfo, &interface_vtable, NULL, NULL, &error); + + owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + "org.mpris.MediaPlayer2.snappy", + G_BUS_NAME_OWNER_FLAGS_NONE, + on_bus_acquired, on_name_acquired, on_name_lost, myobj, NULL); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + g_bus_unown_name (owner_id); + + g_dbus_node_info_unref (introspection_data); + + g_object_unref (myobj); + + return 0; +} diff --git a/src/mpris.h b/src/mpris.h new file mode 100644 index 0000000..2b5fe03 --- /dev/null +++ b/src/mpris.h @@ -0,0 +1,107 @@ +#define MPRIS_BUS_NAME_PREFIX "org.mpris.MediaPlayer2" +#define MPRIS_OBJECT_NAME "/org/mpris/MediaPlayer2" + +#define MPRIS_ROOT_INTERFACE "org.mpris.MediaPlayer2" +#define MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" +#define MPRIS_TRACKLIST_INTERFACE "org.mpris.MediaPlayer2.TrackList" +#define MPRIS_PLAYLISTS_INTERFACE "org.mpris.MediaPlayer2.Playlists" + +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>"; |