2022-04-14 10:53:50

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu

From: Luiz Augusto von Dentz <[email protected]>

This adds endpoint menu:

[bluetooth]# menu endpoint
Menu endpoint:
Available commands:
-------------------
list [local] List available endpoints
show <endpoint> Endpoint information
register <UUID> <codec> [capabilities...] Register Endpoint
unregister <UUID/object> Register Endpoint
config <endpoint> <local endpoint> [preset] Configure Endpoint
presets <UUID> [default] List available presets
---
client/player.c | 1188 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1183 insertions(+), 5 deletions(-)

diff --git a/client/player.c b/client/player.c
index 1095482d4..5db35cfeb 100644
--- a/client/player.c
+++ b/client/player.c
@@ -21,7 +21,9 @@
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
+#include <sys/ioctl.h>
#include <sys/uio.h>
+#include <wordexp.h>

#include <glib.h>

@@ -30,8 +32,12 @@
#include "lib/bluetooth.h"
#include "lib/uuid.h"

+#include "profiles/audio/a2dp-codecs.h"
+
#include "src/shared/util.h"
#include "src/shared/shell.h"
+#include "src/shared/io.h"
+#include "src/shared/queue.h"
#include "player.h"

/* String display constants */
@@ -39,15 +45,51 @@
#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF
#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF

+#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_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+
+#define BLUEZ_MEDIA_ENDPOINT_PATH "/local/endpoint"
+
+#define NSEC_USEC(_t) (_t / 1000L)
+#define SEC_USEC(_t) (_t * 1000000L)
+#define TS_USEC(_ts) (SEC_USEC((_ts)->tv_sec) + NSEC_USEC((_ts)->tv_nsec))
+
+struct endpoint {
+ char *path;
+ char *uuid;
+ uint8_t codec;
+ struct iovec *caps;
+ bool auto_accept;
+ char *transport;
+ DBusMessage *msg;
+};

