mirror of
https://gitlab.freedesktop.org/pipewire/pipewire
synced 2024-07-22 10:44:35 +00:00
pw-cli: use readline() in interactive mode
With history and a simple command completion hook this makes the interactive mode a lot easier to deal with.
This commit is contained in:
parent
7d58ce9e24
commit
ae59185f6f
|
@ -325,6 +325,7 @@ pthread_lib = dependency('threads')
|
||||||
dbus_dep = dependency('dbus-1')
|
dbus_dep = dependency('dbus-1')
|
||||||
sdl_dep = dependency('sdl2', required : get_option('sdl2'))
|
sdl_dep = dependency('sdl2', required : get_option('sdl2'))
|
||||||
summary({'SDL 2': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies')
|
summary({'SDL 2': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies')
|
||||||
|
readline_dep = dependency('readline', required : false)
|
||||||
ncurses_dep = dependency('ncursesw', required : false)
|
ncurses_dep = dependency('ncursesw', required : false)
|
||||||
sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile'))
|
sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile'))
|
||||||
summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain')
|
summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain')
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
tools_sources = [
|
tools_sources = [
|
||||||
[ 'pw-mon', [ 'pw-mon.c' ] ],
|
[ 'pw-mon', [ 'pw-mon.c' ] ],
|
||||||
[ 'pw-cli', [ 'pw-cli.c' ] ],
|
|
||||||
[ 'pw-dot', [ 'pw-dot.c' ] ],
|
[ 'pw-dot', [ 'pw-dot.c' ] ],
|
||||||
[ 'pw-dump', [ 'pw-dump.c' ] ],
|
[ 'pw-dump', [ 'pw-dump.c' ] ],
|
||||||
[ 'pw-profiler', [ 'pw-profiler.c' ] ],
|
[ 'pw-profiler', [ 'pw-profiler.c' ] ],
|
||||||
|
@ -18,6 +17,14 @@ foreach t : tools_sources
|
||||||
)
|
)
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
|
if readline_dep.found()
|
||||||
|
executable('pw-cli',
|
||||||
|
'pw-cli.c',
|
||||||
|
install: true,
|
||||||
|
dependencies: [pipewire_dep, readline_dep]
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
if ncurses_dep.found()
|
if ncurses_dep.found()
|
||||||
executable('pw-top',
|
executable('pw-top',
|
||||||
'pw-top.c',
|
'pw-top.c',
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
#include <alloca.h>
|
#include <alloca.h>
|
||||||
#endif
|
#endif
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
#include <readline/readline.h>
|
||||||
|
#include <readline/history.h>
|
||||||
|
|
||||||
#define spa_debug(...) fprintf(stdout,__VA_ARGS__);fputc('\n', stdout)
|
#define spa_debug(...) fprintf(stdout,__VA_ARGS__);fputc('\n', stdout)
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@
|
||||||
#include <pipewire/extensions/session-manager.h>
|
#include <pipewire/extensions/session-manager.h>
|
||||||
|
|
||||||
static const char WHITESPACE[] = " \t";
|
static const char WHITESPACE[] = " \t";
|
||||||
|
static char prompt[64];
|
||||||
|
|
||||||
struct remote_data;
|
struct remote_data;
|
||||||
|
|
||||||
|
@ -279,11 +282,10 @@ static void on_core_info(void *_data, const struct pw_core_info *info)
|
||||||
fprintf(stdout, "remote %d is named '%s'\n", rd->id, rd->name);
|
fprintf(stdout, "remote %d is named '%s'\n", rd->id, rd->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void show_prompt(struct remote_data *rd)
|
static void set_prompt(struct remote_data *rd)
|
||||||
{
|
{
|
||||||
rd->data->monitoring = true;
|
snprintf(prompt, sizeof(prompt), "%s>> ", rd->name);
|
||||||
fprintf(stdout, "%s>>", rd->name);
|
rl_set_prompt(prompt);
|
||||||
fflush(stdout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_core_done(void *_data, uint32_t id, int seq)
|
static void on_core_done(void *_data, uint32_t id, int seq)
|
||||||
|
@ -292,12 +294,14 @@ static void on_core_done(void *_data, uint32_t id, int seq)
|
||||||
struct data *d = rd->data;
|
struct data *d = rd->data;
|
||||||
|
|
||||||
if (seq == rd->prompt_pending) {
|
if (seq == rd->prompt_pending) {
|
||||||
if (d->interactive)
|
if (d->interactive) {
|
||||||
show_prompt(rd);
|
set_prompt(rd);
|
||||||
else
|
rd->data->monitoring = true;
|
||||||
|
} else {
|
||||||
pw_main_loop_quit(d->loop);
|
pw_main_loop_quit(d->loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int print_global(void *obj, void *data)
|
static int print_global(void *obj, void *data)
|
||||||
{
|
{
|
||||||
|
@ -2880,7 +2884,7 @@ usage:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool parse(struct data *data, char *buf, size_t size, char **error)
|
static bool parse(struct data *data, char *buf, char **error)
|
||||||
{
|
{
|
||||||
char *a[2];
|
char *a[2];
|
||||||
int n;
|
int n;
|
||||||
|
@ -2912,35 +2916,35 @@ static bool parse(struct data *data, char *buf, size_t size, char **error)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_input(void *data, int fd, uint32_t mask)
|
/* We need a global variable, readline doesn't have a closure arg */
|
||||||
|
static struct data *readline_dataptr;
|
||||||
|
|
||||||
|
static void readline_process_line(char *line)
|
||||||
{
|
{
|
||||||
struct data *d = data;
|
struct data *d = readline_dataptr;
|
||||||
char buf[4096], *error;
|
char *error;
|
||||||
ssize_t r;
|
|
||||||
|
|
||||||
if (mask & SPA_IO_IN) {
|
if (!line)
|
||||||
while (true) {
|
line = strdup("quit");
|
||||||
r = read(fd, buf, sizeof(buf)-1);
|
|
||||||
if (r < 0) {
|
|
||||||
if (errno == EAGAIN)
|
|
||||||
continue;
|
|
||||||
perror("read");
|
|
||||||
r = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (r == 0) {
|
|
||||||
fprintf(stdout, "\n");
|
|
||||||
pw_main_loop_quit(d->loop);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
buf[r] = '\0';
|
|
||||||
|
|
||||||
if (!parse(d, buf, r, &error)) {
|
if (line[0] != '\0') {
|
||||||
|
add_history(line);
|
||||||
|
if (!parse(d, line, &error)) {
|
||||||
fprintf(stdout, "Error: \"%s\"\n", error);
|
fprintf(stdout, "Error: \"%s\"\n", error);
|
||||||
free(error);
|
free(error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
free(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_input(void *data, int fd, uint32_t mask)
|
||||||
|
{
|
||||||
|
struct data *d = data;
|
||||||
|
|
||||||
|
if (mask & SPA_IO_IN) {
|
||||||
|
readline_dataptr = d;
|
||||||
|
rl_callback_read_char();
|
||||||
|
|
||||||
if (d->current == NULL)
|
if (d->current == NULL)
|
||||||
pw_main_loop_quit(d->loop);
|
pw_main_loop_quit(d->loop);
|
||||||
else {
|
else {
|
||||||
|
@ -2951,6 +2955,55 @@ static void do_input(void *data, int fd, uint32_t mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
readline_match_command(const char *text, int state)
|
||||||
|
{
|
||||||
|
static size_t idx;
|
||||||
|
static int len;
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
idx = 0;
|
||||||
|
len = strlen(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (idx < SPA_N_ELEMENTS(command_list)) {
|
||||||
|
const char *name = command_list[idx].name;
|
||||||
|
const char *alias = command_list[idx].alias;
|
||||||
|
|
||||||
|
idx++;
|
||||||
|
if (spa_strneq(name, text, len) || spa_strneq(alias, text, len))
|
||||||
|
return strdup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char **
|
||||||
|
readline_command_completion(const char *text, int start, int end)
|
||||||
|
{
|
||||||
|
char **matches = NULL;
|
||||||
|
|
||||||
|
/* Only try to complete the first word in a line */
|
||||||
|
if (start == 0)
|
||||||
|
matches = rl_completion_matches(text, readline_match_command);
|
||||||
|
|
||||||
|
/* Don't fall back to filename completion */
|
||||||
|
rl_attempted_completion_over = true;
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_init()
|
||||||
|
{
|
||||||
|
rl_attempted_completion_function = readline_command_completion;
|
||||||
|
rl_callback_handler_install(">> ", readline_process_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readline_cleanup()
|
||||||
|
{
|
||||||
|
rl_callback_handler_remove();
|
||||||
|
}
|
||||||
|
|
||||||
static void do_quit_on_signal(void *data, int signal_number)
|
static void do_quit_on_signal(void *data, int signal_number)
|
||||||
{
|
{
|
||||||
struct data *d = data;
|
struct data *d = data;
|
||||||
|
@ -3046,12 +3099,16 @@ int main(int argc, char *argv[])
|
||||||
if (optind == argc) {
|
if (optind == argc) {
|
||||||
data.interactive = true;
|
data.interactive = true;
|
||||||
|
|
||||||
pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data);
|
|
||||||
|
|
||||||
fprintf(stdout, "Welcome to PipeWire version %s. Type 'help' for usage.\n",
|
fprintf(stdout, "Welcome to PipeWire version %s. Type 'help' for usage.\n",
|
||||||
pw_get_library_version());
|
pw_get_library_version());
|
||||||
|
|
||||||
|
readline_init();
|
||||||
|
|
||||||
|
pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data);
|
||||||
|
|
||||||
pw_main_loop_run(data.loop);
|
pw_main_loop_run(data.loop);
|
||||||
|
|
||||||
|
readline_cleanup();
|
||||||
} else {
|
} else {
|
||||||
char buf[4096], *p, *error;
|
char buf[4096], *p, *error;
|
||||||
|
|
||||||
|
@ -3063,7 +3120,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
pw_main_loop_run(data.loop);
|
pw_main_loop_run(data.loop);
|
||||||
|
|
||||||
if (!parse(&data, buf, p - buf, &error)) {
|
if (!parse(&data, buf, &error)) {
|
||||||
fprintf(stdout, "Error: \"%s\"\n", error);
|
fprintf(stdout, "Error: \"%s\"\n", error);
|
||||||
free(error);
|
free(error);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue