2022-03-29 05:18:07 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
|
|
*
|
|
|
|
* Copyright (C) 2020 Intel Corporation. All rights reserved.
|
2023-03-31 23:39:27 +08:00
|
|
|
* Copyright 2023 NXP
|
2022-03-29 05:18:07 +08:00
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdbool.h>
|
2022-09-23 18:51:39 +08:00
|
|
|
#include <inttypes.h>
|
2022-03-29 05:18:07 +08:00
|
|
|
#include <errno.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <string.h>
|
2022-04-12 07:51:12 +08:00
|
|
|
#include <sys/ioctl.h>
|
2022-03-29 05:18:07 +08:00
|
|
|
#include <sys/uio.h>
|
2022-04-12 07:51:12 +08:00
|
|
|
#include <wordexp.h>
|
2022-12-13 07:34:50 +08:00
|
|
|
#include <sys/timerfd.h>
|
2022-12-15 05:01:07 +08:00
|
|
|
#include <sys/stat.h>
|
2022-03-29 05:18:07 +08:00
|
|
|
|
|
|
|
#include <glib.h>
|
|
|
|
|
|
|
|
#include "gdbus/gdbus.h"
|
|
|
|
|
|
|
|
#include "lib/bluetooth.h"
|
|
|
|
#include "lib/uuid.h"
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
#include "profiles/audio/a2dp-codecs.h"
|
2020-08-05 08:19:43 +08:00
|
|
|
#include "src/shared/lc3.h"
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
#include "src/shared/util.h"
|
|
|
|
#include "src/shared/shell.h"
|
2022-04-12 07:51:12 +08:00
|
|
|
#include "src/shared/io.h"
|
|
|
|
#include "src/shared/queue.h"
|
2022-12-01 08:04:05 +08:00
|
|
|
#include "print.h"
|
2022-03-29 05:18:07 +08:00
|
|
|
#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
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
#define BLUEZ_MEDIA_INTERFACE "org.bluez.Media1"
|
2022-03-29 05:18:07 +08:00
|
|
|
#define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
|
|
|
|
#define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
|
|
|
|
#define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
|
2022-04-12 07:51:12 +08:00
|
|
|
#define BLUEZ_MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
|
2022-04-12 07:53:56 +08:00
|
|
|
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1"
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
#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;
|
2023-01-24 07:56:46 +08:00
|
|
|
uint16_t cid;
|
|
|
|
uint16_t vid;
|
2022-04-12 07:51:12 +08:00
|
|
|
struct iovec *caps;
|
2022-12-29 23:53:05 +08:00
|
|
|
struct iovec *meta;
|
2022-04-12 07:51:12 +08:00
|
|
|
bool auto_accept;
|
2023-03-17 07:33:07 +08:00
|
|
|
uint8_t max_transports;
|
2023-06-07 16:22:58 +08:00
|
|
|
uint8_t iso_group;
|
|
|
|
uint8_t iso_stream;
|
2023-06-20 04:30:31 +08:00
|
|
|
struct queue *acquiring;
|
|
|
|
struct queue *transports;
|
2022-04-12 07:51:12 +08:00
|
|
|
DBusMessage *msg;
|
2023-05-12 07:43:34 +08:00
|
|
|
struct preset *preset;
|
2023-06-07 16:22:58 +08:00
|
|
|
bool broadcast;
|
|
|
|
struct iovec *bcode;
|
2022-04-12 07:51:12 +08:00
|
|
|
};
|
2022-03-29 05:18:07 +08:00
|
|
|
|
|
|
|
static DBusConnection *dbus_conn;
|
|
|
|
static GDBusProxy *default_player;
|
2022-04-12 07:51:12 +08:00
|
|
|
static GList *medias = NULL;
|
2022-03-29 05:18:07 +08:00
|
|
|
static GList *players = NULL;
|
|
|
|
static GList *folders = NULL;
|
|
|
|
static GList *items = NULL;
|
2022-04-12 07:51:12 +08:00
|
|
|
static GList *endpoints = NULL;
|
|
|
|
static GList *local_endpoints = NULL;
|
2022-04-12 07:53:56 +08:00
|
|
|
static GList *transports = NULL;
|
2022-06-21 06:22:52 +08:00
|
|
|
static struct queue *ios = NULL;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
|
|
|
struct transport {
|
2022-06-21 06:22:52 +08:00
|
|
|
GDBusProxy *proxy;
|
2022-04-12 07:53:56 +08:00
|
|
|
int sk;
|
2022-06-21 06:22:52 +08:00
|
|
|
uint16_t mtu[2];
|
2022-04-26 07:58:54 +08:00
|
|
|
char *filename;
|
|
|
|
int fd;
|
2022-12-15 05:01:07 +08:00
|
|
|
struct stat stat;
|
2022-04-12 07:53:56 +08:00
|
|
|
struct io *io;
|
|
|
|
uint32_t seq;
|
2022-12-13 07:34:50 +08:00
|
|
|
struct io *timer_io;
|
2022-04-12 07:53:56 +08:00
|
|
|
};
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
static const uint8_t base_lc3_16_2_1[] = {
|
|
|
|
0x28, 0x00, 0x00, /* Presentation Delay */
|
|
|
|
0x01, /* Number of Subgroups */
|
|
|
|
0x01, /* Number of BIS */
|
|
|
|
0x06, 0x00, 0x00, 0x00, 0x00, /* Code ID = LC3 (0x06) */
|
2023-08-22 22:29:34 +08:00
|
|
|
0x10, /* Codec Specific Configuration */
|
2023-06-07 16:22:58 +08:00
|
|
|
0x02, 0x01, 0x03, /* 16 KHZ */
|
|
|
|
0x02, 0x02, 0x01, /* 10 ms */
|
|
|
|
0x05, 0x03, 0x01, 0x00, 0x00, 0x00, /* Front Left */
|
|
|
|
0x03, 0x04, 0x28, 0x00, /* Frame Length 40 bytes */
|
|
|
|
0x04, /* Metadata */
|
|
|
|
0x03, 0x02, 0x02, 0x00, /* Audio Context: Convertional */
|
|
|
|
0x01, /* BIS */
|
|
|
|
0x00, /* Codec Specific Configuration */
|
|
|
|
};
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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;
|
|
|
|
}
|
2022-03-29 05:18:07 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!source)
|
|
|
|
return NULL;
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
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);
|
2022-04-12 07:51:12 +08:00
|
|
|
} else {
|
|
|
|
bt_shell_printf("Changing %s succeeded\n", str);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
2022-03-29 05:18:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-30 05:36:06 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-31 05:24:08 +08:00
|
|
|
static void print_player(void *data, void *user_data)
|
2022-08-30 05:36:06 +08:00
|
|
|
{
|
2022-08-31 05:24:08 +08:00
|
|
|
GDBusProxy *proxy = data;
|
|
|
|
const char *description = user_data;
|
2022-08-30 05:36:06 +08:00
|
|
|
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[])
|
|
|
|
{
|
2022-08-31 05:24:08 +08:00
|
|
|
g_list_foreach(players, print_player, NULL);
|
2022-08-30 05:36:06 +08:00
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
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},
|
|
|
|
{} },
|
|
|
|
};
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
|
|
|
|
2023-03-17 07:33:07 +08:00
|
|
|
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--;
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
if (!ep->transports)
|
|
|
|
ep->transports = queue_new();
|
|
|
|
|
|
|
|
queue_push_tail(ep->transports, strdup(path));
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
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 })
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
#define CODEC_DATA(args...) \
|
2022-04-12 07:51:12 +08:00
|
|
|
{ \
|
|
|
|
.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, \
|
|
|
|
}
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
#define LC3_DATA(_freq, _duration, _chan_count, _len_min, _len_max) \
|
|
|
|
CODEC_DATA(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)
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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,
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0xff, 0xff, 2, 64)),
|
2022-04-12 07:51:12 +08:00
|
|
|
/* 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,
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0xff, 0xff, 2, 64)),
|
|
|
|
/* PAC LC3 Sink:
|
|
|
|
*
|
|
|
|
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
|
|
|
|
* Duration: 7.5 ms 10 ms
|
|
|
|
* Channel count: 3
|
|
|
|
* Frame length: 30-240
|
|
|
|
*/
|
|
|
|
CODEC_CAPABILITIES(PAC_SINK_UUID, LC3_ID,
|
|
|
|
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY,
|
|
|
|
3u, 30, 240)),
|
|
|
|
/* PAC LC3 Source:
|
|
|
|
*
|
|
|
|
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
|
|
|
|
* Duration: 7.5 ms 10 ms
|
|
|
|
* Channel count: 3
|
|
|
|
* Frame length: 30-240
|
|
|
|
*/
|
|
|
|
CODEC_CAPABILITIES(PAC_SOURCE_UUID, LC3_ID,
|
|
|
|
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY,
|
|
|
|
3u, 30, 240)),
|
2023-06-07 16:22:58 +08:00
|
|
|
/* Broadcast LC3 Source:
|
|
|
|
*
|
|
|
|
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
|
|
|
|
* Duration: 7.5 ms 10 ms
|
|
|
|
* Channel count: 3
|
|
|
|
* Frame length: 30-240
|
|
|
|
*/
|
2023-07-31 17:16:42 +08:00
|
|
|
CODEC_CAPABILITIES(BCAA_SERVICE_UUID, LC3_ID,
|
2023-06-07 16:22:58 +08:00
|
|
|
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY,
|
|
|
|
3u, 30, 240)),
|
2023-08-08 19:50:34 +08:00
|
|
|
|
|
|
|
/* Broadcast LC3 Sink:
|
|
|
|
*
|
|
|
|
* Frequencies: 8Khz 11Khz 16Khz 22Khz 24Khz 32Khz 44.1Khz 48Khz
|
|
|
|
* Duration: 7.5 ms 10 ms
|
|
|
|
* Channel count: 3
|
|
|
|
* Frame length: 30-240
|
|
|
|
*/
|
|
|
|
CODEC_CAPABILITIES(BAA_SERVICE_UUID, LC3_ID,
|
|
|
|
LC3_DATA(LC3_FREQ_ANY, LC3_DURATION_ANY,
|
|
|
|
3u, 30, 240)),
|
2020-08-05 08:19:43 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
struct codec_qos {
|
|
|
|
uint32_t interval;
|
|
|
|
uint8_t framing;
|
2023-09-28 05:53:47 +08:00
|
|
|
uint8_t phy;
|
2020-08-05 08:19:43 +08:00
|
|
|
uint16_t sdu;
|
|
|
|
uint8_t rtn;
|
|
|
|
uint16_t latency;
|
|
|
|
uint32_t delay;
|
2022-04-12 07:51:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
struct codec_preset {
|
2023-05-12 07:43:34 +08:00
|
|
|
char *name;
|
2022-04-12 07:51:12 +08:00
|
|
|
const struct iovec data;
|
2020-08-05 08:19:43 +08:00
|
|
|
const struct codec_qos qos;
|
2022-11-24 07:27:36 +08:00
|
|
|
uint8_t target_latency;
|
2022-04-12 07:51:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#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",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x28, 0x15, 2, SBC_BITPOOL_MQ_MONO_44100)),
|
2022-04-12 07:51:12 +08:00
|
|
|
SBC_PRESET("MQ_MONO_48",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x18, 0x15, 2, SBC_BITPOOL_MQ_MONO_48000)),
|
2022-04-12 07:51:12 +08:00
|
|
|
SBC_PRESET("MQ_STEREO_44_1",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x21, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_44100)),
|
2022-04-12 07:51:12 +08:00
|
|
|
SBC_PRESET("MQ_STEREO_48",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x11, 0x15, 2, SBC_BITPOOL_MQ_JOINT_STEREO_48000)),
|
2022-04-12 07:51:12 +08:00
|
|
|
SBC_PRESET("HQ_MONO_44_1",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x28, 0x15, 2, SBC_BITPOOL_HQ_MONO_44100)),
|
2022-04-12 07:51:12 +08:00
|
|
|
SBC_PRESET("HQ_MONO_48",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x18, 0x15, 2, SBC_BITPOOL_HQ_MONO_48000)),
|
2022-11-24 07:27:36 +08:00
|
|
|
SBC_PRESET("HQ_STEREO_44_1",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x21, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_44100)),
|
2022-04-12 07:51:12 +08:00
|
|
|
SBC_PRESET("HQ_STEREO_48",
|
2020-08-05 08:19:43 +08:00
|
|
|
CODEC_DATA(0x11, 0x15, 2, SBC_BITPOOL_HQ_JOINT_STEREO_48000)),
|
2022-04-12 07:51:12 +08:00
|
|
|
/* 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
|
|
|
|
*/
|
2020-08-05 08:19:43 +08:00
|
|
|
SBC_PRESET("XQ_DUAL_44_1", CODEC_DATA(0x24, 0x15, 2, 43)),
|
|
|
|
SBC_PRESET("XQ_DUAL_48", CODEC_DATA(0x14, 0x15, 2, 39)),
|
2022-04-12 07:51:12 +08:00
|
|
|
/* Ultra high bitpool that fits in 512 kbps mandatory bitrate */
|
2020-08-05 08:19:43 +08:00
|
|
|
SBC_PRESET("UQ_STEREO_44_1", CODEC_DATA(0x21, 0x15, 2, 64)),
|
|
|
|
SBC_PRESET("UQ_STEREO_48", CODEC_DATA(0x11, 0x15, 2, 58)),
|
|
|
|
};
|
|
|
|
|
|
|
|
#define QOS_CONFIG(_interval, _framing, _phy, _sdu, _rtn, _latency, _delay) \
|
|
|
|
{ \
|
|
|
|
.interval = _interval, \
|
|
|
|
.framing = _framing, \
|
|
|
|
.phy = _phy, \
|
|
|
|
.sdu = _sdu, \
|
|
|
|
.rtn = _rtn, \
|
|
|
|
.latency = _latency, \
|
|
|
|
.delay = _delay, \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define QOS_UNFRAMED(_interval, _phy, _sdu, _rtn, _latency, _delay) \
|
|
|
|
QOS_CONFIG(_interval, 0x00, _phy, _sdu, _rtn, _latency, _delay)
|
|
|
|
|
|
|
|
#define QOS_FRAMED(_interval, _phy, _sdu, _rtn, _latency, _delay) \
|
|
|
|
QOS_CONFIG(_interval, 0x01, _phy, _sdu, _rtn, _latency, _delay)
|
|
|
|
|
|
|
|
#define QOS_UNFRAMED_1M(_interval, _sdu, _rtn, _latency, _delay) \
|
2023-09-28 05:53:47 +08:00
|
|
|
QOS_UNFRAMED(_interval, 0x01, _sdu, _rtn, _latency, _delay) \
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
#define QOS_FRAMED_1M(_interval, _sdu, _rtn, _latency, _delay) \
|
2023-09-28 05:53:47 +08:00
|
|
|
QOS_FRAMED(_interval, 0x01, _sdu, _rtn, _latency, _delay) \
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
#define QOS_UNFRAMED_2M(_interval, _sdu, _rtn, _latency, _delay) \
|
2023-09-28 05:53:47 +08:00
|
|
|
QOS_UNFRAMED(_interval, 0x02, _sdu, _rtn, _latency, _delay) \
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
#define QOS_FRAMED_2M(_interval, _sdu, _rtn, _latency, _delay) \
|
2023-09-28 05:53:47 +08:00
|
|
|
QOS_FRAMED(_interval, 0x02, _sdu, _rtn, _latency, _delay) \
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
#define LC3_7_5_UNFRAMED(_sdu, _rtn, _latency, _delay) \
|
2023-09-28 05:53:47 +08:00
|
|
|
QOS_UNFRAMED(7500u, 0x02, _sdu, _rtn, _latency, _delay)
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
#define LC3_7_5_FRAMED(_sdu, _rtn, _latency, _delay) \
|
2023-09-28 05:53:47 +08:00
|
|
|
QOS_FRAMED(7500u, 0x02, _sdu, _rtn, _latency, _delay)
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
#define LC3_10_UNFRAMED(_sdu, _rtn, _latency, _delay) \
|
|
|
|
QOS_UNFRAMED_2M(10000u, _sdu, _rtn, _latency, _delay)
|
|
|
|
|
|
|
|
#define LC3_10_FRAMED(_sdu, _rtn, _latency, _delay) \
|
|
|
|
QOS_FRAMED_2M(10000u, _sdu, _rtn, _latency, _delay)
|
|
|
|
|
|
|
|
#define LC3_PRESET_DATA(_freq, _duration, _len) \
|
|
|
|
CODEC_DATA(0x02, LC3_CONFIG_FREQ, _freq, \
|
|
|
|
0x02, LC3_CONFIG_DURATION, _duration, \
|
|
|
|
0x03, LC3_CONFIG_FRAME_LEN, _len, _len >> 8)
|
|
|
|
|
|
|
|
#define LC3_PRESET_8KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_8KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_11KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_11KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_16KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_16KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_22KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_22KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_24KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_24KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_32KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_32KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_44KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_44KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_48KHZ(_duration, _len) \
|
|
|
|
LC3_PRESET_DATA(LC3_CONFIG_FREQ_48KHZ, _duration, _len)
|
|
|
|
|
|
|
|
#define LC3_PRESET_LL(_name, _data, _qos) \
|
|
|
|
{ \
|
|
|
|
.name = _name, \
|
|
|
|
.data = _data, \
|
|
|
|
.qos = _qos, \
|
|
|
|
.latency = 0x01, \
|
|
|
|
}
|
|
|
|
|
|
|
|
#define LC3_PRESET(_name, _data, _qos) \
|
|
|
|
{ \
|
|
|
|
.name = _name, \
|
|
|
|
.data = _data, \
|
|
|
|
.qos = _qos, \
|
2022-11-24 07:27:36 +08:00
|
|
|
.target_latency = 0x02, \
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#define LC3_PRESET_HR(_name, _data, _qos) \
|
|
|
|
{ \
|
|
|
|
.name = _name, \
|
|
|
|
.data = _data, \
|
|
|
|
.qos = _qos, \
|
2022-11-24 07:27:36 +08:00
|
|
|
.target_latency = 0x03, \
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct codec_preset lc3_presets[] = {
|
|
|
|
/* Table 4.43: QoS configuration support setting requirements */
|
|
|
|
LC3_PRESET("8_1_1",
|
|
|
|
LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_7_5, 26u),
|
|
|
|
LC3_7_5_UNFRAMED(26u, 2u, 8u, 40000u)),
|
|
|
|
LC3_PRESET("8_2_1",
|
|
|
|
LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_10, 30u),
|
|
|
|
LC3_10_UNFRAMED(30u, 2u, 10u, 40000u)),
|
|
|
|
LC3_PRESET("16_1_1",
|
|
|
|
LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_7_5, 30u),
|
|
|
|
LC3_7_5_UNFRAMED(30u, 2u, 8u, 40000u)),
|
2022-11-24 07:27:36 +08:00
|
|
|
LC3_PRESET("16_2_1",
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_10, 40u),
|
|
|
|
LC3_10_UNFRAMED(40u, 2u, 10u, 40000u)),
|
|
|
|
LC3_PRESET("24_1_1",
|
|
|
|
LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_7_5, 45u),
|
|
|
|
LC3_7_5_UNFRAMED(45u, 2u, 8u, 40000u)),
|
|
|
|
LC3_PRESET("24_2_1",
|
|
|
|
LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_10, 60u),
|
|
|
|
LC3_10_UNFRAMED(60u, 2u, 10u, 40000u)),
|
|
|
|
LC3_PRESET("32_1_1",
|
|
|
|
LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_7_5, 60u),
|
|
|
|
LC3_7_5_UNFRAMED(60u, 2u, 8u, 40000u)),
|
|
|
|
LC3_PRESET("32_2_1",
|
|
|
|
LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_10, 80u),
|
|
|
|
LC3_10_UNFRAMED(80u, 2u, 10u, 40000u)),
|
|
|
|
LC3_PRESET("44_1_1",
|
|
|
|
LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_7_5, 98u),
|
|
|
|
QOS_FRAMED_2M(8163u, 98u, 5u, 24u, 40000u)),
|
|
|
|
LC3_PRESET("44_2_1",
|
|
|
|
LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_10, 130u),
|
|
|
|
QOS_FRAMED_2M(10884u, 130u, 5u, 31u, 40000u)),
|
|
|
|
LC3_PRESET("48_1_1",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 75u),
|
|
|
|
LC3_7_5_UNFRAMED(75u, 5u, 15u, 40000u)),
|
|
|
|
LC3_PRESET("48_2_1",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 100u),
|
|
|
|
LC3_10_UNFRAMED(100u, 5u, 20u, 40000u)),
|
|
|
|
LC3_PRESET("48_3_1",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 90u),
|
|
|
|
LC3_7_5_UNFRAMED(90u, 5u, 15u, 40000u)),
|
|
|
|
LC3_PRESET("48_4_1",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 120u),
|
|
|
|
LC3_10_UNFRAMED(120u, 5u, 20u, 40000u)),
|
|
|
|
LC3_PRESET("48_5_1",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 117u),
|
|
|
|
LC3_7_5_UNFRAMED(117u, 5u, 15u, 40000u)),
|
|
|
|
LC3_PRESET("48_6_1",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 155u),
|
|
|
|
LC3_10_UNFRAMED(155u, 5u, 20u, 40000u)),
|
|
|
|
/* QoS Configuration settings for high reliability audio data */
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_PRESET_HR("8_1_2",
|
|
|
|
LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_7_5, 26u),
|
|
|
|
LC3_7_5_UNFRAMED(26u, 13u, 75u, 40000u)),
|
|
|
|
LC3_PRESET_HR("8_2_2",
|
|
|
|
LC3_PRESET_8KHZ(LC3_CONFIG_DURATION_10, 30u),
|
|
|
|
LC3_10_UNFRAMED(30u, 13u, 95u, 40000u)),
|
|
|
|
LC3_PRESET_HR("16_1_2",
|
|
|
|
LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_7_5, 30u),
|
|
|
|
LC3_7_5_UNFRAMED(30u, 13u, 75u, 40000u)),
|
|
|
|
LC3_PRESET_HR("16_2_2",
|
|
|
|
LC3_PRESET_16KHZ(LC3_CONFIG_DURATION_10, 40u),
|
|
|
|
LC3_10_UNFRAMED(40u, 13u, 95u, 40000u)),
|
|
|
|
LC3_PRESET_HR("24_1_2",
|
|
|
|
LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_7_5, 45u),
|
|
|
|
LC3_7_5_UNFRAMED(45u, 13u, 75u, 40000u)),
|
|
|
|
LC3_PRESET_HR("24_2_2",
|
|
|
|
LC3_PRESET_24KHZ(LC3_CONFIG_DURATION_10, 60u),
|
|
|
|
LC3_10_UNFRAMED(60u, 13u, 95u, 40000u)),
|
|
|
|
LC3_PRESET_HR("32_1_2",
|
|
|
|
LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_7_5, 60u),
|
|
|
|
LC3_7_5_UNFRAMED(60u, 13u, 75u, 40000u)),
|
|
|
|
LC3_PRESET_HR("32_2_2",
|
|
|
|
LC3_PRESET_32KHZ(LC3_CONFIG_DURATION_10, 80u),
|
|
|
|
LC3_10_UNFRAMED(80u, 13u, 95u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("44_1_2",
|
|
|
|
LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_7_5, 98u),
|
2023-03-11 05:18:45 +08:00
|
|
|
QOS_FRAMED_2M(8163u, 98u, 13u, 80u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("44_2_2",
|
|
|
|
LC3_PRESET_44KHZ(LC3_CONFIG_DURATION_10, 130u),
|
2023-03-11 05:18:45 +08:00
|
|
|
QOS_FRAMED_2M(10884u, 130u, 13u, 85u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("48_1_2",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 75u),
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_7_5_UNFRAMED(75u, 13u, 75u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("48_2_2",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 100u),
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_10_UNFRAMED(100u, 13u, 95u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("48_3_2",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 90u),
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_7_5_UNFRAMED(90u, 13u, 75u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("48_4_2",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 120u),
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_10_UNFRAMED(120u, 13u, 100u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("48_5_2",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_7_5, 117u),
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_7_5_UNFRAMED(117u, 13u, 75u, 40000u)),
|
2020-08-05 08:19:43 +08:00
|
|
|
LC3_PRESET_HR("48_6_2",
|
|
|
|
LC3_PRESET_48KHZ(LC3_CONFIG_DURATION_10, 155u),
|
2023-03-11 05:18:45 +08:00
|
|
|
LC3_10_UNFRAMED(155u, 13u, 100u, 40000u)),
|
2022-04-12 07:51:12 +08:00
|
|
|
};
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
#define PRESET(_uuid, _codec, _presets, _default_index) \
|
2022-04-12 07:51:12 +08:00
|
|
|
{ \
|
|
|
|
.uuid = _uuid, \
|
2023-05-12 07:43:34 +08:00
|
|
|
.codec = _codec, \
|
2022-11-24 07:27:36 +08:00
|
|
|
.custom = { .name = "custom" }, \
|
|
|
|
.default_preset = &_presets[_default_index], \
|
2022-04-12 07:51:12 +08:00
|
|
|
.presets = _presets, \
|
|
|
|
.num_presets = ARRAY_SIZE(_presets), \
|
|
|
|
}
|
|
|
|
|
2022-11-24 07:27:36 +08:00
|
|
|
static struct preset {
|
2022-04-12 07:51:12 +08:00
|
|
|
const char *uuid;
|
2023-05-12 07:43:34 +08:00
|
|
|
uint8_t codec;
|
|
|
|
uint16_t cid;
|
|
|
|
uint16_t vid;
|
2022-11-24 07:27:36 +08:00
|
|
|
struct codec_preset custom;
|
|
|
|
struct codec_preset *default_preset;
|
2022-04-12 07:51:12 +08:00
|
|
|
struct codec_preset *presets;
|
|
|
|
size_t num_presets;
|
|
|
|
} presets[] = {
|
2023-05-12 07:43:34 +08:00
|
|
|
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_presets, 3),
|
|
|
|
PRESET(PAC_SOURCE_UUID, LC3_ID, lc3_presets, 3),
|
2023-07-31 17:16:42 +08:00
|
|
|
PRESET(BCAA_SERVICE_UUID, LC3_ID, lc3_presets, 3),
|
2023-08-08 19:50:34 +08:00
|
|
|
PRESET(BAA_SERVICE_UUID, LC3_ID, lc3_presets, 3),
|
2022-04-12 07:51:12 +08:00
|
|
|
};
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
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)
|
2022-04-12 07:51:12 +08:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(presets); i++) {
|
2022-11-24 07:27:36 +08:00
|
|
|
struct preset *preset = &presets[i];
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
if (preset->codec != codec)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!strcasecmp(preset->uuid, uuid))
|
|
|
|
return preset;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
static struct preset *find_vendor_presets(const char *uuid, const char *codec)
|
|
|
|
{
|
|
|
|
uint16_t cid;
|
|
|
|
uint16_t vid;
|
2022-11-24 07:27:36 +08:00
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
if (!uuid || !codec)
|
|
|
|
return NULL;
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
parse_vendor_codec(codec, &vid, &cid);
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
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;
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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;
|
|
|
|
|
2022-07-06 01:31:39 +08:00
|
|
|
return util_memdup(value, i);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
p = preset_find_name(ep->preset, input);
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
|
|
|
|
2023-03-17 07:33:07 +08:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!ep->auto_accept) {
|
|
|
|
ep->msg = dbus_message_ref(msg);
|
|
|
|
bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
|
|
|
|
select_config_response, ep);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
p = preset_find_name(ep->preset, NULL);
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!p) {
|
2022-06-23 02:09:04 +08:00
|
|
|
reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected",
|
|
|
|
NULL);
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
reply = endpoint_select_config_reply(msg, p->data.iov_base,
|
|
|
|
p->data.iov_len);
|
2023-03-17 07:33:07 +08:00
|
|
|
if (!reply) {
|
|
|
|
reply = g_dbus_create_error(msg, "org.bluez.Error.Rejected",
|
|
|
|
NULL);
|
|
|
|
return reply;
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
bt_shell_printf("Auto Accepting using %s...\n", p->name);
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
struct endpoint_config {
|
|
|
|
GDBusProxy *proxy;
|
|
|
|
struct endpoint *ep;
|
|
|
|
struct iovec *caps;
|
2022-12-29 23:53:05 +08:00
|
|
|
struct iovec *meta;
|
2020-08-05 08:19:43 +08:00
|
|
|
uint8_t target_latency;
|
|
|
|
const struct codec_qos *qos;
|
|
|
|
};
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
#define BCODE {0x01, 0x02, 0x68, 0x05, 0x53, 0xf1, 0x41, 0x5a, \
|
|
|
|
0xa2, 0x65, 0xbb, 0xaf, 0xc6, 0xea, 0x03, 0xb8}
|
|
|
|
|
|
|
|
static struct bt_iso_qos bcast_qos = {
|
2023-06-20 04:30:30 +08:00
|
|
|
.bcast = {
|
|
|
|
.big = BT_ISO_QOS_BIG_UNSET,
|
|
|
|
.bis = BT_ISO_QOS_BIS_UNSET,
|
2023-09-28 05:53:44 +08:00
|
|
|
.sync_factor = 24,
|
2023-06-20 04:30:30 +08:00
|
|
|
.packing = 0x00,
|
|
|
|
.framing = 0x00,
|
|
|
|
.encryption = 0x00,
|
|
|
|
.bcode = BCODE,
|
|
|
|
.options = 0x00,
|
|
|
|
.skip = 0x0000,
|
|
|
|
.sync_timeout = 0x4000,
|
|
|
|
.sync_cte_type = 0x00,
|
|
|
|
.mse = 0x00,
|
|
|
|
.timeout = 0x4000,
|
|
|
|
}
|
|
|
|
};
|
2023-06-07 16:22:58 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
static void append_io_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
|
2020-08-05 08:19:43 +08:00
|
|
|
{
|
|
|
|
struct codec_qos *qos = (void *)cfg->qos;
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("Interval %u\n", qos->interval);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Interval", DBUS_TYPE_UINT32,
|
|
|
|
&qos->interval);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("PHY 0x%02x\n", qos->phy);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "PHY", DBUS_TYPE_BYTE, &qos->phy);
|
2022-12-29 23:53:05 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("SDU %u\n", cfg->qos->sdu);
|
2022-12-29 23:53:05 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "SDU", DBUS_TYPE_UINT16, &qos->sdu);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
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);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Latency",
|
|
|
|
DBUS_TYPE_UINT16, &qos->latency);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void append_ucast_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
|
|
|
|
{
|
|
|
|
struct codec_qos *qos = (void *)cfg->qos;
|
|
|
|
|
|
|
|
if (cfg->ep->iso_group != BT_ISO_QOS_GROUP_UNSET) {
|
2023-06-07 16:22:58 +08:00
|
|
|
bt_shell_printf("CIG 0x%2.2x\n", cfg->ep->iso_group);
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "CIG", DBUS_TYPE_BYTE,
|
2023-06-07 16:22:58 +08:00
|
|
|
&cfg->ep->iso_group);
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
if (cfg->ep->iso_stream != BT_ISO_QOS_STREAM_UNSET) {
|
2023-06-07 16:22:58 +08:00
|
|
|
bt_shell_printf("CIS 0x%2.2x\n", cfg->ep->iso_stream);
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "CIS", DBUS_TYPE_BYTE,
|
2023-06-07 16:22:58 +08:00
|
|
|
&cfg->ep->iso_stream);
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("Framing 0x%02x\n", qos->framing);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Framing", DBUS_TYPE_BYTE,
|
2023-06-07 16:22:58 +08:00
|
|
|
&qos->framing);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("PresentationDelay %u\n", qos->delay);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "PresentationDelay",
|
|
|
|
DBUS_TYPE_UINT32, &qos->delay);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
if (cfg->target_latency) {
|
|
|
|
bt_shell_printf("TargetLatency 0x%02x\n", cfg->target_latency);
|
|
|
|
g_dbus_dict_append_entry(iter, "TargetLatency",
|
|
|
|
DBUS_TYPE_BYTE, &cfg->target_latency);
|
|
|
|
}
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
append_io_qos(iter, cfg);
|
|
|
|
}
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
static void append_bcast_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
|
|
|
|
{
|
|
|
|
if (bcast_qos.bcast.big != BT_ISO_QOS_BIG_UNSET) {
|
|
|
|
bt_shell_printf("BIG 0x%2.2x\n", bcast_qos.bcast.big);
|
|
|
|
g_dbus_dict_append_entry(iter, "BIG", DBUS_TYPE_BYTE,
|
|
|
|
&bcast_qos.bcast.big);
|
|
|
|
}
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
if (bcast_qos.bcast.bis != BT_ISO_QOS_BIS_UNSET) {
|
|
|
|
bt_shell_printf("BIS 0x%2.2x\n", bcast_qos.bcast.bis);
|
|
|
|
g_dbus_dict_append_entry(iter, "BIS", DBUS_TYPE_BYTE,
|
|
|
|
&bcast_qos.bcast.bis);
|
|
|
|
}
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("Framing 0x%02x\n", bcast_qos.bcast.framing);
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Framing", DBUS_TYPE_BYTE,
|
|
|
|
&bcast_qos.bcast.framing);
|
2023-06-07 16:22:58 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
bt_shell_printf("SyncFactor %u\n", bcast_qos.bcast.sync_factor);
|
2023-06-07 16:22:58 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "SyncFactor", DBUS_TYPE_BYTE,
|
2023-09-28 05:53:44 +08:00
|
|
|
&bcast_qos.bcast.sync_factor);
|
2023-06-07 16:22:58 +08:00
|
|
|
|
|
|
|
bt_shell_printf("Options %u\n", bcast_qos.bcast.options);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Options", DBUS_TYPE_BYTE,
|
2023-06-07 16:22:58 +08:00
|
|
|
&bcast_qos.bcast.options);
|
|
|
|
|
|
|
|
bt_shell_printf("Skip %u\n", bcast_qos.bcast.skip);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Skip", DBUS_TYPE_UINT16,
|
2023-06-07 16:22:58 +08:00
|
|
|
&bcast_qos.bcast.skip);
|
|
|
|
|
|
|
|
bt_shell_printf("SyncTimeout %u\n", bcast_qos.bcast.sync_timeout);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "SyncTimeout", DBUS_TYPE_UINT16,
|
2023-06-07 16:22:58 +08:00
|
|
|
&bcast_qos.bcast.sync_timeout);
|
|
|
|
|
|
|
|
bt_shell_printf("SyncCteType %u\n", bcast_qos.bcast.sync_cte_type);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "SyncType", DBUS_TYPE_BYTE,
|
2023-06-07 16:22:58 +08:00
|
|
|
&bcast_qos.bcast.sync_cte_type);
|
|
|
|
|
|
|
|
bt_shell_printf("MSE %u\n", bcast_qos.bcast.mse);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "MSE", DBUS_TYPE_BYTE,
|
2023-06-07 16:22:58 +08:00
|
|
|
&bcast_qos.bcast.mse);
|
|
|
|
|
|
|
|
bt_shell_printf("Timeout %u\n", bcast_qos.bcast.timeout);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
g_dbus_dict_append_entry(iter, "Timeout", DBUS_TYPE_UINT16,
|
2023-06-07 16:22:58 +08:00
|
|
|
&bcast_qos.bcast.timeout);
|
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
if (cfg->ep->bcode) {
|
|
|
|
const char *key = "BCode";
|
2023-06-07 16:22:58 +08:00
|
|
|
|
2023-09-28 05:53:47 +08:00
|
|
|
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,
|
2023-06-07 16:22:58 +08:00
|
|
|
&cfg->ep->bcode->iov_base,
|
|
|
|
cfg->ep->bcode->iov_len);
|
2023-09-28 05:53:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
append_io_qos(iter, cfg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void append_qos(DBusMessageIter *iter, struct endpoint_config *cfg)
|
|
|
|
{
|
|
|
|
DBusMessageIter entry, var, dict;
|
|
|
|
struct codec_qos *qos = (void *)cfg->qos;
|
|
|
|
const char *key = "QoS";
|
|
|
|
|
|
|
|
if (!qos)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
bt_shell_printf("Metadata:\n");
|
|
|
|
bt_shell_hexdump(cfg->meta->iov_base, cfg->meta->iov_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
append_qos(&dict, cfg);
|
2023-06-07 16:22:58 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
dbus_message_iter_close_container(iter, &dict);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct iovec *iov_append(struct iovec **iov, const void *data,
|
|
|
|
size_t len)
|
|
|
|
{
|
2022-11-24 07:27:36 +08:00
|
|
|
if (!*iov)
|
2020-08-05 08:19:43 +08:00
|
|
|
*iov = new0(struct iovec, 1);
|
2022-11-24 07:27:36 +08:00
|
|
|
|
|
|
|
if (!((*iov)->iov_base))
|
2020-08-05 08:19:43 +08:00
|
|
|
(*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 DBusMessage *endpoint_select_properties_reply(struct endpoint *ep,
|
|
|
|
DBusMessage *msg,
|
|
|
|
struct codec_preset *preset)
|
|
|
|
{
|
|
|
|
DBusMessage *reply;
|
|
|
|
DBusMessageIter iter;
|
|
|
|
struct endpoint_config *cfg;
|
|
|
|
|
2022-11-22 05:01:46 +08:00
|
|
|
if (!preset)
|
|
|
|
return NULL;
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
|
|
if (!reply)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
cfg = new0(struct endpoint_config, 1);
|
|
|
|
cfg->ep = ep;
|
|
|
|
|
|
|
|
/* Copy capabilities */
|
|
|
|
iov_append(&cfg->caps, preset->data.iov_base, preset->data.iov_len);
|
2022-11-24 07:27:36 +08:00
|
|
|
cfg->target_latency = preset->target_latency;
|
2020-08-05 08:19:43 +08:00
|
|
|
|
2022-12-29 23:53:05 +08:00
|
|
|
/* Copy metadata */
|
2023-05-11 07:29:55 +08:00
|
|
|
if (ep->meta)
|
|
|
|
iov_append(&cfg->meta, ep->meta->iov_base, ep->meta->iov_len);
|
2022-12-29 23:53:05 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
if (preset->qos.phy)
|
|
|
|
/* Set QoS parameters */
|
|
|
|
cfg->qos = &preset->qos;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
p = preset_find_name(ep->preset, input);
|
2020-08-05 08:19:43 +08:00
|
|
|
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);
|
|
|
|
|
2023-03-17 07:33:07 +08:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!ep->auto_accept) {
|
|
|
|
ep->msg = dbus_message_ref(msg);
|
|
|
|
bt_shell_prompt_input("Endpoint", "Enter preset/configuration:",
|
|
|
|
select_properties_response, ep);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
p = preset_find_name(ep->preset, NULL);
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!p)
|
2023-05-12 07:43:34 +08:00
|
|
|
return NULL;
|
2020-08-05 08:19:43 +08:00
|
|
|
|
|
|
|
reply = endpoint_select_properties_reply(ep, msg, p);
|
|
|
|
if (!reply)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
bt_shell_printf("Auto Accepting using %s...\n", p->name);
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
static bool match_str(const void *data, const void *user_data)
|
|
|
|
{
|
|
|
|
return !strcmp(data, user_data);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static DBusMessage *endpoint_clear_configuration(DBusConnection *conn,
|
|
|
|
DBusMessage *msg, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
2023-06-20 04:30:31 +08:00
|
|
|
DBusMessageIter args;
|
|
|
|
const char *path;
|
|
|
|
|
|
|
|
dbus_message_iter_init(msg, &args);
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&args, &path);
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-03-17 07:33:07 +08:00
|
|
|
if (ep->max_transports != UINT8_MAX)
|
|
|
|
ep->max_transports++;
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
queue_remove_if(ep->transports, match_str, (void *)path);
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
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");
|
2023-09-28 05:53:53 +08:00
|
|
|
print_property(proxy, "Locations");
|
|
|
|
print_property(proxy, "SupportedContext");
|
|
|
|
print_property(proxy, "Context");
|
|
|
|
print_property(proxy, "QoS");
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
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" } ),
|
2020-08-05 08:19:43 +08:00
|
|
|
GDBUS_ARGS({ "cfg", "ay" } ),
|
|
|
|
endpoint_select_configuration) },
|
|
|
|
{ GDBUS_ASYNC_METHOD("SelectProperties",
|
|
|
|
GDBUS_ARGS({ "properties", "a{sv}" } ),
|
|
|
|
GDBUS_ARGS({ "properties", "a{sv}" } ),
|
|
|
|
endpoint_select_properties) },
|
2022-04-12 07:51:12 +08:00
|
|
|
{ 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) {
|
2023-01-24 07:56:46 +08:00
|
|
|
if (ep->caps->iov_base)
|
|
|
|
g_free(ep->caps->iov_base);
|
2022-04-12 07:51:12 +08:00
|
|
|
g_free(ep->caps);
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:53:05 +08:00
|
|
|
if (ep->meta) {
|
|
|
|
if (ep->meta->iov_base)
|
|
|
|
g_free(ep->meta->iov_base);
|
|
|
|
g_free(ep->meta);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
if (ep->msg)
|
|
|
|
dbus_message_unref(ep->msg);
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
if (ep->codec == 0xff) {
|
|
|
|
free(ep->preset->custom.name);
|
|
|
|
free(ep->preset);
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
queue_destroy(ep->acquiring, NULL);
|
|
|
|
queue_destroy(ep->transports, free);
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-24 07:56:49 +08:00
|
|
|
struct vendor {
|
|
|
|
uint16_t cid;
|
|
|
|
uint16_t vid;
|
|
|
|
} __packed;
|
|
|
|
|
2023-01-24 07:56:46 +08:00
|
|
|
static gboolean endpoint_get_vendor(const GDBusPropertyTable *property,
|
|
|
|
DBusMessageIter *iter, void *data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = data;
|
2023-01-24 07:56:49 +08:00
|
|
|
struct vendor vendor = { ep->cid, ep->vid };
|
2023-01-24 07:56:46 +08:00
|
|
|
|
2023-01-24 07:56:49 +08:00
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &vendor);
|
2023-01-24 07:56:46 +08:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static gboolean endpoint_vendor_exists(const GDBusPropertyTable *property,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = data;
|
|
|
|
|
|
|
|
return ep->cid && ep->vid;
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:53:05 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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 },
|
2022-12-29 23:53:05 +08:00
|
|
|
{ "Metadata", "ay", endpoint_get_metadata, NULL,
|
|
|
|
endpoint_metadata_exists },
|
2023-01-24 07:56:49 +08:00
|
|
|
{ "Vendor", "u", endpoint_get_vendor, NULL, endpoint_vendor_exists },
|
2022-04-12 07:51:12 +08:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
static void register_endpoint_setup(DBusMessageIter *iter, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
DBusMessageIter dict;
|
|
|
|
const char *key = "Capabilities";
|
2022-12-29 23:53:05 +08:00
|
|
|
const char *meta = "Metadata";
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-01-24 07:56:49 +08:00
|
|
|
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) {
|
2023-01-24 07:56:46 +08:00
|
|
|
g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &key,
|
2022-04-12 07:51:12 +08:00
|
|
|
DBUS_TYPE_BYTE, &ep->caps->iov_base,
|
|
|
|
ep->caps->iov_len);
|
|
|
|
|
2023-01-24 07:56:46 +08:00
|
|
|
bt_shell_printf("Capabilities:\n");
|
|
|
|
bt_shell_hexdump(ep->caps->iov_base, ep->caps->iov_len);
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2022-12-29 23:53:05 +08:00
|
|
|
if (ep->meta) {
|
|
|
|
g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_STRING, &meta,
|
|
|
|
DBUS_TYPE_BYTE, &ep->meta->iov_base,
|
|
|
|
ep->meta->iov_len);
|
|
|
|
|
|
|
|
bt_shell_printf("Metadata:\n");
|
|
|
|
bt_shell_hexdump(ep->meta->iov_base, ep->meta->iov_len);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
2023-05-02 03:27:20 +08:00
|
|
|
if (g_list_find(local_endpoints, ep)) {
|
|
|
|
local_endpoints = g_list_remove(local_endpoints, ep);
|
|
|
|
g_dbus_unregister_interface(dbus_conn, ep->path,
|
2022-04-12 07:51:12 +08:00
|
|
|
BLUEZ_MEDIA_ENDPOINT_INTERFACE);
|
2023-05-02 03:27:20 +08:00
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_printf("Endpoint %s registered\n", ep->path);
|
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2023-05-12 04:23:40 +08:00
|
|
|
static bool media_supports_uuid(GDBusProxy *proxy, const char *uuid)
|
|
|
|
{
|
|
|
|
DBusMessageIter iter, array;
|
|
|
|
|
|
|
|
if (!g_dbus_proxy_get_property(proxy, "SupportedUUIDs", &iter))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&iter, &array);
|
|
|
|
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
|
|
|
|
const char *support_uuid;
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&array, &support_uuid);
|
|
|
|
|
|
|
|
if (!strcasecmp(uuid, support_uuid))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
dbus_message_iter_next(&array);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void endpoint_register(struct endpoint *ep)
|
|
|
|
{
|
|
|
|
GList *l;
|
2023-05-12 04:23:40 +08:00
|
|
|
int registered = 0;
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
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)) {
|
2023-05-12 04:23:40 +08:00
|
|
|
if (!media_supports_uuid(l->data, ep->uuid))
|
|
|
|
continue;
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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;
|
|
|
|
}
|
2023-05-12 04:23:40 +08:00
|
|
|
|
|
|
|
registered++;
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
2023-05-12 04:23:40 +08:00
|
|
|
if (!registered)
|
|
|
|
goto fail;
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
bt_shell_printf("Failed register endpoint\n");
|
|
|
|
local_endpoints = g_list_remove(local_endpoints, ep);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
static void endpoint_iso_stream(const char *input, void *user_data)
|
2020-08-05 08:19:43 +08:00
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
char *endptr = NULL;
|
|
|
|
int value;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
|
2023-06-07 16:22:58 +08:00
|
|
|
ep->iso_stream = BT_ISO_QOS_STREAM_UNSET;
|
2020-08-05 08:19:43 +08:00
|
|
|
} else {
|
|
|
|
value = strtol(input, &endptr, 0);
|
|
|
|
|
|
|
|
if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
ep->iso_stream = value;
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
endpoint_register(ep);
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
static void endpoint_iso_group(const char *input, void *user_data)
|
2020-08-05 08:19:43 +08:00
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
char *endptr = NULL;
|
|
|
|
int value;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
|
2023-06-07 16:22:58 +08:00
|
|
|
ep->iso_group = BT_ISO_QOS_GROUP_UNSET;
|
2020-08-05 08:19:43 +08:00
|
|
|
} else {
|
|
|
|
value = strtol(input, &endptr, 0);
|
|
|
|
|
|
|
|
if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
ep->iso_group = value;
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
if (!ep->broadcast)
|
|
|
|
bt_shell_prompt_input(ep->path, "CIS (auto/value):",
|
|
|
|
endpoint_iso_stream, ep);
|
|
|
|
else
|
|
|
|
bt_shell_prompt_input(ep->path, "BIS (auto/value):",
|
|
|
|
endpoint_iso_stream, ep);
|
2020-08-05 08:19:43 +08:00
|
|
|
}
|
|
|
|
|
2023-03-17 07:33:07 +08:00
|
|
|
static void endpoint_max_transports(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
char *endptr = NULL;
|
|
|
|
int value;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "a") || !strcasecmp(input, "auto")) {
|
|
|
|
ep->max_transports = UINT8_MAX;
|
|
|
|
} else {
|
|
|
|
value = strtol(input, &endptr, 0);
|
|
|
|
|
|
|
|
if (!endptr || *endptr != '\0' || value > UINT8_MAX) {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
ep->max_transports = value;
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
if (ep->broadcast)
|
|
|
|
bt_shell_prompt_input(ep->path, "BIG (auto/value):",
|
|
|
|
endpoint_iso_group, ep);
|
|
|
|
else
|
|
|
|
bt_shell_prompt_input(ep->path, "CIG (auto/value):",
|
|
|
|
endpoint_iso_group, ep);
|
2023-03-17 07:33:07 +08:00
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void endpoint_auto_accept(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
|
2023-08-08 19:50:34 +08:00
|
|
|
if (!strcmp(ep->uuid, BCAA_SERVICE_UUID) ||
|
|
|
|
!strcmp(ep->uuid, BAA_SERVICE_UUID)) {
|
2023-06-07 16:22:58 +08:00
|
|
|
ep->broadcast = true;
|
|
|
|
} else {
|
|
|
|
ep->broadcast = false;
|
|
|
|
}
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) {
|
2022-04-12 07:51:12 +08:00
|
|
|
ep->auto_accept = true;
|
2023-03-17 07:33:07 +08:00
|
|
|
bt_shell_prompt_input(ep->path, "Max Transports (auto/value):",
|
|
|
|
endpoint_max_transports, ep);
|
|
|
|
return;
|
2020-08-05 08:19:43 +08:00
|
|
|
} else if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) {
|
2022-04-12 07:51:12 +08:00
|
|
|
ep->auto_accept = false;
|
2020-08-05 08:19:43 +08:00
|
|
|
} else {
|
2022-04-12 07:51:12 +08:00
|
|
|
bt_shell_printf("Invalid input for Auto Accept\n");
|
2020-08-05 08:19:43 +08:00
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
if (ep->broadcast)
|
|
|
|
bt_shell_prompt_input(ep->path, "BIG (auto/value):",
|
|
|
|
endpoint_iso_group, ep);
|
|
|
|
else
|
|
|
|
bt_shell_prompt_input(ep->path, "CIG (auto/value):",
|
|
|
|
endpoint_iso_group, ep);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
2022-12-29 23:53:05 +08:00
|
|
|
static void endpoint_set_metadata(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "n") || !strcasecmp(input, "no")) {
|
|
|
|
free(ep->meta->iov_base);
|
|
|
|
ep->meta = NULL;
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ep->meta)
|
|
|
|
ep->meta = g_new0(struct iovec, 1);
|
|
|
|
|
|
|
|
ep->meta->iov_base = str2bytearray((char *) input, &ep->meta->iov_len);
|
|
|
|
if (!ep->meta->iov_base) {
|
|
|
|
free(ep->meta);
|
|
|
|
ep->meta = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
bt_shell_prompt_input(ep->path, "Auto Accept (yes/no):",
|
|
|
|
endpoint_auto_accept, ep);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void endpoint_set_capabilities(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint *ep = user_data;
|
|
|
|
|
2023-01-24 07:56:46 +08:00
|
|
|
if (ep->caps && ep->caps->iov_base) {
|
2022-04-12 07:51:12 +08:00
|
|
|
g_free(ep->caps->iov_base);
|
2023-01-24 07:56:46 +08:00
|
|
|
ep->caps = g_new0(struct iovec, 1);
|
|
|
|
} else
|
2022-04-12 07:51:12 +08:00
|
|
|
ep->caps = g_new0(struct iovec, 1);
|
|
|
|
|
|
|
|
ep->caps->iov_base = str2bytearray((char *) input, &ep->caps->iov_len);
|
|
|
|
|
2023-01-24 07:56:46 +08:00
|
|
|
if (ep->caps->iov_len == 0x01 &&
|
|
|
|
(*(uint8_t *)(ep->caps->iov_base)) == 0x00) {
|
|
|
|
g_free(ep->caps->iov_base);
|
|
|
|
ep->caps->iov_base = NULL;
|
|
|
|
ep->caps->iov_len = 0x00;
|
|
|
|
}
|
|
|
|
|
2022-12-29 23:53:05 +08:00
|
|
|
bt_shell_prompt_input(ep->path, "Enter Metadata (value/no):",
|
|
|
|
endpoint_set_metadata, ep);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *uuid_generator(const char *text, int state)
|
|
|
|
{
|
|
|
|
int len = strlen(text);
|
|
|
|
static int index = 0;
|
|
|
|
size_t i;
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
if (!state) {
|
2022-04-12 07:51:12 +08:00
|
|
|
index = 0;
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
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 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);
|
2023-01-24 07:56:46 +08:00
|
|
|
ep->cid = 0x0000;
|
|
|
|
ep->vid = 0x0000;
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
|
|
|
|
2023-01-24 07:56:49 +08:00
|
|
|
if (strrchr(argv[2], ':')) {
|
2023-01-24 07:56:46 +08:00
|
|
|
ep->codec = 0xff;
|
2023-05-12 07:43:34 +08:00
|
|
|
parse_vendor_codec(argv[2], &ep->cid, &ep->vid);
|
|
|
|
ep->preset = new0(struct preset, 1);
|
|
|
|
ep->preset->custom.name = strdup("custom");
|
|
|
|
ep->preset->default_preset = &ep->preset->custom;
|
|
|
|
} else {
|
|
|
|
ep->preset = find_presets_name(ep->uuid, argv[2]);
|
2023-01-24 07:56:46 +08:00
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void config_endpoint_setup(DBusMessageIter *iter, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint_config *cfg = user_data;
|
|
|
|
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
|
|
|
|
&cfg->ep->path);
|
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
append_properties(iter, cfg);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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 endpoint_config(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct endpoint_config *cfg = user_data;
|
|
|
|
uint8_t *data;
|
2022-06-23 02:09:04 +08:00
|
|
|
size_t len = 0;
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
data = str2bytearray((char *) input, &len);
|
|
|
|
|
|
|
|
iov_append(&cfg->caps, data, len);
|
|
|
|
free(data);
|
|
|
|
|
2022-04-19 07:56:48 +08:00
|
|
|
endpoint_set_config(cfg);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
2023-08-08 19:50:34 +08:00
|
|
|
static struct endpoint *endpoint_new(const struct capabilities *cap);
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void cmd_config_endpoint(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
struct endpoint_config *cfg;
|
|
|
|
const struct codec_preset *preset;
|
2023-08-08 19:50:34 +08:00
|
|
|
const struct capabilities *cap;
|
|
|
|
char *uuid;
|
|
|
|
uint8_t codec_id;
|
|
|
|
bool broadcast = false;
|
2022-04-12 07:51:12 +08:00
|
|
|
|
|
|
|
cfg = new0(struct endpoint_config, 1);
|
|
|
|
|
2023-08-08 19:50:34 +08:00
|
|
|
/* Search for the remote endpoint name on DBUS */
|
2022-04-12 07:51:12 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-08-08 19:50:34 +08:00
|
|
|
/* Search for the local endpoint */
|
2022-04-12 07:51:12 +08:00
|
|
|
cfg->ep = endpoint_find(argv[2]);
|
|
|
|
if (!cfg->ep) {
|
2023-08-08 19:50:34 +08:00
|
|
|
|
|
|
|
/* When the local endpoint was not found either we received
|
|
|
|
* UUID, or the provided local endpoint is not available
|
|
|
|
*/
|
|
|
|
uuid = argv[2];
|
|
|
|
codec_id = strtol(argv[3], NULL, 0);
|
|
|
|
cap = find_capabilities(uuid, codec_id);
|
|
|
|
if (cap) {
|
|
|
|
broadcast = true;
|
|
|
|
cfg->ep = endpoint_new(cap);
|
|
|
|
cfg->ep->preset = find_presets_name(uuid, argv[3]);
|
|
|
|
if (!cfg->ep->preset)
|
|
|
|
bt_shell_printf("Preset not found\n");
|
|
|
|
} else {
|
|
|
|
bt_shell_printf("Local Endpoint %s,"
|
|
|
|
"or capabilities not found\n", uuid);
|
|
|
|
goto fail;
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
2023-08-08 19:50:34 +08:00
|
|
|
if (((broadcast == false) && (argc > 3)) ||
|
|
|
|
((broadcast == true) && (argc > 4))) {
|
|
|
|
char *preset_name = (broadcast == false)?argv[3]:argv[4];
|
|
|
|
|
|
|
|
preset = preset_find_name(cfg->ep->preset, preset_name);
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!preset) {
|
2023-08-08 19:50:34 +08:00
|
|
|
bt_shell_printf("Preset %s not found\n", preset_name);
|
2022-04-12 07:51:12 +08:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2023-06-07 16:22:58 +08:00
|
|
|
if (cfg->ep->broadcast) {
|
|
|
|
iov_append(&cfg->ep->bcode, bcast_qos.bcast.bcode,
|
|
|
|
sizeof(bcast_qos.bcast.bcode));
|
|
|
|
/* Copy capabilities for broadcast*/
|
|
|
|
iov_append(&cfg->caps, base_lc3_16_2_1,
|
|
|
|
sizeof(base_lc3_16_2_1));
|
|
|
|
} else {
|
|
|
|
/* Copy capabilities */
|
|
|
|
iov_append(&cfg->caps, preset->data.iov_base,
|
|
|
|
preset->data.iov_len);
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
/* Set QoS parameters */
|
|
|
|
cfg->qos = &preset->qos;
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-11-24 07:27:36 +08:00
|
|
|
static void custom_delay(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
qos->delay = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_latency(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
qos->latency = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter Presentation Delay (us):",
|
|
|
|
custom_delay, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_rtn(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
qos->rtn = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter Max Transport Latency (ms):",
|
|
|
|
custom_latency, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_sdu(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
qos->sdu = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter RTN:", custom_rtn, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_phy(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
|
|
|
|
if (!strcmp(input, "1M"))
|
2023-09-28 05:53:47 +08:00
|
|
|
qos->phy = 0x01;
|
2022-11-24 07:27:36 +08:00
|
|
|
else if (!strcmp(input, "2M"))
|
2023-09-28 05:53:47 +08:00
|
|
|
qos->phy = 0x02;
|
2022-11-24 07:27:36 +08:00
|
|
|
else {
|
|
|
|
char *endptr = NULL;
|
|
|
|
uint8_t phy = strtol(input, &endptr, 0);
|
|
|
|
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (phy) {
|
|
|
|
case 0x01:
|
|
|
|
case 0x02:
|
2023-09-28 05:53:47 +08:00
|
|
|
qos->phy = phy;
|
2022-11-24 07:27:36 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter Max SDU:", custom_sdu, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_framing(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "Unframed"))
|
|
|
|
qos->framing = 0x00;
|
|
|
|
else if (!strcasecmp(input, "Framed"))
|
|
|
|
qos->framing = 0x01;
|
|
|
|
else {
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
qos->framing = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter PHY (1M, 2M):", custom_phy,
|
|
|
|
user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_interval(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct codec_qos *qos = (void *)&p->qos;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
qos->interval = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter Framing (Unframed, Framed):",
|
|
|
|
custom_framing, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_target_latency(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "Low"))
|
|
|
|
p->target_latency = 0x01;
|
|
|
|
else if (!strcasecmp(input, "Balance"))
|
|
|
|
p->target_latency = 0x02;
|
|
|
|
else if (!strcasecmp(input, "High"))
|
|
|
|
p->target_latency = 0x02;
|
|
|
|
else {
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
p->target_latency = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter SDU Interval (us):",
|
|
|
|
custom_interval, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_length(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct iovec *iov = (void *)&p->data;
|
|
|
|
uint8_t ltv[4] = { 0x03, LC3_CONFIG_FRAME_LEN };
|
|
|
|
uint16_t len;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
len = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
ltv[2] = len;
|
|
|
|
ltv[3] = len >> 8;
|
|
|
|
|
|
|
|
iov_append(&iov, ltv, sizeof(ltv));
|
|
|
|
|
|
|
|
bt_shell_prompt_input("QoS", "Enter Target Latency "
|
|
|
|
"(Low, Balance, High):",
|
|
|
|
custom_target_latency, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_location(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct iovec *iov = (void *)&p->data;
|
|
|
|
uint32_t location;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
location = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only add Channel Allocation if set */
|
|
|
|
if (location) {
|
|
|
|
uint8_t ltv[6] = { 0x05, LC3_CONFIG_CHAN_ALLOC };
|
|
|
|
|
|
|
|
location = cpu_to_le32(location);
|
|
|
|
memcpy(<v[2], &location, sizeof(location));
|
|
|
|
iov_append(&iov, ltv, sizeof(ltv));
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_prompt_input("Codec", "Enter frame length:",
|
|
|
|
custom_length, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t val2duration(uint32_t val)
|
|
|
|
{
|
|
|
|
switch (val) {
|
|
|
|
case 7:
|
|
|
|
return 0x00;
|
|
|
|
case 10:
|
|
|
|
return 0x01;
|
|
|
|
default:
|
|
|
|
return 0xff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_duration(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct iovec *iov = (void *)&p->data;
|
|
|
|
uint8_t ltv[3] = { 0x02, LC3_CONFIG_DURATION, 0x00 };
|
|
|
|
char *endptr = NULL;
|
|
|
|
uint32_t val;
|
|
|
|
|
|
|
|
val = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strncmp(input, "0x", 2))
|
|
|
|
ltv[2] = val2duration(val);
|
|
|
|
else
|
|
|
|
ltv[2] = val;
|
|
|
|
|
|
|
|
if (ltv[2] == 0xff) {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
iov_append(&iov, ltv, sizeof(ltv));
|
|
|
|
|
|
|
|
bt_shell_prompt_input("Codec", "Enter channel allocation:",
|
|
|
|
custom_location, user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t val2freq(uint32_t val)
|
|
|
|
{
|
|
|
|
switch (val) {
|
|
|
|
case 8:
|
|
|
|
return 0x01;
|
|
|
|
case 11:
|
|
|
|
return 0x02;
|
|
|
|
case 16:
|
|
|
|
return 0x03;
|
|
|
|
case 22:
|
|
|
|
return 0x04;
|
|
|
|
case 24:
|
|
|
|
return 0x05;
|
|
|
|
case 32:
|
|
|
|
return 0x06;
|
|
|
|
case 44:
|
|
|
|
return 0x07;
|
|
|
|
case 48:
|
|
|
|
return 0x08;
|
|
|
|
case 88:
|
|
|
|
return 0x09;
|
|
|
|
case 96:
|
|
|
|
return 0x0a;
|
|
|
|
case 174:
|
|
|
|
return 0x0b;
|
|
|
|
case 192:
|
|
|
|
return 0x0c;
|
|
|
|
case 384:
|
|
|
|
return 0x0d;
|
|
|
|
default:
|
|
|
|
return 0x00;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void custom_frequency(const char *input, void *user_data)
|
|
|
|
{
|
|
|
|
struct codec_preset *p = user_data;
|
|
|
|
struct iovec *iov = (void *)&p->data;
|
|
|
|
uint8_t ltv[3] = { 0x02, LC3_CONFIG_FREQ, 0x00 };
|
|
|
|
uint32_t val;
|
|
|
|
char *endptr = NULL;
|
|
|
|
|
|
|
|
val = strtol(input, &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0') {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strncmp(input, "0x", 2))
|
|
|
|
ltv[2] = val2freq(val);
|
|
|
|
else
|
|
|
|
ltv[2] = val;
|
|
|
|
|
|
|
|
if (!ltv[2]) {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", input);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset iov to start over the codec configuration */
|
|
|
|
free(iov->iov_base);
|
|
|
|
iov->iov_base = NULL;
|
|
|
|
iov->iov_len = 0;
|
|
|
|
iov_append(&iov, ltv, sizeof(ltv));
|
|
|
|
|
|
|
|
bt_shell_prompt_input("Codec", "Enter frame duration (ms):",
|
|
|
|
custom_duration, user_data);
|
|
|
|
}
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
static void print_presets(struct preset *preset)
|
2022-04-12 07:51:12 +08:00
|
|
|
{
|
|
|
|
size_t i;
|
2023-05-12 07:43:34 +08:00
|
|
|
struct codec_preset *p;
|
|
|
|
|
|
|
|
p = &preset->custom;
|
|
|
|
|
|
|
|
bt_shell_printf("%s%s\n", p == preset->default_preset ? "*" : "",
|
|
|
|
p->name);
|
|
|
|
|
|
|
|
for (i = 0; i < preset->num_presets; i++) {
|
|
|
|
p = &preset->presets[i];
|
|
|
|
bt_shell_printf("%s%s\n", p == preset->default_preset ?
|
|
|
|
"*" : "", p->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cmd_presets_endpoint(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
struct preset *preset;
|
2022-04-12 07:51:12 +08:00
|
|
|
struct codec_preset *default_preset = NULL;
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
if (argc > 3) {
|
|
|
|
default_preset = find_preset(argv[1], argv[2], argv[3]);
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!default_preset) {
|
2023-05-12 07:43:34 +08:00
|
|
|
bt_shell_printf("Preset %s not found\n", argv[3]);
|
2022-04-12 07:51:12 +08:00
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
preset = find_presets_name(argv[1], argv[2]);
|
|
|
|
if (!preset) {
|
|
|
|
bt_shell_printf("No preset found\n");
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:51:12 +08:00
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
if (default_preset) {
|
|
|
|
preset->default_preset = default_preset;
|
|
|
|
goto done;
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
2023-05-12 07:43:34 +08:00
|
|
|
print_presets(preset);
|
|
|
|
|
|
|
|
done:
|
2022-11-24 07:27:36 +08:00
|
|
|
if (default_preset && !strcmp(default_preset->name, "custom")) {
|
|
|
|
bt_shell_prompt_input("Codec", "Enter frequency (Khz):",
|
|
|
|
custom_frequency, default_preset);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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 },
|
2023-01-24 07:56:46 +08:00
|
|
|
{ "register", "<UUID> <codec[:company]> [capabilities...]",
|
2022-04-12 07:51:12 +08:00
|
|
|
cmd_register_endpoint,
|
|
|
|
"Register Endpoint",
|
|
|
|
uuid_generator },
|
|
|
|
{ "unregister", "<UUID/object>", cmd_unregister_endpoint,
|
|
|
|
"Register Endpoint",
|
|
|
|
local_endpoint_generator },
|
2023-08-08 19:50:34 +08:00
|
|
|
{ "config",
|
|
|
|
"<endpoint> [local endpoint/UUID] [preset/codec id] [preset]",
|
2022-04-12 07:51:12 +08:00
|
|
|
cmd_config_endpoint,
|
|
|
|
"Configure Endpoint",
|
|
|
|
endpoint_generator },
|
2023-05-12 07:43:34 +08:00
|
|
|
{ "presets", "<UUID> <codec[:company]> [default]",
|
|
|
|
cmd_presets_endpoint,
|
2022-04-12 07:51:12 +08:00
|
|
|
"List available presets",
|
|
|
|
uuid_generator },
|
|
|
|
{} },
|
|
|
|
};
|
|
|
|
|
2022-08-30 05:36:06 +08:00
|
|
|
static struct endpoint *endpoint_new(const struct capabilities *cap)
|
|
|
|
{
|
|
|
|
struct endpoint *ep;
|
|
|
|
|
|
|
|
ep = new0(struct endpoint, 1);
|
|
|
|
ep->uuid = g_strdup(cap->uuid);
|
2023-08-08 19:50:34 +08:00
|
|
|
ep->broadcast = (strcmp(cap->uuid, BCAA_SERVICE_UUID) &&
|
|
|
|
strcmp(cap->uuid, BAA_SERVICE_UUID)) ? false : true;
|
2022-08-30 05:36:06 +08:00
|
|
|
ep->codec = cap->codec_id;
|
|
|
|
ep->path = g_strdup_printf("%s/ep%u", BLUEZ_MEDIA_ENDPOINT_PATH,
|
|
|
|
g_list_length(local_endpoints));
|
|
|
|
/* Copy capabilities */
|
|
|
|
iov_append(&ep->caps, cap->data.iov_base, cap->data.iov_len);
|
|
|
|
local_endpoints = g_list_append(local_endpoints, ep);
|
|
|
|
|
|
|
|
return ep;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void register_endpoints(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
struct endpoint *ep;
|
2023-05-12 04:23:40 +08:00
|
|
|
size_t i;
|
2022-08-30 05:36:06 +08:00
|
|
|
|
2023-05-12 04:23:40 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(caps); i++) {
|
|
|
|
const struct capabilities *cap = &caps[i];
|
2022-08-30 05:36:06 +08:00
|
|
|
|
2023-05-12 04:23:40 +08:00
|
|
|
if (!media_supports_uuid(proxy, cap->uuid))
|
|
|
|
continue;
|
2022-08-30 05:36:06 +08:00
|
|
|
|
2023-05-12 04:23:40 +08:00
|
|
|
ep = endpoint_new(cap);
|
2023-05-12 07:43:34 +08:00
|
|
|
ep->preset = find_presets(ep->uuid, ep->codec, ep->vid,
|
|
|
|
ep->cid);
|
2023-05-12 04:23:40 +08:00
|
|
|
ep->max_transports = UINT8_MAX;
|
|
|
|
ep->auto_accept = true;
|
2023-06-07 16:22:58 +08:00
|
|
|
ep->iso_group = BT_ISO_QOS_GROUP_UNSET;
|
|
|
|
ep->iso_stream = BT_ISO_QOS_STREAM_UNSET;
|
2023-05-12 04:23:40 +08:00
|
|
|
endpoint_register(ep);
|
2022-08-30 05:36:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void media_added(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
medias = g_list_append(medias, proxy);
|
|
|
|
|
|
|
|
print_media(proxy, COLORED_NEW);
|
2022-08-30 05:36:06 +08:00
|
|
|
|
|
|
|
if (bt_shell_get_env("AUTO_REGISTER_ENDPOINT"))
|
|
|
|
register_endpoints(proxy);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
static void player_added(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
players = g_list_append(players, proxy);
|
|
|
|
|
|
|
|
if (default_player == NULL)
|
|
|
|
default_player = proxy;
|
|
|
|
|
|
|
|
print_player(proxy, COLORED_NEW);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_folder(GDBusProxy *proxy, const char *description)
|
|
|
|
{
|
|
|
|
const char *path;
|
|
|
|
|
|
|
|
path = g_dbus_proxy_get_path(proxy);
|
|
|
|
|
|
|
|
bt_shell_printf("%s%s%sFolder %s\n", description ? "[" : "",
|
|
|
|
description ? : "",
|
|
|
|
description ? "] " : "",
|
|
|
|
path);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void folder_added(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
folders = g_list_append(folders, proxy);
|
|
|
|
|
|
|
|
print_folder(proxy, COLORED_NEW);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_item(GDBusProxy *proxy, const char *description)
|
|
|
|
{
|
|
|
|
const char *path, *name;
|
|
|
|
DBusMessageIter iter;
|
|
|
|
|
|
|
|
path = g_dbus_proxy_get_path(proxy);
|
|
|
|
|
|
|
|
if (g_dbus_proxy_get_property(proxy, "Name", &iter))
|
|
|
|
dbus_message_iter_get_basic(&iter, &name);
|
|
|
|
else
|
|
|
|
name = "<unknown>";
|
|
|
|
|
|
|
|
bt_shell_printf("%s%s%sItem %s %s\n", description ? "[" : "",
|
|
|
|
description ? : "",
|
|
|
|
description ? "] " : "",
|
|
|
|
path, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void item_added(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
items = g_list_append(items, proxy);
|
|
|
|
|
|
|
|
print_item(proxy, COLORED_NEW);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void endpoint_added(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
endpoints = g_list_append(endpoints, proxy);
|
|
|
|
|
|
|
|
print_endpoint(proxy, COLORED_NEW);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
static void proxy_added(GDBusProxy *proxy, void *user_data)
|
|
|
|
{
|
|
|
|
const char *interface;
|
|
|
|
|
|
|
|
interface = g_dbus_proxy_get_interface(proxy);
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!strcmp(interface, BLUEZ_MEDIA_INTERFACE))
|
|
|
|
media_added(proxy);
|
|
|
|
else if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
|
2022-03-29 05:18:07 +08:00
|
|
|
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);
|
2022-04-12 07:51:12 +08:00
|
|
|
else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
|
|
|
|
endpoint_added(proxy);
|
2022-04-12 07:53:56 +08:00
|
|
|
else if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
|
|
|
|
transport_added(proxy);
|
2022-04-12 07:51:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void media_removed(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
print_media(proxy, COLORED_DEL);
|
|
|
|
|
|
|
|
medias = g_list_remove(medias, proxy);
|
2022-03-29 05:18:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void player_removed(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
print_player(proxy, COLORED_DEL);
|
|
|
|
|
|
|
|
if (default_player == proxy)
|
|
|
|
default_player = NULL;
|
|
|
|
|
|
|
|
players = g_list_remove(players, proxy);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void folder_removed(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
folders = g_list_remove(folders, proxy);
|
|
|
|
|
|
|
|
print_folder(proxy, COLORED_DEL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void item_removed(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
items = g_list_remove(items, proxy);
|
|
|
|
|
|
|
|
print_item(proxy, COLORED_DEL);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
static void endpoint_removed(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
endpoints = g_list_remove(endpoints, proxy);
|
|
|
|
|
|
|
|
print_endpoint(proxy, COLORED_DEL);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
static void transport_removed(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
transports = g_list_remove(transports, proxy);
|
|
|
|
|
|
|
|
print_transport(proxy, COLORED_DEL);
|
|
|
|
}
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
static void proxy_removed(GDBusProxy *proxy, void *user_data)
|
|
|
|
{
|
|
|
|
const char *interface;
|
|
|
|
|
|
|
|
interface = g_dbus_proxy_get_interface(proxy);
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!strcmp(interface, BLUEZ_MEDIA_INTERFACE))
|
|
|
|
media_removed(proxy);
|
2022-03-29 05:18:07 +08:00
|
|
|
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);
|
2022-04-12 07:51:12 +08:00
|
|
|
if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
|
|
|
|
endpoint_removed(proxy);
|
2022-04-12 07:53:56 +08:00
|
|
|
if (!strcmp(interface, BLUEZ_MEDIA_TRANSPORT_INTERFACE))
|
|
|
|
transport_removed(proxy);
|
2022-03-29 05:18:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void player_property_changed(GDBusProxy *proxy, const char *name,
|
|
|
|
DBusMessageIter *iter)
|
|
|
|
{
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
str = proxy_description(proxy, "Player", COLORED_CHG);
|
|
|
|
print_iter(str, name, iter);
|
|
|
|
g_free(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void folder_property_changed(GDBusProxy *proxy, const char *name,
|
|
|
|
DBusMessageIter *iter)
|
|
|
|
{
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
str = proxy_description(proxy, "Folder", COLORED_CHG);
|
|
|
|
print_iter(str, name, iter);
|
|
|
|
g_free(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void item_property_changed(GDBusProxy *proxy, const char *name,
|
|
|
|
DBusMessageIter *iter)
|
|
|
|
{
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
str = proxy_description(proxy, "Item", COLORED_CHG);
|
|
|
|
print_iter(str, name, iter);
|
|
|
|
g_free(str);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:51:12 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
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;
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
if (queue_find(ep->transports, match_str, path))
|
2022-04-12 07:53:56 +08:00
|
|
|
return ep;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
static GDBusProxy *find_link_by_proxy(GDBusProxy *proxy)
|
2022-09-29 04:54:47 +08:00
|
|
|
{
|
|
|
|
DBusMessageIter iter, array;
|
|
|
|
|
|
|
|
if (!g_dbus_proxy_get_property(proxy, "Links", &iter))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&iter, &array);
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&array) ==
|
|
|
|
DBUS_TYPE_OBJECT_PATH) {
|
|
|
|
const char *transport;
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&array, &transport);
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
proxy = g_dbus_proxy_lookup(transports, NULL, transport,
|
|
|
|
BLUEZ_MEDIA_TRANSPORT_INTERFACE);
|
|
|
|
if (proxy)
|
|
|
|
return proxy;
|
2022-09-29 04:54:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
static void transport_close(struct transport *transport)
|
|
|
|
{
|
|
|
|
if (transport->fd < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
close(transport->fd);
|
2022-12-13 07:34:50 +08:00
|
|
|
transport->fd = -1;
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
free(transport->filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void transport_free(void *data)
|
|
|
|
{
|
|
|
|
struct transport *transport = data;
|
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
io_destroy(transport->timer_io);
|
2022-06-21 06:22:52 +08:00
|
|
|
io_destroy(transport->io);
|
|
|
|
free(transport);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
static bool transport_disconnected(struct io *io, void *user_data)
|
|
|
|
{
|
2022-06-21 06:22:52 +08:00
|
|
|
struct transport *transport = user_data;
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
bt_shell_printf("Transport fd disconnected\n");
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
if (queue_remove(ios, transport))
|
|
|
|
transport_free(transport);
|
2022-04-12 07:53:56 +08:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool transport_recv(struct io *io, void *user_data)
|
|
|
|
{
|
2022-06-21 06:22:52 +08:00
|
|
|
struct transport *transport = user_data;
|
2022-04-12 07:53:56 +08:00
|
|
|
uint8_t buf[1024];
|
2022-04-26 07:58:54 +08:00
|
|
|
int ret, len;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-12-14 08:09:22 +08:00
|
|
|
bt_shell_echo("[seq %d] recv: %u bytes", transport->seq, ret);
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
transport->seq++;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2022-12-14 08:09:22 +08:00
|
|
|
if (transport->fd >= 0) {
|
2022-06-21 06:22:52 +08:00
|
|
|
len = write(transport->fd, buf, ret);
|
2022-04-26 07:58:54 +08:00
|
|
|
if (len < 0)
|
|
|
|
bt_shell_printf("Unable to write: %s (%d)",
|
|
|
|
strerror(errno), -errno);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
static void transport_new(GDBusProxy *proxy, int sk, uint16_t mtu[2])
|
|
|
|
{
|
|
|
|
struct transport *transport;
|
|
|
|
|
|
|
|
transport = new0(struct transport, 1);
|
|
|
|
transport->proxy = proxy;
|
|
|
|
transport->sk = sk;
|
|
|
|
transport->mtu[0] = mtu[0];
|
|
|
|
transport->mtu[1] = mtu[1];
|
|
|
|
transport->io = io_new(sk);
|
|
|
|
transport->fd = -1;
|
|
|
|
|
|
|
|
io_set_disconnect_handler(transport->io, transport_disconnected,
|
|
|
|
transport, NULL);
|
|
|
|
io_set_read_handler(transport->io, transport_recv, transport, NULL);
|
|
|
|
|
|
|
|
if (!ios)
|
|
|
|
ios = queue_new();
|
|
|
|
|
|
|
|
queue_push_tail(ios, transport);
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
static void ep_set_acquiring(struct endpoint *ep, GDBusProxy *proxy, bool value)
|
|
|
|
{
|
|
|
|
bt_shell_printf("Transport %s %s\n", g_dbus_proxy_get_path(proxy),
|
|
|
|
value ? "acquiring" : "acquiring complete");
|
|
|
|
|
|
|
|
if (value && !ep->acquiring)
|
|
|
|
ep->acquiring = queue_new();
|
|
|
|
|
|
|
|
if (value)
|
|
|
|
queue_push_tail(ep->acquiring, proxy);
|
|
|
|
else
|
|
|
|
queue_remove(ep->acquiring, proxy);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void transport_set_acquiring(GDBusProxy *proxy, bool value)
|
|
|
|
{
|
|
|
|
struct endpoint *ep;
|
|
|
|
GDBusProxy *link;
|
|
|
|
|
|
|
|
ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy));
|
|
|
|
if (!ep)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ep_set_acquiring(ep, proxy, value);
|
|
|
|
|
|
|
|
link = find_link_by_proxy(proxy);
|
|
|
|
if (link) {
|
|
|
|
ep = find_ep_by_transport(g_dbus_proxy_get_path(link));
|
|
|
|
if (!ep)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ep_set_acquiring(ep, link, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
static void acquire_reply(DBusMessage *message, void *user_data)
|
|
|
|
{
|
2022-06-21 06:22:52 +08:00
|
|
|
GDBusProxy *proxy = user_data;
|
2022-04-12 07:53:56 +08:00
|
|
|
DBusError error;
|
2022-06-21 06:22:52 +08:00
|
|
|
int sk;
|
|
|
|
uint16_t mtu[2];
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
transport_set_acquiring(proxy, false);
|
2022-09-29 04:54:47 +08:00
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
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,
|
2022-06-21 06:22:52 +08:00
|
|
|
DBUS_TYPE_UNIX_FD, &sk,
|
|
|
|
DBUS_TYPE_UINT16, &mtu[0],
|
|
|
|
DBUS_TYPE_UINT16, &mtu[1],
|
2022-04-12 07:53:56 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
bt_shell_printf("Acquire successful: fd %d MTU %u:%u\n", sk, mtu[0],
|
|
|
|
mtu[1]);
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
transport_new(proxy, sk, mtu);
|
2022-04-12 07:53:56 +08:00
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
static void prompt_acquire(const char *input, void *user_data)
|
2022-04-12 07:53:56 +08:00
|
|
|
{
|
|
|
|
GDBusProxy *proxy = user_data;
|
|
|
|
|
|
|
|
if (!strcasecmp(input, "y") || !strcasecmp(input, "yes")) {
|
2022-09-29 04:54:47 +08:00
|
|
|
if (g_dbus_proxy_method_call(proxy, "Acquire", NULL,
|
2023-06-20 04:30:31 +08:00
|
|
|
acquire_reply, proxy, NULL)) {
|
|
|
|
transport_set_acquiring(proxy, true);
|
2022-09-29 04:54:47 +08:00
|
|
|
return;
|
2023-06-20 04:30:31 +08:00
|
|
|
}
|
2022-09-29 04:54:47 +08:00
|
|
|
bt_shell_printf("Failed acquire transport\n");
|
|
|
|
}
|
2023-06-20 04:30:31 +08:00
|
|
|
}
|
2022-09-29 04:54:47 +08:00
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
static void transport_acquire(GDBusProxy *proxy, bool prompt)
|
|
|
|
{
|
|
|
|
struct endpoint *ep;
|
|
|
|
GDBusProxy *link;
|
|
|
|
|
|
|
|
/* only attempt to acquire if transport is configured with a local
|
|
|
|
* endpoint.
|
|
|
|
*/
|
2022-09-29 04:54:47 +08:00
|
|
|
ep = find_ep_by_transport(g_dbus_proxy_get_path(proxy));
|
2023-06-20 04:30:31 +08:00
|
|
|
if (!ep || queue_find(ep->acquiring, NULL, proxy))
|
|
|
|
return;
|
|
|
|
|
|
|
|
link = find_link_by_proxy(proxy);
|
|
|
|
if (link) {
|
|
|
|
ep = find_ep_by_transport(g_dbus_proxy_get_path(link));
|
|
|
|
/* if link already acquiring wait it to be complete */
|
|
|
|
if (!ep || queue_find(ep->acquiring, NULL, link))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep->auto_accept || !prompt) {
|
|
|
|
if (!prompt)
|
|
|
|
bt_shell_printf("auto acquiring...\n");
|
|
|
|
if (!g_dbus_proxy_method_call(proxy, "Acquire", NULL,
|
|
|
|
acquire_reply, proxy, NULL)) {
|
|
|
|
bt_shell_printf("failed acquire transport\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
transport_set_acquiring(proxy, true);
|
|
|
|
return;
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
2023-06-20 04:30:31 +08:00
|
|
|
|
|
|
|
bt_shell_prompt_input(g_dbus_proxy_get_path(proxy), "acquire (yes/no):",
|
|
|
|
prompt_acquire, proxy);
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void transport_property_changed(GDBusProxy *proxy, const char *name,
|
|
|
|
DBusMessageIter *iter)
|
|
|
|
{
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
transport_acquire(proxy, true);
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
static void property_changed(GDBusProxy *proxy, const char *name,
|
|
|
|
DBusMessageIter *iter, void *user_data)
|
|
|
|
{
|
|
|
|
const char *interface;
|
|
|
|
|
|
|
|
interface = g_dbus_proxy_get_interface(proxy);
|
|
|
|
|
|
|
|
if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE))
|
|
|
|
player_property_changed(proxy, name, iter);
|
|
|
|
else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE))
|
|
|
|
folder_property_changed(proxy, name, iter);
|
|
|
|
else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE))
|
|
|
|
item_property_changed(proxy, name, iter);
|
2022-04-12 07:51:12 +08:00
|
|
|
else if (!strcmp(interface, BLUEZ_MEDIA_ENDPOINT_INTERFACE))
|
|
|
|
endpoint_property_changed(proxy, name, iter);
|
2022-04-12 07:53:56 +08:00
|
|
|
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");
|
|
|
|
|
2023-09-28 05:53:45 +08:00
|
|
|
print_property(proxy, "QoS");
|
2020-08-05 08:19:43 +08:00
|
|
|
print_property(proxy, "Location");
|
|
|
|
print_property(proxy, "Metadata");
|
|
|
|
print_property(proxy, "Links");
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
static bool match_proxy(const void *data, const void *user_data)
|
|
|
|
{
|
|
|
|
const struct transport *transport = data;
|
|
|
|
const GDBusProxy *proxy = user_data;
|
|
|
|
|
|
|
|
return transport->proxy == proxy;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct transport *find_transport(GDBusProxy *proxy)
|
|
|
|
{
|
|
|
|
return queue_find(ios, match_proxy, proxy);
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
static void cmd_acquire_transport(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
GDBusProxy *proxy;
|
2020-08-05 08:19:43 +08:00
|
|
|
int i;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
proxy = g_dbus_proxy_lookup(transports, NULL, argv[i],
|
2022-04-12 07:53:56 +08:00
|
|
|
BLUEZ_MEDIA_TRANSPORT_INTERFACE);
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!proxy) {
|
|
|
|
bt_shell_printf("Transport %s not found\n", argv[i]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
if (find_transport(proxy)) {
|
|
|
|
bt_shell_printf("Transport %s already acquired\n",
|
|
|
|
argv[i]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-06-21 06:22:52 +08:00
|
|
|
|
2023-06-20 04:30:31 +08:00
|
|
|
transport_acquire(proxy, false);
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void release_reply(DBusMessage *message, void *user_data)
|
|
|
|
{
|
2022-06-21 06:22:52 +08:00
|
|
|
struct transport *transport = user_data;
|
2022-04-12 07:53:56 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
if (queue_remove(ios, transport))
|
|
|
|
transport_free(transport);
|
2022-04-12 07:53:56 +08:00
|
|
|
|
|
|
|
bt_shell_printf("Release successful\n");
|
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
2022-03-29 05:18:07 +08:00
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
static void cmd_release_transport(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
GDBusProxy *proxy;
|
2020-08-05 08:19:43 +08:00
|
|
|
int i;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
struct transport *transport;
|
|
|
|
|
|
|
|
proxy = g_dbus_proxy_lookup(transports, NULL, argv[i],
|
2022-04-12 07:53:56 +08:00
|
|
|
BLUEZ_MEDIA_TRANSPORT_INTERFACE);
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!proxy) {
|
|
|
|
bt_shell_printf("Transport %s not found\n", argv[1]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
transport = find_transport(proxy);
|
|
|
|
if (!transport) {
|
|
|
|
bt_shell_printf("Transport %s not acquired\n", argv[i]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-06-21 06:22:52 +08:00
|
|
|
|
2020-08-05 08:19:43 +08:00
|
|
|
if (!g_dbus_proxy_method_call(proxy, "Release", NULL,
|
2022-06-21 06:22:52 +08:00
|
|
|
release_reply, transport, NULL)) {
|
2020-08-05 08:19:43 +08:00
|
|
|
bt_shell_printf("Failed release transport\n");
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2022-04-26 07:58:54 +08:00
|
|
|
static int open_file(const char *filename, int flags)
|
2022-04-12 07:53:56 +08:00
|
|
|
{
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
bt_shell_printf("Opening %s ...\n", filename);
|
|
|
|
|
2022-04-26 07:58:54 +08:00
|
|
|
if (flags & O_CREAT)
|
|
|
|
fd = open(filename, flags, 0755);
|
|
|
|
else
|
|
|
|
fd = open(filename, flags);
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
if (fd <= 0)
|
|
|
|
bt_shell_printf("Can't open file %s: %s\n", filename,
|
|
|
|
strerror(errno));
|
|
|
|
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
static int elapsed_time(bool reset, int *secs, int *nsecs)
|
2020-09-17 05:07:44 +08:00
|
|
|
{
|
2022-12-13 07:34:50 +08:00
|
|
|
static struct timespec start;
|
|
|
|
struct timespec curr;
|
2020-09-17 05:07:44 +08:00
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
if (reset) {
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) {
|
|
|
|
bt_shell_printf("clock_gettime: %s (%d)",
|
|
|
|
strerror(errno), errno);
|
|
|
|
return -errno;
|
|
|
|
}
|
2020-09-17 05:07:44 +08:00
|
|
|
}
|
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &curr) < 0) {
|
|
|
|
bt_shell_printf("clock_gettime: %s (%d)", strerror(errno),
|
|
|
|
errno);
|
|
|
|
return -errno;
|
2022-12-10 01:44:41 +08:00
|
|
|
}
|
2020-09-17 05:07:44 +08:00
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
*secs = curr.tv_sec - start.tv_sec;
|
|
|
|
*nsecs = curr.tv_nsec - start.tv_nsec;
|
|
|
|
if (*nsecs < 0) {
|
|
|
|
(*secs)--;
|
|
|
|
*nsecs += 1000000000;
|
2020-09-17 05:07:44 +08:00
|
|
|
}
|
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
return 0;
|
2020-09-17 05:07:44 +08:00
|
|
|
}
|
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
static int transport_send_seq(struct transport *transport, int fd, uint32_t num)
|
2022-04-12 07:53:56 +08:00
|
|
|
{
|
|
|
|
uint8_t *buf;
|
2022-12-13 07:34:50 +08:00
|
|
|
uint32_t i;
|
2020-09-17 05:07:44 +08:00
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
if (!num)
|
|
|
|
return 0;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
buf = malloc(transport->mtu[1]);
|
2022-12-13 07:34:50 +08:00
|
|
|
if (!buf)
|
2022-04-12 07:53:56 +08:00
|
|
|
return -ENOMEM;
|
2020-09-17 05:07:44 +08:00
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
for (i = 0; i < num; i++, transport->seq++) {
|
2022-04-12 07:53:56 +08:00
|
|
|
ssize_t ret;
|
2022-12-13 07:34:50 +08:00
|
|
|
int secs = 0, nsecs = 0;
|
2022-12-15 05:01:07 +08:00
|
|
|
off_t offset;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
ret = read(fd, buf, transport->mtu[1]);
|
2022-04-12 07:53:56 +08:00
|
|
|
if (ret <= 0) {
|
|
|
|
if (ret < 0)
|
|
|
|
bt_shell_printf("read failed: %s (%d)",
|
|
|
|
strerror(errno), errno);
|
2022-12-13 07:34:50 +08:00
|
|
|
free(buf);
|
2022-04-12 07:53:56 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
ret = send(transport->sk, buf, ret, 0);
|
2022-04-12 07:53:56 +08:00
|
|
|
if (ret <= 0) {
|
2022-12-13 07:34:50 +08:00
|
|
|
bt_shell_printf("send failed: %s (%d)",
|
2022-04-12 07:53:56 +08:00
|
|
|
strerror(errno), errno);
|
2022-12-13 07:34:50 +08:00
|
|
|
free(buf);
|
2022-04-12 07:53:56 +08:00
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
2022-12-13 07:34:50 +08:00
|
|
|
elapsed_time(!transport->seq, &secs, &nsecs);
|
|
|
|
|
2022-12-15 05:01:07 +08:00
|
|
|
if (!transport->seq && fstat(fd, &transport->stat) < 0) {
|
|
|
|
bt_shell_printf("fstat failed: %s (%d)",
|
|
|
|
strerror(errno), errno);
|
|
|
|
free(buf);
|
|
|
|
return -errno;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = lseek(fd, 0, SEEK_CUR);
|
|
|
|
|
|
|
|
bt_shell_echo("[seq %d %d.%03ds] send: %zd/%zd bytes",
|
2022-12-13 07:34:50 +08:00
|
|
|
transport->seq, secs,
|
|
|
|
(nsecs + 500000) / 1000000,
|
2022-12-15 05:01:07 +08:00
|
|
|
offset, transport->stat.st_size);
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
free(buf);
|
2022-12-13 07:34:50 +08:00
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool transport_timer_read(struct io *io, void *user_data)
|
|
|
|
{
|
|
|
|
struct transport *transport = user_data;
|
|
|
|
struct bt_iso_qos qos;
|
|
|
|
socklen_t len;
|
|
|
|
int ret, fd;
|
|
|
|
uint32_t num;
|
|
|
|
uint64_t exp;
|
|
|
|
|
|
|
|
if (transport->fd < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
fd = io_get_fd(io);
|
|
|
|
ret = read(fd, &exp, sizeof(exp));
|
|
|
|
if (ret < 0) {
|
|
|
|
bt_shell_printf("Failed to read: %s (%d)\n", strerror(errno),
|
|
|
|
-errno);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read QoS if available */
|
|
|
|
memset(&qos, 0, sizeof(qos));
|
|
|
|
len = sizeof(qos);
|
|
|
|
if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos,
|
|
|
|
&len) < 0) {
|
|
|
|
bt_shell_printf("Failed to getsockopt(BT_ISO_QOS): %s (%d)\n",
|
|
|
|
strerror(errno), -errno);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* num of packets = latency (ms) / interval (us) */
|
2023-03-31 23:39:27 +08:00
|
|
|
num = (qos.ucast.out.latency * 1000 / qos.ucast.out.interval);
|
2022-12-13 07:34:50 +08:00
|
|
|
|
|
|
|
ret = transport_send_seq(transport, transport->fd, num);
|
|
|
|
if (ret < 0) {
|
|
|
|
bt_shell_printf("Unable to send: %s (%d)\n",
|
|
|
|
strerror(-ret), ret);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
transport_close(transport);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int transport_send(struct transport *transport, int fd,
|
|
|
|
struct bt_iso_qos *qos)
|
|
|
|
{
|
|
|
|
struct itimerspec ts;
|
|
|
|
int timer_fd;
|
|
|
|
|
|
|
|
transport->seq = 0;
|
|
|
|
|
|
|
|
if (!qos)
|
|
|
|
return transport_send_seq(transport, fd, UINT32_MAX);
|
|
|
|
|
|
|
|
if (transport->fd >= 0)
|
|
|
|
return -EALREADY;
|
|
|
|
|
|
|
|
timer_fd = timerfd_create(CLOCK_MONOTONIC, 0);
|
|
|
|
if (timer_fd < 0)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
memset(&ts, 0, sizeof(ts));
|
2023-03-31 23:39:27 +08:00
|
|
|
ts.it_value.tv_nsec = qos->ucast.out.latency * 1000000;
|
|
|
|
ts.it_interval.tv_nsec = qos->ucast.out.latency * 1000000;
|
2022-12-13 07:34:50 +08:00
|
|
|
|
|
|
|
if (timerfd_settime(timer_fd, TFD_TIMER_ABSTIME, &ts, NULL) < 0)
|
|
|
|
return -errno;
|
|
|
|
|
|
|
|
transport->fd = fd;
|
|
|
|
|
|
|
|
transport->timer_io = io_new(timer_fd);
|
|
|
|
|
|
|
|
io_set_read_handler(transport->timer_io, transport_timer_read,
|
|
|
|
transport, NULL);
|
|
|
|
|
|
|
|
return transport_send_seq(transport, fd, 1);
|
2022-04-12 07:53:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cmd_send_transport(int argc, char *argv[])
|
|
|
|
{
|
2022-06-21 06:22:52 +08:00
|
|
|
GDBusProxy *proxy;
|
|
|
|
struct transport *transport;
|
2023-02-28 09:07:43 +08:00
|
|
|
int fd = -1, err;
|
2020-09-17 05:07:44 +08:00
|
|
|
struct bt_iso_qos qos;
|
|
|
|
socklen_t len;
|
2023-02-28 09:07:43 +08:00
|
|
|
int i;
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2023-02-28 09:07:43 +08:00
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
proxy = g_dbus_proxy_lookup(transports, NULL, argv[i],
|
2022-06-21 06:22:52 +08:00
|
|
|
BLUEZ_MEDIA_TRANSPORT_INTERFACE);
|
2023-02-28 09:07:43 +08:00
|
|
|
if (!proxy) {
|
|
|
|
bt_shell_printf("Transport %s not found\n", argv[i]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-06-21 06:22:52 +08:00
|
|
|
|
2023-02-28 09:07:43 +08:00
|
|
|
transport = find_transport(proxy);
|
|
|
|
if (!transport) {
|
|
|
|
bt_shell_printf("Transport %s not acquired\n", argv[i]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-06-21 06:22:52 +08:00
|
|
|
|
2023-02-28 09:07:43 +08:00
|
|
|
if (transport->sk < 0) {
|
|
|
|
bt_shell_printf("No Transport Socked found\n");
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2023-02-28 09:07:43 +08:00
|
|
|
if (i + 1 < argc) {
|
|
|
|
fd = open_file(argv[++i], O_RDONLY);
|
|
|
|
if (fd < 0)
|
|
|
|
return bt_shell_noninteractive_quit(
|
|
|
|
EXIT_FAILURE);
|
|
|
|
}
|
2022-04-12 07:53:56 +08:00
|
|
|
|
2023-02-28 09:07:43 +08:00
|
|
|
bt_shell_printf("Sending ...\n");
|
2022-06-21 06:22:52 +08:00
|
|
|
|
2023-02-28 09:07:43 +08:00
|
|
|
/* Read QoS if available */
|
|
|
|
memset(&qos, 0, sizeof(qos));
|
|
|
|
len = sizeof(qos);
|
|
|
|
if (getsockopt(transport->sk, SOL_BLUETOOTH, BT_ISO_QOS, &qos,
|
2023-06-20 04:30:31 +08:00
|
|
|
&len) < 0) {
|
|
|
|
bt_shell_printf("Unable to getsockopt(BT_ISO_QOS): %s",
|
|
|
|
strerror(errno));
|
2023-02-28 09:07:43 +08:00
|
|
|
err = transport_send(transport, fd, NULL);
|
2023-06-20 04:30:31 +08:00
|
|
|
} else
|
2023-02-28 09:07:43 +08:00
|
|
|
err = transport_send(transport, fd, &qos);
|
|
|
|
|
|
|
|
if (err < 0) {
|
|
|
|
bt_shell_printf("Unable to send: %s (%d)",
|
|
|
|
strerror(-err), -err);
|
|
|
|
close(fd);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-12-13 07:34:50 +08:00
|
|
|
}
|
2022-04-12 07:53:56 +08:00
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
|
|
|
|
static void cmd_receive_transport(int argc, char *argv[])
|
2022-04-26 07:58:54 +08:00
|
|
|
{
|
2022-06-21 06:22:52 +08:00
|
|
|
GDBusProxy *proxy;
|
|
|
|
struct transport *transport;
|
2022-04-26 07:58:54 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
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);
|
|
|
|
}
|
2022-04-26 07:58:54 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
transport = find_transport(proxy);
|
|
|
|
if (!transport) {
|
|
|
|
bt_shell_printf("Transport %s not acquired\n", argv[1]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
2022-04-26 07:58:54 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
if (transport->sk < 0) {
|
|
|
|
bt_shell_printf("No Transport Socked found\n");
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
2022-04-26 07:58:54 +08:00
|
|
|
}
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
transport_close(transport);
|
2022-04-26 07:58:54 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
transport->fd = open_file(argv[2], O_RDWR | O_CREAT);
|
|
|
|
if (transport->fd < 0)
|
2022-04-26 07:58:54 +08:00
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
transport->filename = strdup(argv[2]);
|
2022-04-26 07:58:54 +08:00
|
|
|
|
2022-06-21 06:22:52 +08:00
|
|
|
bt_shell_printf("Filename: %s\n", transport->filename);
|
2022-04-26 07:58:54 +08:00
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
2022-04-23 07:01:29 +08:00
|
|
|
static void volume_callback(const DBusError *error, void *user_data)
|
|
|
|
{
|
|
|
|
if (dbus_error_is_set(error)) {
|
|
|
|
bt_shell_printf("Failed to set Volume: %s\n", error->name);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
bt_shell_printf("Changing Volume succeeded\n");
|
|
|
|
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cmd_volume_transport(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
GDBusProxy *proxy;
|
|
|
|
char *endptr = NULL;
|
|
|
|
int volume;
|
|
|
|
|
|
|
|
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 (argc == 2) {
|
|
|
|
print_property(proxy, "Volume");
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
volume = strtol(argv[2], &endptr, 0);
|
|
|
|
if (!endptr || *endptr != '\0' || volume > UINT16_MAX) {
|
|
|
|
bt_shell_printf("Invalid argument: %s\n", argv[2]);
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!g_dbus_proxy_set_property_basic(proxy, "Volume", DBUS_TYPE_UINT16,
|
|
|
|
&volume, volume_callback,
|
|
|
|
NULL, NULL)) {
|
|
|
|
bt_shell_printf("Failed release transport\n");
|
|
|
|
return bt_shell_noninteractive_quit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-12 07:53:56 +08:00
|
|
|
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 },
|
2020-08-05 08:19:43 +08:00
|
|
|
{ "acquire", "<transport> [transport1...]", cmd_acquire_transport,
|
2022-04-12 07:53:56 +08:00
|
|
|
"Acquire Transport",
|
|
|
|
transport_generator },
|
2020-08-05 08:19:43 +08:00
|
|
|
{ "release", "<transport> [transport1...]", cmd_release_transport,
|
2022-04-12 07:53:56 +08:00
|
|
|
"Release Transport",
|
|
|
|
transport_generator },
|
2023-02-28 09:07:43 +08:00
|
|
|
{ "send", "<transport> <filename> [transport1...]",
|
|
|
|
cmd_send_transport,
|
2022-12-15 05:45:20 +08:00
|
|
|
"Send contents of a file",
|
|
|
|
transport_generator },
|
2022-06-21 06:22:52 +08:00
|
|
|
{ "receive", "<transport> [filename]", cmd_receive_transport,
|
2022-12-15 05:45:20 +08:00
|
|
|
"Get/Set file to receive",
|
|
|
|
transport_generator },
|
2022-04-23 07:01:29 +08:00
|
|
|
{ "volume", "<transport> [value]", cmd_volume_transport,
|
|
|
|
"Get/Set transport volume",
|
|
|
|
transport_generator },
|
2022-04-12 07:53:56 +08:00
|
|
|
{} },
|
|
|
|
};
|
|
|
|
|
2022-03-29 05:18:07 +08:00
|
|
|
static GDBusClient *client;
|
|
|
|
|
|
|
|
void player_add_submenu(void)
|
|
|
|
{
|
|
|
|
bt_shell_add_submenu(&player_menu);
|
2022-04-12 07:51:12 +08:00
|
|
|
bt_shell_add_submenu(&endpoint_menu);
|
2022-04-12 07:53:56 +08:00
|
|
|
bt_shell_add_submenu(&transport_menu);
|
2022-03-29 05:18:07 +08:00
|
|
|
|
|
|
|
dbus_conn = bt_shell_get_env("DBUS_CONNECTION");
|
|
|
|
if (!dbus_conn || client)
|
|
|
|
return;
|
|
|
|
|
|
|
|
client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez");
|
|
|
|
|
|
|
|
g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed,
|
|
|
|
property_changed, NULL);
|
2022-04-12 07:51:12 +08:00
|
|
|
g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL);
|
2022-03-29 05:18:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void player_remove_submenu(void)
|
|
|
|
{
|
|
|
|
g_dbus_client_unref(client);
|
2022-06-21 06:22:52 +08:00
|
|
|
queue_destroy(ios, transport_free);
|
2022-03-29 05:18:07 +08:00
|
|
|
}
|