3 * BlueZ - Bluetooth protocol stack for Linux
5 * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
37 #include <dbus/dbus.h>
40 #include "gdbus/gdbus.h"
42 #define BLUEZ_BUS_NAME "org.bluez"
43 #define BLUEZ_PATH "/org/bluez"
44 #define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter1"
45 #define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1"
46 #define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
47 #define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
48 #define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
49 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"
50 #define MPRIS_BUS_NAME "org.mpris.MediaPlayer2."
51 #define MPRIS_INTERFACE "org.mpris.MediaPlayer2"
52 #define MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
53 #define MPRIS_TRACKLIST_INTERFACE "org.mpris.MediaPlayer2.TrackList"
54 #define MPRIS_PLAYLISTS_INTERFACE "org.mpris.MediaPlayer2.Playlists"
55 #define MPRIS_PLAYER_PATH "/org/mpris/MediaPlayer2"
56 #define ERROR_INTERFACE "org.mpris.MediaPlayer2.Error"
58 static GMainLoop *main_loop;
59 static GDBusProxy *adapter = NULL;
60 static DBusConnection *sys = NULL;
61 static DBusConnection *session = NULL;
62 static GDBusClient *client = NULL;
63 static GSList *players = NULL;
64 static GSList *transports = NULL;
66 static gboolean option_version = FALSE;
67 static gboolean option_export = FALSE;
80 GDBusProxy *transport;
82 struct tracklist *tracklist;
85 typedef int (* parse_metadata_func) (DBusMessageIter *iter, const char *key,
86 DBusMessageIter *metadata);
88 static void dict_append_entry(DBusMessageIter *dict, const char *key, int type,
91 static void sig_term(int sig)
93 g_main_loop_quit(main_loop);
96 static DBusMessage *get_all(DBusConnection *conn, const char *name)
98 DBusMessage *msg, *reply;
100 const char *iface = MPRIS_PLAYER_INTERFACE;
102 msg = dbus_message_new_method_call(name, MPRIS_PLAYER_PATH,
103 DBUS_INTERFACE_PROPERTIES, "GetAll");
105 fprintf(stderr, "Can't allocate new method call\n");
109 dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface,
112 dbus_error_init(&err);
114 reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
116 dbus_message_unref(msg);
119 if (dbus_error_is_set(&err)) {
120 fprintf(stderr, "%s\n", err.message);
121 dbus_error_free(&err);
129 static void append_variant(DBusMessageIter *iter, int type, void *val)
131 DBusMessageIter value;
132 char sig[2] = { type, '\0' };
134 dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value);
136 dbus_message_iter_append_basic(&value, type, val);
138 dbus_message_iter_close_container(iter, &value);
141 static void append_array_variant(DBusMessageIter *iter, int type, void *val,
144 DBusMessageIter variant, array;
145 char type_sig[2] = { type, '\0' };
146 char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' };
148 dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
149 array_sig, &variant);
151 dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
154 if (dbus_type_is_fixed(type) == TRUE) {
155 dbus_message_iter_append_fixed_array(&array, type, val,
157 } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) {
158 const char ***str_array = val;
161 for (i = 0; i < n_elements; i++)
162 dbus_message_iter_append_basic(&array, type,
166 dbus_message_iter_close_container(&variant, &array);
168 dbus_message_iter_close_container(iter, &variant);
171 static void dict_append_array(DBusMessageIter *dict, const char *key, int type,
172 void *val, int n_elements)
174 DBusMessageIter entry;
176 dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
179 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
181 append_array_variant(&entry, type, val, n_elements);
183 dbus_message_iter_close_container(dict, &entry);
186 static void append_basic(DBusMessageIter *base, DBusMessageIter *iter,
191 dbus_message_iter_get_basic(iter, &value);
192 dbus_message_iter_append_basic(base, type, &value);
195 static void append_iter(DBusMessageIter *base, DBusMessageIter *iter);
196 static void append_container(DBusMessageIter *base, DBusMessageIter *iter,
199 DBusMessageIter iter_sub, base_sub;
202 dbus_message_iter_recurse(iter, &iter_sub);
205 case DBUS_TYPE_ARRAY:
206 case DBUS_TYPE_VARIANT:
207 sig = dbus_message_iter_get_signature(&iter_sub);
214 dbus_message_iter_open_container(base, type, sig, &base_sub);
219 append_iter(&base_sub, &iter_sub);
221 dbus_message_iter_close_container(base, &base_sub);
224 static void append_iter(DBusMessageIter *base, DBusMessageIter *iter)
228 while ((type = dbus_message_iter_get_arg_type(iter)) !=
230 if (dbus_type_is_basic(type))
231 append_basic(base, iter, type);
232 else if (dbus_type_is_container(type))
233 append_container(base, iter, type);
235 dbus_message_iter_next(iter);
239 static void dict_append_iter(DBusMessageIter *dict, const char *key,
240 DBusMessageIter *iter)
242 DBusMessageIter entry;
244 dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
247 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
249 append_iter(&entry, iter);
251 dbus_message_iter_close_container(dict, &entry);
254 static int parse_metadata_entry(DBusMessageIter *entry, const char *key,
255 DBusMessageIter *metadata)
257 if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT)
260 dict_append_iter(metadata, key, entry);
265 static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata,
266 parse_metadata_func func)
268 DBusMessageIter dict;
271 ctype = dbus_message_iter_get_arg_type(args);
272 if (ctype != DBUS_TYPE_ARRAY)
275 dbus_message_iter_recurse(args, &dict);
277 while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
279 DBusMessageIter entry;
282 if (ctype != DBUS_TYPE_DICT_ENTRY)
285 dbus_message_iter_recurse(&dict, &entry);
286 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
289 dbus_message_iter_get_basic(&entry, &key);
290 dbus_message_iter_next(&entry);
292 if (func(&entry, key, metadata) < 0)
295 dbus_message_iter_next(&dict);
301 static void append_metadata(DBusMessageIter *iter, DBusMessageIter *dict,
302 parse_metadata_func func)
304 DBusMessageIter value, metadata;
306 dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a{sv}",
309 dbus_message_iter_open_container(&value, DBUS_TYPE_ARRAY,
310 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
311 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
312 DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
314 parse_metadata(dict, &metadata, func);
316 dbus_message_iter_close_container(&value, &metadata);
317 dbus_message_iter_close_container(iter, &value);
320 static void dict_append_entry(DBusMessageIter *dict, const char *key, int type,
323 DBusMessageIter entry;
325 if (type == DBUS_TYPE_STRING) {
326 const char *str = *((const char **) val);
331 dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
334 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
336 if (strcasecmp(key, "Metadata") == 0)
337 append_metadata(&entry, val, parse_metadata_entry);
339 append_variant(&entry, type, val);
341 dbus_message_iter_close_container(dict, &entry);
344 static char *sender2path(const char *sender)
348 path = g_strconcat("/", sender, NULL);
349 return g_strdelimit(path, ":.", '_');
352 static void copy_reply(DBusPendingCall *call, void *user_data)
354 DBusMessage *msg = user_data;
355 DBusMessage *reply = dbus_pending_call_steal_reply(call);
357 DBusMessageIter args, iter;
359 copy = dbus_message_new_method_return(msg);
361 dbus_message_unref(reply);
365 dbus_message_iter_init_append(copy, &iter);
367 if (!dbus_message_iter_init(reply, &args))
370 append_iter(&iter, &args);
372 dbus_connection_send(sys, copy, NULL);
375 dbus_message_unref(copy);
376 dbus_message_unref(reply);
379 static DBusHandlerResult player_message(DBusConnection *conn,
380 DBusMessage *msg, void *data)
384 DBusMessageIter args, iter;
385 DBusPendingCall *call;
387 dbus_message_iter_init(msg, &args);
389 copy = dbus_message_new_method_call(owner,
391 dbus_message_get_interface(msg),
392 dbus_message_get_member(msg));
394 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
396 dbus_message_iter_init_append(copy, &iter);
397 append_iter(&iter, &args);
399 if (!dbus_connection_send_with_reply(session, copy, &call, -1))
402 dbus_message_ref(msg);
403 dbus_pending_call_set_notify(call, copy_reply, msg, NULL);
404 dbus_pending_call_unref(call);
407 dbus_message_unref(copy);
409 return DBUS_HANDLER_RESULT_HANDLED;
412 static struct player *find_player_by_bus_name(const char *name)
416 for (l = players; l; l = l->next) {
417 struct player *player = l->data;
419 if (strcmp(player->bus_name, name) == 0)
426 static const DBusObjectPathVTable player_table = {
427 .message_function = player_message,
430 static void add_player(DBusConnection *conn, const char *name,
433 DBusMessage *reply = NULL;
435 DBusMessageIter iter, args;
438 struct player *player;
443 player = find_player_by_bus_name(name);
444 if (player == NULL) {
445 reply = get_all(conn, name);
448 dbus_message_iter_init(reply, &args);
451 msg = dbus_message_new_method_call(BLUEZ_BUS_NAME,
452 g_dbus_proxy_get_path(adapter),
453 BLUEZ_MEDIA_INTERFACE,
456 fprintf(stderr, "Can't allocate new method call\n");
460 path = sender2path(sender);
461 dbus_connection_get_object_path_data(sys, path, (void **) &owner);
466 dbus_message_iter_init_append(msg, &iter);
468 dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
470 if (player != NULL) {
471 if (!g_dbus_get_properties(player->conn,
473 MPRIS_PLAYER_INTERFACE,
477 append_iter(&iter, &args);
478 dbus_message_unref(reply);
481 dbus_error_init(&err);
483 owner = strdup(sender);
485 if (!dbus_connection_register_object_path(sys, path, &player_table,
487 fprintf(stderr, "Can't register object path for player\n");
492 reply = dbus_connection_send_with_reply_and_block(sys, msg, -1, &err);
494 fprintf(stderr, "Can't register player\n");
496 if (dbus_error_is_set(&err)) {
497 fprintf(stderr, "%s\n", err.message);
498 dbus_error_free(&err);
504 dbus_message_unref(reply);
505 dbus_message_unref(msg);
509 static void remove_player(DBusConnection *conn, const char *sender)
517 path = sender2path(sender);
518 dbus_connection_get_object_path_data(sys, path, (void **) &owner);
525 msg = dbus_message_new_method_call(BLUEZ_BUS_NAME,
526 g_dbus_proxy_get_path(adapter),
527 BLUEZ_MEDIA_INTERFACE,
530 fprintf(stderr, "Can't allocate new method call\n");
535 dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
538 dbus_connection_send(sys, msg, NULL);
540 dbus_connection_unregister_object_path(sys, path);
542 dbus_message_unref(msg);
547 static gboolean player_signal(DBusConnection *conn, DBusMessage *msg,
551 DBusMessageIter iter, args;
554 dbus_message_iter_init(msg, &iter);
556 path = sender2path(dbus_message_get_sender(msg));
557 dbus_connection_get_object_path_data(sys, path, (void **) &owner);
562 signal = dbus_message_new_signal(path, dbus_message_get_interface(msg),
563 dbus_message_get_member(msg));
564 if (signal == NULL) {
565 fprintf(stderr, "Unable to allocate new %s.%s signal",
566 dbus_message_get_interface(msg),
567 dbus_message_get_member(msg));
571 dbus_message_iter_init_append(signal, &args);
573 append_iter(&args, &iter);
575 dbus_connection_send(sys, signal, NULL);
576 dbus_message_unref(signal);
584 static gboolean name_owner_changed(DBusConnection *conn,
585 DBusMessage *msg, void *data)
587 const char *name, *old, *new;
589 if (!dbus_message_get_args(msg, NULL,
590 DBUS_TYPE_STRING, &name,
591 DBUS_TYPE_STRING, &old,
592 DBUS_TYPE_STRING, &new,
593 DBUS_TYPE_INVALID)) {
594 fprintf(stderr, "Invalid arguments for NameOwnerChanged signal");
595 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
598 if (!g_str_has_prefix(name, "org.mpris"))
599 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
602 printf("player %s at %s disappear\n", name, old);
603 remove_player(conn, old);
604 } else if (option_export || find_player_by_bus_name(name) == NULL) {
605 printf("player %s at %s found\n", name, new);
606 add_player(conn, name, new);
612 static char *get_name_owner(DBusConnection *conn, const char *name)
614 DBusMessage *msg, *reply;
618 msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
619 DBUS_INTERFACE_DBUS, "GetNameOwner");
622 fprintf(stderr, "Can't allocate new method call\n");
626 dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
629 dbus_error_init(&err);
631 reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
633 dbus_message_unref(msg);
636 if (dbus_error_is_set(&err)) {
637 fprintf(stderr, "%s\n", err.message);
638 dbus_error_free(&err);
643 if (!dbus_message_get_args(reply, NULL,
644 DBUS_TYPE_STRING, &owner,
645 DBUS_TYPE_INVALID)) {
646 dbus_message_unref(reply);
650 owner = g_strdup(owner);
652 dbus_message_unref(reply);
654 dbus_connection_flush(conn);
659 static void parse_list_names(DBusConnection *conn, DBusMessageIter *args)
661 DBusMessageIter array;
664 ctype = dbus_message_iter_get_arg_type(args);
665 if (ctype != DBUS_TYPE_ARRAY)
668 dbus_message_iter_recurse(args, &array);
670 while ((ctype = dbus_message_iter_get_arg_type(&array)) !=
675 if (ctype != DBUS_TYPE_STRING)
678 dbus_message_iter_get_basic(&array, &name);
680 if (!g_str_has_prefix(name, "org.mpris"))
683 owner = get_name_owner(conn, name);
688 printf("player %s at %s found\n", name, owner);
690 add_player(conn, name, owner);
694 dbus_message_iter_next(&array);
698 static void list_names(DBusConnection *conn)
700 DBusMessage *msg, *reply;
701 DBusMessageIter iter;
704 msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
705 DBUS_INTERFACE_DBUS, "ListNames");
708 fprintf(stderr, "Can't allocate new method call\n");
712 dbus_error_init(&err);
714 reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err);
716 dbus_message_unref(msg);
719 if (dbus_error_is_set(&err)) {
720 fprintf(stderr, "%s\n", err.message);
721 dbus_error_free(&err);
726 dbus_message_iter_init(reply, &iter);
728 parse_list_names(conn, &iter);
730 dbus_message_unref(reply);
732 dbus_connection_flush(conn);
735 static void usage(void)
737 printf("Bluetooth mpris-player ver %s\n\n", VERSION);
742 static GOptionEntry options[] = {
743 { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
744 "Show version information and exit" },
745 { "export", 'e', 0, G_OPTION_ARG_NONE, &option_export,
746 "Export remote players" },
750 static void connect_handler(DBusConnection *connection, void *user_data)
752 printf("org.bluez appeared\n");
755 static void disconnect_handler(DBusConnection *connection, void *user_data)
757 printf("org.bluez disappeared\n");
760 static void unregister_tracklist(struct player *player)
762 struct tracklist *tracklist = player->tracklist;
764 g_slist_free(tracklist->items);
765 g_dbus_proxy_unref(tracklist->proxy);
767 player->tracklist = NULL;
770 static void player_free(void *data)
772 struct player *player = data;
774 if (player->tracklist != NULL)
775 unregister_tracklist(player);
778 dbus_connection_close(player->conn);
779 dbus_connection_unref(player->conn);
782 g_dbus_proxy_unref(player->device);
783 g_dbus_proxy_unref(player->proxy);
785 if (player->transport)
786 g_dbus_proxy_unref(player->transport);
788 if (player->playlist)
789 g_dbus_proxy_unref(player->playlist);
791 g_free(player->bus_name);
795 struct pending_call {
796 struct player *player;
800 static void pending_call_free(void *data)
802 struct pending_call *p = data;
805 dbus_message_unref(p->msg);
810 static void player_reply(DBusMessage *message, void *user_data)
812 struct pending_call *p = user_data;
813 struct player *player = p->player;
814 DBusMessage *msg = p->msg;
818 dbus_error_init(&err);
819 if (dbus_set_error_from_message(&err, message)) {
820 fprintf(stderr, "error: %s", err.name);
821 reply = g_dbus_create_error(msg, err.name, "%s", err.message);
822 dbus_error_free(&err);
824 reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
826 g_dbus_send_message(player->conn, reply);
829 static void player_control(struct player *player, DBusMessage *msg,
832 struct pending_call *p;
834 p = g_new0(struct pending_call, 1);
836 p->msg = dbus_message_ref(msg);
838 g_dbus_proxy_method_call(player->proxy, name, NULL, player_reply,
839 p, pending_call_free);
842 static const char *status_to_playback(const char *status)
844 if (strcasecmp(status, "playing") == 0)
846 else if (strcasecmp(status, "paused") == 0)
852 static const char *player_get_status(struct player *player)
855 DBusMessageIter value;
857 if (g_dbus_proxy_get_property(player->proxy, "Status", &value)) {
858 dbus_message_iter_get_basic(&value, &status);
859 return status_to_playback(status);
862 if (player->transport == NULL)
865 if (!g_dbus_proxy_get_property(player->transport, "State", &value))
868 dbus_message_iter_get_basic(&value, &status);
870 if (strcasecmp(status, "active") == 0)
877 static DBusMessage *player_toggle(DBusConnection *conn, DBusMessage *msg,
880 struct player *player = data;
883 status = player_get_status(player);
885 if (strcasecmp(status, "Playing") == 0)
886 player_control(player, msg, "Pause");
888 player_control(player, msg, "Play");
893 static DBusMessage *player_play(DBusConnection *conn, DBusMessage *msg,
896 struct player *player = data;
898 player_control(player, msg, "Play");
903 static DBusMessage *player_pause(DBusConnection *conn, DBusMessage *msg,
906 struct player *player = data;
908 player_control(player, msg, "Pause");
913 static DBusMessage *player_stop(DBusConnection *conn, DBusMessage *msg,
916 struct player *player = data;
918 player_control(player, msg, "Stop");
923 static DBusMessage *player_next(DBusConnection *conn, DBusMessage *msg,
926 struct player *player = data;
928 player_control(player, msg, "Next");
933 static DBusMessage *player_previous(DBusConnection *conn, DBusMessage *msg,
936 struct player *player = data;
938 player_control(player, msg, "Previous");
943 static gboolean status_exists(const GDBusPropertyTable *property, void *data)
945 struct player *player = data;
947 return player_get_status(player) != NULL;
950 static gboolean get_status(const GDBusPropertyTable *property,
951 DBusMessageIter *iter, void *data)
953 struct player *player = data;
956 status = player_get_status(player);
958 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
963 static gboolean repeat_exists(const GDBusPropertyTable *property, void *data)
965 DBusMessageIter iter;
966 struct player *player = data;
968 return g_dbus_proxy_get_property(player->proxy, "Repeat", &iter);
971 static const char *repeat_to_loopstatus(const char *value)
973 if (strcasecmp(value, "off") == 0)
975 else if (strcasecmp(value, "singletrack") == 0)
977 else if (strcasecmp(value, "alltracks") == 0)
983 static gboolean get_repeat(const GDBusPropertyTable *property,
984 DBusMessageIter *iter, void *data)
986 struct player *player = data;
987 DBusMessageIter value;
990 if (!g_dbus_proxy_get_property(player->proxy, "Repeat", &value))
993 dbus_message_iter_get_basic(&value, &status);
995 status = repeat_to_loopstatus(status);
999 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
1004 static const char *loopstatus_to_repeat(const char *value)
1006 if (strcasecmp(value, "None") == 0)
1008 else if (strcasecmp(value, "Track") == 0)
1009 return "singletrack";
1010 else if (strcasecmp(value, "Playlist") == 0)
1016 static void property_result(const DBusError *err, void *user_data)
1018 GDBusPendingPropertySet id = GPOINTER_TO_UINT(user_data);
1020 if (!dbus_error_is_set(err))
1021 return g_dbus_pending_property_success(id);
1023 g_dbus_pending_property_error(id, err->name, err->message);
1026 static void set_repeat(const GDBusPropertyTable *property,
1027 DBusMessageIter *iter, GDBusPendingPropertySet id,
1030 struct player *player = data;
1033 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) {
1034 g_dbus_pending_property_error(id,
1035 ERROR_INTERFACE ".InvalidArguments",
1036 "Invalid arguments in method call");
1040 dbus_message_iter_get_basic(iter, &value);
1042 value = loopstatus_to_repeat(value);
1043 if (value == NULL) {
1044 g_dbus_pending_property_error(id,
1045 ERROR_INTERFACE ".InvalidArguments",
1046 "Invalid arguments in method call");
1050 g_dbus_proxy_set_property_basic(player->proxy, "Repeat",
1051 DBUS_TYPE_STRING, &value,
1052 property_result, GUINT_TO_POINTER(id),
1056 static gboolean get_double(const GDBusPropertyTable *property,
1057 DBusMessageIter *iter, void *data)
1061 dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value);
1066 static gboolean shuffle_exists(const GDBusPropertyTable *property, void *data)
1068 DBusMessageIter iter;
1069 struct player *player = data;
1071 return g_dbus_proxy_get_property(player->proxy, "Shuffle", &iter);
1074 static gboolean get_shuffle(const GDBusPropertyTable *property,
1075 DBusMessageIter *iter, void *data)
1077 struct player *player = data;
1078 DBusMessageIter value;
1080 dbus_bool_t shuffle;
1082 if (!g_dbus_proxy_get_property(player->proxy, "Shuffle", &value))
1085 dbus_message_iter_get_basic(&value, &string);
1087 shuffle = strcmp(string, "off") != 0;
1089 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &shuffle);
1094 static void set_shuffle(const GDBusPropertyTable *property,
1095 DBusMessageIter *iter, GDBusPendingPropertySet id,
1098 struct player *player = data;
1099 dbus_bool_t shuffle;
1102 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) {
1103 g_dbus_pending_property_error(id,
1104 ERROR_INTERFACE ".InvalidArguments",
1105 "Invalid arguments in method call");
1109 dbus_message_iter_get_basic(iter, &shuffle);
1110 value = shuffle ? "alltracks" : "off";
1112 g_dbus_proxy_set_property_basic(player->proxy, "Shuffle",
1113 DBUS_TYPE_STRING, &value,
1114 property_result, GUINT_TO_POINTER(id),
1118 static gboolean position_exists(const GDBusPropertyTable *property, void *data)
1120 DBusMessageIter iter;
1121 struct player *player = data;
1123 return g_dbus_proxy_get_property(player->proxy, "Position", &iter);
1126 static gboolean get_position(const GDBusPropertyTable *property,
1127 DBusMessageIter *iter, void *data)
1129 struct player *player = data;
1130 DBusMessageIter var;
1134 if (!g_dbus_proxy_get_property(player->proxy, "Position", &var))
1137 dbus_message_iter_get_basic(&var, &position);
1139 value = position * 1000ll;
1141 dbus_message_iter_append_basic(iter, DBUS_TYPE_INT64, &value);
1146 static gboolean track_exists(const GDBusPropertyTable *property, void *data)
1148 DBusMessageIter iter;
1149 struct player *player = data;
1151 return g_dbus_proxy_get_property(player->proxy, "Track", &iter);
1154 static gboolean parse_string_metadata(DBusMessageIter *iter, const char *key,
1155 DBusMessageIter *metadata)
1159 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
1162 dbus_message_iter_get_basic(iter, &value);
1164 dict_append_entry(metadata, key, DBUS_TYPE_STRING, &value);
1169 static gboolean parse_array_metadata(DBusMessageIter *iter, const char *key,
1170 DBusMessageIter *metadata)
1174 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
1177 value = dbus_malloc0(sizeof(char *));
1179 dbus_message_iter_get_basic(iter, &(value[0]));
1181 dict_append_array(metadata, key, DBUS_TYPE_STRING, &value, 1);
1188 static gboolean parse_int64_metadata(DBusMessageIter *iter, const char *key,
1189 DBusMessageIter *metadata)
1194 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
1197 dbus_message_iter_get_basic(iter, &duration);
1199 value = duration * 1000ll;
1201 dict_append_entry(metadata, key, DBUS_TYPE_INT64, &value);
1206 static gboolean parse_int32_metadata(DBusMessageIter *iter, const char *key,
1207 DBusMessageIter *metadata)
1211 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32)
1214 dbus_message_iter_get_basic(iter, &value);
1216 dict_append_entry(metadata, key, DBUS_TYPE_INT32, &value);
1221 static gboolean parse_path_metadata(DBusMessageIter *iter, const char *key,
1222 DBusMessageIter *metadata)
1226 if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_OBJECT_PATH)
1229 dbus_message_iter_get_basic(iter, &value);
1231 dict_append_entry(metadata, key, DBUS_TYPE_OBJECT_PATH, &value);
1236 static int parse_track_entry(DBusMessageIter *entry, const char *key,
1237 DBusMessageIter *metadata)
1239 DBusMessageIter var;
1241 if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT)
1244 dbus_message_iter_recurse(entry, &var);
1246 if (strcasecmp(key, "Title") == 0) {
1247 if (!parse_string_metadata(&var, "xesam:title", metadata))
1249 } else if (strcasecmp(key, "Artist") == 0) {
1250 if (!parse_array_metadata(&var, "xesam:artist", metadata))
1252 } else if (strcasecmp(key, "Album") == 0) {
1253 if (!parse_string_metadata(&var, "xesam:album", metadata))
1255 } else if (strcasecmp(key, "Genre") == 0) {
1256 if (!parse_array_metadata(&var, "xesam:genre", metadata))
1258 } else if (strcasecmp(key, "Duration") == 0) {
1259 if (!parse_int64_metadata(&var, "mpris:length", metadata))
1261 } else if (strcasecmp(key, "TrackNumber") == 0) {
1262 if (!parse_int32_metadata(&var, "xesam:trackNumber", metadata))
1264 } else if (strcasecmp(key, "Item") == 0) {
1265 if (!parse_path_metadata(&var, "mpris:trackid", metadata))
1272 static gboolean get_track(const GDBusPropertyTable *property,
1273 DBusMessageIter *iter, void *data)
1275 struct player *player = data;
1276 DBusMessageIter var, metadata;
1278 if (!g_dbus_proxy_get_property(player->proxy, "Track", &var))
1281 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1282 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
1283 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
1284 DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
1286 parse_metadata(&var, &metadata, parse_track_entry);
1288 dbus_message_iter_close_container(iter, &metadata);
1293 static gboolean get_enable(const GDBusPropertyTable *property,
1294 DBusMessageIter *iter, void *data)
1296 dbus_bool_t value = TRUE;
1298 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
1304 static gboolean get_volume(const GDBusPropertyTable *property,
1305 DBusMessageIter *iter, void *data)
1307 struct player *player = data;
1310 DBusMessageIter var;
1312 if (player->transport == NULL)
1315 if (!g_dbus_proxy_get_property(player->transport, "Volume", &var))
1318 dbus_message_iter_get_basic(&var, &volume);
1320 value = (double) volume / 127;
1323 dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE, &value);
1328 static const GDBusMethodTable player_methods[] = {
1329 { GDBUS_ASYNC_METHOD("PlayPause", NULL, NULL, player_toggle) },
1330 { GDBUS_ASYNC_METHOD("Play", NULL, NULL, player_play) },
1331 { GDBUS_ASYNC_METHOD("Pause", NULL, NULL, player_pause) },
1332 { GDBUS_ASYNC_METHOD("Stop", NULL, NULL, player_stop) },
1333 { GDBUS_ASYNC_METHOD("Next", NULL, NULL, player_next) },
1334 { GDBUS_ASYNC_METHOD("Previous", NULL, NULL, player_previous) },
1338 static const GDBusSignalTable player_signals[] = {
1339 { GDBUS_SIGNAL("Seeked", GDBUS_ARGS({"Position", "x"})) },
1343 static const GDBusPropertyTable player_properties[] = {
1344 { "PlaybackStatus", "s", get_status, NULL, status_exists },
1345 { "LoopStatus", "s", get_repeat, set_repeat, repeat_exists },
1346 { "Rate", "d", get_double, NULL, NULL },
1347 { "MinimumRate", "d", get_double, NULL, NULL },
1348 { "MaximumRate", "d", get_double, NULL, NULL },
1349 { "Shuffle", "b", get_shuffle, set_shuffle, shuffle_exists },
1350 { "Position", "x", get_position, NULL, position_exists },
1351 { "Metadata", "a{sv}", get_track, NULL, track_exists },
1352 { "Volume", "d", get_volume, NULL, NULL },
1353 { "CanGoNext", "b", get_enable, NULL, NULL },
1354 { "CanGoPrevious", "b", get_enable, NULL, NULL },
1355 { "CanPlay", "b", get_enable, NULL, NULL },
1356 { "CanPause", "b", get_enable, NULL, NULL },
1357 { "CanSeek", "b", get_enable, NULL, NULL },
1358 { "CanControl", "b", get_enable, NULL, NULL },
1362 static gboolean get_disable(const GDBusPropertyTable *property,
1363 DBusMessageIter *iter, void *data)
1365 dbus_bool_t value = FALSE;
1367 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
1372 static gboolean get_name(const GDBusPropertyTable *property,
1373 DBusMessageIter *iter, void *data)
1375 struct player *player = data;
1376 DBusMessageIter var;
1380 if (!g_dbus_proxy_get_property(player->device, "Alias", &var))
1383 dbus_message_iter_get_basic(&var, &alias);
1385 if (g_dbus_proxy_get_property(player->proxy, "Name", &var)) {
1386 dbus_message_iter_get_basic(&var, &name);
1387 name = g_strconcat(alias, " ", name, NULL);
1389 name = g_strdup(alias);
1391 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &name);
1398 static const GDBusMethodTable mpris_methods[] = {
1402 static gboolean get_tracklist(const GDBusPropertyTable *property,
1403 DBusMessageIter *iter, void *data)
1405 struct player *player = data;
1408 value = player->tracklist != NULL ? TRUE : FALSE;
1410 dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
1415 static const GDBusPropertyTable mpris_properties[] = {
1416 { "CanQuit", "b", get_disable, NULL, NULL },
1417 { "Fullscreen", "b", get_disable, NULL, NULL },
1418 { "CanSetFullscreen", "b", get_disable, NULL, NULL },
1419 { "CanRaise", "b", get_disable, NULL, NULL },
1420 { "HasTrackList", "b", get_tracklist, NULL, NULL },
1421 { "Identity", "s", get_name, NULL, NULL },
1425 static GDBusProxy *find_item(struct player *player, const char *path)
1427 struct tracklist *tracklist = player->tracklist;
1430 for (l = tracklist->items; l; l = l->next) {
1431 GDBusProxy *proxy = l->data;
1432 const char *p = g_dbus_proxy_get_path(proxy);
1434 if (g_str_equal(path, p))
1441 static void append_item_metadata(void *data, void *user_data)
1443 GDBusProxy *item = data;
1444 DBusMessageIter *iter = user_data;
1445 DBusMessageIter var, metadata;
1446 const char *path = g_dbus_proxy_get_path(item);
1448 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1449 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
1450 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
1451 DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
1453 dict_append_entry(&metadata, "mpris:trackid", DBUS_TYPE_OBJECT_PATH,
1456 if (g_dbus_proxy_get_property(item, "Metadata", &var))
1457 parse_metadata(&var, &metadata, parse_track_entry);
1459 dbus_message_iter_close_container(iter, &metadata);
1464 static DBusMessage *tracklist_get_metadata(DBusConnection *conn,
1465 DBusMessage *msg, void *data)
1467 struct player *player = data;
1469 DBusMessageIter args, array;
1472 dbus_message_iter_init(msg, &args);
1474 if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
1475 return g_dbus_create_error(msg,
1476 ERROR_INTERFACE ".InvalidArguments",
1477 "Invalid Arguments");
1479 dbus_message_iter_recurse(&args, &array);
1481 while (dbus_message_iter_get_arg_type(&array) ==
1482 DBUS_TYPE_OBJECT_PATH) {
1486 dbus_message_iter_get_basic(&array, &path);
1488 item = find_item(player, path);
1490 return g_dbus_create_error(msg,
1491 ERROR_INTERFACE ".InvalidArguments",
1492 "Invalid Arguments");
1494 l = g_slist_append(l, item);
1496 dbus_message_iter_next(&array);
1499 reply = dbus_message_new_method_return(msg);
1501 dbus_message_iter_init_append(reply, &args);
1503 dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
1504 DBUS_TYPE_ARRAY_AS_STRING
1505 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
1506 DBUS_TYPE_STRING_AS_STRING
1507 DBUS_TYPE_VARIANT_AS_STRING
1508 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
1511 g_slist_foreach(l, append_item_metadata, &array);
1513 dbus_message_iter_close_container(&args, &array);
1518 static void item_play_reply(DBusMessage *message, void *user_data)
1520 struct pending_call *p = user_data;
1521 struct player *player = p->player;
1522 DBusMessage *msg = p->msg;
1526 dbus_error_init(&err);
1527 if (dbus_set_error_from_message(&err, message)) {
1528 fprintf(stderr, "error: %s", err.name);
1529 reply = g_dbus_create_error(msg, err.name, "%s", err.message);
1530 dbus_error_free(&err);
1532 reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1534 g_dbus_send_message(player->conn, reply);
1537 static void item_play(struct player *player, DBusMessage *msg,
1540 struct pending_call *p;
1542 p = g_new0(struct pending_call, 1);
1544 p->msg = dbus_message_ref(msg);
1546 g_dbus_proxy_method_call(item, "Play", NULL, item_play_reply,
1547 p, pending_call_free);
1550 static DBusMessage *tracklist_goto(DBusConnection *conn,
1551 DBusMessage *msg, void *data)
1553 struct player *player = data;
1557 if (!dbus_message_get_args(msg, NULL,
1558 DBUS_TYPE_OBJECT_PATH, &path,
1560 return g_dbus_create_error(msg,
1561 ERROR_INTERFACE ".InvalidArguments",
1562 "Invalid arguments");
1564 item = find_item(player, path);
1566 return g_dbus_create_error(msg,
1567 ERROR_INTERFACE ".InvalidArguments",
1568 "Invalid arguments");
1570 item_play(player, msg, item);
1575 static DBusMessage *tracklist_add_track(DBusConnection *conn,
1576 DBusMessage *msg, void *data)
1578 return g_dbus_create_error(msg, ERROR_INTERFACE ".NotImplemented",
1582 static DBusMessage *tracklist_remove_track(DBusConnection *conn,
1583 DBusMessage *msg, void *data)
1585 return g_dbus_create_error(msg, ERROR_INTERFACE ".NotImplemented",
1589 static const GDBusMethodTable tracklist_methods[] = {
1590 { GDBUS_METHOD("GetTracksMetadata",
1591 GDBUS_ARGS({ "tracks", "ao" }),
1592 GDBUS_ARGS({ "metadata", "aa{sv}" }),
1593 tracklist_get_metadata) },
1594 { GDBUS_METHOD("AddTrack",
1595 GDBUS_ARGS({ "uri", "s" }, { "after", "o" },
1596 { "current", "b" }),
1598 tracklist_add_track) },
1599 { GDBUS_METHOD("RemoveTrack",
1600 GDBUS_ARGS({ "track", "o" }), NULL,
1601 tracklist_remove_track) },
1602 { GDBUS_ASYNC_METHOD("GoTo",
1603 GDBUS_ARGS({ "track", "o" }), NULL,
1608 static const GDBusSignalTable tracklist_signals[] = {
1609 { GDBUS_SIGNAL("TrackAdded", GDBUS_ARGS({"metadata", "a{sv}"},
1611 { GDBUS_SIGNAL("TrackRemoved", GDBUS_ARGS({"track", "o"})) },
1612 { GDBUS_SIGNAL("TrackMetadataChanged", GDBUS_ARGS({"track", "o"},
1613 {"metadata", "a{sv}"})) },
1617 static gboolean tracklist_exists(const GDBusPropertyTable *property, void *data)
1619 struct player *player = data;
1621 return player->tracklist != NULL;
1624 static void append_path(gpointer data, gpointer user_data)
1626 GDBusProxy *proxy = data;
1627 DBusMessageIter *iter = user_data;
1628 const char *path = g_dbus_proxy_get_path(proxy);
1630 dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
1633 static gboolean get_tracks(const GDBusPropertyTable *property,
1634 DBusMessageIter *iter, void *data)
1636 struct player *player = data;
1637 struct tracklist *tracklist = player->tracklist;
1638 DBusMessageIter value;
1640 if (tracklist == NULL)
1643 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1644 DBUS_TYPE_OBJECT_PATH_AS_STRING,
1646 g_slist_foreach(player->tracklist->items, append_path, &value);
1647 dbus_message_iter_close_container(iter, &value);
1652 static const GDBusPropertyTable tracklist_properties[] = {
1653 { "Tracks", "ao", get_tracks, NULL, tracklist_exists },
1654 { "CanEditTracks", "b", get_disable, NULL, NULL },
1658 static void list_items_setup(DBusMessageIter *iter, void *user_data)
1660 DBusMessageIter dict;
1662 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1663 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
1664 DBUS_TYPE_STRING_AS_STRING
1665 DBUS_TYPE_VARIANT_AS_STRING
1666 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
1668 dbus_message_iter_close_container(iter, &dict);
1671 static void change_folder_reply(DBusMessage *message, void *user_data)
1673 struct player *player = user_data;
1674 struct tracklist *tracklist = player->tracklist;
1677 dbus_error_init(&err);
1678 if (dbus_set_error_from_message(&err, message)) {
1679 fprintf(stderr, "error: %s", err.name);
1683 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
1684 MPRIS_PLAYLISTS_INTERFACE,
1687 g_dbus_proxy_method_call(tracklist->proxy, "ListItems",
1688 list_items_setup, NULL, NULL, NULL);
1691 static void change_folder_setup(DBusMessageIter *iter, void *user_data)
1693 struct player *player = user_data;
1696 path = g_dbus_proxy_get_path(player->playlist);
1698 dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
1701 static DBusMessage *playlist_activate(DBusConnection *conn,
1702 DBusMessage *msg, void *data)
1704 struct player *player = data;
1705 struct tracklist *tracklist = player->tracklist;
1708 if (player->playlist == NULL || tracklist == NULL)
1709 return g_dbus_create_error(msg,
1710 ERROR_INTERFACE ".InvalidArguments",
1711 "Invalid Arguments");
1713 if (!dbus_message_get_args(msg, NULL,
1714 DBUS_TYPE_OBJECT_PATH, &path,
1716 return g_dbus_create_error(msg,
1717 ERROR_INTERFACE ".InvalidArguments",
1718 "Invalid Arguments");
1720 if (!g_str_equal(path, g_dbus_proxy_get_path(player->playlist)))
1721 return g_dbus_create_error(msg,
1722 ERROR_INTERFACE ".InvalidArguments",
1723 "Invalid Arguments");
1725 g_dbus_proxy_method_call(tracklist->proxy, "ChangeFolder",
1726 change_folder_setup, change_folder_reply,
1729 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1732 static DBusMessage *playlist_get(DBusConnection *conn, DBusMessage *msg,
1735 struct player *player = data;
1736 uint32_t index, count;
1738 dbus_bool_t reverse;
1740 DBusMessageIter iter, entry, value, name;
1741 const char *string, *path;
1742 const char *empty = "";
1744 if (player->playlist == NULL)
1745 return g_dbus_create_error(msg,
1746 ERROR_INTERFACE ".InvalidArguments",
1747 "Invalid Arguments");
1749 if (!dbus_message_get_args(msg, NULL,
1750 DBUS_TYPE_UINT32, &index,
1751 DBUS_TYPE_UINT32, &count,
1752 DBUS_TYPE_STRING, &order,
1753 DBUS_TYPE_BOOLEAN, &reverse,
1755 return g_dbus_create_error(msg,
1756 ERROR_INTERFACE ".InvalidArguments",
1757 "Invalid Arguments");
1759 path = g_dbus_proxy_get_path(player->playlist);
1761 reply = dbus_message_new_method_return(msg);
1763 dbus_message_iter_init_append(reply, &iter);
1765 dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(oss)",
1767 dbus_message_iter_open_container(&entry, DBUS_TYPE_STRUCT, NULL,
1769 dbus_message_iter_append_basic(&value, DBUS_TYPE_OBJECT_PATH, &path);
1770 if (g_dbus_proxy_get_property(player->playlist, "Name", &name)) {
1771 dbus_message_iter_get_basic(&name, &string);
1772 dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING,
1775 dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING,
1778 dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &empty);
1779 dbus_message_iter_close_container(&entry, &value);
1780 dbus_message_iter_close_container(&iter, &entry);
1785 static const GDBusMethodTable playlist_methods[] = {
1786 { GDBUS_METHOD("ActivatePlaylist",
1787 GDBUS_ARGS({ "playlist", "o" }), NULL,
1788 playlist_activate) },
1789 { GDBUS_METHOD("GetPlaylists",
1790 GDBUS_ARGS({ "index", "u" }, { "maxcount", "u"},
1791 { "order", "s" }, { "reverse", "b" }),
1792 GDBUS_ARGS({ "playlists", "a(oss)"}),
1797 static gboolean playlist_exists(const GDBusPropertyTable *property, void *data)
1799 struct player *player = data;
1801 return player->playlist != NULL;
1804 static gboolean get_playlist_count(const GDBusPropertyTable *property,
1805 DBusMessageIter *iter, void *data)
1807 struct player *player = data;
1810 if (player->playlist == NULL)
1813 dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &count);
1818 static gboolean get_orderings(const GDBusPropertyTable *property,
1819 DBusMessageIter *iter, void *data)
1821 DBusMessageIter value;
1822 const char *order = "User";
1824 dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1825 DBUS_TYPE_OBJECT_PATH_AS_STRING,
1827 dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &order);
1828 dbus_message_iter_close_container(iter, &value);
1833 static gboolean get_active_playlist(const GDBusPropertyTable *property,
1834 DBusMessageIter *iter, void *data)
1836 struct player *player = data;
1837 DBusMessageIter value, entry;
1838 dbus_bool_t enabled = TRUE;
1839 const char *path, *empty = "";
1841 if (player->playlist == NULL)
1844 path = g_dbus_proxy_get_path(player->playlist);
1846 dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT,
1848 dbus_message_iter_append_basic(&value, DBUS_TYPE_BOOLEAN, &enabled);
1849 dbus_message_iter_open_container(&value, DBUS_TYPE_STRUCT, NULL,
1851 dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, &path);
1852 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &path);
1853 dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &empty);
1854 dbus_message_iter_close_container(&value, &entry);
1855 dbus_message_iter_close_container(iter, &value);
1860 static const GDBusPropertyTable playlist_properties[] = {
1861 { "PlaylistCount", "u", get_playlist_count, NULL, playlist_exists },
1862 { "Orderings", "as", get_orderings, NULL, NULL },
1863 { "ActivePlaylist", "(b(oss))", get_active_playlist, NULL,
1868 #define a_z "abcdefghijklmnopqrstuvwxyz"
1869 #define A_Z "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1870 #define _0_9 "_0123456789"
1872 static char *mpris_busname(char *name)
1874 if (g_ascii_isdigit(name[0]))
1875 return g_strconcat(MPRIS_BUS_NAME, "bt_",
1876 g_strcanon(name, A_Z a_z _0_9, '_'), NULL);
1878 return g_strconcat(MPRIS_BUS_NAME,
1879 g_strcanon(name, A_Z a_z _0_9, '_'), NULL);
1882 static GDBusProxy *find_transport_by_path(const char *path)
1886 for (l = transports; l; l = l->next) {
1887 GDBusProxy *transport = l->data;
1888 DBusMessageIter iter;
1891 if (!g_dbus_proxy_get_property(transport, "Device", &iter))
1894 dbus_message_iter_get_basic(&iter, &value);
1896 if (strcmp(path, value) == 0)
1903 static struct player *find_player(GDBusProxy *proxy)
1907 for (l = players; l; l = l->next) {
1908 struct player *player = l->data;
1909 const char *path, *p;
1911 if (player->proxy == proxy)
1914 path = g_dbus_proxy_get_path(proxy);
1915 p = g_dbus_proxy_get_path(player->proxy);
1916 if (g_str_equal(path, p))
1923 static void register_tracklist(GDBusProxy *proxy)
1925 struct player *player;
1926 struct tracklist *tracklist;
1928 player = find_player(proxy);
1932 if (player->tracklist != NULL)
1935 tracklist = g_new0(struct tracklist, 1);
1936 tracklist->proxy = g_dbus_proxy_ref(proxy);
1938 player->tracklist = tracklist;
1940 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
1944 if (player->playlist == NULL)
1947 g_dbus_proxy_method_call(player->tracklist->proxy, "ChangeFolder",
1948 change_folder_setup, change_folder_reply,
1952 static void register_player(GDBusProxy *proxy)
1954 struct player *player;
1955 DBusMessageIter iter;
1956 const char *path, *alias, *name;
1958 GDBusProxy *device, *transport;
1960 if (!g_dbus_proxy_get_property(proxy, "Device", &iter))
1963 dbus_message_iter_get_basic(&iter, &path);
1965 device = g_dbus_proxy_new(client, path, "org.bluez.Device1");
1969 if (!g_dbus_proxy_get_property(device, "Alias", &iter))
1972 dbus_message_iter_get_basic(&iter, &alias);
1974 if (g_dbus_proxy_get_property(proxy, "Name", &iter)) {
1975 dbus_message_iter_get_basic(&iter, &name);
1976 busname = g_strconcat(alias, " ", name, NULL);
1978 busname = g_strdup(alias);
1980 player = g_new0(struct player, 1);
1981 player->bus_name = mpris_busname(busname);
1982 player->proxy = g_dbus_proxy_ref(proxy);
1983 player->device = device;
1987 players = g_slist_prepend(players, player);
1989 printf("Player %s created\n", player->bus_name);
1991 player->conn = g_dbus_setup_private(DBUS_BUS_SESSION, player->bus_name,
1994 fprintf(stderr, "Could not register bus name %s",
1999 if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
2005 fprintf(stderr, "Could not register interface %s",
2010 if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
2011 MPRIS_PLAYER_INTERFACE,
2015 player, player_free)) {
2016 fprintf(stderr, "Could not register interface %s",
2017 MPRIS_PLAYER_INTERFACE);
2021 if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
2022 MPRIS_TRACKLIST_INTERFACE,
2025 tracklist_properties,
2027 fprintf(stderr, "Could not register interface %s",
2028 MPRIS_TRACKLIST_INTERFACE);
2032 if (!g_dbus_register_interface(player->conn, MPRIS_PLAYER_PATH,
2033 MPRIS_PLAYLISTS_INTERFACE,
2036 playlist_properties,
2038 fprintf(stderr, "Could not register interface %s",
2039 MPRIS_PLAYLISTS_INTERFACE);
2043 transport = find_transport_by_path(path);
2045 player->transport = g_dbus_proxy_ref(transport);
2050 players = g_slist_remove(players, player);
2051 player_free(player);
2054 static struct player *find_player_by_device(const char *device)
2058 for (l = players; l; l = l->next) {
2059 struct player *player = l->data;
2060 const char *path = g_dbus_proxy_get_path(player->device);
2062 if (g_strcmp0(device, path) == 0)
2069 static void register_transport(GDBusProxy *proxy)
2071 struct player *player;
2072 DBusMessageIter iter;
2075 if (g_slist_find(transports, proxy) != NULL)
2078 if (!g_dbus_proxy_get_property(proxy, "Volume", &iter))
2081 if (!g_dbus_proxy_get_property(proxy, "Device", &iter))
2084 dbus_message_iter_get_basic(&iter, &path);
2086 transports = g_slist_append(transports, proxy);
2088 player = find_player_by_device(path);
2089 if (player == NULL || player->transport != NULL)
2092 player->transport = g_dbus_proxy_ref(proxy);
2095 static struct player *find_player_by_item(const char *item)
2099 for (l = players; l; l = l->next) {
2100 struct player *player = l->data;
2101 const char *path = g_dbus_proxy_get_path(player->proxy);
2103 if (g_str_has_prefix(item, path))
2110 static void register_playlist(struct player *player, GDBusProxy *proxy)
2113 DBusMessageIter iter;
2115 if (!g_dbus_proxy_get_property(player->proxy, "Playlist", &iter))
2118 dbus_message_iter_get_basic(&iter, &path);
2120 if (!g_str_equal(path, g_dbus_proxy_get_path(proxy)))
2123 player->playlist = g_dbus_proxy_ref(proxy);
2125 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
2126 MPRIS_PLAYLISTS_INTERFACE,
2129 if (player->tracklist == NULL)
2132 g_dbus_proxy_method_call(player->tracklist->proxy, "ChangeFolder",
2133 change_folder_setup, change_folder_reply,
2137 static void register_item(struct player *player, GDBusProxy *proxy)
2139 struct tracklist *tracklist;
2140 const char *path, *playlist;
2141 DBusMessage *signal;
2142 DBusMessageIter iter, args, metadata;
2146 if (player->playlist == NULL) {
2147 register_playlist(player, proxy);
2151 tracklist = player->tracklist;
2152 if (tracklist == NULL)
2155 path = g_dbus_proxy_get_path(proxy);
2156 playlist = g_dbus_proxy_get_path(player->playlist);
2157 if (!g_str_has_prefix(path, playlist))
2160 l = g_slist_last(tracklist->items);
2161 tracklist->items = g_slist_append(tracklist->items, proxy);
2163 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
2164 MPRIS_TRACKLIST_INTERFACE,
2170 signal = dbus_message_new_signal(MPRIS_PLAYER_PATH,
2171 MPRIS_TRACKLIST_INTERFACE,
2174 fprintf(stderr, "Unable to allocate new %s.TrackAdded signal",
2175 MPRIS_TRACKLIST_INTERFACE);
2179 dbus_message_iter_init_append(signal, &args);
2181 if (!g_dbus_proxy_get_property(proxy, "Metadata", &iter)) {
2182 dbus_message_unref(signal);
2186 dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
2187 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
2188 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
2189 DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata);
2191 parse_metadata(&iter, &metadata, parse_track_entry);
2193 dbus_message_iter_close_container(&args, &metadata);
2196 path = g_dbus_proxy_get_path(after);
2197 dbus_message_iter_append_basic(&args, DBUS_TYPE_OBJECT_PATH, &path);
2199 g_dbus_send_message(player->conn, signal);
2202 static void proxy_added(GDBusProxy *proxy, void *user_data)
2204 const char *interface;
2207 interface = g_dbus_proxy_get_interface(proxy);
2208 path = g_dbus_proxy_get_path(proxy);
2210 if (!strcmp(interface, BLUEZ_ADAPTER_INTERFACE)) {
2211 if (adapter != NULL)
2214 printf("Bluetooth Adapter %s found\n", path);
2216 list_names(session);
2217 } else if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE)) {
2218 printf("Bluetooth Player %s found\n", path);
2219 register_player(proxy);
2220 } else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) {
2221 printf("Bluetooth Transport %s found\n", path);
2222 register_transport(proxy);
2223 } else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE)) {
2224 printf("Bluetooth Folder %s found\n", path);
2225 register_tracklist(proxy);
2226 } else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE)) {
2227 struct player *player;
2229 player = find_player_by_item(path);
2233 printf("Bluetooth Item %s found\n", path);
2234 register_item(player, proxy);
2238 static void unregister_player(struct player *player)
2240 players = g_slist_remove(players, player);
2242 if (player->tracklist != NULL) {
2243 g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
2244 MPRIS_PLAYLISTS_INTERFACE);
2245 g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
2246 MPRIS_TRACKLIST_INTERFACE);
2249 g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
2252 g_dbus_unregister_interface(player->conn, MPRIS_PLAYER_PATH,
2253 MPRIS_PLAYER_INTERFACE);
2256 static struct player *find_player_by_transport(GDBusProxy *proxy)
2260 for (l = players; l; l = l->next) {
2261 struct player *player = l->data;
2263 if (player->transport == proxy)
2270 static void unregister_transport(GDBusProxy *proxy)
2272 struct player *player;
2274 if (g_slist_find(transports, proxy) == NULL)
2277 transports = g_slist_remove(transports, proxy);
2279 player = find_player_by_transport(proxy);
2283 g_dbus_proxy_unref(player->transport);
2284 player->transport = NULL;
2287 static void unregister_item(struct player *player, GDBusProxy *proxy)
2289 struct tracklist *tracklist = player->tracklist;
2292 if (tracklist == NULL)
2295 if (g_slist_find(tracklist->items, proxy) == NULL)
2298 path = g_dbus_proxy_get_path(proxy);
2300 tracklist->items = g_slist_remove(tracklist->items, proxy);
2302 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
2303 MPRIS_TRACKLIST_INTERFACE,
2306 g_dbus_emit_signal(player->conn, MPRIS_PLAYER_PATH,
2307 MPRIS_TRACKLIST_INTERFACE, "TrackRemoved",
2308 DBUS_TYPE_OBJECT_PATH, &path,
2312 static void remove_players(DBusConnection *conn)
2317 dbus_connection_list_registered(conn, "/", &paths);
2319 for (i = 0; paths[i]; i++) {
2323 path = g_strdup_printf("/%s", paths[i]);
2324 dbus_connection_get_object_path_data(sys, path, &data);
2325 dbus_connection_unregister_object_path(sys, path);
2331 dbus_free_string_array(paths);
2334 static void proxy_removed(GDBusProxy *proxy, void *user_data)
2336 const char *interface;
2339 if (adapter == NULL)
2342 interface = g_dbus_proxy_get_interface(proxy);
2343 path = g_dbus_proxy_get_path(proxy);
2345 if (strcmp(interface, BLUEZ_ADAPTER_INTERFACE) == 0) {
2346 if (adapter != proxy)
2348 printf("Bluetooth Adapter %s removed\n", path);
2350 remove_players(sys);
2351 } else if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0) {
2352 struct player *player;
2354 player = find_player(proxy);
2358 printf("Bluetooth Player %s removed\n", path);
2359 unregister_player(player);
2360 } else if (strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE) == 0) {
2361 printf("Bluetooth Transport %s removed\n", path);
2362 unregister_transport(proxy);
2363 } else if (strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE) == 0) {
2364 struct player *player;
2366 player = find_player_by_item(path);
2370 printf("Bluetooth Item %s removed\n", path);
2371 unregister_item(player, proxy);
2375 static const char *property_to_mpris(const char *property)
2377 if (strcasecmp(property, "Repeat") == 0)
2378 return "LoopStatus";
2379 else if (strcasecmp(property, "Shuffle") == 0)
2381 else if (strcasecmp(property, "Status") == 0)
2382 return "PlaybackStatus";
2383 else if (strcasecmp(property, "Position") == 0)
2385 else if (strcasecmp(property, "Track") == 0)
2391 static void player_property_changed(GDBusProxy *proxy, const char *name,
2392 DBusMessageIter *iter, void *user_data)
2394 struct player *player;
2395 const char *property;
2399 player = find_player(proxy);
2403 property = property_to_mpris(name);
2404 if (property == NULL)
2407 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
2408 MPRIS_PLAYER_INTERFACE,
2411 if (strcasecmp(name, "Position") != 0)
2414 dbus_message_iter_get_basic(iter, &position);
2416 value = position * 1000ll;
2418 g_dbus_emit_signal(player->conn, MPRIS_PLAYER_PATH,
2419 MPRIS_PLAYER_INTERFACE, "Seeked",
2420 DBUS_TYPE_INT64, &value,
2424 static void transport_property_changed(GDBusProxy *proxy, const char *name,
2425 DBusMessageIter *iter, void *user_data)
2427 struct player *player;
2428 DBusMessageIter var;
2431 if (strcasecmp(name, "Volume") != 0 && strcasecmp(name, "State") != 0)
2434 if (!g_dbus_proxy_get_property(proxy, "Device", &var))
2437 dbus_message_iter_get_basic(&var, &path);
2439 player = find_player_by_device(path);
2443 if (strcasecmp(name, "State") == 0) {
2444 if (!g_dbus_proxy_get_property(player->proxy, "Status", &var))
2445 g_dbus_emit_property_changed(player->conn,
2447 MPRIS_PLAYER_INTERFACE,
2452 g_dbus_emit_property_changed(player->conn, MPRIS_PLAYER_PATH,
2453 MPRIS_PLAYER_INTERFACE,
2457 static void item_property_changed(GDBusProxy *proxy, const char *name,
2458 DBusMessageIter *iter, void *user_data)
2460 struct player *player;
2461 DBusMessage *signal;
2462 DBusMessageIter args;
2465 path = g_dbus_proxy_get_path(proxy);
2467 player = find_player_by_item(path);
2471 if (strcasecmp(name, "Metadata") != 0)
2474 signal = dbus_message_new_signal(MPRIS_PLAYER_PATH,
2475 MPRIS_TRACKLIST_INTERFACE,
2476 "TrackMetadataChanged");
2478 fprintf(stderr, "Unable to allocate new %s.TrackAdded signal",
2479 MPRIS_TRACKLIST_INTERFACE);
2483 dbus_message_iter_init_append(signal, &args);
2485 dbus_message_iter_append_basic(&args, DBUS_TYPE_OBJECT_PATH, &path);
2487 append_iter(&args, iter);
2489 g_dbus_send_message(player->conn, signal);
2492 static void property_changed(GDBusProxy *proxy, const char *name,
2493 DBusMessageIter *iter, void *user_data)
2495 const char *interface;
2497 interface = g_dbus_proxy_get_interface(proxy);
2499 if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0)
2500 return player_property_changed(proxy, name, iter, user_data);
2502 if (strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE) == 0)
2503 return transport_property_changed(proxy, name, iter,
2506 if (strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE) == 0)
2507 return item_property_changed(proxy, name, iter, user_data);
2510 int main(int argc, char *argv[])
2512 GOptionContext *context;
2513 GError *error = NULL;
2514 guint owner_watch, properties_watch, signal_watch;
2515 struct sigaction sa;
2517 context = g_option_context_new(NULL);
2518 g_option_context_add_main_entries(context, options, NULL);
2520 if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) {
2521 if (error != NULL) {
2522 g_printerr("%s\n", error->message);
2523 g_error_free(error);
2525 g_printerr("An unknown error occurred\n");
2529 g_option_context_free(context);
2531 if (option_version == TRUE) {
2536 main_loop = g_main_loop_new(NULL, FALSE);
2538 sys = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL);
2540 fprintf(stderr, "Can't get on system bus");
2544 session = g_dbus_setup_bus(DBUS_BUS_SESSION, NULL, NULL);
2546 fprintf(stderr, "Can't get on session bus");
2550 owner_watch = g_dbus_add_signal_watch(session, NULL, NULL,
2551 DBUS_INTERFACE_DBUS,
2556 properties_watch = g_dbus_add_properties_watch(session, NULL, NULL,
2557 MPRIS_PLAYER_INTERFACE,
2561 signal_watch = g_dbus_add_signal_watch(session, NULL, NULL,
2562 MPRIS_PLAYER_INTERFACE,
2563 NULL, player_signal,
2566 memset(&sa, 0, sizeof(sa));
2567 sa.sa_flags = SA_NOCLDSTOP;
2568 sa.sa_handler = sig_term;
2569 sigaction(SIGTERM, &sa, NULL);
2570 sigaction(SIGINT, &sa, NULL);
2572 client = g_dbus_client_new(sys, BLUEZ_BUS_NAME, BLUEZ_PATH);
2574 g_dbus_client_set_connect_watch(client, connect_handler, NULL);
2575 g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
2577 g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
2578 property_changed, NULL);
2580 g_main_loop_run(main_loop);
2582 g_dbus_remove_watch(session, owner_watch);
2583 g_dbus_remove_watch(session, properties_watch);
2584 g_dbus_remove_watch(session, signal_watch);
2586 g_dbus_client_unref(client);
2588 dbus_connection_unref(session);
2589 dbus_connection_unref(sys);
2591 g_main_loop_unref(main_loop);