diff --git a/tools/mpris-player.c b/tools/mpris-player.c
index 246791a..d58e53f 100644
--- a/tools/mpris-player.c
+++ b/tools/mpris-player.c
#include <signal.h>
#include <getopt.h>
#include <string.h>
+#include <inttypes.h>
#include <dbus/dbus.h>
#include <glib.h>
#define BLUEZ_PATH "/org/bluez"
#define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter1"
#define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1"
+#define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
+#define MPRIS_BUS_NAME "org.mpris.MediaPlayer2."
#define MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
#define MPRIS_PLAYER_PATH "/org/mpris/MediaPlayer2"
+#define ERROR_INTERFACE "org.mpris.MediaPlayer2.Error"
static GMainLoop *main_loop;
static GDBusProxy *adapter = NULL;
static DBusConnection *sys = NULL;
static DBusConnection *session = NULL;
+static GDBusClient *client = NULL;
+static GSList *players = NULL;
+
+struct player {
+ char *name;
+ char *bus_name;
+ DBusConnection *conn;
+ GDBusProxy *proxy;
+ GDBusProxy *device;
+};
+
+typedef int (* parse_metadata_func) (DBusMessageIter *iter, const char *key,
+ DBusMessageIter *metadata);
static void dict_append_entry(DBusMessageIter *dict, const char *key, int type,
void *val);
return 0;
}
-static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata)
+static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata,
+ parse_metadata_func func)
{
DBusMessageIter dict;
int ctype;
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
- if (parse_metadata_entry(&entry, key, metadata) < 0)
+ if (func(&entry, key, metadata) < 0)
return -EINVAL;
dbus_message_iter_next(&dict);
return 0;
}
-static void append_metadata(DBusMessageIter *iter, DBusMessageIter *dict)
+static void append_metadata(DBusMessageIter *iter, DBusMessageIter *dict,
+ parse_metadata_func func)
{
DBusMessageIter value, metadata;
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
- parse_metadata(dict, &metadata);
+ parse_metadata(dict, &metadata, func);
dbus_message_iter_close_container(&value, &metadata);
dbus_message_iter_close_container(iter, &value);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
if (strcasecmp(key, "Metadata") == 0)
- append_metadata(&entry, val);
+ append_metadata(&entry, val, parse_metadata_entry);
else
append_variant(&entry, type, val);
static void remove_player(DBusConnection *conn, const char *sender)
{
DBusMessage *msg;
- char *path;
+ char *path, *owner;
if (!adapter)
return;
+ path = sender2path(sender);
+ dbus_connection_get_object_path_data(sys, path, (void **) &owner);
+
+ if (owner == NULL) {
+ g_free(path);
+ return;
+ }
+
msg = dbus_message_new_method_call(BLUEZ_BUS_NAME,
g_dbus_proxy_get_path(adapter),
BLUEZ_MEDIA_INTERFACE,
"UnregisterPlayer");
if (!msg) {
fprintf(stderr, "Can't allocate new method call\n");
+ g_free(path);
return;
}
- path = sender2path(sender);
dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID);
{
DBusMessageIter iter;
const char *iface;
- char *path;
+ char *path, *owner;
dbus_message_iter_init(msg, &iter);
+ path = sender2path(dbus_message_get_sender(msg));
+ dbus_connection_get_object_path_data(sys, path, (void **) &owner);
+
+ if (owner == NULL)
+ goto done;
+
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
dbus_message_iter_next(&iter);
- path = sender2path(dbus_message_get_sender(msg));
parse_properties(conn, path, &iter, NULL);
+done:
g_free(path);
return TRUE;
}
+static struct player *find_player_by_bus_name(const char *name)
+{
+ GSList *l;
+
+ for (l = players; l; l = l->next) {
+ struct player *player = l->data;
+
+ if (strcmp(player->bus_name, name) == 0)
+ return player;
+ }
+
+ return NULL;
+}
+
static gboolean name_owner_changed(DBusConnection *conn,
DBusMessage *msg, void *data)
{
if (*new == '\0') {
printf("player %s at %s disappear\n", name, old);
remove_player(conn, old);
- } else {
+ } else if (find_player_by_bus_name(name) == NULL) {
printf("player %s at %s found\n", name, new);
add_player(conn, name, new);
}
printf("org.bluez disappeared\n");
}
-static void proxy_added(GDBusProxy *proxy, void *user_data)
+static void player_free(void *data)
{
- const char *interface;
+ struct player *player = data;
+
+ if (player->conn) {
+ dbus_connection_close(player->conn);
+ dbus_connection_unref(player->conn);
+ }
+
+ g_dbus_proxy_unref(player->device);
+ g_dbus_proxy_unref(player->proxy);
+
+ g_free(player->name);
+ g_free(player->bus_name);
+ g_free(player);
+}
+
+struct pending_call {
+ struct player *player;
+ DBusMessage *msg;
+};
+
+static void pending_call_free(void *data)
+{
+ struct pending_call *p = data;
+
+ if (p->msg)
+ dbus_message_unref(p->msg);
+
+ g_free(p);
+}
+
+static void player_reply(DBusMessage *message, void *user_data)
+{
+ struct pending_call *p = user_data;
+ struct player *player = p->player;
+ DBusMessage *msg = p->msg;
+ DBusMessage *reply;
+ DBusError err;
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, message)) {
+ fprintf(stderr, "error: %s", err.name);
+ reply = g_dbus_create_error(msg, err.name, err.message);
+ dbus_error_free(&err);
+ } else
+ reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+
+ g_dbus_send_message(player->conn, reply);
+}
+
+static void player_control(struct player *player, DBusMessage *msg,
+ const char *name)
+{
+ struct pending_call *p;
+
+ p = g_new0(struct pending_call, 1);
+ p->player = player;
+ p->msg = dbus_message_ref(msg);
+
+ g_dbus_proxy_method_call(player->proxy, name, NULL, player_reply,
+ p, pending_call_free);
+}
+
+static DBusMessage *player_toggle(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct player *player = data;
+ DBusMessageIter value;
+ const char *status;
+
+ if (!g_dbus_proxy_get_property(player->proxy, "Status", &value))
+ return FALSE;
+
+ dbus_message_iter_get_basic(&value, &status);
+
+ if (strcasecmp(status, "Playing") == 0)
+ player_control(player, msg, "Pause");
+ else
+ player_control(player, msg, "Play");
+
+ return NULL;
+}
+
+static DBusMessage *player_play(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct player *player = data;
+
+ player_control(player, msg, "Play");
+
+ return NULL;
+}
+
+static DBusMessage *player_pause(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct player *player = data;
+
+ player_control(player, msg, "Pause");
+
+ return NULL;
+}
+
+static DBusMessage *player_stop(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct player *player = data;
+
+ player_control(player, msg, "Stop");
+
+ return NULL;
+}
+
+static DBusMessage *player_next(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct player *player = data;
+
+ player_control(player, msg, "Next");
+
+ return NULL;
+}
+
+static DBusMessage *player_previous(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct player *player = data;
+
+ player_control(player, msg, "Previous");
+
+ return NULL;
+}
+
+static gboolean status_exists(const GDBusPropertyTable *property, void *data)
+{
+ DBusMessageIter iter;
+ struct player *player = data;
+
+ return g_dbus_proxy_get_property(player->proxy, "Status", &iter);
+}
+
+static const char *status_to_playback(const char *status)
+{
+ if (strcasecmp(status, "playing") == 0)
+ return "Playing";
+ else if (strcasecmp(status, "paused") == 0)
+ return "Paused";
+ else
+ return "Stopped";
+}
+
+static gboolean get_status(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct player *player = data;
+ DBusMessageIter value;
+ const char *status;
+
+ if (!g_dbus_proxy_get_property(player->proxy, "Status", &value))
+ return FALSE;
+
+ dbus_message_iter_get_basic(&value, &status);
+
+ status = status_to_playback(status);
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
+
+ return TRUE;
+}
+
+static gboolean repeat_exists(const GDBusPropertyTable *property, void *data)
+{
+ DBusMessageIter iter;
+ struct player *player = data;
+
+ return g_dbus_proxy_get_property(player->proxy, "Repeat", &iter);
+}
+
+static const char *repeat_to_loopstatus(const char *value)
+{
+ if (strcasecmp(value, "off") == 0)
+ return "None";
+ else if (strcasecmp(value, "singletrack") == 0)
+ return "Track";
+ else if (strcasecmp(value, "alltracks") == 0)
+ return "Playlist";
+
+ return NULL;
+}
+
+static gboolean get_repeat(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct player *player = data;
+ DBusMessageIter value;
+ const char *status;
+
+ if (!g_dbus_proxy_get_property(player->proxy, "Repeat", &value))
+ return FALSE;
+
+ dbus_message_iter_get_basic(&value, &status);
+
+ status = repeat_to_loopstatus(status);
+ if (status == NULL)
+ return FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
+
+ return TRUE;
+}
+
+static gboolean get_double(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ double value = 1.0;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value);
+
+ return TRUE;
+}
+
+static gboolean shuffle_exists(const GDBusPropertyTable *property, void *data)
+{
+ DBusMessageIter iter;
+ struct player *player = data;
+
+ return g_dbus_proxy_get_property(player->proxy, "Shuffle", &iter);
+}
+
+static gboolean get_shuffle(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct player *player = data;
+ DBusMessageIter value;
+ const char *string;
+ dbus_bool_t shuffle;
+
+ if (!g_dbus_proxy_get_property(player->proxy, "Shuffle", &value))
+ return FALSE;
+
+ dbus_message_iter_get_basic(&value, &string);
+
+ shuffle = strcmp(string, "off") != 0;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &shuffle);
+
+ return TRUE;
+}
+
+static gboolean position_exists(const GDBusPropertyTable *property, void *data)
+{
+ DBusMessageIter iter;
+ struct player *player = data;
+
+ return g_dbus_proxy_get_property(player->proxy, "Position", &iter);
+}
- if (adapter != NULL)
+static gboolean get_position(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct player *player = data;
+ DBusMessageIter var;
+ uint32_t position;
+ int64_t value;
+
+ if (!g_dbus_proxy_get_property(player->proxy, "Position", &var))
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &position);
+
+ value = position * 1000;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &value);
+
+ return TRUE;
+}
+
+static gboolean track_exists(const GDBusPropertyTable *property, void *data)
+{
+ DBusMessageIter iter;
+ struct player *player = data;
+
+ return g_dbus_proxy_get_property(player->proxy, "Track", &iter);
+}
+
+static gboolean parse_string_metadata(DBusMessageIter *iter, const char *key,
+ DBusMessageIter *metadata)
+{
+ const char *value;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(iter, &value);
+
+ dict_append_entry(metadata, key, DBUS_TYPE_STRING, &value);
+
+ return TRUE;
+}
+
+static gboolean parse_array_metadata(DBusMessageIter *iter, const char *key,
+ DBusMessageIter *metadata)
+{
+ char **value;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+ return FALSE;
+
+ value = dbus_malloc0(sizeof(char *));
+
+ dbus_message_iter_get_basic(iter, &(value[0]));
+
+ dict_append_array(metadata, key, DBUS_TYPE_STRING, &value, 1);
+
+ dbus_free(value);
+
+ return TRUE;
+}
+
+static gboolean parse_int64_metadata(DBusMessageIter *iter, const char *key,
+ DBusMessageIter *metadata)
+{
+ uint32_t duration;
+ int64_t value;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
+ return FALSE;
+
+ dbus_message_iter_get_basic(iter, &duration);
+
+ value = duration * 1000;
+
+ dict_append_entry(metadata, key, DBUS_TYPE_INT64, &value);
+
+ return TRUE;
+}
+
+static gboolean parse_int32_metadata(DBusMessageIter *iter, const char *key,
+ DBusMessageIter *metadata)
+{
+ uint32_t value;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
+ return FALSE;
+
+ dbus_message_iter_get_basic(iter, &value);
+
+ dict_append_entry(metadata, key, DBUS_TYPE_INT32, &value);
+
+ return TRUE;
+}
+
+static int parse_track_entry(DBusMessageIter *entry, const char *key,
+ DBusMessageIter *metadata)
+{
+ DBusMessageIter var;
+
+ printf("metadata %s found\n", key);
+
+ if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT)
+ return -EINVAL;
+
+ dbus_message_iter_recurse(entry, &var);
+
+ if (strcasecmp(key, "Title") == 0) {
+ if (!parse_string_metadata(&var, "xesam:title", metadata))
+ return -EINVAL;
+ } else if (strcasecmp(key, "Artist") == 0) {
+ if (!parse_array_metadata(&var, "xesam:artist", metadata))
+ return -EINVAL;
+ } else if (strcasecmp(key, "Album") == 0) {
+ if (!parse_string_metadata(&var, "xesam:album", metadata))
+ return -EINVAL;
+ } else if (strcasecmp(key, "Genre") == 0) {
+ if (!parse_array_metadata(&var, "xesam:genre", metadata))
+ return -EINVAL;
+ } else if (strcasecmp(key, "Duration") == 0) {
+ if (!parse_int64_metadata(&var, "mpris:length", metadata))
+ return -EINVAL;
+ } else if (strcasecmp(key, "TrackNumber") == 0) {
+ if (!parse_int32_metadata(&var, "xesam:trackNumber", metadata))
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static gboolean get_track(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct player *player = data;
+ DBusMessageIter var, metadata;
+
+ if (!g_dbus_proxy_get_property(player->proxy, "Track", &var))
+ return FALSE;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
+
+ parse_metadata(&var, &metadata, parse_track_entry);
+
+ dbus_message_iter_close_container(iter, &metadata);
+
+ return TRUE;
+}
+
+static gboolean get_enable(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ dbus_bool_t value = TRUE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
+
+ return TRUE;
+}
+
+static const GDBusMethodTable player_methods[] = {
+ { GDBUS_ASYNC_METHOD("PlayPause", NULL, NULL, player_toggle) },
+ { GDBUS_ASYNC_METHOD("Play", NULL, NULL, player_play) },
+ { GDBUS_ASYNC_METHOD("Pause", NULL, NULL, player_pause) },
+ { GDBUS_ASYNC_METHOD("Stop", NULL, NULL, player_stop) },
+ { GDBUS_ASYNC_METHOD("Next", NULL, NULL, player_next) },
+ { GDBUS_ASYNC_METHOD("Previous", NULL, NULL, player_previous) },
+ { }
+};
+
+static const GDBusSignalTable player_signals[] = {
+ { GDBUS_SIGNAL("Seeked", GDBUS_ARGS({"Position", "x"})) },
+ { }
+};
+
+static const GDBusPropertyTable player_properties[] = {
+ { "PlaybackStatus", "s", get_status, NULL, status_exists },
+ { "LoopStatus", "s", get_repeat, NULL, repeat_exists },
+ { "Rate", "d", get_double, NULL, NULL },
+ { "MinimumRate", "d", get_double, NULL, NULL },
+ { "MaximumRate", "d", get_double, NULL, NULL },
+ { "Shuffle", "b", get_shuffle, NULL, shuffle_exists },
+ { "Position", "x", get_position, NULL, position_exists },
+ { "Metadata", "a{sv}", get_track, NULL, track_exists },
+ { "Volume", "d", get_double, NULL, NULL },
+ { "CanGoNext", "b", get_enable, NULL, NULL },
+ { "CanGoPrevious", "b", get_enable, NULL, NULL },
+ { "CanPlay", "b", get_enable, NULL, NULL },
+ { "CanPause", "b", get_enable, NULL, NULL },
+ { "CanSeek", "b", get_enable, NULL, NULL },
+ { "CanControl", "b", get_enable, NULL, NULL },
+ { }
+};
+
+
+#define a_z "abcdefghijklmnopqrstuvwxyz"
+#define A_Z "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define _0_9 "_0123456789"
+
+static char *mpris_busname(char *name)
+{
+ return g_strconcat(MPRIS_BUS_NAME,
+ g_strcanon(name, A_Z a_z _0_9, '_'), NULL);
+}
+
+static void register_player(GDBusProxy *proxy)
+{
+ struct player *player;
+ DBusMessageIter iter;
+ const char *path, *name;
+ GDBusProxy *device;
+
+ if (!g_dbus_proxy_get_property(proxy, "Device", &iter))
+ return;
+
+ dbus_message_iter_get_basic(&iter, &path);
+
+ device = g_dbus_proxy_new(client, path, "org.bluez.Device1");
+ if (device == NULL)
+ return;
+
+ if (!g_dbus_proxy_get_property(device, "Name", &iter))
return;
+ dbus_message_iter_get_basic(&iter, &name);
+
+ player = g_new0(struct player, 1);
+ player->name = g_strdup(name);
+ player->bus_name = mpris_busname(player->name);
+ player->proxy = g_dbus_proxy_ref(proxy);
+ player->device = device;
+
+ players = g_slist_prepend(players, player);
+
+ printf("Player %s created\n", player->bus_name);
+
+ player->conn = g_dbus_setup_private(DBUS_BUS_SESSION, player->bus_name,
+ NULL);
+ if (!session) {
+ fprintf(stderr, "Could not register bus name %s",
+ player->bus_name);
+ goto fail;
+ }
+
+ if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
+ MPRIS_PLAYER_INTERFACE,
+ player_methods,
+ player_signals,
+ player_properties,
+ player, player_free)) {
+ fprintf(stderr, "Could not register interface %s",
+ MPRIS_PLAYER_INTERFACE);
+ goto fail;
+ }
+
+ return;
+
+fail:
+ players = g_slist_remove(players, player);
+ player_free(player);
+}
+
+static void proxy_added(GDBusProxy *proxy, void *user_data)
+{
+ const char *interface;
+
interface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(interface, BLUEZ_ADAPTER_INTERFACE)) {
+ if (adapter != NULL)
+ return;
+
printf("Bluetooth Adapter %s found\n",
g_dbus_proxy_get_path(proxy));
adapter = proxy;
list_names(session);
+ } else if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE)) {
+ printf("Bluetooth Player %s found\n",
+ g_dbus_proxy_get_path(proxy));
+ register_player(proxy);
}
}
+static void unregister_player(struct player *player)
+{
+ players = g_slist_remove(players, player);
+
+ g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
+ MPRIS_PLAYER_INTERFACE);
+}
+
+static struct player *find_player(GDBusProxy *proxy)
+{
+ GSList *l;
+
+ for (l = players; l; l = l->next) {
+ struct player *player = l->data;
+
+ if (player->proxy == proxy)
+ return player;
+ }
+
+ return NULL;
+}
+
static void proxy_removed(GDBusProxy *proxy, void *user_data)
{
const char *interface;
interface = g_dbus_proxy_get_interface(proxy);
- if (strcmp(interface, BLUEZ_ADAPTER_INTERFACE))
- return;
+ if (strcmp(interface, BLUEZ_ADAPTER_INTERFACE) == 0) {
+ if (adapter != proxy)
+ return;
+ printf("Bluetooth Adapter %s removed\n",
+ g_dbus_proxy_get_path(proxy));
+ adapter = NULL;
+ } else if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0) {
+ struct player *player;
- if (adapter != proxy)
- return;
+ player = find_player(proxy);
+ if (player == NULL)
+ return;
- printf("Bluetooth Adapter %s removed\n", g_dbus_proxy_get_path(proxy));
- adapter = NULL;
+ printf("Bluetooth Player %s removed\n",
+ g_dbus_proxy_get_path(proxy));
+ unregister_player(player);
+ }
}
int main(int argc, char *argv[])
{
- GDBusClient *client;
guint owner_watch, properties_watch;
struct sigaction sa;
int opt;
g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
- NULL, NULL);
+ NULL, NULL);
g_main_loop_run(main_loop);