mpris2-client: first version of the client
authorJanos Kovacs <jankovac503@gmail.com>
Thu, 6 Jun 2013 13:37:53 +0000 (16:37 +0300)
committerKrisztian Litkey <krisztian.litkey@intel.com>
Thu, 6 Jun 2013 13:51:47 +0000 (16:51 +0300)
src/Makefile.am
src/plugins/mpris2-client/clients.c [new file with mode: 0644]
src/plugins/mpris2-client/clients.h [new file with mode: 0644]
src/plugins/mpris2-client/dbusif.c [new file with mode: 0644]
src/plugins/mpris2-client/dbusif.h [new file with mode: 0644]
src/plugins/mpris2-client/mpris2-plugin.c [new file with mode: 0644]
src/plugins/mpris2-client/mpris2-plugin.h [new file with mode: 0644]

index cf61cea..843613b 100644 (file)
@@ -127,6 +127,22 @@ plugin_simple_disambiguator_la_LDFLAGS =                   \
 
 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::
diff --git a/src/plugins/mpris2-client/clients.c b/src/plugins/mpris2-client/clients.c
new file mode 100644 (file)
index 0000000..dc94e10
--- /dev/null
@@ -0,0 +1,442 @@
+#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:
+ *
+ */
diff --git a/src/plugins/mpris2-client/clients.h b/src/plugins/mpris2-client/clients.h
new file mode 100644 (file)
index 0000000..a2bce01
--- /dev/null
@@ -0,0 +1,67 @@
+#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:
+ *
+ */
diff --git a/src/plugins/mpris2-client/dbusif.c b/src/plugins/mpris2-client/dbusif.c
new file mode 100644 (file)
index 0000000..ed47a80
--- /dev/null
@@ -0,0 +1,473 @@
+#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:
+ *
+ */
diff --git a/src/plugins/mpris2-client/dbusif.h b/src/plugins/mpris2-client/dbusif.h
new file mode 100644 (file)
index 0000000..13d6bb0
--- /dev/null
@@ -0,0 +1,34 @@
+#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:
+ *
+ */
diff --git a/src/plugins/mpris2-client/mpris2-plugin.c b/src/plugins/mpris2-client/mpris2-plugin.c
new file mode 100644 (file)
index 0000000..eb718f1
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * 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:
+ *
+ */
diff --git a/src/plugins/mpris2-client/mpris2-plugin.h b/src/plugins/mpris2-client/mpris2-plugin.h
new file mode 100644 (file)
index 0000000..fa0ff7e
--- /dev/null
@@ -0,0 +1,36 @@
+#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:
+ *
+ */