pfr btw, I finally got around to testing your "clever" function rewrite... nothing happens
My error: I didn't read shuffle(1)
properly. You'll have to change shuffle
to shuffle -f-
. Or, just omit it altogether since mpv
has a --shuffle
option. See simplified script, above.
pfr (or perhaps there is tno) another way..
Here's one (quickly cobbled together):
/**
* qmpv: run mpv(1) quietly and interactively, printing only the filename
* being played. Press `q' to quit.
*
* Compile:
* cc -Wall -o qmpv qmpv.c # -lutil (on *BSD)
*
* Usage:
* qmpv --shuffle [other mpv(1) options] FILE...
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#ifdef __linux__
#include <pty.h>
#else
#include <util.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
/* Functions */
static char** mkvec(int argc, char* argv[]);
static int process(int srcfd, int dstfd);
static void error(char* fmt, ...);
static void ttycbrk(struct termios* term);
static void ttyrest(void);
static void ttysave(struct termios* term);
static void ttysize(struct winsize* win);
/* Globals */
static struct termios orig;
static char* prog;
/**
* M A I N
*/
int
main(int argc, char* argv[])
{
struct termios term;
struct winsize win;
int fdm, rc, status;
pid_t pid;
prog = argv[0];
if (!isatty(STDIN_FILENO)) {
error("%s: error: stdin not a terminal.", prog);
exit(EXIT_FAILURE);
}
ttysave(&term);
orig = term; /* save current settings */
ttysize(&win);
atexit(ttyrest);
pid = forkpty(&fdm, NULL, &term, &win);
if (pid < 0) { /* error */
error("%s: fork failed", prog);
exit(EXIT_FAILURE);
} else if (pid == 0) { /* child */
int nullfd = open("/dev/null", O_RDWR|O_CLOEXEC);
dup2(nullfd, STDERR_FILENO);
execvp("mpv", mkvec(argc, argv));
// error("%s: error executing mpv", prog);
exit(EXIT_FAILURE);
} else { /* parent */
ttycbrk(&term);
for (;;)
if ((rc = process(STDIN_FILENO, fdm)) <= 0)
break;
}
wait(&status);
if (WIFSIGNALED(status)) {
errno = 0;
error("%s: mpv killed by signal %d", prog, WTERMSIG(status));
rc = -1;
} else if (WIFEXITED(status) && WEXITSTATUS(status)) {
errno = 0;
error("%s: mpv exited with status %d", prog, WEXITSTATUS(status));
}
exit(rc < 0 ? EXIT_FAILURE : WEXITSTATUS(status));
}
static char**
mkvec(int argc, char* argv[])
{
char** vec;
if ((vec = calloc(argc + 1, sizeof(char* ))) == NULL) {
error("%s: calloc failed", prog);
exit(EXIT_FAILURE);
}
vec[0] = "mpv";
for (int i = 1; i <= argc; i++)
vec[i] = argv[i];
return vec;
}
#define MAX(x, y) ((x) > (y) ? (x) : (y))
/**
* Send keystrokes from `srcfd' to `dstfd'.
* Read names of files being played from `dstfd'.
*
* Returns 0 on EOF, -1 on errors and >0 if some data was copied.
*/
static int
process(int srcfd, int dstfd)
{
char buf[BUFSIZ] = "";
int rc;
fd_set fds;
FD_ZERO(&fds);
FD_SET(srcfd, &fds);
FD_SET(dstfd, &fds);
rc = select(MAX(srcfd,dstfd) + 1, &fds, NULL, NULL, NULL);
if (rc < 0) {
error("%s: select error.", prog);
return rc;
}
if (FD_ISSET(srcfd, &fds)) {
rc = read(srcfd, buf, sizeof buf);
if (rc <= 0)
return rc;
if (write(dstfd, buf, rc) != rc)
rc = -1;
}
if (FD_ISSET(dstfd, &fds)) {
rc = read(dstfd, buf, sizeof buf);
if (rc <= 0)
return rc;
char* p = strstr(buf, "Playing: ");
if (p == NULL)
return rc;
char* s = strchr(p, ':') + 2;
char* e = strchr(s, '\n');
if (e == NULL) /* buf too small; go for end */
e = buf + rc - 1;
size_t n = e - s;
struct timeval tv;
char tbuf[32], str[BUFSIZ];
gettimeofday(&tv, NULL);
strftime(tbuf, sizeof tbuf, "%H:%M:%S", localtime(&tv.tv_sec));
n = snprintf(str, sizeof str, "%s | %.*s\n", tbuf, (int)n, s);
if (write(STDOUT_FILENO, str, n) != n)
rc = -1;
}
return rc;
}
/**
* Get STDIN's terminal size.
*/
static void
ttysize(struct winsize* win)
{
if (ioctl(STDIN_FILENO, TIOCGWINSZ, win) < 0) {
error("%s: Error getting terminal's screen size.", prog);
exit(EXIT_FAILURE);
}
}
/**
* Save STDIN's current settings.
*/
static void
ttysave(struct termios* term)
{
if (tcgetattr(STDIN_FILENO, term) < 0) {
error("%s: error getting terminal's settings.", prog);
exit(EXIT_FAILURE);
}
}
/**
* Put STDIN into cbreak mode and disable echo.
*/
static void
ttycbrk(struct termios* term)
{
struct termios new;
new = *term; /* copy original settings */
new.c_lflag &= ~(ECHO|ICANON); /* and change what we want */
if (tcsetattr(STDIN_FILENO, TCSANOW, &new) < 0) {
error("%s: error setting terminal to CBREAK mode", prog);
exit(EXIT_FAILURE);
}
}
/**
* Restore STDIN's saved settings.
*/
static void
ttyrest(void)
{
if (tcsetattr(STDIN_FILENO, TCSANOW, &orig) < 0) {
error("%s: error restoring terminal's settings", prog);
exit(EXIT_FAILURE);
}
}
static void
error(char* fmt, ...)
{
va_list ap;
int e = errno;
fflush(stdout);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (e != 0) {
fprintf(stderr, " (%s)", strerror(e));
errno = 0;
}
fprintf(stderr, "\n");
fflush(stderr);
}