blob: 584fc5e8148ac4aee6b83960a67b91016df90a6a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
* Copyright 2023-2024 NXP
*
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <wordexp.h>
#include <sys/timerfd.h>
#include <sys/stat.h>
#include <glib.h>
#include "gdbus/gdbus.h"
#include "lib/bluetooth.h"
#include "lib/uuid.h"
#include "lib/iso.h"
#include "profiles/audio/a2dp-codecs.h"
#include "src/shared/lc3.h"
#include "src/shared/util.h"
#include "src/shared/shell.h"
#include "src/shared/io.h"
#include "src/shared/queue.h"
#include "src/shared/bap-debug.h"
#include "print.h"
#include "player.h"
/* String display constants */
#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF
#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_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"
#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))
#define ROUND_CLOSEST(_x, _y) (((_x) + (_y / 2)) / (_y))
#define EP_SRC_LOCATIONS 0x00000003
#define EP_SNK_LOCATIONS 0x00000003
#define EP_SRC_CTXT 0x000f
#define EP_SUPPORTED_SRC_CTXT EP_SRC_CTXT
#define EP_SNK_CTXT 0x0fff
#define EP_SUPPORTED_SNK_CTXT EP_SNK_CTXT
#if __BYTE_ORDER == __LITTLE_ENDIAN
struct avdtp_media_codec_capability {
uint8_t rfa0:4;
uint8_t media_type:4;
uint8_t media_codec_type;
uint8_t data[0];
} __attribute__ ((packed));
#elif __BYTE_ORDER == __BIG_ENDIAN
struct avdtp_media_codec_capability {
uint8_t media_type:4;
uint8_t rfa0:4;
uint8_t media_codec_type;
uint8_t data[0];
} __attribute__ ((packed));
#else
#error "Unknown byte order"
#endif
#define BCAST_CODE {0x01, 0x02, 0x68, 0x05, 0x53, 0xf1, 0x41, 0x5a, \
0xa2, 0x65, 0xbb, 0xaf, 0xc6, 0xea, 0x03, 0xb8}
struct endpoint {
char *path;
char *uuid;
uint8_t codec;
uint16_t cid;
uint16_t vid;
struct iovec *caps;
struct iovec *meta;
uint32_t locations;
uint16_t supported_context;
uint16_t context;
bool auto_accept;
uint8_t max_transports;
uint8_t iso_group;
uint8_t iso_stream;
struct queue *acquiring;
struct queue *transports;
DBusMessage *msg;
struct preset *preset;
bool broadcast;
struct iovec *bcode;
};
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 GList *transports = NULL;
static struct queue *ios = NULL;
static uint8_t bcast_code[] = BCAST_CODE;
struct transport {
GDBusProxy *proxy;
int sk;
uint16_t mtu[2];
char *filename;
int fd;
struct stat stat;
struct io *io;
uint32_t seq;
struct io *timer_io;
int num;
};
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)
{
if (!default_player) {
bt_shell_printf("No default player available\n");
return FALSE;
}
return TRUE;
}
static char *generic_generator(const char *text, int state, GList *source)
{
static int index = 0;
if (!source)
return NULL;
if (!state)
index = 0;
return g_dbus_proxy_path_lookup(source, &index, text);
}
static char *player_generator(const char *text, int state)
{
return generic_generator(text, state, players);
}
static char *item_generator(const char *text, int state)
{
return generic_generator(text, state, items);
}
static void play_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 play: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Play successful\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
static void cmd_play(int argc, char *argv[])
{
GDBusProxy *proxy;
if (argc > 1) {
proxy = g_dbus_proxy_lookup(items, NULL, argv[1],
BLUEZ_MEDIA_ITEM_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Item %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
} else {
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = default_player;
}
if (g_dbus_proxy_method_call(proxy, "Play", NULL, play_reply,
NULL, NULL) == FALSE) {
bt_shell_printf("Failed to play\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to play %s\n", argv[1] ? : "");
}
static void pause_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 pause: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Pause successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_pause(int argc, char *argv[])
{
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (g_dbus_proxy_method_call(default_player, "Pause", NULL,
pause_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to play\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to pause\n");
}
static void stop_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 stop: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Stop successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_stop(int argc, char *argv[])
{
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (g_dbus_proxy_method_call(default_player, "Stop", NULL, stop_reply,
NULL, NULL) == FALSE) {
bt_shell_printf("Failed to stop\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to stop\n");
}
static void next_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 jump to next: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Next successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_next(int argc, char *argv[])
{
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (g_dbus_proxy_method_call(default_player, "Next", NULL, next_reply,
NULL, NULL) == FALSE) {
bt_shell_printf("Failed to jump to next\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to jump to next\n");
}
static void previous_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 jump to previous: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Previous successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_previous(int argc, char *argv[])
{
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (g_dbus_proxy_method_call(default_player, "Previous", NULL,
previous_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to jump to previous\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to jump to previous\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void fast_forward_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 fast forward: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("FastForward successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_fast_forward(int argc, char *argv[])
{
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (g_dbus_proxy_method_call(default_player, "FastForward", NULL,
fast_forward_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to jump to previous\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Fast forward playback\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void rewind_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 rewind: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Rewind successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_rewind(int argc, char *argv[])
{
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (g_dbus_proxy_method_call(default_player, "Rewind", NULL,
rewind_reply, NULL, NULL) == FALSE) {
bt_shell_printf("Failed to rewind\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Rewind playback\n");
}
static void generic_callback(const DBusError *error, void *user_data)
{
char *str = 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);
}
}
static void cmd_equalizer(int argc, char *argv[])
{
char *value;
DBusMessageIter iter;
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (!g_dbus_proxy_get_property(default_player, "Equalizer", &iter)) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
value = g_strdup(argv[1]);
if (g_dbus_proxy_set_property_basic(default_player, "Equalizer",
DBUS_TYPE_STRING, &value,
generic_callback, value,
g_free) == FALSE) {
bt_shell_printf("Failed to setting equalizer\n");
g_free(value);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to set equalizer\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_repeat(int argc, char *argv[])
{
char *value;
DBusMessageIter iter;
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (!g_dbus_proxy_get_property(default_player, "Repeat", &iter)) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
value = g_strdup(argv[1]);
if (g_dbus_proxy_set_property_basic(default_player, "Repeat",
DBUS_TYPE_STRING, &value,
generic_callback, value,
g_free) == FALSE) {
bt_shell_printf("Failed to set repeat\n");
g_free(value);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to set repeat\n");
}
static void cmd_shuffle(int argc, char *argv[])
{
char *value;
DBusMessageIter iter;
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (!g_dbus_proxy_get_property(default_player, "Shuffle", &iter)) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
value = g_strdup(argv[1]);
if (g_dbus_proxy_set_property_basic(default_player, "Shuffle",
DBUS_TYPE_STRING, &value,
generic_callback, value,
g_free) == FALSE) {
bt_shell_printf("Failed to set shuffle\n");
g_free(value);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to set shuffle\n");
}
static void cmd_scan(int argc, char *argv[])
{
char *value;
DBusMessageIter iter;
if (!check_default_player())
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (!g_dbus_proxy_get_property(default_player, "Shuffle", &iter)) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
value = g_strdup(argv[1]);
if (g_dbus_proxy_set_property_basic(default_player, "Shuffle",
DBUS_TYPE_STRING, &value,
generic_callback, value,
g_free) == FALSE) {
bt_shell_printf("Failed to set scan\n");
g_free(value);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to set scan\n");
}
static char *proxy_description(GDBusProxy *proxy, const char *title,
const char *description)
{
const char *path;
path = g_dbus_proxy_get_path(proxy);
return g_strdup_printf("%s%s%s%s %s ",
description ? "[" : "",
description ? : "",
description ? "] " : "",
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);
print_property(proxy, "SupportedUUIDs");
g_free(str);
}
static void print_player(void *data, void *user_data)
{
GDBusProxy *proxy = data;
const char *description = user_data;
char *str;
str = proxy_description(proxy, "Player", description);
bt_shell_printf("%s%s\n", str,
default_player == proxy ? "[default]" : "");
g_free(str);
}
static void cmd_list(int argc, char *arg[])
{
g_list_foreach(players, print_player, NULL);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_show_item(int argc, char *argv[])
{
GDBusProxy *proxy;
proxy = g_dbus_proxy_lookup(items, NULL, argv[1],
BLUEZ_MEDIA_ITEM_INTERFACE);
if (!proxy) {
bt_shell_printf("Item %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
bt_shell_printf("Item %s\n", g_dbus_proxy_get_path(proxy));
print_property(proxy, "Player");
print_property(proxy, "Name");
print_property(proxy, "Type");
print_property(proxy, "FolderType");
print_property(proxy, "Playable");
print_property(proxy, "Metadata");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_show(int argc, char *argv[])
{
GDBusProxy *proxy;
GDBusProxy *folder;
GDBusProxy *item;
DBusMessageIter iter;
const char *path;
if (argc < 2) {
if (check_default_player() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = default_player;
} else {
proxy = g_dbus_proxy_lookup(players, NULL, argv[1],
BLUEZ_MEDIA_PLAYER_INTERFACE);
if (!proxy) {
bt_shell_printf("Player %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
bt_shell_printf("Player %s\n", g_dbus_proxy_get_path(proxy));
print_property(proxy, "Name");
print_property(proxy, "Repeat");
print_property(proxy, "Equalizer");
print_property(proxy, "Shuffle");
print_property(proxy, "Scan");
print_property(proxy, "Status");
print_property(proxy, "Position");
print_property(proxy, "Track");
folder = g_dbus_proxy_lookup(folders, NULL,
g_dbus_proxy_get_path(proxy),
BLUEZ_MEDIA_FOLDER_INTERFACE);
if (folder == NULL)
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
bt_shell_printf("Folder %s\n", g_dbus_proxy_get_path(proxy));
print_property(folder, "Name");
print_property(folder, "NumberOfItems");
if (!g_dbus_proxy_get_property(proxy, "Playlist", &iter))
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
dbus_message_iter_get_basic(&iter, &path);
item = g_dbus_proxy_lookup(items, NULL, path,
BLUEZ_MEDIA_ITEM_INTERFACE);
if (item == NULL)
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
bt_shell_printf("Playlist %s\n", path);
print_property(item, "Name");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_select(int argc, char *argv[])
{
GDBusProxy *proxy;
proxy = g_dbus_proxy_lookup(players, NULL, argv[1],
BLUEZ_MEDIA_PLAYER_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Player %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (default_player == proxy)
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
default_player = proxy;
print_player(proxy, NULL);
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void change_folder_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 change folder: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("ChangeFolder successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void change_folder_setup(DBusMessageIter *iter, void *user_data)
{
const char *path = user_data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
}
static void cmd_change_folder(int argc, char *argv[])
{
GDBusProxy *proxy;
if (dbus_validate_path(argv[1], NULL) == FALSE) {
bt_shell_printf("Not a valid path\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (check_default_player() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = g_dbus_proxy_lookup(folders, NULL,
g_dbus_proxy_get_path(default_player),
BLUEZ_MEDIA_FOLDER_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (g_dbus_proxy_method_call(proxy, "ChangeFolder", change_folder_setup,
change_folder_reply, argv[1], NULL) == FALSE) {
bt_shell_printf("Failed to change current folder\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to change folder\n");
}
struct list_items_args {
int start;
int end;
};
static void list_items_setup(DBusMessageIter *iter, void *user_data)
{
struct list_items_args *args = user_data;
DBusMessageIter dict;
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,
&dict);
if (args->start < 0)
goto done;
g_dbus_dict_append_entry(&dict, "Start",
DBUS_TYPE_UINT32, &args->start);
if (args->end < 0)
goto done;
g_dbus_dict_append_entry(&dict, "End", DBUS_TYPE_UINT32, &args->end);
done:
dbus_message_iter_close_container(iter, &dict);
}
static void list_items_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 list items: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("ListItems successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_list_items(int argc, char *argv[])
{
GDBusProxy *proxy;
struct list_items_args *args;
if (check_default_player() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = g_dbus_proxy_lookup(folders, NULL,
g_dbus_proxy_get_path(default_player),
BLUEZ_MEDIA_FOLDER_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
args = g_new0(struct list_items_args, 1);
args->start = -1;
args->end = -1;
if (argc < 2)
goto done;
errno = 0;
args->start = strtol(argv[1], NULL, 10);
if (errno != 0) {
bt_shell_printf("%s(%d)\n", strerror(errno), errno);
g_free(args);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (argc < 3)
goto done;
errno = 0;
args->end = strtol(argv[2], NULL, 10);
if (errno != 0) {
bt_shell_printf("%s(%d)\n", strerror(errno), errno);
g_free(args);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
done:
if (g_dbus_proxy_method_call(proxy, "ListItems", list_items_setup,
list_items_reply, args, g_free) == FALSE) {
bt_shell_printf("Failed to change current folder\n");
g_free(args);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to list items\n");
}
static void search_setup(DBusMessageIter *iter, void *user_data)
{
char *string = user_data;
DBusMessageIter dict;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
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,
&dict);
dbus_message_iter_close_container(iter, &dict);
}
static void search_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 search: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Search successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_search(int argc, char *argv[])
{
GDBusProxy *proxy;
char *string;
if (check_default_player() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
proxy = g_dbus_proxy_lookup(folders, NULL,
g_dbus_proxy_get_path(default_player),
BLUEZ_MEDIA_FOLDER_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Operation not supported\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
string = g_strdup(argv[1]);
if (g_dbus_proxy_method_call(proxy, "Search", search_setup,
search_reply, string, g_free) == FALSE) {
bt_shell_printf("Failed to search\n");
g_free(string);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to search\n");
}
static void add_to_nowplaying_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 queue: %s\n", error.name);
dbus_error_free(&error);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("AddToNowPlaying successful\n");
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
}
static void cmd_queue(int argc, char *argv[])
{
GDBusProxy *proxy;
proxy = g_dbus_proxy_lookup(items, NULL, argv[1],
BLUEZ_MEDIA_ITEM_INTERFACE);
if (proxy == NULL) {
bt_shell_printf("Item %s not available\n", argv[1]);
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
if (g_dbus_proxy_method_call(proxy, "AddtoNowPlaying", NULL,
add_to_nowplaying_reply, NULL,
NULL) == FALSE) {
bt_shell_printf("Failed to play\n");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
bt_shell_printf("Attempting to queue %s\n", argv[1]);
}
static const struct bt_shell_menu player_menu = {
.name = "player",
.desc = "Media Player Submenu",
.entries = {
{ "list", NULL, cmd_list, "List available players" },
{ "show", "[player]", cmd_show, "Player information",
player_generator},
{ "select", "<player>", cmd_select, "Select default player",
player_generator},
{ "play", "[item]", cmd_play, "Start playback",
item_generator},
{ "pause", NULL, cmd_pause, "Pause playback" },
{ "stop", NULL, cmd_stop, "Stop playback" },
{ "next", NULL, cmd_next, "Jump to next item" },
{ "previous", NULL, cmd_previous, "Jump to previous item" },
{ "fast-forward", NULL, cmd_fast_forward,
"Fast forward playback" },
{ "rewind", NULL, cmd_rewind, "Rewind playback" },
{ "equalizer", "<on/off>", cmd_equalizer,
"Enable/Disable equalizer"},
{ "repeat", "<singletrack/alltrack/group/off>", cmd_repeat,
"Set repeat mode"},
{ "shuffle", "<alltracks/group/off>", cmd_shuffle,
"Set shuffle mode"},
{ "scan", "<alltracks/group/off>", cmd_scan,
"Set scan mode"},
{ "change-folder", "<item>", cmd_change_folder,
"Change current folder",
item_generator},
{ "list-items", "[start] [end]", cmd_list_items,
"List items of current folder" },
{ "search", "<string>", cmd_search,
"Search items containing string" },
{ "queue", "<item>", cmd_queue, "Add item to playlist queue",
item_generator},
{ "show-item", "<item>", cmd_show_item, "Show item information",
item_generator},
{} },
};
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 char *endpoint_generator(const char *text, int state)
{
char *ret;
ret = generic_generator(text, state, endpoints);
if (ret)
return ret;
return local_endpoint_generator(text, state);
}
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);
if (!ep->max_transports) {
bt_shell_printf("Maximum transports reached: rejecting\n");
return g_dbus_create_error(msg,
"org.bluez.Error.Rejected",
"Maximum transports reached");
}
ep->max_transports--;
if (!ep->transports)
ep->transports = queue_new();
queue_push_tail(ep->transports, 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;
}
#define CODEC_CAPABILITIES(_name, _uuid, _codec_id, _data, _meta) \
{ \
.name = _name, \
.uuid = _uuid, \
.codec_id = _codec_id, \
.data = _data, \
.meta = _meta, \
}
#define LC3_DATA(_freq, _duration, _chan_count, _len_min, _len_max) \
UTIL_IOV_INIT(0x03, LC3_FREQ, _freq, _freq >> 8, \
0x02, LC3_DURATION, _duration, \
0x02, LC3_CHAN_COUNT, _chan_count, \
0x05, LC3_FRAME_LEN, _len_min, _len_min >> 8, \
_len_max, _len_max >> 8)
static const struct capabilities {
const char *name;
const char *uuid;
uint8_t codec_id;
struct iovec data;
struct iovec meta;
} 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_src/sbc", A2DP_SOURCE_UUID, A2DP_CODEC_SBC,
UTIL_IOV_INIT(0xff, 0xff, 2, 64),
UTIL_IOV_INIT()),
/* 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_snk/sbc", A2DP_SINK_UUID, A2DP_CODEC_SBC,
UTIL_IOV_INIT(0xff, 0xff, 2, 64),
UTIL_IOV_INIT()),
/* PAC LC3 Sink:
*
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
* Duration: 7.5 ms 10 ms
* Channel count: 3
* Frame length: 26-240
*/
CODEC_CAPABILITIES("pac_snk/lc3", PAC_SINK_UUID, LC3_ID,
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26,
240),
UTIL_IOV_INIT()),
/* PAC LC3 Source:
*
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
* Duration: 7.5 ms 10 ms
* Channel count: 3
* Frame length: 26-240
*/
CODEC_CAPABILITIES("pac_src/lc3", PAC_SOURCE_UUID, LC3_ID,
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26,
240),
UTIL_IOV_INIT()),
/* Broadcast LC3 Source:
*
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
* Duration: 7.5 ms 10 ms
* Channel count: 3
* Frame length: 26-240
*/
CODEC_CAPABILITIES("bcaa/lc3", BCAA_SERVICE_UUID, LC3_ID,
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26,
240),
UTIL_IOV_INIT()),
/* Broadcast LC3 Sink:
*
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
* Duration: 7.5 ms 10 ms
* Channel count: 3
* Frame length: 26-240
*/
CODEC_CAPABILITIES("baa/lc3", BAA_SERVICE_UUID, LC3_ID,
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY, 3u, 26,
240),
UTIL_IOV_INIT()),
};
struct codec_preset {
char *name;
const struct iovec data;
struct bt_bap_qos qos;
uint8_t target_latency;
};
#define SBC_PRESET(_name, _data) \
{ \
.name = _name, \
.data = _data, \
}
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",
UTIL_IOV_INIT(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)),
SBC_PRESET("MQ_MONO_48",
UTIL_IOV_INIT(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)),
SBC_PRESET("MQ_STEREO_44_1",
UTIL_IOV_INIT(0x21, 0x15, 2,
SBC_BITPOOL_MQ_JOINT_STEREO_44100)),
SBC_PRESET("MQ_STEREO_48",
UTIL_IOV_INIT(0x11, 0x15, 2,
SBC_BITPOOL_MQ_JOINT_STEREO_48000)),
SBC_PRESET("HQ_MONO_44_1",
UTIL_IOV_INIT(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)),
SBC_PRESET("HQ_MONO_48",
UTIL_IOV_INIT(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)),
SBC_PRESET("HQ_STEREO_44_1",
UTIL_IOV_INIT(0x21, 0x15, 2,
SBC_BITPOOL_HQ_JOINT_STEREO_44100)),
SBC_PRESET("HQ_STEREO_48",
UTIL_IOV_INIT(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", UTIL_IOV_INIT(0x24, 0x15, 2, 43)),
SBC_PRESET("XQ_DUAL_48", UTIL_IOV_INIT(0x14, 0x15, 2, 39)),
/* Ultra high bitpool that fits in 512 kbps mandatory bitrate */
SBC_PRESET("UQ_STEREO_44_1", UTIL_IOV_INIT(0x21, 0x15, 2, 64)),
SBC_PRESET("UQ_STEREO_48", UTIL_IOV_INIT(0x11, 0x15, 2, 58)),
};
#define LC3_PRESET_LL(_name, _data, _qos) \
{ \
.name = _name, \
.data = _data, \
.qos = _qos, \
.target_latency = 0x01, \
}
#define LC3_PRESET(_name, _data, _qos) \
{ \
.name = _name, \
.data = _data, \
.qos = _qos, \
.target_latency = 0x02, \
}
#define LC3_PRESET_HR(_name, _data, _qos) \
{ \
.name = _name, \
.data = _data, \
.qos = _qos, \
.target_latency = 0x03, \
}
#define LC3_PRESET_B(_name, _data, _qos) \
{ \
.name = _name, \
.data = _data, \
.qos = _qos, \
.target_latency = 0x00, \
}
static struct codec_preset lc3_ucast_presets[] = {
/* Table 4.43: QoS configuration support setting requirements */
LC3_PRESET("8_1_1", LC3_CONFIG_8_1, LC3_QOS_8_1_1),
LC3_PRESET("8_2_1", LC3_CONFIG_8_2, LC3_QOS_8_2_1),
LC3_PRESET("16_1_1", LC3_CONFIG_16_1, LC3_QOS_16_1_1),
LC3_PRESET("16_2_1", LC3_CONFIG_16_2, LC3_QOS_16_2_1),
LC3_PRESET("24_1_1", LC3_CONFIG_24_1, LC3_QOS_24_1_1),
LC3_PRESET("24_2_1", LC3_CONFIG_24_2, LC3_QOS_24_2_1),
LC3_PRESET("32_1_1", LC3_CONFIG_32_1, LC3_QOS_32_1_1),
LC3_PRESET("32_2_1", LC3_CONFIG_32_2, LC3_QOS_32_1_1),
LC3_PRESET("44_1_1", LC3_CONFIG_44_1, LC3_QOS_44_1_1),
LC3_PRESET("44_2_1", LC3_CONFIG_44_2, LC3_QOS_44_2_1),
LC3_PRESET("48_1_1", LC3_CONFIG_48_1, LC3_QOS_48_1_1),
LC3_PRESET("48_2_1", LC3_CONFIG_48_2, LC3_QOS_48_2_1),
LC3_PRESET("48_3_1", LC3_CONFIG_48_3, LC3_QOS_48_3_1),
LC3_PRESET("48_4_1", LC3_CONFIG_48_4, LC3_QOS_48_4_1),
LC3_PRESET("48_5_1", LC3_CONFIG_48_5, LC3_QOS_48_5_1),
LC3_PRESET("48_6_1", LC3_CONFIG_48_6, LC3_QOS_48_6_1),
/* QoS Configuration settings for high reliability audio data */
LC3_PRESET_HR("8_1_2", LC3_CONFIG_8_1, LC3_QOS_8_1_2),
LC3_PRESET_HR("8_2_2", LC3_CONFIG_8_2, LC3_QOS_8_2_2),
LC3_PRESET_HR("16_1_2", LC3_CONFIG_16_1, LC3_QOS_16_1_2),
LC3_PRESET_HR("16_2_2", LC3_CONFIG_16_2, LC3_QOS_16_2_2),
LC3_PRESET_HR("24_1_2", LC3_CONFIG_24_1, LC3_QOS_24_1_2),
LC3_PRESET_HR("24_2_2", LC3_CONFIG_24_2, LC3_QOS_24_2_2),
LC3_PRESET_HR("32_1_2", LC3_CONFIG_32_1, LC3_QOS_32_1_2),
LC3_PRESET_HR("32_2_2", LC3_CONFIG_32_2, LC3_QOS_32_2_2),
LC3_PRESET_HR("44_1_2", LC3_CONFIG_44_1, LC3_QOS_44_1_2),
LC3_PRESET_HR("44_2_2", LC3_CONFIG_44_2, LC3_QOS_44_2_2),
LC3_PRESET_HR("48_1_2", LC3_CONFIG_48_1, LC3_QOS_48_1_2),
LC3_PRESET_HR("48_2_2", LC3_CONFIG_48_2, LC3_QOS_48_2_2),
LC3_PRESET_HR("48_3_2", LC3_CONFIG_48_3, LC3_QOS_48_3_2),
LC3_PRESET_HR("48_4_2", LC3_CONFIG_48_4, LC3_QOS_48_4_2),
LC3_PRESET_HR("48_5_2", LC3_CONFIG_48_5, LC3_QOS_48_5_2),
LC3_PRESET_HR("48_6_2", LC3_CONFIG_48_6, LC3_QOS_48_6_2),
/* QoS configuration support setting requirements for the UGG and UGT */
LC3_PRESET_LL("16_1_gs", LC3_CONFIG_16_1, LC3_QOS_16_1_GS),
LC3_PRESET_LL("16_2_gs", LC3_CONFIG_16_2, LC3_QOS_16_2_GS),
LC3_PRESET_LL("32_1_gs", LC3_CONFIG_32_1, LC3_QOS_32_1_GS),
LC3_PRESET_LL("32_2_gs", LC3_CONFIG_32_2, LC3_QOS_32_2_GS),
LC3_PRESET_LL("48_1_gs", LC3_CONFIG_48_1, LC3_QOS_48_1_GS),
LC3_PRESET_LL("48_2_gs", LC3_CONFIG_48_2, LC3_QOS_48_2_GS),
LC3_PRESET_LL("32_1_gr", LC3_CONFIG_32_1, LC3_QOS_32_1_GR),
LC3_PRESET_LL("32_2_gr", LC3_CONFIG_32_2, LC3_QOS_32_2_GR),
LC3_PRESET_LL("48_1_gr", LC3_CONFIG_48_1, LC3_QOS_48_1_GR),
LC3_PRESET_LL("48_2_gr", LC3_CONFIG_48_2, LC3_QOS_48_2_GR),
LC3_PRESET_LL("48_3_gr", LC3_CONFIG_48_3, LC3_QOS_48_3_GR),
LC3_PRESET_LL("48_4_gr", LC3_CONFIG_48_4, LC3_QOS_48_4_GR),
LC3_PRESET_LL("32_1_gr_l+r", LC3_CONFIG_32_1_AC(2),
LC3_QOS_32_1_GR_AC(2)),
LC3_PRESET_LL("32_2_gr_l+r", LC3_CONFIG_32_2_AC(2),
LC3_QOS_32_2_GR_AC(2)),
LC3_PRESET_LL("48_1_gr_l+r", LC3_CONFIG_48_1_AC(2),
LC3_QOS_48_1_GR_AC(2)),
LC3_PRESET_LL("48_2_gr_l+r", LC3_CONFIG_48_2_AC(2),
LC3_QOS_48_2_GR_AC(2)),
LC3_PRESET_LL("48_3_gr_l+r", LC3_CONFIG_48_3_AC(2),
LC3_QOS_48_3_GR_AC(2)),
LC3_PRESET_LL("48_4_gr_l+r", LC3_CONFIG_48_4_AC(2),
LC3_QOS_48_4_GR_AC(2)),
};
static struct codec_preset lc3_bcast_presets[] = {
/* Table 6.4: Broadcast Audio Stream configuration support requirements
* for the Broadcast Source and Broadcast Sink
*/
LC3_PRESET_B("8_1_1", LC3_CONFIG_8_1, LC3_QOS_8_1_1_B),
LC3_PRESET_B("8_2_1", LC3_CONFIG_8_2, LC3_QOS_8_2_1_B),
LC3_PRESET_B("16_1_1", LC3_CONFIG_16_1, LC3_QOS_16_1_1_B),
LC3_PRESET_B("16_2_1", LC3_CONFIG_16_2, LC3_QOS_16_2_1_B),
LC3_PRESET_B("24_1_1", LC3_CONFIG_24_1, LC3_QOS_24_1_1_B),
LC3_PRESET_B("24_2_1", LC3_CONFIG_24_2, LC3_QOS_24_2_1_B),
LC3_PRESET_B("32_1_1", LC3_CONFIG_32_1, LC3_QOS_32_1_1_B),
LC3_PRESET_B("32_2_1", LC3_CONFIG_32_2, LC3_QOS_32_2_1_B),
LC3_PRESET_B("44_1_1", LC3_CONFIG_44_1, LC3_QOS_44_1_1_B),
LC3_PRESET_B("44_2_1", LC3_CONFIG_44_2, LC3_QOS_44_2_1_B),
LC3_PRESET_B("48_1_1", LC3_CONFIG_48_1, LC3_QOS_48_1_1_B),
LC3_PRESET_B("48_2_1", LC3_CONFIG_48_2, LC3_QOS_48_2_1_B),
LC3_PRESET_B("48_3_1", LC3_CONFIG_48_3, LC3_QOS_48_3_1_B),
LC3_PRESET_B("48_4_1", LC3_CONFIG_48_4, LC3_QOS_48_4_1_B),
LC3_PRESET_B("48_5_1", LC3_CONFIG_48_5, LC3_QOS_48_5_1_B),
LC3_PRESET_B("48_6_1", LC3_CONFIG_48_6, LC3_QOS_48_6_1_B),
/* Broadcast Audio Stream configuration settings for high-reliability
* audio data.
*/
LC3_PRESET_B("8_1_2", LC3_CONFIG_8_1, LC3_QOS_8_1_1_B),
LC3_PRESET_B("8_2_2", LC3_CONFIG_8_2, LC3_QOS_8_2_2_B),
LC3_PRESET_B("16_1_2", LC3_CONFIG_16_1, LC3_QOS_16_1_2_B),
LC3_PRESET_B("16_2_2", LC3_CONFIG_16_2, LC3_QOS_16_2_2_B),
LC3_PRESET_B("24_1_2", LC3_CONFIG_24_1, LC3_QOS_24_1_2_B),
LC3_PRESET_B("24_2_2", LC3_CONFIG_24_2, LC3_QOS_24_2_2_B),
LC3_PRESET_B("32_1_2", LC3_CONFIG_32_1, LC3_QOS_32_1_2_B),
LC3_PRESET_B("32_2_2", LC3_CONFIG_32_2, LC3_QOS_32_2_2_B),
LC3_PRESET_B("44_1_2", LC3_CONFIG_44_1, LC3_QOS_44_1_2_B),
LC3_PRESET_B("44_2_2", LC3_CONFIG_44_2, LC3_QOS_44_2_2_B),
LC3_PRESET_B("48_1_2", LC3_CONFIG_48_1, LC3_QOS_48_1_2_B),
LC3_PRESET_B("48_2_2", LC3_CONFIG_48_2, LC3_QOS_48_2_2_B),
LC3_PRESET_B("48_3_2", LC3_CONFIG_48_3, LC3_QOS_48_3_2_B),
LC3_PRESET_B("48_4_2", LC3_CONFIG_48_4, LC3_QOS_48_4_2_B),
LC3_PRESET_B("48_5_2", LC3_CONFIG_48_5, LC3_QOS_48_5_2_B),
LC3_PRESET_B("48_6_2", LC3_CONFIG_48_6, LC3_QOS_48_6_2_B),
};
static void print_ltv(const char *str, void *user_data)
{
const char *label = user_data;
bt_shell_printf("\t%s.%s\n", label, str);
}
static void print_lc3_caps(uint8_t *data, int len)
{
const char *label = "Capabilities";
bt_bap_debug_caps(data, len, print_ltv, (void *)label);
}
static void print_lc3_cfg(void *data, int len)
{
const char *label = "Configuration";
bt_bap_debug_config(data, len, print_ltv, (void *)label);
}
static void print_lc3_meta(void *data, int len)
{
const char *label = "Metadata";
bt_bap_debug_metadata(data, len, print_ltv, (void *)label);
}
#define PRESET(_uuid, _codec, _presets, _default_index) \
{ \
.uuid = _uuid, \
.codec = _codec, \
.custom = { .name = "custom" }, \
.default_preset = &_presets[_default_index], \
.presets = _presets, \
.num_presets = ARRAY_SIZE(_presets), \
}
static struct preset {
const char *uuid;
uint8_t codec;
uint16_t cid;
uint16_t vid;
struct codec_preset custom;
struct codec_preset *default_preset;
struct codec_preset *presets;
size_t num_presets;
} presets[] = {
PRESET(A2DP_SOURCE_UUID, A2DP_CODEC_SBC, sbc_presets, 6),
PRESET(A2DP_SINK_UUID, A2DP_CODEC_SBC, sbc_presets, 6),
PRESET(PAC_SINK_UUID, LC3_ID, lc3_ucast_presets, 3),
PRESET(PAC_SOURCE_UUID, LC3_ID, lc3_ucast_presets, 3),
PRESET(BCAA_SERVICE_UUID, LC3_ID, lc3_bcast_presets, 3),
PRESET(BAA_SERVICE_UUID, LC3_ID, lc3_bcast_presets, 3),
};
static void parse_vendor_codec(const char *codec, uint16_t *vid, uint16_t *cid)
{
char **list;
char *endptr = NULL;
if (!codec)
return;
list = g_strsplit(codec, ":", 2);
if (vid)
*vid = strtol(list[0], &endptr, 0);
if (cid)
*cid = strtol(list[1], &endptr, 0);
g_strfreev(list);
}
static struct preset *find_presets(const char *uuid, uint8_t codec,
uint16_t vid, uint16_t cid)
{
size_t i;
if (codec == 0xff) {
GList *l;
for (l = local_endpoints; l; l = g_list_next(l)) {
struct endpoint *ep = l->data;
if (strcasecmp(ep->uuid, uuid) || ep->codec != codec)
continue;
if (ep->codec == 0xff && (ep->vid != vid ||
ep->cid != cid))
continue;
return ep->preset;
}
return NULL;
}
for (i = 0; i < ARRAY_SIZE(presets); i++) {
struct preset *preset = &presets[i];
if (preset->codec != codec)
continue;
if (!strcasecmp(preset->uuid, uuid))
return preset;
}
return NULL;
}
static struct preset *find_vendor_presets(const char *uuid, const char *codec)
{
uint16_t cid;
uint16_t vid;
if (!uuid || !codec)
return NULL;
parse_vendor_codec(codec, &vid, &cid);
return find_presets(uuid, 0xff, vid, cid);
}
static struct preset *find_presets_name(const char *uuid, const char *codec)
{
uint8_t id;
char *endptr = NULL;
if (!uuid || !codec)
return NULL;
if (strrchr(codec, ':'))
return find_vendor_presets(uuid, codec);
id = strtol(codec, &endptr, 0);
return find_presets(uuid, id, 0x0000, 0x0000);
}
static struct codec_preset *preset_find_name(struct preset *preset,
const char *name)
{
size_t i;
if (!preset)
return NULL;
if (!name)
return preset->default_preset;
else if (!strcmp(name, "custom"))
return &preset->custom;
for (i = 0; i < preset->num_presets; i++) {
struct codec_preset *p;
p = &preset->presets[i];
if (!strcmp(p->name, name))
return p;
}
return NULL;
}
static struct codec_preset *find_preset(const char *uuid, const char *codec,
const char *name)
{
struct preset *preset;
preset = find_presets_name(uuid, codec);
if (!preset)
return NULL;
return preset_find_name(preset, name);
}
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 util_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 = preset_find_name(ep->preset, 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->max_transports) {
bt_shell_printf("Maximum transports reached: rejecting\n");
return g_dbus_create_error(msg,
"org.bluez.Error.Rejected",
"Maximum transports reached");
}
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 = preset_find_name(ep->preset, NULL);
if (!p) {
reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected",
NULL);
return reply;
}
reply = endpoint_select_config_reply(msg, p->data.iov_base,
p->data.iov_len);
if (!reply) {
reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected",
NULL);
return reply;
}
bt_shell_printf("Auto Accepting using %s...\n", p->name);
return reply;
}
struct endpoint_config {
GDBusProxy *proxy;
struct endpoint *ep;
struct iovec *caps; /* Codec Specific Configuration LTVs */
struct iovec *meta; /* Metadata LTVs*/
uint8_t target_latency;
struct bt_bap_qos qos; /* BAP QOS configuration parameters */
};
static void append_io_qos(DBusMessageIter *iter, struct bt_bap_io_qos *qos)
{
bt_shell_printf("Interval %u\n", qos->interval);
g_dbus_dict_append_entry(iter, "Interval", DBUS_TYPE_UINT32,
&qos->interval);
bt_shell_printf("PHY 0x%02x\n", qos->phy);
g_dbus_dict_append_entry(iter, "PHY", DBUS_TYPE_BYTE, &qos->phy);
bt_shell_printf("SDU %u\n", qos->sdu);
g_dbus_dict_append_entry(iter, "SDU", DBUS_TYPE_UINT16, &qos->sdu);
bt_shell_printf("Retransmissions %u\n", qos->rtn);
g_dbus_dict_append_entry(iter, "Retransmissions", DBUS_TYPE_BYTE,
&qos->rtn);
bt_shell_printf("Latency %u\n", qos->latency);
g_dbus_dict_append_entry(iter, "Latency", DBUS_TYPE_UINT16,
&qos->latency);
}
static void append_ucast_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
{
struct bt_bap_ucast_qos *qos = &cfg->qos.ucast;
if (cfg->ep->iso_group != BT_ISO_QOS_GROUP_UNSET) {
bt_shell_printf("CIG 0x%2.2x\n", cfg->ep->iso_group);
g_dbus_dict_append_entry(iter, "CIG", DBUS_TYPE_BYTE,
&cfg->ep->iso_group);
}
if (cfg->ep->iso_stream != BT_ISO_QOS_STREAM_UNSET) {
bt_shell_printf("CIS 0x%2.2x\n", cfg->ep->iso_stream);
g_dbus_dict_append_entry(iter, "CIS", DBUS_TYPE_BYTE,
&cfg->ep->iso_stream);
}
bt_shell_printf("Framing 0x%02x\n", qos->framing);
g_dbus_dict_append_entry(iter, "Framing", DBUS_TYPE_BYTE,
&qos->framing);
bt_shell_printf("PresentationDelay %u\n", qos->delay);
g_dbus_dict_append_entry(iter, "PresentationDelay",
DBUS_TYPE_UINT32, &qos->delay);
if (cfg->target_latency) {
bt_shell_printf("TargetLatency 0x%02x\n", qos->target_latency);
g_dbus_dict_append_entry(iter, "TargetLatency", DBUS_TYPE_BYTE,
&qos->target_latency);
}
append_io_qos(iter, &qos->io_qos);
}
static void append_bcast_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
{
struct bt_bap_bcast_qos *qos = &cfg->qos.bcast;
if (cfg->ep->iso_group != BT_ISO_QOS_BIG_UNSET) {
bt_shell_printf("BIG 0x%2.2x\n", cfg->ep->iso_group);
g_dbus_dict_append_entry(iter, "BIG", DBUS_TYPE_BYTE,
&cfg->ep->iso_group);
}
if (cfg->ep->iso_stream != BT_ISO_QOS_BIS_UNSET) {
bt_shell_printf("BIS 0x%2.2x\n", cfg->ep->iso_stream);
g_dbus_dict_append_entry(iter, "BIS", DBUS_TYPE_BYTE,
&cfg->ep->iso_stream);
}
if (qos->sync_factor) {
bt_shell_printf("SyncFactor %u\n", qos->sync_factor);
g_dbus_dict_append_entry(iter, "SyncFactor", DBUS_TYPE_BYTE,
&qos->sync_factor);
}
if (qos->options) {
bt_shell_printf("Options %u\n", qos->options);
g_dbus_dict_append_entry(iter, "Options", DBUS_TYPE_BYTE,
&qos->options);
}
if (qos->skip) {
bt_shell_printf("Skip %u\n", qos->skip);
g_dbus_dict_append_entry(iter, "Skip", DBUS_TYPE_UINT16,
&qos->skip);
}
if (qos->sync_timeout) {
bt_shell_printf("SyncTimeout %u\n", qos->sync_timeout);
g_dbus_dict_append_entry(iter, "SyncTimeout", DBUS_TYPE_UINT16,
&qos->sync_timeout);
}
if (qos->sync_cte_type) {
bt_shell_printf("SyncCteType %u\n", qos->sync_cte_type);
g_dbus_dict_append_entry(iter, "SyncCteType", DBUS_TYPE_BYTE,
&qos->sync_cte_type);
}
if (qos->mse) {
bt_shell_printf("MSE %u\n", qos->mse);
g_dbus_dict_append_entry(iter, "MSE", DBUS_TYPE_BYTE,
&qos->mse);
}
if (qos->timeout) {
bt_shell_printf("Timeout %u\n", qos->timeout);
g_dbus_dict_append_entry(iter, "Timeout", DBUS_TYPE_UINT16,
&qos->timeout);
}
if (cfg->ep->bcode->iov_len != 0) {
const char *key = "BCode";
bt_shell_printf("BCode:\n");
bt_shell_hexdump(cfg->ep->bcode->iov_base,
cfg->ep->bcode->iov_len);
g_dbus_dict_append_basic_array(iter, DBUS_TYPE_STRING,
&key, DBUS_TYPE_BYTE,
&cfg->ep->bcode->iov_base,
cfg->ep->bcode->iov_len);
}
bt_shell_printf("Framing 0x%02x\n", qos->framing);
g_dbus_dict_append_entry(iter, "Framing", DBUS_TYPE_BYTE,
&qos->framing);
bt_shell_printf("PresentationDelay %u\n", qos->delay);
g_dbus_dict_append_entry(iter, "PresentationDelay",
DBUS_TYPE_UINT32, &qos->delay);
/* Add BAP codec QOS configuration */
append_io_qos(iter, &qos->io_qos);
}
static void append_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
{
DBusMessageIter entry, var, dict;
const char *key = "QoS";
dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY,
NULL, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
"a{sv}", &var);
dbus_message_iter_open_container(&var, DBUS_TYPE_ARRAY, "{sv}",
&dict);
if (cfg->ep->broadcast)
append_bcast_qos(&dict, cfg);
else
append_ucast_qos(&dict, cfg);
dbus_message_iter_close_container(&var, &dict);
dbus_message_iter_close_container(&entry, &var);
dbus_message_iter_close_container(iter, &entry);
}
static void append_properties(DBusMessageIter *iter,
struct endpoint_config *cfg)
{
DBusMessageIter dict;
const char *key = "Capabilities";
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
if (cfg->ep->codec == LC3_ID) {
print_lc3_cfg(cfg->caps->iov_base, cfg->caps->iov_len);
} else {
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);
if (cfg->meta && cfg->meta->iov_len) {
const char *meta = "Metadata";
g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta,
DBUS_TYPE_BYTE, &cfg->meta->iov_base,
cfg->meta->iov_len);
if (cfg->ep->codec == LC3_ID) {
print_lc3_meta(cfg->meta->iov_base, cfg->meta->iov_len);
} else {
bt_shell_printf("Metadata:\n");
bt_shell_hexdump(cfg->meta->iov_base,
cfg->meta->iov_len);
}
}
append_qos(&dict, cfg);
dbus_message_iter_close_container(iter, &dict);
}
static int parse_chan_alloc(DBusMessageIter *iter, uint32_t *location,
uint8_t *channels)
{
while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) {
const char *key;
DBusMessageIter value, entry;
int var;
dbus_message_iter_recurse(iter, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &value);
var = dbus_message_iter_get_arg_type(&value);
if (!strcasecmp(key, "ChannelAllocation")) {
if (var != DBUS_TYPE_UINT32)
return -EINVAL;
dbus_message_iter_get_basic(&value, location);
if (*channels)
*channels = __builtin_popcount(*location);
return 0;
}
dbus_message_iter_next(iter);
}
return -EINVAL;
}
static DBusMessage *endpoint_select_properties_reply(struct endpoint *ep,
DBusMessage *msg,
struct codec_preset *preset)
{
DBusMessage *reply;
DBusMessageIter iter, props;
struct endpoint_config *cfg;
struct bt_bap_io_qos *qos;
uint32_t location = 0;
uint8_t channels = 1;
if (!preset)
return NULL;
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
cfg = new0(struct endpoint_config, 1);
cfg->ep = ep;
/* Copy capabilities */
cfg->caps = util_iov_dup(&preset->data, 1);
cfg->target_latency = preset->target_latency;
dbus_message_iter_init(msg, &iter);
dbus_message_iter_recurse(&iter, &props);
if (!parse_chan_alloc(&props, &location, &channels)) {
uint8_t chan_alloc_ltv[] = {
0x05, LC3_CONFIG_CHAN_ALLOC, location & 0xff,
location >> 8, location >> 16, location >> 24
};
util_iov_append(cfg->caps, &chan_alloc_ltv,
sizeof(chan_alloc_ltv));
}
/* Copy metadata */
cfg->meta = util_iov_dup(ep->meta, 1);
if (ep->broadcast)
qos = &preset->qos.bcast.io_qos;
else
qos = &preset->qos.ucast.io_qos;
if (qos->phy) {
/* Set QoS parameters */
cfg->qos = preset->qos;
/* Adjust the SDU size based on the number of
* locations/channels that is being requested.
*/
if (channels > 1)
qos->sdu *= channels;
}
dbus_message_iter_init_append(reply, &iter);
append_properties(&iter, cfg);
free(cfg);
return reply;
}
static void select_properties_response(const char *input, void *user_data)
{
struct endpoint *ep = user_data;
struct codec_preset *p;
DBusMessage *reply;
p = preset_find_name(ep->preset, input);
if (p) {
reply = endpoint_select_properties_reply(ep, ep->msg, p);
goto done;
}
bt_shell_printf("Preset %s not found\n", input);
reply = g_dbus_create_error(ep->msg, "org.bluez.Error.Rejected", NULL);
done:
g_dbus_send_message(dbus_conn, reply);
dbus_message_unref(ep->msg);
ep->msg = NULL;
}
static DBusMessage *endpoint_select_properties(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: SelectProperties\n");
print_iter("\t", "Properties", &args);
if (!ep->max_transports) {
bt_shell_printf("Maximum transports reached: rejecting\n");
return g_dbus_create_error(msg,
"org.bluez.Error.Rejected",
"Maximum transports reached");
}
if (!ep->auto_accept) {
ep->msg = dbus_message_ref(msg);
bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
select_properties_response, ep);
return NULL;
}
p = preset_find_name(ep->preset, NULL);
if (!p)
return NULL;
reply = endpoint_select_properties_reply(ep, msg, p);
if (!reply)
return NULL;
bt_shell_printf("Auto Accepting using %s...\n", p->name);
return reply;
}
static bool match_str(const void *data, const void *user_data)
{
return !strcmp(data, user_data);
}
static DBusMessage *endpoint_clear_configuration(DBusConnection *conn,
DBusMessage *msg, void *user_data)
{
struct endpoint *ep = user_data;
DBusMessageIter args;
const char *path;
dbus_message_iter_init(msg, &args);
dbus_message_iter_get_basic(&args, &path);
if (ep->max_transports != UINT8_MAX)
ep->max_transports++;
queue_remove_if(ep->transports, match_str, (void *)path);
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 print_aptx_common(a2dp_aptx_t *aptx)
{
bt_shell_printf("\n\t\tFrequencies: ");
if (aptx->frequency & APTX_SAMPLING_FREQ_16000)
bt_shell_printf("16kHz ");
if (aptx->frequency & APTX_SAMPLING_FREQ_32000)
bt_shell_printf("32kHz ");
if (aptx->frequency & APTX_SAMPLING_FREQ_44100)
bt_shell_printf("44.1kHz ");
if (aptx->frequency & APTX_SAMPLING_FREQ_48000)
bt_shell_printf("48kHz ");
bt_shell_printf("\n\t\tChannel modes: ");
if (aptx->channel_mode & APTX_CHANNEL_MODE_MONO)
bt_shell_printf("Mono ");
if (aptx->channel_mode & APTX_CHANNEL_MODE_STEREO)
bt_shell_printf("Stereo ");
}
static void print_aptx(a2dp_aptx_t *aptx, uint8_t size)
{
bt_shell_printf("\t\tVendor Specific Value (aptX)");
if (size < sizeof(*aptx)) {
bt_shell_printf(" (broken)\n");
return;
}
print_aptx_common(aptx);
bt_shell_printf("\n");
}
static void print_faststream(a2dp_faststream_t *faststream, uint8_t size)
{
bt_shell_printf("\t\tVendor Specific Value (FastStream)");
if (size < sizeof(*faststream)) {
bt_shell_printf(" (broken)\n");
return;
}
bt_shell_printf("\n\t\tDirections: ");
if (faststream->direction & FASTSTREAM_DIRECTION_SINK)
bt_shell_printf("sink ");
if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE)
bt_shell_printf("source ");
if (faststream->direction & FASTSTREAM_DIRECTION_SINK) {
bt_shell_printf("\n\t\tSink Frequencies: ");
if (faststream->sink_frequency &
FASTSTREAM_SINK_SAMPLING_FREQ_44100)
bt_shell_printf("44.1kHz ");
if (faststream->sink_frequency &
FASTSTREAM_SINK_SAMPLING_FREQ_48000)
bt_shell_printf("48kHz ");
}
if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE) {
bt_shell_printf("\n\t\tSource Frequencies: ");
if (faststream->source_frequency &
FASTSTREAM_SOURCE_SAMPLING_FREQ_16000)
bt_shell_printf("16kHz ");
}
bt_shell_printf("\n");
}
static void print_aptx_ll(a2dp_aptx_ll_t *aptx_ll, uint8_t size)
{
a2dp_aptx_ll_new_caps_t *aptx_ll_new;
bt_shell_printf("\t\tVendor Specific Value (aptX Low Latency)");
if (size < sizeof(*aptx_ll)) {
bt_shell_printf(" (broken)\n");
return;
}
print_aptx_common(&aptx_ll->aptx);
bt_shell_printf("\n\tBidirectional link: %s",
aptx_ll->bidirect_link ? "Yes" : "No");
aptx_ll_new = &aptx_ll->new_caps[0];
if (aptx_ll->has_new_caps &&
size >= sizeof(*aptx_ll) + sizeof(*aptx_ll_new)) {
bt_shell_printf("\n\tTarget codec buffer level: %u",
(unsigned int)aptx_ll_new->target_level2 |
((unsigned int)(aptx_ll_new->target_level1) << 8));
bt_shell_printf("\n\tInitial codec buffer level: %u",
(unsigned int)aptx_ll_new->initial_level2 |
((unsigned int)(aptx_ll_new->initial_level1) << 8));
bt_shell_printf("\n\tSRA max rate: %g",
aptx_ll_new->sra_max_rate / 10000.0);
bt_shell_printf("\n\tSRA averaging time: %us",
(unsigned int)aptx_ll_new->sra_avg_time);
bt_shell_printf("\n\tGood working codec buffer level: %u",
(unsigned int)aptx_ll_new->good_working_level2 |
((unsigned int)(aptx_ll_new->good_working_level1) << 8)
);
}
bt_shell_printf("\n");
}
static void print_aptx_hd(a2dp_aptx_hd_t *aptx_hd, uint8_t size)
{
bt_shell_printf("\t\tVendor Specific Value (aptX HD)");
if (size < sizeof(*aptx_hd)) {
bt_shell_printf(" (broken)\n");
return;
}
print_aptx_common(&aptx_hd->aptx);
bt_shell_printf("\n");
}
static void print_ldac(a2dp_ldac_t *ldac, uint8_t size)
{
bt_shell_printf("\t\tVendor Specific Value (LDAC)");
if (size < sizeof(*ldac)) {
bt_shell_printf(" (broken)\n");
return;
}
bt_shell_printf("\n\t\tFrequencies: ");
if (ldac->frequency & LDAC_SAMPLING_FREQ_44100)
bt_shell_printf("44.1kHz ");
if (ldac->frequency & LDAC_SAMPLING_FREQ_48000)
bt_shell_printf("48kHz ");
if (ldac->frequency & LDAC_SAMPLING_FREQ_88200)
bt_shell_printf("88.2kHz ");
if (ldac->frequency & LDAC_SAMPLING_FREQ_96000)
bt_shell_printf("96kHz ");
if (ldac->frequency & LDAC_SAMPLING_FREQ_176400)
bt_shell_printf("176.4kHz ");
if (ldac->frequency & LDAC_SAMPLING_FREQ_192000)
bt_shell_printf("192kHz ");
bt_shell_printf("\n\t\tChannel modes: ");
if (ldac->channel_mode & LDAC_CHANNEL_MODE_MONO)
bt_shell_printf("Mono ");
if (ldac->channel_mode & LDAC_CHANNEL_MODE_DUAL)
bt_shell_printf("Dual ");
if (ldac->channel_mode & LDAC_CHANNEL_MODE_STEREO)
bt_shell_printf("Stereo ");
bt_shell_printf("\n");
}
static void print_opus_g(a2dp_opus_g_t *opus, uint8_t size)
{
bt_shell_printf("\t\tVendor Specific Value (Opus [Google])");
if (size < sizeof(*opus)) {
bt_shell_printf(" (broken)\n");
return;
}
bt_shell_printf("\n\t\tFrequencies: ");
if (opus->data & OPUS_G_FREQUENCY_48000)
bt_shell_printf("48kHz ");
bt_shell_printf("\n\t\tChannel modes: ");
if (opus->data & OPUS_G_CHANNELS_MONO)
bt_shell_printf("Mono ");
if (opus->data & OPUS_G_CHANNELS_STEREO)
bt_shell_printf("Stereo ");
if (opus->data & OPUS_G_CHANNELS_DUAL)
bt_shell_printf("Dual Mono ");
bt_shell_printf("\n\t\tFrame durations: ");
if (opus->data & OPUS_G_DURATION_100)
bt_shell_printf("10 ms ");
if (opus->data & OPUS_G_DURATION_200)
bt_shell_printf("20 ms ");
bt_shell_printf("\n");
}
static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size)
{
uint32_t vendor_id;
uint16_t codec_id;
int i;
if (size < sizeof(*vendor)) {
bt_shell_printf("\tMedia Codec: Vendor Specific A2DP Codec "
"(broken)");
return;
}
vendor_id = A2DP_GET_VENDOR_ID(*vendor);
codec_id = A2DP_GET_CODEC_ID(*vendor);
bt_shell_printf("\tMedia Codec: Vendor Specific A2DP Codec");
bt_shell_printf("\n\tVendor ID 0x%08x", vendor_id);
bt_shell_printf("\n\tVendor Specific Codec ID 0x%04x", codec_id);
bt_shell_printf("\n\tVendor Specific Data:");
for (i = 6; i < size; ++i)
bt_shell_printf(" 0x%.02x", ((unsigned char *)vendor)[i]);
bt_shell_printf("\n");
if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
print_aptx((void *) vendor, size);
else if (vendor_id == FASTSTREAM_VENDOR_ID &&
codec_id == FASTSTREAM_CODEC_ID)
print_faststream((void *) vendor, size);
else if (vendor_id == APTX_LL_VENDOR_ID && codec_id == APTX_LL_CODEC_ID)
print_aptx_ll((void *) vendor, size);
else if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID)
print_aptx_hd((void *) vendor, size);
else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID)
print_ldac((void *) vendor, size);
else if (vendor_id == OPUS_G_VENDOR_ID && codec_id == OPUS_G_CODEC_ID)
print_opus_g((void *) vendor, size);
}
static void print_mpeg24(a2dp_aac_t *aac, uint8_t size)
{
unsigned int freq, bitrate;
if (size < sizeof(*aac)) {
bt_shell_printf("\tMedia Codec: MPEG24 (broken)\n");
return;
}
freq = AAC_GET_FREQUENCY(*aac);
bitrate = AAC_GET_BITRATE(*aac);
bt_shell_printf("\tMedia Codec: MPEG24\n\tObject Types: ");
if (aac->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
bt_shell_printf("MPEG-2 AAC LC ");
if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
bt_shell_printf("MPEG-4 AAC LC ");
if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LTP)
bt_shell_printf("MPEG-4 AAC LTP ");
if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_SCA)
bt_shell_printf("MPEG-4 AAC scalable ");
bt_shell_printf("\n\tFrequencies: ");
if (freq & AAC_SAMPLING_FREQ_8000)
bt_shell_printf("8kHz ");
if (freq & AAC_SAMPLING_FREQ_11025)
bt_shell_printf("11.025kHz ");
if (freq & AAC_SAMPLING_FREQ_12000)
bt_shell_printf("12kHz ");
if (freq & AAC_SAMPLING_FREQ_16000)
bt_shell_printf("16kHz ");
if (freq & AAC_SAMPLING_FREQ_22050)
bt_shell_printf("22.05kHz ");
if (freq & AAC_SAMPLING_FREQ_24000)
bt_shell_printf("24kHz ");
if (freq & AAC_SAMPLING_FREQ_32000)
bt_shell_printf("32kHz ");
if (freq & AAC_SAMPLING_FREQ_44100)
bt_shell_printf("44.1kHz ");
if (freq & AAC_SAMPLING_FREQ_48000)
bt_shell_printf("48kHz ");
if (freq & AAC_SAMPLING_FREQ_64000)
bt_shell_printf("64kHz ");
if (freq & AAC_SAMPLING_FREQ_88200)
bt_shell_printf("88.2kHz ");
if (freq & AAC_SAMPLING_FREQ_96000)
bt_shell_printf("96kHz ");
bt_shell_printf("\n\tChannels: ");
if (aac->channels & AAC_CHANNELS_1)
bt_shell_printf("1 ");
if (aac->channels & AAC_CHANNELS_2)
bt_shell_printf("2 ");
bt_shell_printf("\n\tBitrate: %u", bitrate);
bt_shell_printf("\n\tVBR: %s", aac->vbr ? "Yes\n" : "No\n");
}
static void print_mpeg12(a2dp_mpeg_t *mpeg, uint8_t size)
{
uint16_t bitrate;
if (size < sizeof(*mpeg)) {
bt_shell_printf("\tMedia Codec: MPEG12 (broken)\n");
return;
}
bitrate = MPEG_GET_BITRATE(*mpeg);
bt_shell_printf("\tMedia Codec: MPEG12\n\tChannel Modes: ");
if (mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO)
bt_shell_printf("Mono ");
if (mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL)
bt_shell_printf("DualChannel ");
if (mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO)
bt_shell_printf("Stereo ");
if (mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO)
bt_shell_printf("JointStereo");
bt_shell_printf("\n\tFrequencies: ");
if (mpeg->frequency & MPEG_SAMPLING_FREQ_16000)
bt_shell_printf("16Khz ");
if (mpeg->frequency & MPEG_SAMPLING_FREQ_22050)
bt_shell_printf("22.05Khz ");
if (mpeg->frequency & MPEG_SAMPLING_FREQ_24000)
bt_shell_printf("24Khz ");
if (mpeg->frequency & MPEG_SAMPLING_FREQ_32000)
bt_shell_printf("32Khz ");
if (mpeg->frequency & MPEG_SAMPLING_FREQ_44100)
bt_shell_printf("44.1Khz ");
if (mpeg->frequency & MPEG_SAMPLING_FREQ_48000)
bt_shell_printf("48Khz ");
bt_shell_printf("\n\tCRC: %s", mpeg->crc ? "Yes" : "No");
bt_shell_printf("\n\tLayer: ");
if (mpeg->layer & MPEG_LAYER_MP1)
bt_shell_printf("1 ");
if (mpeg->layer & MPEG_LAYER_MP2)
bt_shell_printf("2 ");
if (mpeg->layer & MPEG_LAYER_MP3)
bt_shell_printf("3 ");
if (bitrate & MPEG_BIT_RATE_FREE) {
bt_shell_printf("\n\tBit Rate: Free format");
} else {
if (mpeg->layer & MPEG_LAYER_MP1) {
bt_shell_printf("\n\tLayer 1 Bit Rate: ");
if (bitrate & MPEG_MP1_BIT_RATE_32000)
bt_shell_printf("32kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_64000)
bt_shell_printf("64kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_96000)
bt_shell_printf("96kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_128000)
bt_shell_printf("128kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_160000)
bt_shell_printf("160kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_192000)
bt_shell_printf("192kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_224000)
bt_shell_printf("224kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_256000)
bt_shell_printf("256kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_320000)
bt_shell_printf("320kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_352000)
bt_shell_printf("352kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_384000)
bt_shell_printf("384kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_416000)
bt_shell_printf("416kbps ");
if (bitrate & MPEG_MP1_BIT_RATE_448000)
bt_shell_printf("448kbps ");
}
if (mpeg->layer & MPEG_LAYER_MP2) {
bt_shell_printf("\n\tLayer 2 Bit Rate: ");
if (bitrate & MPEG_MP2_BIT_RATE_32000)
bt_shell_printf("32kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_48000)
bt_shell_printf("48kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_56000)
bt_shell_printf("56kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_64000)
bt_shell_printf("64kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_80000)
bt_shell_printf("80kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_96000)
bt_shell_printf("96kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_112000)
bt_shell_printf("112kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_128000)
bt_shell_printf("128kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_160000)
bt_shell_printf("160kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_192000)
bt_shell_printf("192kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_224000)
bt_shell_printf("224kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_256000)
bt_shell_printf("256kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_320000)
bt_shell_printf("320kbps ");
if (bitrate & MPEG_MP2_BIT_RATE_384000)
bt_shell_printf("384kbps ");
}
if (mpeg->layer & MPEG_LAYER_MP3) {
bt_shell_printf("\n\tLayer 3 Bit Rate: ");
if (bitrate & MPEG_MP3_BIT_RATE_32000)
bt_shell_printf("32kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_40000)
bt_shell_printf("40kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_48000)
bt_shell_printf("48kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_56000)
bt_shell_printf("56kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_64000)
bt_shell_printf("64kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_80000)
bt_shell_printf("80kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_96000)
bt_shell_printf("96kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_112000)
bt_shell_printf("112kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_128000)
bt_shell_printf("128kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_160000)
bt_shell_printf("160kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_192000)
bt_shell_printf("192kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_224000)
bt_shell_printf("224kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_256000)
bt_shell_printf("256kbps ");
if (bitrate & MPEG_MP3_BIT_RATE_320000)
bt_shell_printf("320kbps ");
}
}
bt_shell_printf("\n\tVBR: %s", mpeg->vbr ? "Yes" : "No");
bt_shell_printf("\n\tPayload Format: ");
if (mpeg->mpf)
bt_shell_printf("RFC-2250 RFC-3119\n");
else
bt_shell_printf("RFC-2250\n");
}
static void print_sbc(a2dp_sbc_t *sbc, uint8_t size)
{
if (size < sizeof(*sbc)) {
bt_shell_printf("\tMedia Codec: SBC (broken)\n");
return;
}
bt_shell_printf("\tMedia Codec: SBC\n\tChannel Modes: ");
if (sbc->channel_mode & SBC_CHANNEL_MODE_MONO)
bt_shell_printf("Mono ");
if (sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
bt_shell_printf("DualChannel ");
if (sbc->channel_mode & SBC_CHANNEL_MODE_STEREO)
bt_shell_printf("Stereo ");
if (sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
bt_shell_printf("JointStereo");
bt_shell_printf("\n\tFrequencies: ");
if (sbc->frequency & SBC_SAMPLING_FREQ_16000)
bt_shell_printf("16Khz ");
if (sbc->frequency & SBC_SAMPLING_FREQ_32000)
bt_shell_printf("32Khz ");
if (sbc->frequency & SBC_SAMPLING_FREQ_44100)
bt_shell_printf("44.1Khz ");
if (sbc->frequency & SBC_SAMPLING_FREQ_48000)
bt_shell_printf("48Khz ");
bt_shell_printf("\n\tSubbands: ");
if (sbc->allocation_method & SBC_SUBBANDS_4)
bt_shell_printf("4 ");
if (sbc->allocation_method & SBC_SUBBANDS_8)
bt_shell_printf("8");
bt_shell_printf("\n\tBlocks: ");
if (sbc->block_length & SBC_BLOCK_LENGTH_4)
bt_shell_printf("4 ");
if (sbc->block_length & SBC_BLOCK_LENGTH_8)
bt_shell_printf("8 ");
if (sbc->block_length & SBC_BLOCK_LENGTH_12)
bt_shell_printf("12 ");
if (sbc->block_length & SBC_BLOCK_LENGTH_16)
bt_shell_printf("16 ");
bt_shell_printf("\n\tBitpool Range: %d-%d\n",
sbc->min_bitpool, sbc->max_bitpool);
}
static int print_a2dp_codec(uint8_t codec, void *data, uint8_t size)
{
int i;
switch (codec) {
case A2DP_CODEC_SBC:
print_sbc(data, size);
break;
case A2DP_CODEC_MPEG12:
print_mpeg12(data, size);
break;
case A2DP_CODEC_MPEG24:
print_mpeg24(data, size);
break;
case A2DP_CODEC_VENDOR:
print_vendor(data, size);
break;
default:
bt_shell_printf("\tMedia Codec: Unknown\n");
bt_shell_printf("\t\tCodec Data:");
for (i = 0; i < size - 2; ++i)
bt_shell_printf(" 0x%.02x", ((unsigned char *)data)[i]);
bt_shell_printf("\n");
}
return 0;
}
static void print_hexdump(const char *label, struct iovec *iov)
{
if (!iov)
return;
bt_shell_printf("%s:\n", label);
bt_shell_hexdump(iov->iov_base, iov->iov_len);
}
static void print_codec(const char *uuid, uint8_t codec, struct iovec *caps,
struct iovec *meta)
{
if (!strcasecmp(uuid, A2DP_SINK_UUID) ||
!strcasecmp(uuid, A2DP_SOURCE_UUID)) {
print_a2dp_codec(codec, caps->iov_base, caps->iov_len);
return;
}
if (codec != LC3_ID) {
print_hexdump("Capabilities", caps);
print_hexdump("Metadata", meta);
return;
}
print_lc3_caps(caps->iov_base, caps->iov_len);
if (!meta)
return;
print_lc3_meta(meta->iov_base, meta->iov_len);
}
static void print_capabilities(GDBusProxy *proxy)
{
DBusMessageIter iter, subiter;
const char *uuid;
uint8_t codec;
struct iovec caps, meta;
if (!g_dbus_proxy_get_property(proxy, "UUID", &iter))
return;
dbus_message_iter_get_basic(&iter, &uuid);
if (!g_dbus_proxy_get_property(proxy, "Codec", &iter))
return;
dbus_message_iter_get_basic(&iter, &codec);
if (!g_dbus_proxy_get_property(proxy, "Capabilities", &iter))
return;
dbus_message_iter_recurse(&iter, &subiter);
dbus_message_iter_get_fixed_array(&subiter, &caps.iov_base,
(int *)&caps.iov_len);
if (g_dbus_proxy_get_property(proxy, "Metadata", &iter)) {
dbus_message_iter_recurse(&iter, &subiter);
dbus_message_iter_get_fixed_array(&subiter, &meta.iov_base,
(int *)&meta.iov_len);
} else {
meta.iov_base = NULL;
meta.iov_len = 0;
}
print_codec(uuid, codec, &caps, &meta);
}
static void print_local_endpoint(struct endpoint *ep)
{
bt_shell_printf("Endpoint %s\n", ep->path);
bt_shell_printf("\tUUID %s\n", ep->uuid);
bt_shell_printf("\tCodec 0x%02x (%u)\n", ep->codec, ep->codec);
if (ep->caps)
print_codec(ep->uuid, ep->codec, ep->caps, ep->meta);
if (ep->locations)
bt_shell_printf("\tLocations 0x%08x (%u)\n", ep->locations,
ep->locations);
if (ep->supported_context)
bt_shell_printf("\tSupportedContext 0x%08x (%u)\n",
ep->supported_context, ep->supported_context);
if (ep->context)
bt_shell_printf("\tContext 0x%08x (%u)\n", ep->context,
ep->context);
}
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) {
struct endpoint *ep;
ep = endpoint_find(argv[1]);
if (ep)
return print_local_endpoint(ep);
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_capabilities(proxy);
print_property(proxy, "Device");
print_property(proxy, "DelayReporting");
print_property(proxy, "Locations");
print_property(proxy, "SupportedContext");
print_property(proxy, "Context");
print_property(proxy, "QoS");
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" } ),
GDBUS_ARGS({ "cfg", "ay" } ),
endpoint_select_configuration) },
{ GDBUS_ASYNC_METHOD("SelectProperties",
GDBUS_ARGS({ "properties", "a{sv}" } ),
GDBUS_ARGS({ "properties", "a{sv}" } ),
endpoint_select_properties) },
{ GDBUS_ASYNC_METHOD("ClearConfiguration",
GDBUS_ARGS({ "transport", "o" } ),
NULL, endpoint_clear_configuration) },
{ },
};
static void endpoint_free(void *data)
{
struct endpoint *ep = data;
util_iov_free(ep->caps, 1);
util_iov_free(ep->meta, 1);
if (ep->msg)
dbus_message_unref(ep->msg);
if (ep->codec == 0xff) {
free(ep->preset->custom.name);
free(ep->preset);
}
queue_destroy(ep->acquiring, NULL);
queue_destroy(ep->transports, free);
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;
}
struct vendor {
uint16_t cid;
uint16_t vid;
} __packed;
static gboolean endpoint_get_vendor(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct endpoint *ep = data;
struct vendor vendor = { ep->cid, ep->vid };
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &vendor);
return TRUE;
}
static gboolean endpoint_vendor_exists(const GDBusPropertyTable *property,
void *data)
{
struct endpoint *ep = data;
return ep->cid && ep->vid;
}
static gboolean endpoint_get_metadata(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->meta->iov_base,
ep->meta->iov_len);
dbus_message_iter_close_container(iter, &array);
return TRUE;
}
static gboolean endpoint_metadata_exists(const GDBusPropertyTable *property,
void *data)
{
struct endpoint *ep = data;
return ep->meta ? TRUE : FALSE;
}
static gboolean endpoint_get_locations(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct endpoint *ep = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &ep->locations);
return TRUE;
}
static gboolean endpoint_locations_exists(const GDBusPropertyTable *property,
void *data)
{
struct endpoint *ep = data;
return ep->supported_context ? TRUE : FALSE;
}
static gboolean
endpoint_get_supported_context(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct endpoint *ep = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16,
&ep->supported_context);
return TRUE;
}
static gboolean
endpoint_supported_context_exists(const GDBusPropertyTable *property,
void *data)
{
struct endpoint *ep = data;
return ep->supported_context ? TRUE : FALSE;
}
static gboolean endpoint_get_context(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct endpoint *ep = data;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ep->context);
return TRUE;
}
static gboolean endpoint_context_exists(const GDBusPropertyTable *property,
void *data)
{
struct endpoint *ep = data;
return ep->context ? TRUE : FALSE;
}
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 },
{ "Metadata", "ay", endpoint_get_metadata, NULL,
endpoint_metadata_exists },
{ "Vendor", "u", endpoint_get_vendor, NULL, endpoint_vendor_exists },
{ "Locations", "u", endpoint_get_locations, NULL,
endpoint_locations_exists },
{ "SupportedContext", "q", endpoint_get_supported_context, NULL,
endpoint_supported_context_exists },
{ "Context", "q", endpoint_get_context, NULL, endpoint_context_exists },
{ }
};
static void register_endpoint_setup(DBusMessageIter *iter, void *user_data)
{
struct endpoint *ep = user_data;
DBusMessageIter dict;
const char *key = "Capabilities";
const char *meta = "Metadata";
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);
if (ep->cid && ep->vid) {
struct vendor vendor = { ep->cid, ep->vid };
g_dbus_dict_append_entry(&dict, "Vendor", DBUS_TYPE_UINT32,
&vendor);
}
if (ep->caps) {
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);
}
if (ep->meta) {