static DBusConnection *dbus_conn;
static GDBusProxy *default_player;
+static GList *medias = NULL;
static GList *players = NULL;
static GList *folders = NULL;
static GList *items = NULL;
+static GList *endpoints = NULL;
+static GList *local_endpoints = NULL;
+
+static void endpoint_unregister(void *data)
+{
+ struct endpoint *ep = data;
+
+ bt_shell_printf("Endpoint %s unregistered\n", ep->path);
+ g_dbus_unregister_interface(dbus_conn, ep->path,
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+}
+
+static void disconnect_handler(DBusConnection *connection, void *user_data)
+{
+ g_list_free_full(local_endpoints, endpoint_unregister);
+ local_endpoints = NULL;
+}

static bool check_default_player(void)
{
@@ -63,6 +105,9 @@ static char *generic_generator(const char *text, int state, GList *source)
{
static int index = 0;

+ if (!source)
+ return NULL;
+
if (!state)
index = 0;

@@ -319,11 +364,10 @@ static void generic_callback(const DBusError *error, void *user_data)
if (dbus_error_is_set(error)) {
bt_shell_printf("Failed to set %s: %s\n", str, error->name);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ } else {
+ bt_shell_printf("Changing %s succeeded\n", str);
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
-
- bt_shell_printf("Changing %s succeeded\n", str);
-
- return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}

static void cmd_equalizer(int argc, char *argv[])
@@ -450,6 +494,17 @@ static char *proxy_description(GDBusProxy *proxy, const char *title,
title, path);
}

+static void print_media(GDBusProxy *proxy, const char *description)
+{
+ char *str;
+
+ str = proxy_description(proxy, "Media", description);
+
+ bt_shell_printf("%s\n", str);
+
+ g_free(str);
+}
+
static void print_player(GDBusProxy *proxy, const char *description)
{
char *str;
@@ -481,6 +536,7 @@ static void print_iter(const char *label, const char *name,
dbus_uint32_t valu32;
dbus_uint16_t valu16;
dbus_int16_t vals16;
+ unsigned char byte;
const char *valstr;
DBusMessageIter subiter;

@@ -515,6 +571,10 @@ static void print_iter(const char *label, const char *name,
dbus_message_iter_get_basic(iter, &vals16);
bt_shell_printf("%s%s: %d\n", label, name, vals16);
break;
+ case DBUS_TYPE_BYTE:
+ dbus_message_iter_get_basic(iter, &byte);
+ bt_shell_printf("%s%s: 0x%02x (%d)\n", label, name, byte, byte);
+ break;
case DBUS_TYPE_VARIANT:
dbus_message_iter_recurse(iter, &subiter);
print_iter(label, name, &subiter);
@@ -948,6 +1008,1081 @@ static const struct bt_shell_menu player_menu = {
{} },
};

+static char *endpoint_generator(const char *text, int state)
+{
+ return generic_generator(text, state, endpoints);
+}
+
+static char *local_endpoint_generator(const char *text, int state)
+{
+ int len = strlen(text);
+ GList *l;
+ static int index = 0;
+
+ if (!state)
+ index = 0;
+
+ for (l = g_list_nth(local_endpoints, index); l; l = g_list_next(l)) {
+ struct endpoint *ep = l->data;
+
+ index++;
+
+ if (!strncasecmp(ep->path, text, len))
+ return strdup(ep->path);
+ }
+
+ return NULL;
+}
+
+static void print_endpoint(void *data, void *user_data)
+{
+ GDBusProxy *proxy = data;
+ const char *description = user_data;
+ char *str;
+
+ str = proxy_description(proxy, "Endpoint", description);
+
+ bt_shell_printf("%s\n", str);
+
+ g_free(str);
+}
+
+static void cmd_list_endpoints(int argc, char *argv[])
+{
+ GList *l;
+
+ if (argc > 1) {
+ if (strcmp("local", argv[1])) {
+ bt_shell_printf("Endpoint list %s not available\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+ }
+
+ for (l = local_endpoints; l; l = g_list_next(l)) {
+ struct endpoint *ep = l->data;
+
+ bt_shell_printf("%s\n", ep->path);
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+ }
+
+ for (l = endpoints; l; l = g_list_next(l)) {
+ GDBusProxy *proxy = l->data;
+ print_endpoint(proxy, NULL);
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void confirm_response(const char *input, void *user_data)
+{
+ DBusMessage *msg = user_data;
+
+ if (!strcasecmp(input, "y") || !strcasecmp(input, "yes"))
+ g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
+ else if (!strcasecmp(input, "n") || !strcmp(input, "no"))
+ g_dbus_send_error(dbus_conn, msg, "org.bluez.Error.Rejected",
+ NULL);
+ else
+ g_dbus_send_error(dbus_conn, msg, "org.bluez.Error.Canceled",
+ NULL);
+}
+
+static DBusMessage *endpoint_set_configuration(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct endpoint *ep = user_data;
+ DBusMessageIter args, props;
+ const char *path;
+
+ dbus_message_iter_init(msg, &args);
+
+ dbus_message_iter_get_basic(&args, &path);
+ dbus_message_iter_next(&args);
+
+ dbus_message_iter_recurse(&args, &props);
+ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+ return g_dbus_create_error(msg,
+ "org.bluez.Error.InvalidArguments",
+ NULL);
+
+ bt_shell_printf("Endpoint: SetConfiguration\n");
+ bt_shell_printf("\tTransport %s\n", path);
+ print_iter("\t", "Properties", &props);
+
+ free(ep->transport);
+ ep->transport = strdup(path);
+
+ if (ep->auto_accept) {
+ bt_shell_printf("Auto Accepting...\n");
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+ }
+
+ bt_shell_prompt_input("Endpoint", "Accept (yes/no):", confirm_response,
+ dbus_message_ref(msg));
+
+ return NULL;
+}
+
+struct codec_capabilities {
+ uint8_t len;
+ uint8_t type;
+ uint8_t data[UINT8_MAX];
+};
+
+#define data(args...) ((const unsigned char[]) { args })
+
+#define SBC_DATA(args...) \
+ { \
+ .iov_base = (void *)data(args), \
+ .iov_len = sizeof(data(args)), \
+ }
+
+#define CODEC_CAPABILITIES(_uuid, _codec_id, _data) \
+ { \
+ .uuid = _uuid, \
+ .codec_id = _codec_id, \
+ .data = _data, \
+ }
+
+static const struct capabilities {
+ const char *uuid;
+ uint8_t codec_id;
+ struct iovec data;
+} caps[] = {
+ /* A2DP SBC Source:
+ *
+ * Channel Modes: Mono DualChannel Stereo JointStereo
+ * Frequencies: 16Khz 32Khz 44.1Khz 48Khz
+ * Subbands: 4 8
+ * Blocks: 4 8 12 16
+ * Bitpool Range: 2-64
+ */
+ CODEC_CAPABILITIES(A2DP_SOURCE_UUID, A2DP_CODEC_SBC,
+ SBC_DATA(0xff, 0xff, 2, 64)),
+ /* A2DP SBC Sink:
+ *
+ * Channel Modes: Mono DualChannel Stereo JointStereo
+ * Frequencies: 16Khz 32Khz 44.1Khz 48Khz
+ * Subbands: 4 8
+ * Blocks: 4 8 12 16
+ * Bitpool Range: 2-64
+ */
+ CODEC_CAPABILITIES(A2DP_SINK_UUID, A2DP_CODEC_SBC,
+ SBC_DATA(0xff, 0xff, 2, 64)),
+};
+
+struct codec_preset {
+ const char *name;
+ const struct iovec data;
+ bool is_default;
+};
+
+#define SBC_PRESET(_name, _data) \
+ { \
+ .name = _name, \
+ .data = _data, \
+ }
+
+#define SBC_DEFAULT_PRESET(_name, _data) \
+ { \
+ .name = _name, \
+ .data = _data, \
+ .is_default = true, \
+ }
+
+static struct codec_preset sbc_presets[] = {
+ /* Table 4.7: Recommended sets of SBC parameters in the SRC device
+ * Other settings: Block length = 16, Allocation method = Loudness,
+ * Subbands = 8.
+ * A2DP spec sets maximum bitrates as follows:
+ * This profile limits the available maximum bit rate to 320kb/s for
+ * mono, and 512kb/s for two-channel modes.
+ */
+ SBC_PRESET("MQ_MONO_44_1",
+ SBC_DATA(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)),
+ SBC_PRESET("MQ_MONO_48",
+ SBC_DATA(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)),
+ SBC_PRESET("MQ_STEREO_44_1",
+ SBC_DATA(0x21, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_44100)),
+ SBC_PRESET("MQ_STEREO_48",
+ SBC_DATA(0x11, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_48000)),
+ SBC_PRESET("HQ_MONO_44_1",
+ SBC_DATA(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)),
+ SBC_PRESET("HQ_MONO_48",
+ SBC_DATA(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)),
+ SBC_DEFAULT_PRESET("HQ_STEREO_44_1",
+ SBC_DATA(0x21, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_44100)),
+ SBC_PRESET("HQ_STEREO_48",
+ SBC_DATA(0x11, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_48000)),
+ /* Higher bitrates not recommended by A2DP spec, it dual channel to
+ * avoid going above 53 bitpool:
+ *
+ * https://habr.com/en/post/456476/
+ * https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/1092
+ */
+ SBC_PRESET("XQ_DUAL_44_1", SBC_DATA(0x24, 0x15, 2, 43)),
+ SBC_PRESET("XQ_DUAL_48", SBC_DATA(0x14, 0x15, 2, 39)),
+ /* Ultra high bitpool that fits in 512 kbps mandatory bitrate */
+ SBC_PRESET("UQ_STEREO_44_1", SBC_DATA(0x21, 0x15, 2, 64)),
+ SBC_PRESET("UQ_STEREO_48", SBC_DATA(0x11, 0x15, 2, 58)),
+};
+
+#define PRESET(_uuid, _presets) \
+ { \
+ .uuid = _uuid, \
+ .presets = _presets, \
+ .num_presets = ARRAY_SIZE(_presets), \
+ }
+
+static const struct preset {
+ const char *uuid;
+ struct codec_preset *presets;
+ size_t num_presets;
+} presets[] = {
+ PRESET(A2DP_SOURCE_UUID, sbc_presets),
+ PRESET(A2DP_SINK_UUID, sbc_presets),
+};
+
+static struct codec_preset *find_preset(const char *uuid, const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(presets); i++) {
+ const struct preset *preset = &presets[i];
+
+ if (!strcasecmp(preset->uuid, uuid)) {
+ size_t j;
+
+ for (j = 0; j < preset->num_presets; j++) {
+ struct codec_preset *p;
+
+ p = &preset->presets[j];
+
+ if (!name) {
+ if (p->is_default)
+ return p;
+ } else if (!strcmp(p->name, name))
+ return p;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static DBusMessage *endpoint_select_config_reply(DBusMessage *msg,
+ uint8_t *data, size_t len)
+{
+ DBusMessage *reply;
+ DBusMessageIter args, array;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &args);
+
+ dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING,
+ &array);
+
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &data,
+ len);
+
+ dbus_message_iter_close_container(&args, &array);
+
+ return reply;
+}
+
+static uint8_t *str2bytearray(char *arg, size_t *val_len)
+{
+ uint8_t value[UINT8_MAX];
+ char *entry;
+ unsigned int i;
+
+ for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) {
+ long val;
+ char *endptr = NULL;
+
+ if (*entry == '\0')
+ continue;
+
+ if (i >= G_N_ELEMENTS(value)) {
+ bt_shell_printf("Too much data\n");
+ return NULL;
+ }
+
+ val = strtol(entry, &endptr, 0);
+ if (!endptr || *endptr != '\0' || val > UINT8_MAX) {
+ bt_shell_printf("Invalid value at index %d\n", i);
+ return NULL;
+ }
+
+ value[i] = val;
+ }
+
+ *val_len = i;
+
+ return g_memdup(value, i);
+}
+
+static void select_config_response(const char *input, void *user_data)
+{
+ struct endpoint *ep = user_data;
+ struct codec_preset *p;
+ DBusMessage *reply;
+ uint8_t *data;
+ size_t len;
+
+ p = find_preset(ep->uuid, input);
+ if (p) {
+ data = p->data.iov_base;
+ len = p->data.iov_len;
+ goto done;
+ }
+
+ data = str2bytearray((void *) input, &len);
+ if (!data) {
+ g_dbus_send_error(dbus_conn, ep->msg,
+ "org.bluez.Error.Rejected", NULL);
+ ep->msg = NULL;
+ return;
+ }
+
+done:
+ reply = endpoint_select_config_reply(ep->msg, data, len);
+ if (!reply)
+ return;
+
+ if (!p)
+ free(data);
+
+ g_dbus_send_message(dbus_conn, reply);
+ dbus_message_unref(ep->msg);
+ ep->msg = NULL;
+}
+
+static DBusMessage *endpoint_select_configuration(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct endpoint *ep = user_data;
+ struct codec_preset *p;
+ DBusMessageIter args;
+ DBusMessage *reply;
+
+ dbus_message_iter_init(msg, &args);
+
+ bt_shell_printf("Endpoint: SelectConfiguration\n");
+ print_iter("\t", "Capabilities", &args);
+
+ if (!ep->auto_accept) {
+ ep->msg = dbus_message_ref(msg);
+ bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
+ select_config_response, ep);
+ return NULL;
+ }
+
+ p = find_preset(ep->uuid, NULL);
+ if (!p)
+ NULL;
+
+ reply = endpoint_select_config_reply(msg, p->data.iov_base,
+ p->data.iov_len);
+ if (!reply)
+ return NULL;
+
+ bt_shell_printf("Auto Accepting using %s...\n", p->name);
+
+ return reply;
+}
+
+static DBusMessage *endpoint_clear_configuration(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct endpoint *ep = user_data;
+
+ free(ep->transport);
+ ep->transport = NULL;
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static struct endpoint *endpoint_find(const char *pattern)
+{
+ GList *l;
+
+ for (l = local_endpoints; l; l = g_list_next(l)) {
+ struct endpoint *ep = l->data;
+
+ /* match object path */
+ if (!strcmp(ep->path, pattern))
+ return ep;
+
+ /* match UUID */
+ if (!strcmp(ep->uuid, pattern))
+ return ep;
+ }
+
+ return NULL;
+}
+
+static void cmd_show_endpoint(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ proxy = g_dbus_proxy_lookup(endpoints, NULL, argv[1],
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Endpoint %s not found\n", argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+ }
+
+ bt_shell_printf("Endpoint %s\n", g_dbus_proxy_get_path(proxy));
+
+ print_property(proxy, "UUID");
+ print_property(proxy, "Codec");
+ print_property(proxy, "Capabilities");
+ print_property(proxy, "Device");
+ print_property(proxy, "DelayReporting");
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const GDBusMethodTable endpoint_methods[] = {
+ { GDBUS_ASYNC_METHOD("SetConfiguration",
+ GDBUS_ARGS({ "endpoint", "o" },
+ { "properties", "a{sv}" } ),
+ NULL, endpoint_set_configuration) },
+ { GDBUS_ASYNC_METHOD("SelectConfiguration",
+ GDBUS_ARGS({ "caps", "ay" } ),
+ NULL, endpoint_select_configuration) },
+ { GDBUS_ASYNC_METHOD("ClearConfiguration",
+ GDBUS_ARGS({ "transport", "o" } ),
+ NULL, endpoint_clear_configuration) },
+ { },
+};
+
+static void endpoint_free(void *data)
+{
+ struct endpoint *ep = data;
+
+ if (ep->caps) {
+ g_free(ep->caps->iov_base);
+ g_free(ep->caps);
+ }
+
+ if (ep->msg)
+ dbus_message_unref(ep->msg);
+
+ g_free(ep->path);
+ g_free(ep->uuid);
+ g_free(ep);
+}
+
+static gboolean endpoint_get_uuid(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct endpoint *ep = data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ep->uuid);
+
+ return TRUE;
+}
+
+static gboolean endpoint_get_codec(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct endpoint *ep = data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &ep->codec);
+
+ return TRUE;
+}
+
+static gboolean endpoint_get_capabilities(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct endpoint *ep = data;
+ DBusMessageIter array;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING, &array);
+
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
+ &ep->caps->iov_base,
+ ep->caps->iov_len);
+
+ dbus_message_iter_close_container(iter, &array);
+
+ return TRUE;
+}
+
+static const GDBusPropertyTable endpoint_properties[] = {
+ { "UUID", "s", endpoint_get_uuid, NULL, NULL },
+ { "Codec", "y", endpoint_get_codec, NULL, NULL },
+ { "Capabilities", "ay", endpoint_get_capabilities, NULL, NULL },
+ { }
+};
+
+static void register_endpoint_setup(DBusMessageIter *iter, void *user_data)
+{
+ struct endpoint *ep = user_data;
+ DBusMessageIter dict;
+ const char *key = "Capabilities";
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &ep->path);
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+ g_dbus_dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &ep->uuid);
+
+ g_dbus_dict_append_entry(&dict, "Codec", DBUS_TYPE_BYTE, &ep->codec);
+
+ g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key,
+ DBUS_TYPE_BYTE, &ep->caps->iov_base,
+ ep->caps->iov_len);
+
+ bt_shell_printf("Capabilities:\n");
+ bt_shell_hexdump(ep->caps->iov_base, ep->caps->iov_len);
+
+ dbus_message_iter_close_container(iter, &dict);
+}
+
+static void register_endpoint_reply(DBusMessage *message, void *user_data)
+{
+ struct endpoint *ep = user_data;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message)) {
+ bt_shell_printf("Failed to register endpoint: %s\n",
+ error.name);
+ dbus_error_free(&error);
+ local_endpoints = g_list_remove(local_endpoints, ep);
+ g_dbus_unregister_interface(dbus_conn, ep->path,
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Endpoint %s registered\n", ep->path);
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void endpoint_register(struct endpoint *ep)
+{
+ GList *l;
+
+ if (!g_dbus_register_interface(dbus_conn, ep->path,
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE,
+ endpoint_methods, NULL,
+ endpoint_properties, ep,
+ endpoint_free)) {
+ goto fail;
+ }
+
+ for (l = medias; l; l = g_list_next(l)) {
+ if (!g_dbus_proxy_method_call(l->data, "RegisterEndpoint",
+ register_endpoint_setup,
+ register_endpoint_reply,
+ ep, NULL)) {
+ g_dbus_unregister_interface(dbus_conn, ep->path,
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+ goto fail;
+ }
+ }
+
+ return;
+
+fail:
+ bt_shell_printf("Failed register endpoint\n");
+ local_endpoints = g_list_remove(local_endpoints, ep);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+}
+
+static void endpoint_auto_accept(const char *input, void *user_data)
+{
+ struct endpoint *ep = user_data;
+
+ if (!strcasecmp(input, "y") || !strcasecmp(input, "yes"))
+ ep->auto_accept = true;
+ else if (!strcasecmp(input, "n") || !strcasecmp(input, "no"))
+ ep->auto_accept = false;
+ else
+ bt_shell_printf("Invalid input for Auto Accept\n");
+
+ endpoint_register(ep);
+}
+
+static void endpoint_set_capabilities(const char *input, void *user_data)
+{
+ struct endpoint *ep = user_data;
+
+ if (ep->caps)
+ g_free(ep->caps->iov_base);
+ else
+ ep->caps = g_new0(struct iovec, 1);
+
+ ep->caps->iov_base = str2bytearray((char *) input, &ep->caps->iov_len);
+
+ bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):",
+ endpoint_auto_accept, ep);
+}
+
+static char *uuid_generator(const char *text, int state)
+{
+ int len = strlen(text);
+ static int index = 0;
+ size_t i;
+
+ if (!state)
+ index = 0;
+
+ for (i = index; i < ARRAY_SIZE(caps); i++) {
+ const struct capabilities *cap = &caps[i];
+
+ index++;
+
+ if (!strncasecmp(cap->uuid, text, len))
+ return strdup(cap->uuid);
+ }
+
+ return NULL;
+}
+
+static const struct capabilities *find_capabilities(const char *uuid,
+ uint8_t codec_id)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(caps); i++) {
+ const struct capabilities *cap = &caps[i];
+
+ if (strcasecmp(cap->uuid, uuid))
+ continue;
+
+ if (cap->codec_id == codec_id)
+ return cap;
+ }
+
+ return NULL;
+}
+
+static struct iovec *iov_append(struct iovec **iov, const void *data,
+ size_t len)
+{
+ if (!*iov) {
+ *iov = new0(struct iovec, 1);
+ (*iov)->iov_base = new0(uint8_t, UINT8_MAX);
+ }
+
+ if (data && len) {
+ memcpy((*iov)->iov_base + (*iov)->iov_len, data, len);
+ (*iov)->iov_len += len;
+ }
+
+ return *iov;
+}
+
+static void cmd_register_endpoint(int argc, char *argv[])
+{
+ struct endpoint *ep;
+ char *endptr = NULL;
+
+ ep = g_new0(struct endpoint, 1);
+ ep->uuid = g_strdup(argv[1]);
+ ep->codec = strtol(argv[2], &endptr, 0);
+ ep->path = g_strdup_printf("%s/ep%u", BLUEZ_MEDIA_ENDPOINT_PATH,
+ g_list_length(local_endpoints));
+ local_endpoints = g_list_append(local_endpoints, ep);
+
+ if (argc > 3)
+ endpoint_set_capabilities(argv[3], ep);
+ else {
+ const struct capabilities *cap;
+
+ cap = find_capabilities(ep->uuid, ep->codec);
+ if (cap) {
+ if (ep->caps)
+ ep->caps->iov_len = 0;
+
+ /* Copy capabilities */
+ iov_append(&ep->caps, cap->data.iov_base,
+ cap->data.iov_len);
+
+ bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):",
+ endpoint_auto_accept, ep);
+ } else
+ bt_shell_prompt_input(ep->path, "Enter capabilities:",
+ endpoint_set_capabilities, ep);
+ }
+}
+
+static void unregister_endpoint_setup(DBusMessageIter *iter, void *user_data)
+{
+ struct endpoint *ep = user_data;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &ep->path);
+}
+
+static void unregister_endpoint_reply(DBusMessage *message, void *user_data)
+{
+ struct endpoint *ep = user_data;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message)) {
+ bt_shell_printf("Failed to unregister endpoint: %s\n",
+ error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Endpoint %s unregistered\n", ep->path);
+
+ local_endpoints = g_list_remove(local_endpoints, ep);
+ g_dbus_unregister_interface(dbus_conn, ep->path,
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_unregister_endpoint(int argc, char *argv[])
+{
+ struct endpoint *ep;
+ GList *l;
+
+ ep = endpoint_find(argv[1]);
+ if (!ep) {
+ bt_shell_printf("Unable to find endpoint object: %s\n",
+ argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ for (l = medias; l; l = g_list_next(l)) {
+ if (!g_dbus_proxy_method_call(l->data, "UnregisterEndpoint",
+ unregister_endpoint_setup,
+ unregister_endpoint_reply,
+ ep, NULL)) {
+ bt_shell_printf("Failed unregister endpoint\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+struct codec_qos {
+ uint32_t interval;
+ uint8_t framing;
+ char *phy;
+ uint16_t sdu;
+ uint8_t rtn;
+ uint16_t latency;
+ uint32_t delay;
+};
+
+struct endpoint_config {
+ GDBusProxy *proxy;
+ struct endpoint *ep;
+ struct iovec *caps;
+ struct codec_qos qos;
+};
+
+static void config_endpoint_setup(DBusMessageIter *iter, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+ DBusMessageIter dict;
+ const char *key = "Capabilities";
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
+ &cfg->ep->path);
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+ bt_shell_printf("Capabilities: ");
+ bt_shell_hexdump(cfg->caps->iov_base, cfg->caps->iov_len);
+
+ g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key,
+ DBUS_TYPE_BYTE, &cfg->caps->iov_base,
+ cfg->caps->iov_len);
+
+ bt_shell_printf("Interval %u\n", cfg->qos.interval);
+
+ g_dbus_dict_append_entry(&dict, "Interval", DBUS_TYPE_UINT32,
+ &cfg->qos.interval);
+
+ bt_shell_printf("Framing %s\n", cfg->qos.framing ? "true" : "false");
+
+ g_dbus_dict_append_entry(&dict, "Framing", DBUS_TYPE_BOOLEAN,
+ &cfg->qos.framing);
+
+ bt_shell_printf("PHY %s\n", cfg->qos.phy);
+
+ g_dbus_dict_append_entry(&dict, "PHY", DBUS_TYPE_STRING, &cfg->qos.phy);
+
+ bt_shell_printf("SDU %u\n", cfg->qos.sdu);
+
+ g_dbus_dict_append_entry(&dict, "SDU", DBUS_TYPE_UINT16,
+ &cfg->qos.sdu);
+
+ bt_shell_printf("Retransmissions %u\n", cfg->qos.rtn);
+
+ g_dbus_dict_append_entry(&dict, "Retransmissions", DBUS_TYPE_BYTE,
+ &cfg->qos.rtn);
+
+ bt_shell_printf("Latency %u\n", cfg->qos.latency);
+
+ g_dbus_dict_append_entry(&dict, "Latency", DBUS_TYPE_UINT16,
+ &cfg->qos.latency);
+
+ bt_shell_printf("Delay %u\n", cfg->qos.delay);
+
+ g_dbus_dict_append_entry(&dict, "Delay", DBUS_TYPE_UINT32,
+ &cfg->qos.delay);
+
+ dbus_message_iter_close_container(iter, &dict);
+}
+
+static void config_endpoint_reply(DBusMessage *message, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+ struct endpoint *ep = cfg->ep;
+ DBusError error;
+
+ free(cfg->caps->iov_base);
+ free(cfg->caps);
+ free(cfg);
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message)) {
+ bt_shell_printf("Failed to config endpoint: %s\n",
+ error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Endpoint %s configured\n", ep->path);
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void endpoint_set_config(struct endpoint_config *cfg)
+{
+ if (!g_dbus_proxy_method_call(cfg->proxy, "SetConfiguration",
+ config_endpoint_setup,
+ config_endpoint_reply,
+ cfg, NULL)) {
+ bt_shell_printf("Failed to config endpoint\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+}
+
+static void qos_delay(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.delay = strtol(input, NULL, 0);
+
+ endpoint_set_config(cfg);
+}
+
+static void qos_latency(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.latency = strtol(input, NULL, 0);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter Delay:", qos_delay, cfg);
+}
+
+static void qos_rtn(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.rtn = strtol(input, NULL, 0);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter Latency:",
+ qos_latency, cfg);
+}
+
+static void qos_sdu(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.sdu = strtol(input, NULL, 0);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter Retransmissions:",
+ qos_rtn, cfg);
+}
+
+static void qos_phy(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.phy = strdup(input);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter SDU:", qos_sdu, cfg);
+}
+
+static void qos_framing(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.framing = strtol(input, NULL, 0);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter PHY:", qos_phy, cfg);
+}
+
+static void qos_interval(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+
+ cfg->qos.interval = strtol(input, NULL, 0);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter Framing:", qos_framing,
+ cfg);
+}
+
+static void endpoint_config(const char *input, void *user_data)
+{
+ struct endpoint_config *cfg = user_data;
+ uint8_t *data;
+ size_t len;
+
+ data = str2bytearray((char *) input, &len);
+
+ iov_append(&cfg->caps, data, len);
+ free(data);
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter Interval:", qos_interval,
+ cfg);
+}
+
+static void cmd_config_endpoint(int argc, char *argv[])
+{
+ struct endpoint_config *cfg;
+ const struct codec_preset *preset;
+
+ cfg = new0(struct endpoint_config, 1);
+
+ cfg->proxy = g_dbus_proxy_lookup(endpoints, NULL, argv[1],
+ BLUEZ_MEDIA_ENDPOINT_INTERFACE);
+ if (!cfg->proxy) {
+ bt_shell_printf("Endpoint %s not found\n", argv[1]);
+ goto fail;
+ }
+
+ cfg->ep = endpoint_find(argv[2]);
+ if (!cfg->ep) {
+ bt_shell_printf("Local Endpoint %s not found\n", argv[2]);
+ goto fail;
+ }
+
+ if (argc > 3) {
+ preset = find_preset(cfg->ep->uuid, argv[3]);
+ if (!preset) {
+ bt_shell_printf("Preset %s not found\n", argv[3]);
+ goto fail;
+ }
+
+ /* Copy capabilities */
+ iov_append(&cfg->caps, preset->data.iov_base,
+ preset->data.iov_len);
+
+ endpoint_set_config(cfg);
+ return;
+ }
+
+ bt_shell_prompt_input(cfg->ep->path, "Enter configuration:",
+ endpoint_config, cfg);
+
+ return;
+
+fail:
+ g_free(cfg);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_presets_endpoint(int argc, char *argv[])
+{
+ size_t i;
+ struct codec_preset *default_preset = NULL;
+
+ if (argc > 2) {
+ default_preset = find_preset(argv[1], argv[2]);
+ if (!default_preset) {
+ bt_shell_printf("Preset %s not found\n", argv[2]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ default_preset->is_default = true;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(presets); i++) {
+ const struct preset *preset = &presets[i];
+
+ if (!strcasecmp(preset->uuid, argv[1])) {
+ size_t j;
+
+ for (j = 0; j < preset->num_presets; j++) {
+ struct codec_preset *p;
+
+ p = &preset->presets[j];
+
+ if (default_preset && p != default_preset)
+ p->is_default = false;
+
+ if (p->is_default)
+ bt_shell_printf("*%s\n", p->name);
+ else
+ bt_shell_printf("%s\n", p->name);
+ }
+ }
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const struct bt_shell_menu endpoint_menu = {
+ .name = "endpoint",
+ .desc = "Media Endpoint Submenu",
+ .entries = {
+ { "list", "[local]", cmd_list_endpoints,
+ "List available endpoints" },
+ { "show", "<endpoint>", cmd_show_endpoint,
+ "Endpoint information",
+ endpoint_generator },
+ { "register", "<UUID> <codec> [capabilities...]",
+ cmd_register_endpoint,
+ "Register Endpoint",
+ uuid_generator },
+ { "unregister", "<UUID/object>", cmd_unregister_endpoint,
+ "Register Endpoint",
+ local_endpoint_generator },
+ { "config", "<endpoint> <local endpoint> [preset]",
+ cmd_config_endpoint,
+ "Configure Endpoint",
+ endpoint_generator },
+ { "presets", "<UUID> [default]", cmd_presets_endpoint,
+ "List available presets",
+ uuid_generator },
+ {} },
+};
+
+static void media_added(GDBusProxy *proxy)
+{
+ medias = g_list_append(medias, proxy);
+
+ print_media(proxy, COLORED_NEW);
+}
+
static void player_added(GDBusProxy *proxy)
{
players = g_list_append(players, proxy);
@@ -1002,18 +2137,36 @@ static void item_added(GDBusProxy *proxy)
print_item(proxy, COLORED_NEW);
}

+static void endpoint_added(GDBusProxy *proxy)
+{
+ endpoints = g_list_append(endpoints, proxy);
+
+ print_endpoint(proxy, COLORED_NEW);
+}
+
static void proxy_added(GDBusProxy *proxy, void *user_data)
{
const char *interface;

interface = g_dbus_proxy_get_interface(proxy);

- if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
+ if (!strcmp(interface, BLUEZ_MEDIA_INTERFACE))
+ media_added(proxy);
+ else if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
player_added(proxy);
else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE))
folder_added(proxy);
else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
item_added(proxy);
+ else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+ endpoint_added(proxy);
+}
+
+static void media_removed(GDBusProxy *proxy)
+{
+ print_media(proxy, COLORED_DEL);
+
+ medias = g_list_remove(medias, proxy);
}

static void player_removed(GDBusProxy *proxy)
@@ -1040,18 +2193,29 @@ static void item_removed(GDBusProxy *proxy)
print_item(proxy, COLORED_DEL);
}

+static void endpoint_removed(GDBusProxy *proxy)
+{
+ endpoints = g_list_remove(endpoints, proxy);
+
+ print_endpoint(proxy, COLORED_DEL);
+}
+
static void proxy_removed(GDBusProxy *proxy, void *user_data)
{
const char *interface;

interface = g_dbus_proxy_get_interface(proxy);

+ if (!strcmp(interface, BLUEZ_MEDIA_INTERFACE))
+ media_removed(proxy);
if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
player_removed(proxy);
if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE))
folder_removed(proxy);
if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
item_removed(proxy);
+ if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+ endpoint_removed(proxy);
}

static void player_property_changed(GDBusProxy *proxy, const char *name,
@@ -1084,6 +2248,16 @@ static void item_property_changed(GDBusProxy *proxy, const char *name,
g_free(str);
}

+static void endpoint_property_changed(GDBusProxy *proxy, const char *name,
+ DBusMessageIter *iter)
+{
+ char *str;
+
+ str = proxy_description(proxy, "Endpoint", COLORED_CHG);
+ print_iter(str, name, iter);
+ g_free(str);
+}
+
static void property_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter, void *user_data)
{
@@ -1097,6 +2271,8 @@ static void property_changed(GDBusProxy *proxy, const char *name,
folder_property_changed(proxy, name, iter);
else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
item_property_changed(proxy, name, iter);
+ else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
+ endpoint_property_changed(proxy, name, iter);
}

static GDBusClient *client;
@@ -1104,6 +2280,7 @@ static GDBusClient *client;
void player_add_submenu(void)
{
bt_shell_add_submenu(&player_menu);
+ bt_shell_add_submenu(&endpoint_menu);

dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
if (!dbus_conn || client)
@@ -1113,6 +2290,7 @@ void player_add_submenu(void)

g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
property_changed, NULL);
+ g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
}

void player_remove_submenu(void)
--
2.35.1


2022-04-14 14:11:29

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH v2 BlueZ 2/2] client/player: Add transport menu

From: Luiz Augusto von Dentz <[email protected]>

This adds transport menu:

[bluetooth]# menu transport
Menu transport:
Available commands:
-------------------
list List available transports
show <transport> Transport information
acquire <transport> Acquire Transport
release <transport> Release Transport
send <filename> Send contents of a file
---
client/player.c | 385 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 384 insertions(+), 1 deletion(-)

diff --git a/client/player.c b/client/player.c
index 5db35cfeb..bcd12d594 100644
--- a/client/player.c
+++ b/client/player.c
@@ -50,6 +50,7 @@
#define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
#define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
#define BLUEZ_MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+#define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"

#define BLUEZ_MEDIA_ENDPOINT_PATH "/local/endpoint"

@@ -75,6 +76,16 @@ static GList *folders = NULL;
static GList *items = NULL;
static GList *endpoints = NULL;
static GList *local_endpoints = NULL;
+static GList *transports = NULL;
+
+struct transport {
+ int sk;
+ int mtu[2];
+ struct io *io;
+ uint32_t seq;
+} transport = {
+ .sk = -1,
+};

static void endpoint_unregister(void *data)
{
@@ -1639,8 +1650,9 @@ static char *uuid_generator(const char *text, int state)
static int index = 0;
size_t i;

- if (!state)
+ if (!state) {
index = 0;
+ }

for (i = index; i < ARRAY_SIZE(caps); i++) {
const struct capabilities *cap = &caps[i];
@@ -2144,6 +2156,26 @@ static void endpoint_added(GDBusProxy *proxy)
print_endpoint(proxy, COLORED_NEW);
}

+static void print_transport(void *data, void *user_data)
+{
+ GDBusProxy *proxy = data;
+ const char *description = user_data;
+ char *str;
+
+ str = proxy_description(proxy, "Transport", description);
+
+ bt_shell_printf("%s\n", str);
+
+ g_free(str);
+}
+
+static void transport_added(GDBusProxy *proxy)
+{
+ transports = g_list_append(transports, proxy);
+
+ print_transport(proxy, COLORED_NEW);
+}
+
static void proxy_added(GDBusProxy *proxy, void *user_data)
{
const char *interface;
@@ -2160,6 +2192,8 @@ static void proxy_added(GDBusProxy *proxy, void *user_data)
item_added(proxy);
else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
endpoint_added(proxy);
+ else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
+ transport_added(proxy);
}

static void media_removed(GDBusProxy *proxy)
@@ -2200,6 +2234,13 @@ static void endpoint_removed(GDBusProxy *proxy)
print_endpoint(proxy, COLORED_DEL);
}

+static void transport_removed(GDBusProxy *proxy)
+{
+ transports = g_list_remove(transports, proxy);
+
+ print_transport(proxy, COLORED_DEL);
+}
+
static void proxy_removed(GDBusProxy *proxy, void *user_data)
{
const char *interface;
@@ -2216,6 +2257,8 @@ static void proxy_removed(GDBusProxy *proxy, void *user_data)
item_removed(proxy);
if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
endpoint_removed(proxy);
+ if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
+ transport_removed(proxy);
}

static void player_property_changed(GDBusProxy *proxy, const char *name,
@@ -2258,6 +2301,134 @@ static void endpoint_property_changed(GDBusProxy *proxy, const char *name,
g_free(str);
}

+static struct endpoint *find_ep_by_transport(const char *path)
+{
+ GList *l;
+
+ for (l = local_endpoints; l; l = g_list_next(l)) {
+ struct endpoint *ep = l->data;
+
+ if (ep->transport && !strcmp(ep->transport, path))
+ return ep;
+ }
+
+ return NULL;
+}
+
+static bool transport_disconnected(struct io *io, void *user_data)
+{
+ bt_shell_printf("Transport fd disconnected\n");
+
+ io_destroy(transport.io);
+ transport.io = NULL;
+ transport.sk = -1;
+
+ return false;
+}
+
+static bool transport_recv(struct io *io, void *user_data)
+{
+ uint8_t buf[1024];
+ int ret;
+
+ ret = read(io_get_fd(io), buf, sizeof(buf));
+ if (ret < 0) {
+ bt_shell_printf("Failed to read: %s (%d)\n", strerror(errno),
+ -errno);
+ return true;
+ }
+
+ bt_shell_printf("[seq %d] recv: %u bytes\n", transport.seq, ret);
+
+ transport.seq++;
+
+ return true;
+}
+
+static void acquire_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to acquire: %s\n", error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ if (!dbus_message_get_args(message, &error,
+ DBUS_TYPE_UNIX_FD, &transport.sk,
+ DBUS_TYPE_UINT16, &transport.mtu[0],
+ DBUS_TYPE_UINT16, &transport.mtu[1],
+ DBUS_TYPE_INVALID)) {
+ bt_shell_printf("Failed to parse Acquire() reply: %s",
+ error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Acquire successful: fd %d MTU %d:%d\n", transport.sk,
+ transport.mtu[0], transport.mtu[1]);
+
+ io_destroy(transport.io);
+ transport.io = io_new(transport.sk);
+
+ io_set_disconnect_handler(transport.io, transport_disconnected, NULL,
+ NULL);
+ io_set_read_handler(transport.io, transport_recv, NULL, NULL);
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void transport_acquire(const char *input, void *user_data)
+{
+ GDBusProxy *proxy = user_data;
+
+ if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) {
+ if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
+ acquire_reply, NULL, NULL))
+ bt_shell_printf("Failed acquire transport\n");
+ }
+}
+
+static void transport_property_changed(GDBusProxy *proxy, const char *name,
+ DBusMessageIter *iter)
+{
+ char *str;
+ struct endpoint *ep;
+
+ str = proxy_description(proxy, "Transport", COLORED_CHG);
+ print_iter(str, name, iter);
+ g_free(str);
+
+ if (strcmp(name, "State"))
+ return;
+
+ dbus_message_iter_get_basic(iter, &str);
+
+ if (strcmp(str, "pending"))
+ return;
+
+ /* Only attempt to acquire if transport is configured with a local
+ * endpoint.
+ */
+ ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy));
+ if (!ep)
+ return;
+
+ if (ep->auto_accept) {
+ bt_shell_printf("Auto Accepting...\n");
+ if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
+ acquire_reply, NULL, NULL))
+ bt_shell_printf("Failed acquire transport\n");
+ return;
+ }
+
+ bt_shell_prompt_input(g_dbus_proxy_get_path(proxy), "Acquire (yes/no):",
+ transport_acquire, proxy);
+}
+
static void property_changed(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter, void *user_data)
{
@@ -2273,14 +2444,226 @@ static void property_changed(GDBusProxy *proxy, const char *name,
item_property_changed(proxy, name, iter);
else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
endpoint_property_changed(proxy, name, iter);
+ else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
+ transport_property_changed(proxy, name, iter);
}

+static char *transport_generator(const char *text, int state)
+{
+ return generic_generator(text, state, transports);
+}
+
+static void cmd_list_transport(int argc, char *argv[])
+{
+ GList *l;
+
+ for (l = transports; l; l = g_list_next(l)) {
+ GDBusProxy *proxy = l->data;
+ print_transport(proxy, NULL);
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_show_transport(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+ BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Transport %s not found\n", argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Transport %s\n", g_dbus_proxy_get_path(proxy));
+
+ print_property(proxy, "UUID");
+ print_property(proxy, "Codec");
+ print_property(proxy, "Configuration");
+ print_property(proxy, "Device");
+ print_property(proxy, "State");
+ print_property(proxy, "Delay");
+ print_property(proxy, "Volume");
+ print_property(proxy, "Endpoint");
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_acquire_transport(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (transport.sk >= 0) {
+ bt_shell_printf("Transport socked %d already acquired\n",
+ transport.sk);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+ BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Transport %s not found\n", argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
+ acquire_reply, NULL, NULL)) {
+ bt_shell_printf("Failed acquire transport\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void release_reply(DBusMessage *message, void *user_data)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (dbus_set_error_from_message(&error, message) == TRUE) {
+ bt_shell_printf("Failed to release: %s\n", error.name);
+ dbus_error_free(&error);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ close(transport.sk);
+ transport.sk = -1;
+
+ bt_shell_printf("Release successful\n");
+
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+}
+
+static void cmd_release_transport(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+
+ if (transport.sk < 0) {
+ bt_shell_printf("No Transport Socked found\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+ BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Transport %s not found\n", argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ if (!g_dbus_proxy_method_call(proxy, "Release", NULL,
+ release_reply, NULL, NULL)) {
+ bt_shell_printf("Failed release transport\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static int open_file(const char *filename)
+{
+ int fd = -1;
+
+ bt_shell_printf("Opening %s ...\n", filename);
+
+ fd = open(filename, O_RDONLY);
+ if (fd <= 0)
+ bt_shell_printf("Can't open file %s: %s\n", filename,
+ strerror(errno));
+
+ return fd;
+}
+
+static int transport_send(int fd)
+{
+ uint8_t *buf;
+
+ buf = malloc(transport.mtu[1]);
+ if (!buf) {
+ bt_shell_printf("malloc: %s (%d)", strerror(errno), errno);
+ return -ENOMEM;
+ }
+
+ for (transport.seq = 0; ; transport.seq++) {
+ ssize_t ret;
+ int queued;
+
+ ret = read(fd, buf, transport.mtu[1]);
+ if (ret <= 0) {
+ if (ret < 0)
+ bt_shell_printf("read failed: %s (%d)",
+ strerror(errno), errno);
+ close(fd);
+ return ret;
+ }
+
+ ret = send(transport.sk, buf, ret, 0);
+ if (ret <= 0) {
+ bt_shell_printf("Send failed: %s (%d)",
+ strerror(errno), errno);
+ return -errno;
+ }
+
+ ioctl(transport.sk, TIOCOUTQ, &queued);
+
+ bt_shell_printf("[seq %d] send: %zd bytes "
+ "(TIOCOUTQ %d bytes)\n",
+ transport.seq, ret, queued);
+ }
+
+ free(buf);
+}
+
+static void cmd_send_transport(int argc, char *argv[])
+{
+ int fd, err;
+
+ if (transport.sk < 0) {
+ bt_shell_printf("No Transport Socked found\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ fd = open_file(argv[1]);
+
+ bt_shell_printf("Sending ...\n");
+ err = transport_send(fd);
+
+ close(fd);
+
+ if (err < 0)
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static const struct bt_shell_menu transport_menu = {
+ .name = "transport",
+ .desc = "Media Transport Submenu",
+ .entries = {
+ { "list", NULL, cmd_list_transport,
+ "List available transports" },
+ { "show", "<transport>", cmd_show_transport,
+ "Transport information",
+ transport_generator },
+ { "acquire", "<transport>", cmd_acquire_transport,
+ "Acquire Transport",
+ transport_generator },
+ { "release", "<transport>", cmd_release_transport,
+ "Release Transport",
+ transport_generator },
+ { "send", "<filename>", cmd_send_transport,
+ "Send contents of a file" },
+ {} },
+};
+
static GDBusClient *client;

void player_add_submenu(void)
{
bt_shell_add_submenu(&player_menu);
bt_shell_add_submenu(&endpoint_menu);
+ bt_shell_add_submenu(&transport_menu);

dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
if (!dbus_conn || client)
--
2.35.1

2022-04-16 01:53:35

by bluez.test.bot

[permalink] [raw]
Subject: RE: [v2,BlueZ,1/2] client/player: Add endpoint menu

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=632031

---Test result---

Test Summary:
CheckPatch FAIL 2.97 seconds
GitLint PASS 0.91 seconds
Prep - Setup ELL PASS 41.43 seconds
Build - Prep PASS 0.45 seconds
Build - Configure PASS 8.12 seconds
Build - Make PASS 1247.20 seconds
Make Check PASS 11.23 seconds
Make Check w/Valgrind PASS 435.67 seconds
Make Distcheck PASS 223.18 seconds
Build w/ext ELL - Configure PASS 8.22 seconds
Build w/ext ELL - Make PASS 1229.19 seconds
Incremental Build with patchesPASS 2507.39 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script with rule in .checkpatch.conf
Output:
[v2,BlueZ,1/2] client/player: Add endpoint menu
ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#152: FILE: client/player.c:72:
+static GList *medias = NULL;

ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#156: FILE: client/player.c:76:
+static GList *endpoints = NULL;

ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#157: FILE: client/player.c:77:
+static GList *local_endpoints = NULL;

ERROR:INITIALISED_STATIC: do not initialise statics to 0
#251: FILE: client/player.c:1020:
+ static int index = 0;

WARNING:LINE_SPACING: Missing a blank line after declarations
#303: FILE: client/player.c:1072:
+ GDBusProxy *proxy = l->data;
+ print_endpoint(proxy, NULL);

ERROR:SPACING: space prohibited before that close parenthesis ')'
#687: FILE: client/player.c:1456:
+ { "properties", "a{sv}" } ),

ERROR:SPACING: space prohibited before that close parenthesis ')'
#690: FILE: client/player.c:1459:
+ GDBUS_ARGS({ "caps", "ay" } ),

ERROR:SPACING: space prohibited before that close parenthesis ')'
#693: FILE: client/player.c:1462:
+ GDBUS_ARGS({ "transport", "o" } ),

ERROR:INITIALISED_STATIC: do not initialise statics to 0
#870: FILE: client/player.c:1639:
+ static int index = 0;

/github/workspace/src/12812703.patch total: 8 errors, 1 warnings, 1314 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/12812703.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.

[v2,BlueZ,2/2] client/player: Add transport menu
ERROR:INITIALISED_STATIC: do not initialise statics to NULL
#113: FILE: client/player.c:79:
+static GList *transports = NULL;

WARNING:LINE_SPACING: Missing a blank line after declarations
#350: FILE: client/player.c:2462:
+ GDBusProxy *proxy = l->data;
+ print_transport(proxy, NULL);

/github/workspace/src/12812704.patch total: 1 errors, 1 warnings, 448 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/12812704.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.




---
Regards,
Linux Bluetooth

2022-04-19 08:49:54

by patchwork-bot+bluetooth

[permalink] [raw]
Subject: Re: [PATCH v2 BlueZ 1/2] client/player: Add endpoint menu

Hello:

This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <[email protected]>:

On Wed, 13 Apr 2022 14:16:29 -0700 you wrote:
> From: Luiz Augusto von Dentz <[email protected]>
>
> This adds endpoint menu:
>
> [bluetooth]# menu endpoint
> Menu endpoint:
> Available commands:
>
> [...]

Here is the summary with links:
- [v2,BlueZ,1/2] client/player: Add endpoint menu
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=46f171a86c17
- [v2,BlueZ,2/2] client/player: Add transport menu
(no matching commit)

You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html