Message ID | 20250124164611.1175429-1-frederic.danis@collabora.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | [BlueZ,1/2] mpris-proxy: Add mpris:artUrl support | expand |
Context | Check | Description |
---|---|---|
tedd_an/pre-ci_am | success | Success |
tedd_an/BuildEll | success | Build ELL PASS |
tedd_an/BluezMake | success | Bluez Make PASS |
tedd_an/MakeCheck | success | Bluez Make Check PASS |
tedd_an/MakeDistcheck | success | Make Distcheck PASS |
tedd_an/CheckValgrind | success | Check Valgrind PASS |
tedd_an/CheckSmatch | success | CheckSparse PASS |
tedd_an/bluezmakeextell | success | Make External ELL PASS |
tedd_an/ScanBuild | success | Scan Build PASS |
Hi Frédéric, On Fri, Jan 24, 2025 at 11:48 AM Frédéric Danis <frederic.danis@collabora.com> wrote: > > This commit connects to the bip-avrcp Obex service if the > org.bluez.MediaPlayer ObexPort property exists. > Once connected, the Track properties update may contain an > ImgHandle which is automatically downloaded, then a Metadata > property updated signal is sent on org.mpris.MediaPlayer2.Player > interface. > > Some devices share the Obex session between multiple players. So > the Obex session is created by device. Can you add a sample output? Also our long term plan is to integrate mpris-player functionality into bluetoothctl. > --- > tools/mpris-proxy.c | 435 +++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 425 insertions(+), 10 deletions(-) > > diff --git a/tools/mpris-proxy.c b/tools/mpris-proxy.c > index e5fc91fdb..1f6c86777 100644 > --- a/tools/mpris-proxy.c > +++ b/tools/mpris-proxy.c > @@ -30,11 +30,18 @@ > #define BLUEZ_BUS_NAME "org.bluez" > #define BLUEZ_PATH "/org/bluez" > #define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter1" > +#define BLUEZ_DEVICE_INTERFACE "org.bluez.Device1" > #define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1" > #define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1" > #define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1" > #define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1" > #define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1" > +#define BLUEZ_OBEX_BUS_NAME "org.bluez.obex" > +#define BLUEZ_OBEX_PATH "/org/bluez/obex" > +#define BLUEZ_OBEX_CLIENT_PATH BLUEZ_OBEX_PATH "/client" > +#define BLUEZ_OBEX_CLIENT_INTERFACE "org.bluez.obex.Client1" > +#define BLUEZ_OBEX_IMAGE_INTERFACE "org.bluez.obex.Image1" > +#define BLUEZ_OBEX_TRANSFER_INTERFACE "org.bluez.obex.Transfer1" > #define MPRIS_BUS_NAME "org.mpris.MediaPlayer2." > #define MPRIS_INTERFACE "org.mpris.MediaPlayer2" > #define MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" > @@ -48,8 +55,10 @@ static GDBusProxy *adapter = NULL; > static DBusConnection *sys = NULL; > static DBusConnection *session = NULL; > static GDBusClient *client = NULL; > +static GDBusClient *obex_client; > static GSList *players = NULL; > static GSList *transports = NULL; > +static GSList *obex_sessions; > > static gboolean option_version = FALSE; > static gboolean option_export = FALSE; > @@ -59,6 +68,12 @@ struct tracklist { > GSList *items; > }; > > +struct obex_session { > + GDBusProxy *device; > + GDBusProxy *obex; > + uint16_t port; > +}; > + > struct player { > char *bus_name; > DBusConnection *conn; > @@ -67,11 +82,14 @@ struct player { > GDBusProxy *device; > GDBusProxy *transport; > GDBusProxy *playlist; > + struct obex_session *obex; > struct tracklist *tracklist; > + char *filename; > }; > > typedef int (* parse_metadata_func) (DBusMessageIter *iter, const char *key, > - DBusMessageIter *metadata); > + DBusMessageIter *metadata, > + void *userdata); > > static void dict_append_entry(DBusMessageIter *dict, const char *key, int type, > void *val); > @@ -240,7 +258,8 @@ static void dict_append_iter(DBusMessageIter *dict, const char *key, > } > > static int parse_metadata_entry(DBusMessageIter *entry, const char *key, > - DBusMessageIter *metadata) > + DBusMessageIter *metadata, > + void *userdata) > { > if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT) > return -EINVAL; > @@ -251,7 +270,8 @@ static int parse_metadata_entry(DBusMessageIter *entry, const char *key, > } > > static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata, > - parse_metadata_func func) > + parse_metadata_func func, > + void *userdata) > { > DBusMessageIter dict; > int ctype; > @@ -277,7 +297,7 @@ static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata, > dbus_message_iter_get_basic(&entry, &key); > dbus_message_iter_next(&entry); > > - if (func(&entry, key, metadata) < 0) > + if (func(&entry, key, metadata, userdata) < 0) > return -EINVAL; > > dbus_message_iter_next(&dict); > @@ -299,7 +319,7 @@ static void append_metadata(DBusMessageIter *iter, DBusMessageIter *dict, > DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING > DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata); > > - parse_metadata(dict, &metadata, func); > + parse_metadata(dict, &metadata, func, NULL); > > dbus_message_iter_close_container(&value, &metadata); > dbus_message_iter_close_container(iter, &value); > @@ -1223,7 +1243,8 @@ static gboolean parse_path_metadata(DBusMessageIter *iter, const char *key, > } > > static int parse_track_entry(DBusMessageIter *entry, const char *key, > - DBusMessageIter *metadata) > + DBusMessageIter *metadata, > + void *userdata) > { > DBusMessageIter var; > > @@ -1253,6 +1274,30 @@ static int parse_track_entry(DBusMessageIter *entry, const char *key, > } else if (strcasecmp(key, "Item") == 0) { > if (!parse_path_metadata(&var, "mpris:trackid", metadata)) > return -EINVAL; > + } else if (strcasecmp(key, "ImgHandle") == 0) { > + struct player *player = userdata; > + const char *handle, *path; > + char *filename, *uri; > + > + if (!player || !player->obex) > + return -EINVAL; > + > + path = g_dbus_proxy_get_path(player->obex->obex); > + > + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) > + return -EINVAL; > + dbus_message_iter_get_basic(&var, &handle); > + > + filename = g_strconcat(g_get_tmp_dir(), "/", > + path + strlen(BLUEZ_OBEX_CLIENT_PATH "/"), > + "-", handle, NULL); > + if (access(filename, F_OK) == 0) { > + uri = g_strconcat("file://", filename, NULL); > + dict_append_entry(metadata, "mpris:artUrl", > + DBUS_TYPE_STRING, &uri); > + g_free(uri); > + } > + g_free(filename); > } > > return 0; > @@ -1272,7 +1317,7 @@ static gboolean get_track(const GDBusPropertyTable *property, > 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); > + parse_metadata(&var, &metadata, parse_track_entry, player); > > dbus_message_iter_close_container(iter, &metadata); > > @@ -1443,7 +1488,7 @@ static void append_item_metadata(void *data, void *user_data) > &path); > > if (g_dbus_proxy_get_property(item, "Metadata", &var)) > - parse_metadata(&var, &metadata, parse_track_entry); > + parse_metadata(&var, &metadata, parse_track_entry, NULL); > > dbus_message_iter_close_container(iter, &metadata); > > @@ -1938,11 +1983,72 @@ static void register_tracklist(GDBusProxy *proxy) > player, NULL); > } > > +static GDBusProxy *connect_obex_session(const char *address, uint16_t port) > +{ > + static const char *target_str = "bip-avrcp"; > + DBusMessage *msg, *reply; > + DBusMessageIter iter, array; > + const char *path; > + DBusError err; > + > + msg = dbus_message_new_method_call(BLUEZ_OBEX_BUS_NAME, > + BLUEZ_OBEX_PATH, > + BLUEZ_OBEX_CLIENT_INTERFACE, > + "CreateSession"); > + dbus_message_iter_init_append(msg, &iter); > + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address); > + 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, > + &array); > + dict_append_entry(&array, "Target", DBUS_TYPE_STRING, &target_str); > + dict_append_entry(&array, "PSM", DBUS_TYPE_UINT16, &port); > + dbus_message_iter_close_container(&iter, &array); > + > + dbus_error_init(&err); > + reply = dbus_connection_send_with_reply_and_block(session, msg, -1, > + &err); > + dbus_message_unref(msg); > + if (!reply) { > + if (dbus_error_is_set(&err)) { > + fprintf(stderr, "%s\n", err.message); > + dbus_error_free(&err); > + } > + return NULL; > + } > + > + if (!dbus_message_get_args(reply, NULL, > + DBUS_TYPE_OBJECT_PATH, &path, > + DBUS_TYPE_INVALID)) { > + dbus_message_unref(reply); > + return NULL; > + } > + > + return g_dbus_proxy_new(obex_client, path, BLUEZ_OBEX_IMAGE_INTERFACE); > +} > + > +static struct obex_session *find_obex_session_by_device(const char *device) > +{ > + GSList *l; > + > + for (l = obex_sessions; l; l = l->next) { > + struct obex_session *session = l->data; > + const char *path = g_dbus_proxy_get_path(session->device); > + > + if (g_strcmp0(device, path) == 0) > + return session; > + } > + > + return NULL; > +} > + > static void register_player(GDBusProxy *proxy) > { > struct player *player; > DBusMessageIter iter; > - const char *path, *alias, *name; > + const char *path, *alias, *name, *address; > char *busname; > GDBusProxy *device, *transport; > > @@ -1960,6 +2066,11 @@ static void register_player(GDBusProxy *proxy) > > dbus_message_iter_get_basic(&iter, &alias); > > + if (!g_dbus_proxy_get_property(device, "Address", &iter)) > + return; > + > + dbus_message_iter_get_basic(&iter, &address); > + > if (g_dbus_proxy_get_property(proxy, "Name", &iter)) { > dbus_message_iter_get_basic(&iter, &name); > busname = g_strconcat(alias, " ", name, NULL); > @@ -1971,6 +2082,27 @@ static void register_player(GDBusProxy *proxy) > player->proxy = g_dbus_proxy_ref(proxy); > player->device = device; > > + if (g_dbus_proxy_get_property(proxy, "ObexPort", &iter)) { > + uint16_t port; > + struct obex_session *session; > + > + dbus_message_iter_get_basic(&iter, &port); > + > + session = find_obex_session_by_device(path); > + if (session == NULL || session->port != port) { > + printf("Create new session\n"); > + session = g_new0(struct obex_session, 1); > + session->obex = connect_obex_session(address, port); > + session->device = g_dbus_proxy_ref(device); > + session->port = port; > + > + obex_sessions = g_slist_prepend(obex_sessions, session); > + } > + player->obex = session; > + } else { > + player->obex = NULL; > + } > + > g_free(busname); > > players = g_slist_prepend(players, player); > @@ -2177,7 +2309,7 @@ static void register_item(struct player *player, GDBusProxy *proxy) > DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING > DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata); > > - parse_metadata(&iter, &metadata, parse_track_entry); > + parse_metadata(&iter, &metadata, parse_track_entry, player); > > dbus_message_iter_close_container(&args, &metadata); > > @@ -2377,6 +2509,121 @@ static const char *property_to_mpris(const char *property) > return NULL; > } > > +static const char *obex_get_image_handle(DBusMessageIter *args) > +{ > + DBusMessageIter dict, var; > + int ctype; > + > + ctype = dbus_message_iter_get_arg_type(args); > + if (ctype != DBUS_TYPE_ARRAY) > + return NULL; > + > + dbus_message_iter_recurse(args, &dict); > + > + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != > + DBUS_TYPE_INVALID) { > + DBusMessageIter entry; > + const char *key; > + > + if (ctype != DBUS_TYPE_DICT_ENTRY) > + return NULL; > + > + dbus_message_iter_recurse(&dict, &entry); > + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) > + return NULL; > + > + dbus_message_iter_get_basic(&entry, &key); > + dbus_message_iter_next(&entry); > + > + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) > + return NULL; > + > + dbus_message_iter_recurse(&entry, &var); > + > + if (strcasecmp(key, "ImgHandle") == 0) { > + const char *handle; > + > + dbus_message_iter_get_basic(&var, &handle); > + return handle; > + } > + > + dbus_message_iter_next(&dict); > + } > + > + return NULL; > +} > + > +static void obex_get_image(struct player *player, const char *handle) > +{ > + DBusMessage *msg; > + DBusMessageIter iter, array; > + struct obex_session *obex_session = player->obex; > + const char *path = g_dbus_proxy_get_path(obex_session->obex); > + char *filename; > + > + player->filename = g_strconcat(g_get_tmp_dir(), "/", > + path + strlen(BLUEZ_OBEX_CLIENT_PATH "/"), > + "-", handle, NULL); > + filename = g_strconcat(player->filename, ".tmp", NULL); > + > + msg = dbus_message_new_method_call(BLUEZ_OBEX_BUS_NAME, > + path, > + BLUEZ_OBEX_IMAGE_INTERFACE, > + "Get"); > + dbus_message_iter_init_append(msg, &iter); > + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &filename); > + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &handle); > + 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, > + &array); > + dbus_message_iter_close_container(&iter, &array); > + > + if (!g_dbus_send_message(session, msg)) { > + g_free(player->filename); > + player->filename = NULL; > + } > + g_free(filename); > +} > + > +static void device_property_changed(GDBusProxy *proxy, const char *name, > + DBusMessageIter *iter, void *user_data) > +{ > + const char *path; > + struct obex_session *session; > + gboolean connected; > + GSList *l; > + > + path = g_dbus_proxy_get_path(proxy); > + > + if (strcasecmp(name, "Connected") != 0) > + return; > + > + dbus_message_iter_get_basic(iter, &connected); > + > + if (connected) > + return; > + > + printf("Bluetooth Device %s disconnected\n", path); > + session = find_obex_session_by_device(path); > + if (session == NULL) > + return; > + > + for (l = players; l; l = l->next) { > + struct player *player = l->data; > + > + if (player->obex == session) > + player->obex = NULL; > + } > + > + g_dbus_proxy_unref(session->obex); > + g_dbus_proxy_unref(session->device); > + obex_sessions = g_slist_remove(obex_sessions, session); > + g_free(session); > +} > + > static void player_property_changed(GDBusProxy *proxy, const char *name, > DBusMessageIter *iter, void *user_data) > { > @@ -2397,6 +2644,13 @@ static void player_property_changed(GDBusProxy *proxy, const char *name, > MPRIS_PLAYER_INTERFACE, > property); > > + if (strcasecmp(name, "Track") == 0 && player->obex) { > + const char *handle = obex_get_image_handle(iter); > + > + if (handle) > + obex_get_image(player, handle); > + } > + > if (strcasecmp(name, "Position") != 0) > return; > > @@ -2485,6 +2739,9 @@ static void property_changed(GDBusProxy *proxy, const char *name, > > interface = g_dbus_proxy_get_interface(proxy); > > + if (strcmp(interface, BLUEZ_DEVICE_INTERFACE) == 0) > + return device_property_changed(proxy, name, iter, user_data); > + > if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0) > return player_property_changed(proxy, name, iter, user_data); > > @@ -2496,6 +2753,151 @@ static void property_changed(GDBusProxy *proxy, const char *name, > return item_property_changed(proxy, name, iter, user_data); > } > > +static struct player *find_player_by_obex(const char *path) > +{ > + GSList *l; > + > + for (l = players; l; l = l->next) { > + struct player *player = l->data; > + struct obex_session *session = player->obex; > + const char *obex_path = g_dbus_proxy_get_path(session->obex); > + > + if (g_str_has_prefix(path, obex_path)) > + return player; > + } > + > + return NULL; > +} > + > +static void obex_connect_handler(DBusConnection *connection, void *user_data) > +{ > + printf("org.bluez.obex appeared\n"); > +} > + > +static void obex_disconnect_handler(DBusConnection *connection, > + void *user_data) > +{ > + printf("org.bluez.obex disappeared\n"); > +} > + > +static void obex_proxy_added(GDBusProxy *proxy, void *user_data) > +{ > + const char *interface; > + const char *path; > + > + interface = g_dbus_proxy_get_interface(proxy); > + path = g_dbus_proxy_get_path(proxy); > + > + if (!strcmp(interface, BLUEZ_OBEX_CLIENT_INTERFACE)) { > + GSList *l; > + > + printf("Bluetooth Obex Client %s found\n", path); > + > + for (l = players; l; l = l->next) { > + struct player *player = l->data; > + DBusMessageIter iter; > + const char *address; > + uint16_t port; > + struct obex_session *session; > + > + if (!g_dbus_proxy_get_property(player->proxy, > + "ObexPort", &iter) || > + player->obex) > + continue; > + > + dbus_message_iter_get_basic(&iter, &port); > + > + if (!g_dbus_proxy_get_property(player->device, > + "Address", &iter)) > + continue; > + > + dbus_message_iter_get_basic(&iter, &address); > + > + session = find_obex_session_by_device(path); > + if (session == NULL || session->port != port) { > + printf("Bluetooth Obex Create new session\n"); > + session = g_new0(struct obex_session, 1); > + session->obex = connect_obex_session(address, > + port); > + session->device = g_dbus_proxy_ref( > + player->device); > + session->port = port; > + > + obex_sessions = g_slist_prepend(obex_sessions, > + session); > + } > + player->obex = session; > + } > + } > +} > + > +static void obex_proxy_removed(GDBusProxy *proxy, void *user_data) > +{ > + const char *interface; > + const char *path; > + > + if (adapter == NULL) > + return; > + > + interface = g_dbus_proxy_get_interface(proxy); > + path = g_dbus_proxy_get_path(proxy); > + > + if (strcmp(interface, BLUEZ_OBEX_CLIENT_INTERFACE) == 0) { > + GSList *l; > + > + printf("Bluetooth Obex Client %s removed\n", path); > + > + for (l = players; l; l = l->next) { > + struct player *player = l->data; > + > + player->obex = NULL; > + } > + > + while (obex_sessions) { > + struct obex_session *session = obex_sessions->data; > + > + g_dbus_proxy_unref(session->device); > + g_dbus_proxy_unref(session->obex); > + obex_sessions = g_slist_remove(obex_sessions, session); > + } > + } > +} > + > +static void obex_property_changed(GDBusProxy *proxy, const char *name, > + DBusMessageIter *iter, void *user_data) > +{ > + const char *interface; > + const char *path; > + > + interface = g_dbus_proxy_get_interface(proxy); > + path = g_dbus_proxy_get_path(proxy); > + > + if (strcmp(interface, BLUEZ_OBEX_TRANSFER_INTERFACE) == 0) { > + struct player *player; > + const char *status; > + > + if (strcasecmp(name, "Status") != 0) > + return; > + > + dbus_message_iter_get_basic(iter, &status); > + > + player = find_player_by_obex(path); > + if (player && strcasecmp(status, "complete") == 0) { > + char *filename; > + > + filename = g_strconcat(player->filename, ".tmp", NULL); > + rename(filename, player->filename); > + g_free(player->filename); > + player->filename = NULL; > + > + g_dbus_emit_property_changed(player->conn, > + MPRIS_PLAYER_PATH, > + MPRIS_PLAYER_INTERFACE, > + "Metadata"); > + } > + } > +} > + > int main(int argc, char *argv[]) > { > GOptionContext *context; > @@ -2566,6 +2968,19 @@ int main(int argc, char *argv[]) > g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, > property_changed, NULL); > > + obex_client = g_dbus_client_new(session, BLUEZ_OBEX_BUS_NAME, > + BLUEZ_OBEX_PATH); > + > + g_dbus_client_set_connect_watch(obex_client, obex_connect_handler, > + NULL); > + g_dbus_client_set_disconnect_watch(obex_client, > + obex_disconnect_handler, > + NULL); > + > + g_dbus_client_set_proxy_handlers(obex_client, obex_proxy_added, > + obex_proxy_removed, > + obex_property_changed, NULL); > + > g_main_loop_run(main_loop); > > g_dbus_remove_watch(session, owner_watch); > -- > 2.43.0 > >
This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=928196 ---Test result--- Test Summary: CheckPatch PENDING 0.31 seconds GitLint PENDING 0.28 seconds BuildEll PASS 20.34 seconds BluezMake PASS 1454.28 seconds MakeCheck PASS 13.11 seconds MakeDistcheck PASS 159.40 seconds CheckValgrind PASS 215.86 seconds CheckSmatch PASS 271.86 seconds bluezmakeextell PASS 98.20 seconds IncrementalBuild PENDING 0.33 seconds ScanBuild PASS 861.03 seconds Details ############################## Test: CheckPatch - PENDING Desc: Run checkpatch.pl script Output: ############################## Test: GitLint - PENDING Desc: Run gitlint Output: ############################## Test: IncrementalBuild - PENDING Desc: Incremental build with the patches in the series Output: --- Regards, Linux Bluetooth
diff --git a/tools/mpris-proxy.c b/tools/mpris-proxy.c index e5fc91fdb..1f6c86777 100644 --- a/tools/mpris-proxy.c +++ b/tools/mpris-proxy.c @@ -30,11 +30,18 @@ #define BLUEZ_BUS_NAME "org.bluez" #define BLUEZ_PATH "/org/bluez" #define BLUEZ_ADAPTER_INTERFACE "org.bluez.Adapter1" +#define BLUEZ_DEVICE_INTERFACE "org.bluez.Device1" #define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1" #define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1" #define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1" #define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1" #define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1" +#define BLUEZ_OBEX_BUS_NAME "org.bluez.obex" +#define BLUEZ_OBEX_PATH "/org/bluez/obex" +#define BLUEZ_OBEX_CLIENT_PATH BLUEZ_OBEX_PATH "/client" +#define BLUEZ_OBEX_CLIENT_INTERFACE "org.bluez.obex.Client1" +#define BLUEZ_OBEX_IMAGE_INTERFACE "org.bluez.obex.Image1" +#define BLUEZ_OBEX_TRANSFER_INTERFACE "org.bluez.obex.Transfer1" #define MPRIS_BUS_NAME "org.mpris.MediaPlayer2." #define MPRIS_INTERFACE "org.mpris.MediaPlayer2" #define MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" @@ -48,8 +55,10 @@ static GDBusProxy *adapter = NULL; static DBusConnection *sys = NULL; static DBusConnection *session = NULL; static GDBusClient *client = NULL; +static GDBusClient *obex_client; static GSList *players = NULL; static GSList *transports = NULL; +static GSList *obex_sessions; static gboolean option_version = FALSE; static gboolean option_export = FALSE; @@ -59,6 +68,12 @@ struct tracklist { GSList *items; }; +struct obex_session { + GDBusProxy *device; + GDBusProxy *obex; + uint16_t port; +}; + struct player { char *bus_name; DBusConnection *conn; @@ -67,11 +82,14 @@ struct player { GDBusProxy *device; GDBusProxy *transport; GDBusProxy *playlist; + struct obex_session *obex; struct tracklist *tracklist; + char *filename; }; typedef int (* parse_metadata_func) (DBusMessageIter *iter, const char *key, - DBusMessageIter *metadata); + DBusMessageIter *metadata, + void *userdata); static void dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val); @@ -240,7 +258,8 @@ static void dict_append_iter(DBusMessageIter *dict, const char *key, } static int parse_metadata_entry(DBusMessageIter *entry, const char *key, - DBusMessageIter *metadata) + DBusMessageIter *metadata, + void *userdata) { if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT) return -EINVAL; @@ -251,7 +270,8 @@ static int parse_metadata_entry(DBusMessageIter *entry, const char *key, } static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata, - parse_metadata_func func) + parse_metadata_func func, + void *userdata) { DBusMessageIter dict; int ctype; @@ -277,7 +297,7 @@ static int parse_metadata(DBusMessageIter *args, DBusMessageIter *metadata, dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); - if (func(&entry, key, metadata) < 0) + if (func(&entry, key, metadata, userdata) < 0) return -EINVAL; dbus_message_iter_next(&dict); @@ -299,7 +319,7 @@ static void append_metadata(DBusMessageIter *iter, DBusMessageIter *dict, DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata); - parse_metadata(dict, &metadata, func); + parse_metadata(dict, &metadata, func, NULL); dbus_message_iter_close_container(&value, &metadata); dbus_message_iter_close_container(iter, &value); @@ -1223,7 +1243,8 @@ static gboolean parse_path_metadata(DBusMessageIter *iter, const char *key, } static int parse_track_entry(DBusMessageIter *entry, const char *key, - DBusMessageIter *metadata) + DBusMessageIter *metadata, + void *userdata) { DBusMessageIter var; @@ -1253,6 +1274,30 @@ static int parse_track_entry(DBusMessageIter *entry, const char *key, } else if (strcasecmp(key, "Item") == 0) { if (!parse_path_metadata(&var, "mpris:trackid", metadata)) return -EINVAL; + } else if (strcasecmp(key, "ImgHandle") == 0) { + struct player *player = userdata; + const char *handle, *path; + char *filename, *uri; + + if (!player || !player->obex) + return -EINVAL; + + path = g_dbus_proxy_get_path(player->obex->obex); + + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&var, &handle); + + filename = g_strconcat(g_get_tmp_dir(), "/", + path + strlen(BLUEZ_OBEX_CLIENT_PATH "/"), + "-", handle, NULL); + if (access(filename, F_OK) == 0) { + uri = g_strconcat("file://", filename, NULL); + dict_append_entry(metadata, "mpris:artUrl", + DBUS_TYPE_STRING, &uri); + g_free(uri); + } + g_free(filename); } return 0; @@ -1272,7 +1317,7 @@ static gboolean get_track(const GDBusPropertyTable *property, 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); + parse_metadata(&var, &metadata, parse_track_entry, player); dbus_message_iter_close_container(iter, &metadata); @@ -1443,7 +1488,7 @@ static void append_item_metadata(void *data, void *user_data) &path); if (g_dbus_proxy_get_property(item, "Metadata", &var)) - parse_metadata(&var, &metadata, parse_track_entry); + parse_metadata(&var, &metadata, parse_track_entry, NULL); dbus_message_iter_close_container(iter, &metadata); @@ -1938,11 +1983,72 @@ static void register_tracklist(GDBusProxy *proxy) player, NULL); } +static GDBusProxy *connect_obex_session(const char *address, uint16_t port) +{ + static const char *target_str = "bip-avrcp"; + DBusMessage *msg, *reply; + DBusMessageIter iter, array; + const char *path; + DBusError err; + + msg = dbus_message_new_method_call(BLUEZ_OBEX_BUS_NAME, + BLUEZ_OBEX_PATH, + BLUEZ_OBEX_CLIENT_INTERFACE, + "CreateSession"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address); + 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, + &array); + dict_append_entry(&array, "Target", DBUS_TYPE_STRING, &target_str); + dict_append_entry(&array, "PSM", DBUS_TYPE_UINT16, &port); + dbus_message_iter_close_container(&iter, &array); + + dbus_error_init(&err); + reply = dbus_connection_send_with_reply_and_block(session, msg, -1, + &err); + dbus_message_unref(msg); + if (!reply) { + if (dbus_error_is_set(&err)) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } + return NULL; + } + + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) { + dbus_message_unref(reply); + return NULL; + } + + return g_dbus_proxy_new(obex_client, path, BLUEZ_OBEX_IMAGE_INTERFACE); +} + +static struct obex_session *find_obex_session_by_device(const char *device) +{ + GSList *l; + + for (l = obex_sessions; l; l = l->next) { + struct obex_session *session = l->data; + const char *path = g_dbus_proxy_get_path(session->device); + + if (g_strcmp0(device, path) == 0) + return session; + } + + return NULL; +} + static void register_player(GDBusProxy *proxy) { struct player *player; DBusMessageIter iter; - const char *path, *alias, *name; + const char *path, *alias, *name, *address; char *busname; GDBusProxy *device, *transport; @@ -1960,6 +2066,11 @@ static void register_player(GDBusProxy *proxy) dbus_message_iter_get_basic(&iter, &alias); + if (!g_dbus_proxy_get_property(device, "Address", &iter)) + return; + + dbus_message_iter_get_basic(&iter, &address); + if (g_dbus_proxy_get_property(proxy, "Name", &iter)) { dbus_message_iter_get_basic(&iter, &name); busname = g_strconcat(alias, " ", name, NULL); @@ -1971,6 +2082,27 @@ static void register_player(GDBusProxy *proxy) player->proxy = g_dbus_proxy_ref(proxy); player->device = device; + if (g_dbus_proxy_get_property(proxy, "ObexPort", &iter)) { + uint16_t port; + struct obex_session *session; + + dbus_message_iter_get_basic(&iter, &port); + + session = find_obex_session_by_device(path); + if (session == NULL || session->port != port) { + printf("Create new session\n"); + session = g_new0(struct obex_session, 1); + session->obex = connect_obex_session(address, port); + session->device = g_dbus_proxy_ref(device); + session->port = port; + + obex_sessions = g_slist_prepend(obex_sessions, session); + } + player->obex = session; + } else { + player->obex = NULL; + } + g_free(busname); players = g_slist_prepend(players, player); @@ -2177,7 +2309,7 @@ static void register_item(struct player *player, GDBusProxy *proxy) DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &metadata); - parse_metadata(&iter, &metadata, parse_track_entry); + parse_metadata(&iter, &metadata, parse_track_entry, player); dbus_message_iter_close_container(&args, &metadata); @@ -2377,6 +2509,121 @@ static const char *property_to_mpris(const char *property) return NULL; } +static const char *obex_get_image_handle(DBusMessageIter *args) +{ + DBusMessageIter dict, var; + int ctype; + + ctype = dbus_message_iter_get_arg_type(args); + if (ctype != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(args, &dict); + + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != + DBUS_TYPE_INVALID) { + DBusMessageIter entry; + const char *key; + + if (ctype != DBUS_TYPE_DICT_ENTRY) + return NULL; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + return NULL; + + dbus_message_iter_recurse(&entry, &var); + + if (strcasecmp(key, "ImgHandle") == 0) { + const char *handle; + + dbus_message_iter_get_basic(&var, &handle); + return handle; + } + + dbus_message_iter_next(&dict); + } + + return NULL; +} + +static void obex_get_image(struct player *player, const char *handle) +{ + DBusMessage *msg; + DBusMessageIter iter, array; + struct obex_session *obex_session = player->obex; + const char *path = g_dbus_proxy_get_path(obex_session->obex); + char *filename; + + player->filename = g_strconcat(g_get_tmp_dir(), "/", + path + strlen(BLUEZ_OBEX_CLIENT_PATH "/"), + "-", handle, NULL); + filename = g_strconcat(player->filename, ".tmp", NULL); + + msg = dbus_message_new_method_call(BLUEZ_OBEX_BUS_NAME, + path, + BLUEZ_OBEX_IMAGE_INTERFACE, + "Get"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &filename); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &handle); + 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, + &array); + dbus_message_iter_close_container(&iter, &array); + + if (!g_dbus_send_message(session, msg)) { + g_free(player->filename); + player->filename = NULL; + } + g_free(filename); +} + +static void device_property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + const char *path; + struct obex_session *session; + gboolean connected; + GSList *l; + + path = g_dbus_proxy_get_path(proxy); + + if (strcasecmp(name, "Connected") != 0) + return; + + dbus_message_iter_get_basic(iter, &connected); + + if (connected) + return; + + printf("Bluetooth Device %s disconnected\n", path); + session = find_obex_session_by_device(path); + if (session == NULL) + return; + + for (l = players; l; l = l->next) { + struct player *player = l->data; + + if (player->obex == session) + player->obex = NULL; + } + + g_dbus_proxy_unref(session->obex); + g_dbus_proxy_unref(session->device); + obex_sessions = g_slist_remove(obex_sessions, session); + g_free(session); +} + static void player_property_changed(GDBusProxy *proxy, const char *name, DBusMessageIter *iter, void *user_data) { @@ -2397,6 +2644,13 @@ static void player_property_changed(GDBusProxy *proxy, const char *name, MPRIS_PLAYER_INTERFACE, property); + if (strcasecmp(name, "Track") == 0 && player->obex) { + const char *handle = obex_get_image_handle(iter); + + if (handle) + obex_get_image(player, handle); + } + if (strcasecmp(name, "Position") != 0) return; @@ -2485,6 +2739,9 @@ static void property_changed(GDBusProxy *proxy, const char *name, interface = g_dbus_proxy_get_interface(proxy); + if (strcmp(interface, BLUEZ_DEVICE_INTERFACE) == 0) + return device_property_changed(proxy, name, iter, user_data); + if (strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE) == 0) return player_property_changed(proxy, name, iter, user_data); @@ -2496,6 +2753,151 @@ static void property_changed(GDBusProxy *proxy, const char *name, return item_property_changed(proxy, name, iter, user_data); } +static struct player *find_player_by_obex(const char *path) +{ + GSList *l; + + for (l = players; l; l = l->next) { + struct player *player = l->data; + struct obex_session *session = player->obex; + const char *obex_path = g_dbus_proxy_get_path(session->obex); + + if (g_str_has_prefix(path, obex_path)) + return player; + } + + return NULL; +} + +static void obex_connect_handler(DBusConnection *connection, void *user_data) +{ + printf("org.bluez.obex appeared\n"); +} + +static void obex_disconnect_handler(DBusConnection *connection, + void *user_data) +{ + printf("org.bluez.obex disappeared\n"); +} + +static void obex_proxy_added(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + const char *path; + + interface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + if (!strcmp(interface, BLUEZ_OBEX_CLIENT_INTERFACE)) { + GSList *l; + + printf("Bluetooth Obex Client %s found\n", path); + + for (l = players; l; l = l->next) { + struct player *player = l->data; + DBusMessageIter iter; + const char *address; + uint16_t port; + struct obex_session *session; + + if (!g_dbus_proxy_get_property(player->proxy, + "ObexPort", &iter) || + player->obex) + continue; + + dbus_message_iter_get_basic(&iter, &port); + + if (!g_dbus_proxy_get_property(player->device, + "Address", &iter)) + continue; + + dbus_message_iter_get_basic(&iter, &address); + + session = find_obex_session_by_device(path); + if (session == NULL || session->port != port) { + printf("Bluetooth Obex Create new session\n"); + session = g_new0(struct obex_session, 1); + session->obex = connect_obex_session(address, + port); + session->device = g_dbus_proxy_ref( + player->device); + session->port = port; + + obex_sessions = g_slist_prepend(obex_sessions, + session); + } + player->obex = session; + } + } +} + +static void obex_proxy_removed(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + const char *path; + + if (adapter == NULL) + return; + + interface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + if (strcmp(interface, BLUEZ_OBEX_CLIENT_INTERFACE) == 0) { + GSList *l; + + printf("Bluetooth Obex Client %s removed\n", path); + + for (l = players; l; l = l->next) { + struct player *player = l->data; + + player->obex = NULL; + } + + while (obex_sessions) { + struct obex_session *session = obex_sessions->data; + + g_dbus_proxy_unref(session->device); + g_dbus_proxy_unref(session->obex); + obex_sessions = g_slist_remove(obex_sessions, session); + } + } +} + +static void obex_property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + const char *interface; + const char *path; + + interface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + if (strcmp(interface, BLUEZ_OBEX_TRANSFER_INTERFACE) == 0) { + struct player *player; + const char *status; + + if (strcasecmp(name, "Status") != 0) + return; + + dbus_message_iter_get_basic(iter, &status); + + player = find_player_by_obex(path); + if (player && strcasecmp(status, "complete") == 0) { + char *filename; + + filename = g_strconcat(player->filename, ".tmp", NULL); + rename(filename, player->filename); + g_free(player->filename); + player->filename = NULL; + + g_dbus_emit_property_changed(player->conn, + MPRIS_PLAYER_PATH, + MPRIS_PLAYER_INTERFACE, + "Metadata"); + } + } +} + int main(int argc, char *argv[]) { GOptionContext *context; @@ -2566,6 +2968,19 @@ int main(int argc, char *argv[]) g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, property_changed, NULL); + obex_client = g_dbus_client_new(session, BLUEZ_OBEX_BUS_NAME, + BLUEZ_OBEX_PATH); + + g_dbus_client_set_connect_watch(obex_client, obex_connect_handler, + NULL); + g_dbus_client_set_disconnect_watch(obex_client, + obex_disconnect_handler, + NULL); + + g_dbus_client_set_proxy_handlers(obex_client, obex_proxy_added, + obex_proxy_removed, + obex_property_changed, NULL); + g_main_loop_run(main_loop); g_dbus_remove_watch(session, owner_watch);