[alsa-devel] [RFC][PATCH 22/23] aplay: add a handler for key events

Takashi Sakamoto o-takashi at sakamocchi.jp
Thu Aug 17 14:00:03 CEST 2017


In current implementation of aplay, when an option '--interactive' (-i)
is given, users can pause data transmission by pushing 'space' or 'enter'
key. The key event is handled via control terminal.

This commit adds support for this feature with a different shape.
---
 aplay/Makefile.am       |   7 ++-
 aplay/key-event.c       | 120 ++++++++++++++++++++++++++++++++++++++++++++++++
 aplay/key-event.h       |  21 +++++++++
 aplay/options.c         |  19 +++++++-
 aplay/options.h         |   1 +
 aplay/subcmd-transfer.c |  22 +++++++++
 6 files changed, 187 insertions(+), 3 deletions(-)
 create mode 100644 aplay/key-event.c
 create mode 100644 aplay/key-event.h

diff --git a/aplay/Makefile.am b/aplay/Makefile.am
index a0d498c..cffa4bc 100644
--- a/aplay/Makefile.am
+++ b/aplay/Makefile.am
@@ -13,7 +13,8 @@ noinst_HEADERS = \
 	xfer.h \
 	xfer-alsa.h \
 	main.h \
-	vumeter.h
+	vumeter.h \
+	key-event.h
 
 aplay_SOURCES = \
 	container.h \
@@ -44,7 +45,9 @@ aplay_SOURCES = \
 	subcmd-transfer.c \
 	main.c \
 	vumeter.h \
-	vumeter.c
+	vumeter.c \
+	key-event.h \
+	key-event.c
 
 EXTRA_DIST = aplay.1 arecord.1
 EXTRA_CLEAN = arecord
diff --git a/aplay/key-event.c b/aplay/key-event.c
new file mode 100644
index 0000000..df36740
--- /dev/null
+++ b/aplay/key-event.c
@@ -0,0 +1,120 @@
+/*
+ * key-event.c - a handler for key events via stdin.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "key-event.h"
+
+#include <stdio.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <gettext.h>
+
+int keyevent_context_init(struct keyevent_context *key)
+{
+	struct termios term;
+	int err;
+
+	key->fd = fileno(stdin);
+	if (!isatty(key->fd)) {
+		printf(_("isatty(3): %s\n"),
+		       strerror(errno));
+		return -EINVAL;
+	}
+
+	err = tcgetattr(key->fd, &term);
+	if (err < 0) {
+		printf(_("tcgetattr(3): %s\n"),
+		       strerror(errno));
+		return -errno;
+	}
+	key->c_lflag = term.c_lflag;
+
+	term.c_lflag &= ~ICANON;
+	err = tcsetattr(key->fd, TCSANOW, &term);
+	if (err < 0) {
+		printf(_("tcsetattr(3): %s\n"),
+			 strerror(errno));
+		return -errno;
+	}
+
+	return 0;
+}
+
+static int set_nonblock_flag(int fd, bool nonblock)
+{
+	int err;
+
+	err = fcntl(fd, F_GETFL);
+	if (err < 0) {
+		printf(_("fcntl(2) with F_GETFL: %s\n"),
+			 strerror(errno));
+		return -errno;
+	}
+
+	if (nonblock)
+		err |= O_NONBLOCK;
+	else
+		err &= ~O_NONBLOCK;
+	err = fcntl(fd, F_SETFL, err);
+	if (err < 0) {
+		printf(_("fcntl(2) with F_SETFL: %s\n"),
+			 strerror(errno));
+		return -errno;
+	}
+
+	return 0;
+}
+
+int keyevent_context_check(struct keyevent_context *key,
+			   struct xfer_context *xfer)
+{
+	char b;
+	ssize_t len;
+	bool paused = false;
+	int err;
+
+	while (1) {
+		err = set_nonblock_flag(key->fd, !paused);
+		if (err < 0)
+			return err;
+
+		len = read(key->fd, &b, sizeof(b));
+		if (len == -EINTR)
+			continue;
+		if (len == -EAGAIN)
+			break;
+		if (len < 0)
+			return len;
+		if (len == 0)
+			break;
+		if (b == ' ' || b == '\r') {
+			xfer_context_pause(xfer, !paused);
+			if (paused)
+				break;
+			paused = !paused;
+		}
+	}
+
+	return 0;
+}
+
+void keyevent_context_destroy(struct keyevent_context *key)
+{
+	struct termios term;
+	int err;
+
+	tcgetattr(key->fd, &term);
+	term.c_lflag = key->c_lflag;
+	err = tcsetattr(key->fd, TCSANOW, &term);
+	if (err < 0) {
+		printf(_("tcsetattr(3): %s\n"),
+		       strerror(errno));
+	}
+}
diff --git a/aplay/key-event.h b/aplay/key-event.h
new file mode 100644
index 0000000..967e6d0
--- /dev/null
+++ b/aplay/key-event.h
@@ -0,0 +1,21 @@
+/*
+ * key-event.h - a header to a handler for key events via stdin.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "xfer.h"
+
+#include <stdbool.h>
+
+struct keyevent_context {
+	int fd;
+	long c_lflag;
+};
+
+int keyevent_context_init(struct keyevent_context *key);
+int keyevent_context_check(struct keyevent_context *key,
+			   struct xfer_context *xfer);
+void keyevent_context_destroy(struct keyevent_context *key);
diff --git a/aplay/options.c b/aplay/options.c
index 7663703..005a7bf 100644
--- a/aplay/options.c
+++ b/aplay/options.c
@@ -315,6 +315,20 @@ static int apply_policies(struct context_options *opts,
 		}
 	}
 
+	if (opts->interactive) {
+		/*
+		 * In interactive mode, users can suspend/resume PCM substream
+		 * by keyboard input, thus stdin cannot used for source of
+		 * PCM frames.
+		 */
+		if (direction == SND_PCM_STREAM_PLAYBACK &&
+		    !strcmp(opts->paths[0], "-")) {
+			printf(_("An option for interactive mode is not "
+				 "available with stdin.\n"));
+			return -EINVAL;
+		}
+	}
+
 	opts->sample_format = SND_PCM_FORMAT_UNKNOWN;
 	if (sample_format_literal) {
 		err = verify_sample_format(opts, sample_format_literal);
@@ -331,7 +345,7 @@ static int apply_policies(struct context_options *opts,
 int context_options_init(struct context_options *opts, int argc,
 			 char *const *argv, snd_pcm_stream_t direction)
 {
-	static const char *s_opts = "hqid:vt:D:c:f:r:MNF:A:R:T:B:IV:m:";
+	static const char *s_opts = "hqd:vt:D:c:f:r:MNF:A:R:T:B:IV:m:i";
 	static const struct option l_opts[] = {
 		/* For generic purposes. */
 		{"help",		0, 0, 'h'},
@@ -369,6 +383,7 @@ int context_options_init(struct context_options *opts, int argc,
 		{"fatal-errors",	0, 0, OPT_FATAL_ERRORS},
 		{"vumeter",		1, 0, 'V'},
 		{"chmap",		1, 0, 'm'},
+		{"interactive",		0, 0, 'i'},
 		{NULL,			0, 0, 0},
 	};
 	const char *cntr_format_literal = NULL;
@@ -447,6 +462,8 @@ int context_options_init(struct context_options *opts, int argc,
 			opts->fatal_errors = true;
 		else if (c == 'm')
 			chmap_literal = optarg;
+		else if (c == 'i')
+			opts->interactive = true;
 		else
 			continue;
 
diff --git a/aplay/options.h b/aplay/options.h
index 3bece52..48ba859 100644
--- a/aplay/options.h
+++ b/aplay/options.h
@@ -54,6 +54,7 @@ struct context_options {
 
 	enum vumeter_mode vu_mode;
 	char *chmap_literal;
+	bool interactive;
 
 	char **paths;
 	unsigned int path_count;
diff --git a/aplay/subcmd-transfer.c b/aplay/subcmd-transfer.c
index 9138558..814c90f 100644
--- a/aplay/subcmd-transfer.c
+++ b/aplay/subcmd-transfer.c
@@ -7,6 +7,7 @@
  */
 
 #include "xfer.h"
+#include "key-event.h"
 
 #include <signal.h>
 #include <gettext.h>
@@ -288,9 +289,19 @@ static int context_process_frames(struct context *ctx,
 {
 	bool verbose = ctx->opts.verbose;
 	unsigned int frame_count;
+	struct keyevent_context *key = NULL;
 	int i;
 	int err;
 
+	if (ctx->opts.interactive) {
+		key = malloc(sizeof(*key));
+		if (key == NULL)
+			return -ENOMEM;
+		err = keyevent_context_init(key);
+		if (err < 0)
+			return err;
+	}
+
 	*actual_frame_count = 0;
 	while (!ctx->interrupted) {
 		/* Tell remains to expected frame count. */
@@ -316,6 +327,17 @@ static int context_process_frames(struct context *ctx,
 		*actual_frame_count += frame_count;
 		if (*actual_frame_count >= expected_frame_count)
 			break;
+
+		if (key) {
+			err = keyevent_context_check(key, &ctx->xfer);
+			if (err < 0 && err != -EAGAIN)
+				break;
+		}
+	}
+
+	if (key) {
+		keyevent_context_destroy(key);
+		free(key);
 	}
 
 	if (verbose) {
-- 
2.11.0



More information about the Alsa-devel mailing list