Skip to content

Commit

Permalink
cli: add verbose option to list-pcms and monitor commands
Browse files Browse the repository at this point in the history
Also switches stdout to line-buffered mode in the monitor command.
This makes the monitor command easier to use in scripts where it
typically writes to a pipe or fifo.
  • Loading branch information
borine authored and arkq committed Oct 30, 2021
1 parent 5e781ba commit 57ac831
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 40 deletions.
32 changes: 29 additions & 3 deletions doc/bluealsa-cli.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ bluealsa-cli
a simple command line interface for the BlueALSA D-Bus API
----------------------------------------------------------

:Date: July 2021
:Date: October 2021
:Manual section: 1
:Manual group: General Commands Manual
:Version: $VERSION$
Expand Down Expand Up @@ -42,6 +42,9 @@ OPTIONS
-q, --quiet
Do not print any error messages.

-v, --verbose
Include extra information in normal output - see COMMANDS_ for details.

COMMANDS
========

Expand All @@ -51,6 +54,10 @@ list-services
list-pcms
Print a list of BlueALSA PCM D-Bus paths, one per line.

If the *--verbose* option is given then the properties of each connected
PCM are printed after each path, one per line, in the same format as the
**info** command.

info *PCM_PATH*
Print the properties and available codecs of the given PCM.
The properties are printed one per line, in the format
Expand Down Expand Up @@ -107,13 +114,32 @@ soft-volume *PCM_PATH* [y|n]
PCM.

monitor
Listen for ``PCMAdded`` and ``PCMRemoved`` signals and print a message on
standard output for each one received. Output lines are formed as:
Listen for D-Bus ``PCMAdded`` and ``PCMRemoved`` signals and also detect
service running and service stopped events. Print a line on
standard output for each one received. PCM event output lines are formed as:

``PCMAdded PCM_PATH``

``PCMRemoved PCM_PATH``

Service start/stop event lines are formed as:

``ServiceRunning SERVICE_NAME``

``ServiceStopped SERVICE_NAME``

If the *--verbose* option is given then the properties of each added PCM are
printed after the PCMAdded line, one per line, in the same format as the
**info** command. In this case a blank line is printed after the last
property.

When the monitor starts, it begins by printing a ``ServiceRunning`` or
``ServiceStopped`` message according to the current state of the service.

Note that the **bluealsa(8)** service does not emit ``PCMRemoved`` signals
when the service stops. The ``ServiceStopped`` message here can be used
to indicate that all remaining PCMs have been removed.

open *PCM_PATH*
Transfer raw audio frames to or from the given PCM. For sink PCMs
the frames are read from standard input and written to the PCM. For
Expand Down
2 changes: 1 addition & 1 deletion misc/bash-completion/bluealsa
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ _bluealsa_cli() {
return
;;
$((COMP_CWORD - 1)))
# if previous word was command then list available paths
# if previous word was path command then list available paths
if [[ "$path_commands" =~ "$prev" ]]; then
COMPREPLY=( $(compgen -W "$("$1" $dbus_opt list-pcms 2>/dev/null)" -- $cur) )
return
Expand Down
179 changes: 143 additions & 36 deletions utils/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
static struct ba_dbus_ctx dbus_ctx;
static char dbus_ba_service[32] = BLUEALSA_SERVICE;
static bool quiet = false;
static bool verbose = false;

static const char *transport_code_to_string(int transport_code) {
switch (transport_code) {
Expand Down Expand Up @@ -183,54 +184,82 @@ static bool print_codecs(const char *path, DBusError *err) {
return result;
}

static void print_adapters(struct ba_service_props *props) {
static void print_adapters(const struct ba_service_props *props) {
printf("Adapters:");
for (size_t i = 0; i < ARRAYSIZE(props->adapters); i++)
if (strlen(props->adapters[i]) > 0)
printf(" %s", props->adapters[i]);
printf("\n");
}

static void print_volume(struct ba_pcm *pcm) {
static void print_volume(const struct ba_pcm *pcm) {
if (pcm->channels == 2)
printf("Volume: L: %u R: %u\n", pcm->volume.ch1_volume, pcm->volume.ch2_volume);
else
printf("Volume: %u\n", pcm->volume.ch1_volume);
}

static void print_mute(struct ba_pcm *pcm) {
static void print_mute(const struct ba_pcm *pcm) {
if (pcm->channels == 2)
printf("Muted: L: %c R: %c\n",
pcm->volume.ch1_muted ? 'Y' : 'N', pcm->volume.ch2_muted ? 'Y' : 'N');
else
printf("Muted: %c\n", pcm->volume.ch1_muted ? 'Y' : 'N');
}

static int cmd_list_services(int argc, char *argv[]) {
static void print_properties(const struct ba_pcm *pcm, DBusError *err) {
printf("Device: %s\n", pcm->device_path);
printf("Sequence: %u\n", pcm->sequence);
printf("Transport: %s\n", transport_code_to_string(pcm->transport));
printf("Mode: %s\n", pcm_mode_to_string(pcm->mode));
printf("Format: %s\n", pcm_format_to_string(pcm->format));
printf("Channels: %d\n", pcm->channels);
printf("Sampling: %d Hz\n", pcm->sampling);
print_codecs(pcm->pcm_path, err);
printf("Selected codec: %s\n", pcm->codec);
printf("Delay: %#.1f ms\n", (double)pcm->delay / 10);
printf("SoftVolume: %s\n", pcm->soft_volume ? "Y" : "N");
print_volume(pcm);
print_mute(pcm);
}

if (argc != 1) {
cmd_print_error("Invalid number of arguments");
return EXIT_FAILURE;
typedef bool (*get_services_cb)(const char *name, void *data);

static bool print_bluealsa_service(const char *name, void *data) {
(void) data;
if (strncmp(name, BLUEALSA_SERVICE, sizeof(BLUEALSA_SERVICE) - 1) == 0)
printf("%s\n", name);
return true;
}

static bool test_bluealsa_service(const char *name, void *data) {
bool *result = data;
if (strcmp(name, BLUEALSA_SERVICE) == 0) {
*result = true;
return false;
}
*result = false;
return true;
}

static void get_services(get_services_cb func, void *data, DBusError *err) {

DBusMessage *msg = NULL, *rep = NULL;
DBusError err = DBUS_ERROR_INIT;
int result = EXIT_FAILURE;

if ((msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "ListNames")) == NULL) {
dbus_set_error(&err, DBUS_ERROR_NO_MEMORY, NULL);
dbus_set_error(err, DBUS_ERROR_NO_MEMORY, NULL);
goto fail;
}

if ((rep = dbus_connection_send_with_reply_and_block(dbus_ctx.conn,
msg, DBUS_TIMEOUT_USE_DEFAULT, &err)) == NULL) {
msg, DBUS_TIMEOUT_USE_DEFAULT, err)) == NULL) {
goto fail;
}

DBusMessageIter iter;
if (!dbus_message_iter_init(rep, &iter)) {
dbus_set_error(&err, DBUS_ERROR_INVALID_SIGNATURE, "Empty response message");
dbus_set_error(err, DBUS_ERROR_INVALID_SIGNATURE, "Empty response message");
goto fail;
}

Expand All @@ -241,29 +270,42 @@ static int cmd_list_services(int argc, char *argv[]) {

if (dbus_message_iter_get_arg_type(&iter_names) != DBUS_TYPE_STRING) {
char *signature = dbus_message_iter_get_signature(&iter);
dbus_set_error(&err, DBUS_ERROR_INVALID_SIGNATURE,
dbus_set_error(err, DBUS_ERROR_INVALID_SIGNATURE,
"Incorrect signature: %s != as", signature);
dbus_free(signature);
goto fail;
}

const char *name;
dbus_message_iter_get_basic(&iter_names, &name);
if (strncmp(name, BLUEALSA_SERVICE, sizeof(BLUEALSA_SERVICE) - 1) == 0)
printf("%s\n", name);
if (!func(name, data)) {
break;
}

}

result = EXIT_SUCCESS;

fail:
if (dbus_error_is_set(&err))
cmd_print_error("D-Bus error: %s", err.message);
if (msg != NULL)
dbus_message_unref(msg);
if (rep != NULL)
dbus_message_unref(rep);
return result;
}

static int cmd_list_services(int argc, char *argv[]) {

if (argc != 1) {
cmd_print_error("Invalid number of arguments");
return EXIT_FAILURE;
}

DBusError err = DBUS_ERROR_INIT;
get_services(print_bluealsa_service, NULL, &err);
if (dbus_error_is_set(&err)) {
cmd_print_error("D-Bus error: %s", err.message);
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

static int cmd_list_pcms(int argc, char *argv[]) {
Expand All @@ -283,8 +325,11 @@ static int cmd_list_pcms(int argc, char *argv[]) {
}

size_t i;
for (i = 0; i < pcms_count; i++)
for (i = 0; i < pcms_count; i++) {
printf("%s\n", pcms[i].pcm_path);
if (verbose)
print_properties(&pcms[i], &err);
}

free(pcms);
return EXIT_SUCCESS;
Expand Down Expand Up @@ -328,20 +373,7 @@ static int cmd_info(int argc, char *argv[]) {
return EXIT_FAILURE;
}

printf("Device: %s\n", pcm.device_path);
printf("Sequence: %u\n", pcm.sequence);
printf("Transport: %s\n", transport_code_to_string(pcm.transport));
printf("Mode: %s\n", pcm_mode_to_string(pcm.mode));
printf("Format: %s\n", pcm_format_to_string(pcm.format));
printf("Channels: %d\n", pcm.channels);
printf("Sampling: %d Hz\n", pcm.sampling);
print_codecs(path, &err);
printf("Selected codec: %s\n", pcm.codec);
printf("Delay: %#.1f ms\n", (double)pcm.delay / 10);
printf("SoftVolume: %s\n", pcm.soft_volume ? "Y" : "N");
print_volume(&pcm);
print_mute(&pcm);

print_properties(&pcm, &err);
if (dbus_error_is_set(&err))
warn("Unable to read available codecs: %s", err.message);

Expand Down Expand Up @@ -617,6 +649,16 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage *
dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) {
dbus_message_iter_get_basic(&iter, &path);
printf("PCMAdded %s\n", path);
if (verbose) {
struct ba_pcm pcm;
DBusError err = DBUS_ERROR_INIT;
if (!bluealsa_dbus_message_iter_get_pcm(&iter, NULL, &pcm)) {
error("Couldn't read PCM properties: %s", "Invalid signal signature");
goto fail;
}
print_properties(&pcm, &err);
printf("\n");
}
return DBUS_HANDLER_RESULT_HANDLED;
}

Expand All @@ -629,7 +671,41 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage *
}

}
else if (strcmp(interface, DBUS_INTERFACE_DBUS) == 0) {
if (strcmp(signal, "NameOwnerChanged") == 0) {

const char *arg0 = NULL, *arg1 = NULL, *arg2 = NULL;
if (dbus_message_iter_init(message, &iter) &&
dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
dbus_message_iter_get_basic(&iter, &arg0);
else
goto fail;
if (dbus_message_iter_next(&iter) &&
dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
dbus_message_iter_get_basic(&iter, &arg1);
else
goto fail;
if (dbus_message_iter_next(&iter) &&
dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
dbus_message_iter_get_basic(&iter, &arg2);
else
goto fail;

if (strcmp(arg0, dbus_ctx.ba_service))
goto fail;

if (strlen(arg1) == 0)
printf("ServiceRunning %s\n", dbus_ctx.ba_service);
else if (strlen(arg2) == 0)
printf("ServiceStopped %s\n", dbus_ctx.ba_service);
else
goto fail;

return DBUS_HANDLER_RESULT_HANDLED;
}
}

fail:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

Expand All @@ -640,16 +716,42 @@ static int cmd_monitor(int argc, char *argv[]) {
return EXIT_FAILURE;
}

/* Force line buffered output to be sure each event will be flushed
* immediately, as this command will most likely be used to write to
* a pipe. */
setvbuf(stdout, NULL, _IOLBF, 0);

bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
dbus_ba_service, NULL, BLUEALSA_INTERFACE_MANAGER, "PCMAdded", NULL);
bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
dbus_ba_service, NULL, BLUEALSA_INTERFACE_MANAGER, "PCMRemoved", NULL);

char dbus_args[50];
snprintf(dbus_args, sizeof(dbus_args), "arg0='%s',arg2=''", dbus_ctx.ba_service);
bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
DBUS_SERVICE_DBUS, NULL, DBUS_INTERFACE_DBUS, "NameOwnerChanged", dbus_args);
snprintf(dbus_args, sizeof(dbus_args), "arg0='%s',arg1=''", dbus_ctx.ba_service);
bluealsa_dbus_connection_signal_match_add(&dbus_ctx,
DBUS_SERVICE_DBUS, NULL, DBUS_INTERFACE_DBUS, "NameOwnerChanged", dbus_args);

if (!dbus_connection_add_filter(dbus_ctx.conn, dbus_signal_handler, NULL, NULL)) {
cmd_print_error("Couldn't add D-Bus filter");
return EXIT_FAILURE;
}

bool running = false;
DBusError err = DBUS_ERROR_INIT;
get_services(test_bluealsa_service, &running, &err);
if (dbus_error_is_set(&err)) {
cmd_print_error("D-Bus error: %s", err.message);
return EXIT_FAILURE;
}

if (running)
printf("ServiceRunning %s\n", dbus_ctx.ba_service);
else
printf("ServiceStopped %s\n", dbus_ctx.ba_service);

while (dbus_connection_read_write_dispatch(dbus_ctx.conn, -1))
continue;

Expand Down Expand Up @@ -692,6 +794,7 @@ static void usage(const char *progname) {
printf(" -V, --version Show version\n");
printf(" -B, --dbus=NAME BlueALSA service name suffix\n");
printf(" -q, --quiet Do not print any error messages\n");
printf(" -v, --verbose Show extra information\n");
printf("\nCommands:\n");
for (i = 0; i < ARRAYSIZE(commands); i++)
printf(" %s %-*s%s\n", commands[i].name,
Expand All @@ -711,12 +814,13 @@ static void usage(const char *progname) {
int main(int argc, char *argv[]) {

int opt;
const char *opts = "+B:Vhq";
const char *opts = "+B:Vhqv";
const struct option longopts[] = {
{"dbus", required_argument, NULL, 'B'},
{"version", no_argument, NULL, 'V'},
{"help", no_argument, NULL, 'h'},
{"quiet", no_argument, NULL, 'q'},
{"verbose", no_argument, NULL, 'v'},
{ 0 },
};

Expand All @@ -734,6 +838,9 @@ int main(int argc, char *argv[]) {
case 'q' /* --quiet */ :
quiet = true;
break;
case 'v' /* --verbose */ :
verbose = true;
break;
default:
fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
return EXIT_FAILURE;
Expand Down

0 comments on commit 57ac831

Please sign in to comment.