plugin_simple_disambiguator_la_LIBADD =
+# Mpris2 client plugin
+plugin_LTLIBRARIES += plugin-mpris2-client.la
+
+plugin_mpris2_client_la_SOURCES = \
+ plugins/mpris2-client/mpris2-plugin.c \
+ plugins/mpris2-client/dbusif.c \
+ plugins/mpris2-client/clients.c
+
+plugin_mpris2_client_la_CFLAGS = \
+ $(AM_CFLAGS)
+
+plugin_mpris2_client_la_LDFLAGS = \
+ -module -avoid-version
+
+plugin_mpris2_client_la_LIBADD =
+
# cleanup
clean-local::
--- /dev/null
+#include <sys/time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <murphy/common/debug.h>
+
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+
+#include "clients.h"
+#include "dbusif.h"
+
+
+struct clients_s {
+ srs_client_t *srs_client;
+ struct {
+ mrp_htbl_t *name;
+ mrp_htbl_t *addr;
+ } player;
+ player_t *current;
+};
+
+static int notify_focus(srs_client_t *, srs_voice_focus_t);
+static int notify_command(srs_client_t *, int, char **);
+
+static void schedule_delayed_request(player_t *);
+
+static int player_register(void *, void *, void *);
+static void player_free(void *, void *);
+
+static playlist_t *playlist_dup(size_t, playlist_t *);
+static void playlist_free(size_t, playlist_t *);
+
+static uint64_t get_current_time(void);
+
+static char *commands[] = {
+ "play music",
+ "stop music",
+ "show player",
+ "hide player",
+ NULL
+};
+static int ncommand = (sizeof(commands) / sizeof(commands[0])) - 1;
+
+int clients_create(context_t *ctx)
+{
+ clients_t *clients;
+ srs_plugin_t *pl;
+ srs_context_t *srs;
+ srs_client_ops_t callbacks;
+ mrp_htbl_config_t cfg;
+
+ if (!ctx || !(pl = ctx->plugin) || !(srs = pl->srs))
+ return -1;
+
+ if (!(clients = mrp_allocz(sizeof(clients_t))))
+ return -1;
+
+ callbacks.notify_focus = notify_focus;
+ callbacks.notify_command = notify_command;
+
+ clients->srs_client = client_create(srs, SRS_CLIENT_TYPE_BUILTIN,
+ PLUGIN_NAME, "player",
+ commands, ncommand,
+ PLUGIN_NAME, &callbacks);
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.nentry = 10;
+ cfg.comp = mrp_string_comp;
+ cfg.hash = mrp_string_hash;
+ cfg.free = player_free;
+ cfg.nbucket = cfg.nentry;
+ clients->player.name = mrp_htbl_create(&cfg);
+
+ cfg.free = NULL;
+ clients->player.addr = mrp_htbl_create(&cfg);
+
+ clients->current = NULL;
+
+ ctx->clients = clients;
+
+ return 0;
+}
+
+
+void clients_destroy(context_t *ctx)
+{
+ clients_t *clients;
+
+ if (ctx && (clients = ctx->clients)) {
+ ctx->clients = NULL;
+
+ client_destroy(clients->srs_client);
+
+ mrp_htbl_destroy(clients->player.addr, FALSE);
+ mrp_htbl_destroy(clients->player.name, TRUE);
+ free(clients);
+ }
+}
+
+int clients_start(context_t *ctx)
+{
+ clients_t *clients;
+ player_t *player;
+
+ if (!ctx || !(clients = ctx->clients))
+ return -1;
+
+ mrp_htbl_foreach(clients->player.name, player_register, (void *)ctx);
+
+ return 0;
+}
+
+int clients_stop(context_t *ctx)
+{
+ return 0;
+}
+
+
+int clients_register_player(context_t *ctx,
+ const char *name,
+ const char *service,
+ const char *object)
+{
+ clients_t *clients;
+ player_t *player;
+
+ if (!ctx || !name || !(clients = ctx->clients))
+ return -1;
+
+ if (!(player = mrp_allocz(sizeof(player_t))))
+ return -1;
+
+ player->ctx = ctx;
+ player->name = mrp_strdup(name);
+ player->service = service ? mrp_strdup(service) : NULL;
+ player->object = object ? mrp_strdup(object) : NULL;
+
+ mrp_htbl_insert(clients->player.name, (void*)player->name, (void*)player);
+
+ mrp_log_info("Mpris2 player '%s' (service '%s' object '%s') registered",
+ player->name, player->service ? player->service : "none",
+ player->object ? player->object : "none");
+
+ return 0;
+}
+
+player_t *clients_find_player_by_address(context_t *ctx, const char *address)
+{
+ clients_t *clients;
+ player_t *player;
+
+ if (!ctx || !(clients = ctx->clients))
+ return NULL;
+
+ player = mrp_htbl_lookup(clients->player.addr, (void *)address);
+
+ return player;
+}
+
+player_t *clients_find_player_by_name(context_t *ctx, const char *name)
+{
+ clients_t *clients;
+ player_t *player;
+
+ if (!ctx || !(clients = ctx->clients))
+ return NULL;
+
+ player = mrp_htbl_lookup(clients->player.name, (void *)name);
+
+ return player;
+}
+
+void clients_player_appeared(context_t *ctx,
+ const char *name,
+ const char *address)
+{
+ clients_t *clients;
+ player_t *player;
+
+ if (ctx && (clients = ctx->clients) && clients->player.name) {
+ if ((player = mrp_htbl_lookup(clients->player.name, (void *)name))) {
+ mrp_free((void *)player->address);
+ player->address = mrp_strdup(address);
+
+ mrp_htbl_insert(clients->player.addr, (void *)player->address,
+ player);
+
+ mrp_log_info("mrpis2 client '%s' appeared (address %s)",
+ name, address);
+
+ if (!clients->current)
+ clients->current = player;
+
+ dbusif_query_player_properties(player);
+ }
+ }
+}
+
+void clients_player_disappeared(context_t *ctx, const char *name)
+{
+ clients_t *clients;
+ player_t *player, *removed;
+
+ if (ctx && (clients = ctx->clients) && clients->player.name) {
+ if ((player = mrp_htbl_lookup(clients->player.name, (void *)name))) {
+ removed = mrp_htbl_remove(clients->player.addr,
+ (void *)player->address,
+ FALSE);
+ if (player != removed) {
+ mrp_log_error("mpris2 client: confused with data structures "
+ "when removing '%s'", player->address);
+ }
+ else {
+ mrp_free((void *)player->address);
+ player->address = NULL;
+ player->state = UNKNOWN;
+ player->ready = false;
+
+ playlist_free(player->nlist, player->lists);
+ player->nlist = 0;
+ player->lists = NULL;
+ player->active_list = NULL;
+
+ mrp_log_info("mrpis2 client '%s' disappeared", name);
+
+ if (player == clients->current) {
+ clients->current = NULL;
+ }
+ }
+ }
+ }
+}
+
+void clients_player_state_changed(player_t *player, player_state_t state)
+{
+ if (player)
+ player->state = state;
+}
+
+void clients_player_status_changed(player_t *player, bool ready)
+{
+ if (player) {
+ if (!player->ready && ready)
+ schedule_delayed_request(player);
+
+ player->ready = ready;
+ }
+}
+
+void clients_playlist_changed(player_t *player, size_t nlist,playlist_t *lists)
+{
+ context_t *ctx;
+ playlist_t *list, *active;
+ size_t i, idx_of_active_list;
+
+ if (!player || !(ctx = player->ctx))
+ return;
+
+ idx_of_active_list = 0;
+
+ if ((active = player->active_list)) {
+ for (i = 0; i < nlist; i++) {
+ list = lists + i;
+
+ if (!strcmp(active->id, list->id)) {
+ idx_of_active_list = i;
+ break;
+ }
+ }
+ }
+
+ playlist_free(player->nlist, player->lists);
+ player->nlist = nlist;
+ player->lists = playlist_dup(nlist, lists);
+ player->active_list = player->lists + idx_of_active_list;
+}
+
+void clients_player_request_state(player_t *player, player_state_t state)
+{
+ if (!player || (state != PLAY && state != PAUSE && state != STOP))
+ return;
+
+ if (state == player->state)
+ return;
+
+ player->request.state = state;
+ player->request.time = get_current_time();
+
+ if (player->address)
+ dbusif_set_player_state(player, state);
+ else if (state == PLAY)
+ /* this supposed to launch the player */
+ dbusif_introspect_player(player);
+}
+
+static int notify_focus(srs_client_t *srs_client, srs_voice_focus_t focus)
+{
+ return TRUE;
+}
+
+static int notify_command(srs_client_t *srs_client, int ntoken, char **tokens)
+{
+ return TRUE;
+}
+
+static void handle_delayed_request(pa_mainloop_api *api,
+ pa_time_event *e,
+ const struct timeval *tv,
+ void *user_data)
+{
+ player_t *player = (player_t *)user_data;
+
+ dbusif_query_playlists(player);
+ dbusif_set_player_state(player, player->request.state);
+ memset(&player->request, 0, sizeof(player->request));
+}
+
+static void schedule_delayed_request(player_t *player)
+{
+ context_t *ctx;
+ uint64_t age_of_request;
+ srs_plugin_t *pl;
+ srs_context_t *srs;
+ pa_mainloop_api *api;
+ struct timeval tv;
+
+ if (!player || !(ctx = player->ctx) ||
+ !(pl = ctx->plugin) || !(srs = pl->srs) ||
+ !(api = pa_mainloop_get_api(srs->pa)))
+ return;
+
+ age_of_request = get_current_time() - player->request.time;
+
+ if (player->request.state && player->request.state != player->state) {
+ if (age_of_request < 3000000ULL) {
+ gettimeofday(&tv, NULL);
+ tv.tv_sec += 2;
+
+ if (player->timer)
+ api->time_restart(player->timer, &tv);
+ else {
+ player->timer = api->time_new(api, &tv, handle_delayed_request,
+ player);
+ }
+ }
+ }
+}
+
+static int player_register(void *key, void *object, void *user_data)
+{
+ context_t *ctx = (context_t *)user_data;
+ player_t *player = (player_t *)object;
+ int sts;
+
+ dbusif_register_player(ctx, player->name);
+
+ return MRP_HTBL_ITER_MORE;
+}
+
+static void player_free(void *key, void *object)
+{
+ player_t *player = (player_t *)object;
+ context_t *ctx;
+ srs_plugin_t *pl;
+ srs_context_t *srs;
+ pa_mainloop_api *api;
+
+
+ if (strcmp(key, player->name))
+ mrp_log_error("mpris2-client: corrupt hashtable (key '%s')", key);
+ else {
+ if (player->timer && (ctx = player->ctx) && (pl = ctx->plugin) &&
+ (srs = pl->srs) && (api = pa_mainloop_get_api(srs->pa)))
+ {
+ api->time_free(player->timer);
+ }
+
+ mrp_free((void *)player->name);
+ mrp_free((void *)player->service);
+ mrp_free((void *)player->object);
+ mrp_free((void *)player);
+ playlist_free(player->nlist, player->lists);
+ }
+}
+
+static playlist_t *playlist_dup(size_t nlist, playlist_t *lists)
+{
+ playlist_t *dup = mrp_allocz(sizeof(playlist_t) * (nlist + 1));
+ playlist_t *src, *dst;
+ size_t i;
+
+ for (i = 0; i < nlist; i++) {
+ src = lists + i;
+ dst = dup + i;
+
+ dst->id = mrp_strdup(src->id);
+ dst->name = mrp_strdup(src->name);
+ }
+
+ return dup;
+}
+
+static void playlist_free(size_t nlist, playlist_t *lists)
+{
+ playlist_t *list;
+ size_t i;
+
+ if (lists) {
+ for (i = 0; i < nlist; i++) {
+ list = lists + i;
+
+ free((void *)list->id);
+ free((void *)list->name);
+ }
+
+ free((void *)lists);
+ }
+}
+
+
+static uint64_t get_current_time(void)
+{
+ struct timeval tv;
+ uint64_t now;
+
+ gettimeofday(&tv, NULL);
+
+ now = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_usec;
+
+ return now;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
--- /dev/null
+#ifndef __SRS_MPRIS2_CLIENT_H__
+#define __SRS_MPRIS2_CLIENT_H__
+
+#include "mpris2-plugin.h"
+
+enum player_state_e {
+ UNKNOWN = 0,
+ PLAY,
+ PAUSE,
+ STOP
+};
+
+struct player_s {
+ context_t *ctx;
+ const char *name;
+ const char *service;
+ const char *object;
+ const char *address;
+ player_state_t state;
+ struct {
+ player_state_t state;
+ uint64_t time;
+ } request;
+ bool ready;
+ size_t nlist;
+ playlist_t *lists;
+ playlist_t *active_list;
+ pa_time_event *timer;
+};
+
+struct playlist_s {
+ const char *id;
+ const char *name;
+};
+
+
+int clients_create(context_t *ctx);
+void clients_destroy(context_t *ctx);
+
+int clients_register_player(context_t *ctx, const char *name,
+ const char *service, const char *object);
+
+int clients_start(context_t *ctx);
+int clients_stop(context_t *ctx);
+
+player_t *clients_find_player_by_address(context_t *ctx, const char *address);
+player_t *clients_find_player_by_name(context_t *ctx, const char *name);
+
+void clients_player_appeared(context_t *ctx, const char *name,
+ const char *address);
+void clients_player_disappeared(context_t *ctx, const char *name);
+void clients_player_state_changed(player_t *player, player_state_t state);
+void clients_player_status_changed(player_t *player, bool ready);
+void clients_playlist_changed(player_t *player,size_t nlist,playlist_t *lists);
+
+void clients_player_request_state(player_t *player, player_state_t state);
+
+
+#endif /* __SRS_MPRIS2_CLIENT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/dbus.h>
+
+#include "dbusif.h"
+#include "clients.h"
+
+#define PLAYLIST_MAX 20
+
+struct dbusif_s {
+ const char *bustype;
+ mrp_dbus_t *dbus;
+};
+
+static void name_follow_cb(mrp_dbus_t *,const char *,int,const char *,void *);
+static void property_query_cb(mrp_dbus_t *, DBusMessage *, void *);
+static void introspect_cb(mrp_dbus_t *, DBusMessage *, void *);
+static int property_changed_cb(mrp_dbus_t *, DBusMessage *, void *);
+static void playlist_query_cb(mrp_dbus_t *, DBusMessage *, void *);
+static int parse_properties(context_t *, player_t *, DBusMessageIter *);
+
+int dbusif_create(context_t *ctx, mrp_mainloop_t *ml)
+{
+ dbusif_t *dbusif;
+
+ if (!(dbusif = mrp_allocz(sizeof(dbusif_t))))
+ return -1;
+
+ dbusif->bustype = mrp_strdup("session");
+ dbusif->dbus = mrp_dbus_get(ml, dbusif->bustype, NULL);
+
+ if (!dbusif->dbus) {
+ mrp_log_error("mpris2 plugin: failed to obtain DBus");
+ mrp_free(dbusif);
+ return -1;
+ }
+
+ ctx->dbusif = dbusif;
+
+ return 0;
+}
+
+void dbusif_destroy(context_t *ctx)
+{
+ dbusif_t *dbusif;
+
+ if (ctx && (dbusif = ctx->dbusif)) {
+ ctx->dbusif = NULL;
+
+ mrp_dbus_unref(dbusif->dbus);
+
+ mrp_free((void *)dbusif->bustype);
+ mrp_free((void *)dbusif);
+ }
+}
+
+int dbusif_register_player(context_t *ctx, const char *name)
+{
+ dbusif_t *dbusif;
+ mrp_dbus_t *dbus;
+ char dbus_name[1024];
+
+ if (!ctx || !name || !(dbusif = ctx->dbusif) || !(dbus = dbusif->dbus))
+ return -1;
+
+ snprintf(dbus_name, sizeof(dbus_name), "org.mpris.MediaPlayer2.%s", name);
+
+ if (!mrp_dbus_follow_name(dbus, dbus_name, name_follow_cb, ctx))
+ return -1;
+
+ return 0;
+}
+
+void dbusif_unregister_player(context_t *ctx, const char *name)
+{
+ dbusif_t *dbusif;
+ mrp_dbus_t *dbus;
+
+ if (!ctx || !name || !(dbusif = ctx->dbusif) || !(dbus = dbusif->dbus))
+ return;
+
+ mrp_dbus_forget_name(dbus, name, name_follow_cb, ctx);
+}
+
+void dbusif_query_player_properties(player_t *player)
+{
+ const char *interface = "org.mpris.MediaPlayer2.Player";
+
+ context_t *ctx;
+ dbusif_t *dbusif;
+ DBusMessage *msg;
+ int success;
+
+ if (!player || !player->name || !player->address ||
+ !(ctx = player->ctx) || !(dbusif = ctx->dbusif))
+ return;
+
+ msg = dbus_message_new_method_call(player->address,
+ "/org/mpris/MediaPlayer2",
+ "org.freedesktop.DBus.Properties",
+ "GetAll");
+
+ if (!msg)
+ return;
+
+ success = dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_INVALID);
+ if (success) {
+ mrp_dbus_send(dbusif->dbus,
+ player->address,
+ "/org/mpris/MediaPlayer2",
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ 1000,
+ property_query_cb, player, msg);
+ }
+
+ dbus_message_unref(msg);
+}
+
+void dbusif_introspect_player(player_t *player)
+{
+ context_t *ctx;
+ dbusif_t *dbusif;
+ DBusMessage *msg;
+
+ if (!player || !player->service || !player->object ||
+ !(ctx = player->ctx) || !(dbusif = ctx->dbusif))
+ return;
+
+ msg = dbus_message_new_method_call(player->service, player->object,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect");
+ if (msg) {
+ mrp_dbus_send(dbusif->dbus,
+ player->service,
+ player->object,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect",
+ 3000,
+ introspect_cb, player, msg);
+ dbus_message_unref(msg);
+ }
+}
+
+
+void dbusif_set_player_state(player_t *player, player_state_t state)
+{
+ context_t *ctx;
+ dbusif_t *dbusif;
+ DBusMessage *msg;
+ const char *member;
+
+ printf("address: %s\n", player->address ? player->address : "unknown");
+
+ if (!player || !player->address ||
+ !(ctx = player->ctx) || !(dbusif = ctx->dbusif))
+ return;
+
+ switch (state) {
+ case PLAY: member = (player->state == PAUSE) ? "Play":"PlayPause"; break;
+ case PAUSE: member = "Pause"; break;
+ case STOP: member = "Stop"; break;
+ default: return;
+ }
+
+ printf("member: %s\n", member);
+
+
+ msg = dbus_message_new_method_call(player->address,
+ "/org/mpris/MediaPlayer2",
+ "org.mpris.MediaPlayer2.Player",
+ member);
+ if (msg) {
+ mrp_dbus_send_msg(dbusif->dbus, msg);
+ dbus_message_unref(msg);
+ }
+}
+
+void dbusif_set_playlist(player_t *player, const char *id)
+{
+ context_t *ctx;
+ dbusif_t *dbusif;
+ DBusMessage *msg;
+ int success;
+
+ printf("playlist id: %s\n");
+
+ if (!player || !player->address ||
+ !(ctx = player->ctx) || !(dbusif = ctx->dbusif))
+ return;
+
+ msg = dbus_message_new_method_call(player->address,
+ "/org/mpris/MediaPlayer2",
+ "org.mpris.MediaPlayer2.Playlists",
+ "ActivatePlaylist");
+ if (!msg)
+ return;
+
+ success = dbus_message_append_args(msg,
+ DBUS_TYPE_OBJECT_PATH, &id,
+ DBUS_TYPE_INVALID);
+ if (success)
+ mrp_dbus_send_msg(dbusif->dbus, msg);
+
+ dbus_message_unref(msg);
+}
+
+void dbusif_query_playlists(player_t *player)
+{
+ const char *order = "Alpahbetical";
+ dbus_bool_t reverse = FALSE;
+ dbus_uint32_t index = 0;
+ dbus_uint32_t max_count = PLAYLIST_MAX;
+
+ context_t *ctx;
+ dbusif_t *dbusif;
+ DBusMessage *msg;
+ int success;
+
+ if (!player || !player->name || !player->address ||
+ !(ctx = player->ctx) || !(dbusif = ctx->dbusif))
+ return;
+
+ msg = dbus_message_new_method_call(player->address,
+ "/org/mpris/MediaPlayer2",
+ "org.mpris.MediaPlayer2.Playlists",
+ "GetPlaylists");
+
+ if (!msg)
+ return;
+
+ success = dbus_message_append_args(msg,
+ DBUS_TYPE_UINT32, &index,
+ DBUS_TYPE_UINT32, &max_count,
+ DBUS_TYPE_STRING, &order,
+ DBUS_TYPE_BOOLEAN, &reverse,
+ DBUS_TYPE_INVALID);
+ if (success) {
+ mrp_dbus_send(dbusif->dbus,
+ player->address,
+ "/org/mpris/MediaPlayer2",
+ "org.mpris.MediaPlayer2.Playlists",
+ "GetPlaylists",
+ 1000,
+ playlist_query_cb, player, msg);
+ }
+
+ dbus_message_unref(msg);
+}
+
+static void name_follow_cb(mrp_dbus_t *dbus,
+ const char *dbus_name,
+ int error,
+ const char *owner,
+ void *user_data)
+{
+ context_t *ctx = (context_t *)user_data;
+ dbusif_t *dbusif;
+ const char *dot;
+ const char *name;
+
+ if (ctx && (dbusif = ctx->dbusif) && (dot = strrchr(dbus_name, '.'))) {
+ name = dot + 1;
+
+ if (owner && owner[0]) {
+ if (owner[0] == ':') {
+ clients_player_appeared(ctx, name, owner);
+
+ mrp_dbus_subscribe_signal(dbusif->dbus,property_changed_cb,ctx,
+ owner,
+ "/org/mpris/MediaPlayer2",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged", NULL);
+ }
+ }
+ else {
+ clients_player_disappeared(ctx, name);
+
+ mrp_dbus_unsubscribe_signal(dbusif->dbus, property_changed_cb, ctx,
+ owner,
+ "/org/mpris/MediaPlayer2",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged", NULL);
+ }
+ }
+}
+
+static void property_query_cb(mrp_dbus_t *dbus,
+ DBusMessage *msg,
+ void *user_data)
+{
+ player_t *player = (player_t *)user_data;
+ context_t *ctx;
+ DBusMessageIter mit;
+
+ if (player && (ctx = player->ctx) && dbus_message_iter_init(msg, &mit))
+ parse_properties(ctx, player, &mit);
+}
+
+static void introspect_cb(mrp_dbus_t *dbus,
+ DBusMessage *msg,
+ void *user_data)
+{
+ player_t *player = (player_t *)user_data;
+ context_t *ctx;
+ DBusMessageIter mit;
+ dbus_bool_t success;
+ const char *xml;
+
+ if (player && (ctx = player->ctx)) {
+ success = dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &xml,
+ DBUS_TYPE_INVALID);
+ if (success)
+ mrp_log_info("%s", xml);
+ }
+}
+
+static void playlist_query_cb(mrp_dbus_t *dbus,
+ DBusMessage *msg,
+ void *user_data)
+{
+ player_t *player = (player_t *)user_data;
+ context_t *ctx;
+ DBusMessageIter mit;
+ DBusMessageIter ait;
+ DBusMessageIter sit;
+ playlist_t lists[PLAYLIST_MAX + 1];
+ playlist_t *list;
+ size_t nlist;
+
+ if (!player || !(ctx = player->ctx))
+ return;
+
+ if (!dbus_message_iter_init(msg, &mit))
+ return;
+
+ if (dbus_message_iter_get_arg_type(&mit) != DBUS_TYPE_ARRAY)
+ return;
+
+ dbus_message_iter_recurse(&mit, &ait);
+
+ nlist = 0;
+
+ while (dbus_message_iter_get_arg_type(&ait) == DBUS_TYPE_STRUCT) {
+ list = lists + nlist++;
+
+ dbus_message_iter_recurse(&ait, &sit);
+
+ dbus_message_iter_get_basic(&sit, &list->id);
+ dbus_message_iter_next(&sit);
+ dbus_message_iter_get_basic(&sit, &list->name);
+
+ printf("*** %d: '%s' '%s'\n", nlist-1, list->id, list->name);
+
+ dbus_message_iter_next(&ait);
+ }
+
+ memset(lists + nlist, 0, sizeof(playlist_t));
+
+ clients_playlist_changed(player, nlist, lists);
+}
+
+static int property_changed_cb(mrp_dbus_t *dbus,
+ DBusMessage *msg,
+ void *user_data)
+{
+ context_t *ctx = (context_t *)user_data;
+ const char *sender = dbus_message_get_sender(msg);
+ player_t *player;
+ const char *interface;
+ DBusMessageIter mit;
+
+ if (!ctx || ! sender)
+ return FALSE;
+
+ if (!(player = clients_find_player_by_address(ctx, sender)))
+ return FALSE;
+
+ if (!dbus_message_iter_init(msg, &mit))
+ return FALSE;
+
+ if (dbus_message_iter_get_arg_type(&mit) != DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&mit, &interface);
+
+ if (strcmp(interface, "org.mpris.MediaPlayer2.Player"))
+ return FALSE;
+
+ if (!dbus_message_iter_next(&mit))
+ return FALSE;
+
+ return parse_properties(ctx, player, &mit);
+}
+
+static int parse_properties(context_t *ctx,
+ player_t *player,
+ DBusMessageIter *mit)
+{
+ const char *prop;
+ int type;
+ DBusBasicValue value;
+ DBusMessageIter ait;
+ DBusMessageIter dit;
+ DBusMessageIter vit;
+ player_state_t state;
+
+ if (!ctx || !player)
+ return FALSE;
+
+ if (dbus_message_iter_get_arg_type(mit) != DBUS_TYPE_ARRAY)
+ return FALSE;
+
+ dbus_message_iter_recurse(mit, &ait);
+
+ while (dbus_message_iter_get_arg_type(&ait) == DBUS_TYPE_DICT_ENTRY) {
+ dbus_message_iter_recurse(&ait, &dit);
+
+ dbus_message_iter_get_basic(&dit, &prop);
+ dbus_message_iter_next(&dit);
+
+ dbus_message_iter_recurse(&dit, &vit);
+ type = dbus_message_iter_get_arg_type(&vit);
+
+ if (type != DBUS_TYPE_ARRAY) {
+ memset(&value, 0, sizeof(value));
+ dbus_message_iter_get_basic(&vit, &value);
+
+ if (!strcmp(prop, "PlaybackStatus") && type == DBUS_TYPE_STRING) {
+ if (!strcmp(value.str, "Playing"))
+ state = PLAY;
+ else if (!strcmp(value.str, "Paused"))
+ state = PAUSE;
+ else if (!strcmp(value.str, "Stopped"))
+ state = STOP;
+ else
+ state = UNKNOWN;
+
+ printf("*** state %d\n", state);
+
+ if (state != UNKNOWN)
+ clients_player_state_changed(player, state);
+ }
+ else if (!strcmp(prop, "Volume") && type == DBUS_TYPE_DOUBLE) {
+ printf("*** volume %.4lf\n", value.dbl);
+ }
+ else if (!strcmp(prop, "CanPlay") && type == DBUS_TYPE_BOOLEAN) {
+ printf("*** %s play\n", value.bool_val ? "can" : "unable to");
+ clients_player_status_changed(player, value.bool_val);
+ }
+ }
+
+ dbus_message_iter_next(&ait);
+ }
+
+
+ return TRUE;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
--- /dev/null
+#ifndef __SRS_MPRIS2_DBUS_INTERFACE_H__
+#define __SRS_MPRIS2_DBUS_INTERFACE_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <murphy/common/mainloop.h>
+
+#include "mpris2-plugin.h"
+
+int dbusif_create(context_t *ctx, mrp_mainloop_t *ml);
+void dbusif_destroy(context_t *ctx);
+
+int dbusif_register_player(context_t *ctx, const char *name);
+void dbusif_unregister_player(context_t *ctx, const char *name);
+
+void dbusif_query_player_properties(player_t *player);
+void dbusif_introspect_player(player_t *player);
+
+void dbusif_set_player_state(player_t *player, player_state_t state);
+
+void dbusif_query_playlists(player_t *player);
+void dbusif_set_playlist(player_t *player, const char *id);
+
+
+#endif /* __SRS_MPRIS2_DBUS_INTERFACE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
--- /dev/null
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mainloop.h>
+
+#include "dbusif.h"
+#include "clients.h"
+
+
+#define PLUGIN_DESCRIPTION "Mpris2 client to drive various media players."
+#define PLUGIN_AUTHORS "Janos Kovacs <janos.kovacs@intel.com>"
+#define PLUGIN_VERSION "0.0.1"
+
+
+/*************************************************************/
+pa_io_event *ioev;
+
+static void handle_input(pa_mainloop_api *api, pa_io_event *e, int fd,
+ pa_io_event_flags_t events, void *ud)
+{
+ context_t *ctx = (context_t *)ud;
+ player_t *player;
+ int cnt;
+ char cmd;
+
+ for (;;) {
+ if ((cnt = read(0, &cmd, 1)) != 1) {
+ if (errno == EINTR)
+ continue;
+ return;
+ }
+
+ if (!(player = clients_find_player_by_name(ctx, "rhythmbox"))) {
+ printf("can't find rhythmbox player\n");
+ return;
+ }
+
+ switch (cmd) {
+ case 'p': clients_player_request_state(player, PLAY); break;
+ case 's': clients_player_request_state(player, PAUSE); break;
+ case 'e': clients_player_request_state(player, STOP); break;
+ case 'a': printf("Show\n"); break;
+ default: break;
+ }
+
+ return;
+ }
+}
+
+static void input_create(context_t *ctx)
+{
+ srs_plugin_t *pl = ctx->plugin;
+ srs_context_t *srs = pl->srs;
+ pa_mainloop_api *api = pa_mainloop_get_api(srs->pa);
+
+ ioev = api->io_new(api, 0, PA_IO_EVENT_INPUT, handle_input, ctx);
+}
+
+/*************************************************************/
+
+
+
+static int create_mpris2(srs_plugin_t *plugin)
+{
+ srs_context_t *srs = plugin->srs;
+ context_t *ctx = NULL;
+ int sts;
+
+ mrp_debug("creating Mpris2 client plugin");
+
+ if ((ctx = mrp_allocz(sizeof(context_t)))) {
+ ctx->plugin = plugin;
+
+ if (dbusif_create(ctx, srs->ml) == 0 &&
+ clients_create(ctx) == 0 )
+ {
+ plugin->plugin_data = ctx;
+ return TRUE;
+ }
+
+ mrp_free(ctx);
+ }
+
+ mrp_log_error("Failed to create Mpris2 client plugin.");
+
+ return FALSE;
+}
+
+
+static int config_mpris2(srs_plugin_t *plugin, srs_cfg_t *settings)
+{
+ context_t *ctx = (context_t *)plugin->plugin_data;
+ srs_cfg_t *cfgs, *c, *s;
+ const char *service;
+ const char *object;
+ char srv[256];
+ char obj[256];
+ int pfxlen;
+ int n, i, j, l;
+ int success;
+
+ mrp_debug("configuring Mpris2 client plugin");
+
+ n = srs_collect_config(settings, MPRIS2_PREFIX, &cfgs);
+ pfxlen = strlen(MPRIS2_PREFIX);
+
+ mrp_log_info("Found %d Mpris2 configuration keys.", n);
+
+ for (i = 0, success = TRUE; i < n ; i++) {
+ c = cfgs + i;
+
+ if (!strcmp("player", c->key + pfxlen)) {
+ snprintf(srv, sizeof(srv), "%s%s", c->value, ".service");
+ snprintf(obj, sizeof(obj), "%s%s", c->value, ".object");
+ for (j = 0, service = object = NULL;
+ j < n && (!service || !object);
+ j++)
+ {
+ s = cfgs + j;
+ if (!strcmp(srv, s->key + pfxlen))
+ service = s->value;
+ else if (!strcmp(obj, s->key + pfxlen))
+ object = s->value;
+ }
+
+ clients_register_player(ctx, c->value, service, object);
+ }
+ else {
+ l = strlen(c->key);
+
+ if (!(l > 8 && !strcmp(c->key + (l - 8), ".service")) ||
+ !(l > 7 && !strcmp(c->key + (l - 7), ".object" )) )
+ {
+ c->used = FALSE;
+ success = FALSE;
+ }
+ }
+ }
+
+ srs_free_config(cfgs);
+
+ return TRUE;
+}
+
+
+static int start_mpris2(srs_plugin_t *plugin)
+{
+ srs_context_t *srs = plugin->srs;
+ context_t *ctx = (context_t *)plugin->plugin_data;
+
+ mrp_debug("start Mpris2 client plugin");
+
+ clients_start(ctx);
+
+ // input_create(ctx);
+
+ return TRUE;
+}
+
+
+static void stop_mpris2(srs_plugin_t *plugin)
+{
+ context_t *ctx = (context_t *)plugin->plugin_data;
+
+ mrp_debug("stop mpris2 client plugin");
+
+ clients_stop(ctx);
+}
+
+
+static void destroy_mpris2(srs_plugin_t *plugin)
+{
+ srs_context_t *srs = plugin->srs;
+ context_t *ctx = (context_t *)plugin->plugin_data;
+
+ mrp_debug("destroy Mpris2 client plugin");
+
+ clients_destroy(ctx);
+ dbusif_destroy(ctx);
+}
+
+
+
+SRS_DECLARE_PLUGIN(PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_AUTHORS,
+ PLUGIN_VERSION, create_mpris2, config_mpris2,
+ start_mpris2, stop_mpris2, destroy_mpris2)
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
--- /dev/null
+#ifndef __SRS_MPRIS2_PLUGIN_H__
+#define __SRS_MPRIS2_PLUGIN_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "src/daemon/plugin.h"
+#include "src/daemon/client.h"
+
+#define PLUGIN_NAME "music-player"
+#define MPRIS2_PREFIX "mpris2."
+
+typedef enum player_state_e player_state_t;
+
+typedef struct context_s context_t;
+typedef struct dbusif_s dbusif_t;
+typedef struct clients_s clients_t;
+typedef struct player_s player_t;
+typedef struct playlist_s playlist_t;
+
+struct context_s {
+ srs_plugin_t *plugin;
+ dbusif_t *dbusif;
+ clients_t *clients;
+};
+
+
+#endif /* __SRS_MPRIS2_PLUGIN_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */