Commit Diff
Commit:
3baa2617f8fa715685929c415068ab226626a2a3
From:
Omar Polo <op@omarpolo.com>
Date:
Wed Feb 16 18:28:23 2022 UTC
Message:
initial commit
commit - /dev/null
commit + 3baa2617f8fa715685929c415068ab226626a2a3
blob - /dev/null
blob + a8da1ad97ac240b0df0d7be7c216417cd8aa6ba8 (mode 644)
--- /dev/null
+++ LICENSE
@@ -0,0 +1,15 @@
+Copyright 2022 Omar Polo
+
+Permission to use, copy, modify, and/or distribute this software for
+any purpose with or without fee is hereby granted, provided that the
+above copyright notice and this permission notice appear in all
+copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
blob - /dev/null
blob + f696ec5349ed952a8c3b4c52ee353be0523a0114 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,47 @@
+PROG= amused
+SRCS= amused.c control.c log.c xmalloc.c player.c ctl.c playlist.c \
+ player_flac.c player_mad.c player_opus.c player_oggvorbis.c
+
+.include "amused-version.mk"
+
+CPPFLAGS += -DAMUSED_VERSION=\"${AMUSED_VERSION}\" \
+ -I/usr/local/include -I/usr/local/include/opus
+
+LDADD = -levent -lm -lsndio -lutil \
+ -L/usr/local/lib -lmad -lvorbisfile -lopusfile -lFLAC
+DPADD = ${LIBEVENT} ${LIBM} ${LIBSNDIO} ${LIBUTIL}
+
+.if "${AMUSED_RELEASE}" == "Yes"
+PREFIX ?= /usr/local
+BINDIR ?= ${PREFIX}/bin
+MANDIR ?= ${PREFIX}/man/man
+.else
+CFLAGS += -Werror -Wall -Wstrict-prototypes -Wunused-variable
+PREFIX ?= ${HOME}
+BINDIR ?= ${PREFIX}/bin
+BINOWN ?= ${USER}
+.if !defined(BINGRP)
+BINGRP != id -g -n
+.endif
+DEBUG = -O0 -g
+.endif
+
+release: clean
+ sed -i -e 's/_RELEASE=No/_RELEASE=Yes/' amused-version.mk
+ ${MAKE} dist
+ sed -i -e 's/_RELEASE=Yes/_RELEASE=No/' amused-version.mk
+
+dist: clean
+ mkdir /tmp/amused-${AMUSED_VERSION}
+ pax -rw * /tmp/amused-${AMUSED_VERSION}
+ find /tmp/amused-${AMUSED_VERSION} -type d -name obj -delete
+ rm /tmp/amused-${AMUSED_VERSION}/amused-dist.txt
+ tar -C /tmp -zcf amused-${AMUSED_VERSION}.tar.gz amused-${AMUSED_VERSION}
+ rm -rf /tmp/amused-${AMUSED_VERSION}
+ tar -ztf amused-${AMUSED_VERSION}.tar.gz | \
+ sed -e 's/^amused-${AMUSED_VERSION}//' | \
+ sort > amused-dist.txt.new
+ diff -u amused-dist.txt{,new}
+ rm amused-dist.txt.new
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + ae9b2a9dc2825c6e0d4b56d4a0a489641f4e7a56 (mode 644)
--- /dev/null
+++ amused-version.mk
@@ -0,0 +1,8 @@
+AMUSED_RELEASE=No
+AMUSED_VERSION_NUMBER=0.1
+
+.if ${AMUSED_RELEASE} == Yes
+AMUSED_VERSION=${AMUSED_VERSION_NUMBER}
+.else
+AMUSED_VERSION=${AMUSED_VERSION_NUMBER}-current
+.endif
blob - /dev/null
blob + 25d20a6822f5854ddc669f139ae4496bd5919d69 (mode 644)
--- /dev/null
+++ amused.1
@@ -0,0 +1,82 @@
+.\" Copyright (c) 2022 Omar Polo <op@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd February 16 2022
+.Dt AMUSED 1
+.Os
+.Sh NAME
+.Nm amused
+.Nd music player
+.Sh SYNOPSIS
+.Nm
+.Op Fl dv
+.Op Fl s Ar socket
+.Ar command
+.Op Ar argument ...
+.Sh DESCRIPTION
+.Nm
+is a music player daemon and command-line utility.
+.Pp
+If called without any sub-
+.Ar command
+it forks in the background and starts the daemon.
+Once the daemon is running, music files can be enqueued and the reproduction
+controlled.
+.Pp
+The following options are available:
+.Bl -tag -width 12m
+.It Fl d
+Do not daemonize.
+if this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl v
+Produce more verbose output.
+.It Fl s Ar socket
+Use
+.Ar socket
+instead of the default
+.Pa /tmp/amused-$UID
+to communicate with the daemon.
+.El
+.Pp
+The following commands are available:
+.Bl -tag -width 12m
+.It Cm play
+Start playing the playlist.
+.It Cm pause
+Pause the reproduction.
+.It Cm toggle
+Toggle pause state.
+.It Cm stop
+Stop the music player.
+.It Cm restart
+Restart the current song from the beginning.
+.It Cm add Ar
+Enqueue the given files.
+.It Cm flush
+Erase the playlist.
+.El
+.Sh FILES
+.Bl -tag -width "/tmp/amused-$UID" -compact
+.It Pa /tmp/amused-$UID
+UNIX-domain socket used for communication with the daemon.
+.El
+.Sh AUTHORS
+.An -nosplit
+Then
+.Nm
+utility was written by
+.An Omar Polo Aq Mt op@openbsd.org .
blob - /dev/null
blob + fb8aab8c435d364c7c7a4bf64e6919e1d2bb6b71 (mode 644)
--- /dev/null
+++ amused.c
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <event.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sndio.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "amused.h"
+#include "control.h"
+#include "log.h"
+#include "playlist.h"
+#include "xmalloc.h"
+
+struct sio_hdl *hdl;
+char *csock = NULL;
+int debug;
+int verbose;
+struct imsgev *iev_player;
+
+const char *argv0;
+pid_t player_pid;
+struct event ev_sigint;
+struct event ev_sigterm;
+
+enum amused_process {
+ PROC_MAIN,
+ PROC_PLAYER,
+};
+
+__dead void
+main_shutdown(void)
+{
+ pid_t pid;
+ int status;
+
+ /* close pipes. */
+ msgbuf_clear(&iev_player->ibuf.w);
+ close(iev_player->ibuf.fd);
+ free(iev_player);
+
+ log_debug("waiting for children to terminate");
+ do {
+ pid = wait(&status);
+ if (pid == -1) {
+ if (errno != EINTR && errno != ECHILD)
+ fatal("wait");
+ } else if (WIFSIGNALED(status))
+ log_warnx("player terminated; signal %d",
+ WTERMSIG(status));
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ log_info("terminating");
+ exit(0);
+}
+
+static void
+main_sig_handler(int sig, short event, void *arg)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ main_shutdown();
+ break;
+ default:
+ fatalx("unexpected signal %d", sig);
+ }
+}
+
+static void
+main_dispatch_player(int sig, short event, void *d)
+{
+ struct imsgev *iev = d;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int shut = 0;
+ const char *song;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed */
+ shut = 1;
+ }
+ if (event & EV_WRITE) {
+ if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed */
+ shut = 1;
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("imsg_get");
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_EOF:
+ case IMSG_ERR:
+ song = playlist_advance();
+ if (song == NULL)
+ break;
+ /* XXX: watch out for failures! */
+ main_play_song(song);
+ break;
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ if (!shut)
+ imsg_event_add(iev);
+ else {
+ /* This pipe is dead. Remove its event handler. */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ }
+}
+
+static pid_t
+start_child(enum amused_process proc, int fd)
+{
+ const char *argv[5];
+ int argc = 0;
+ pid_t pid;
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("cannot fork");
+ case 0:
+ break;
+ default:
+ close(fd);
+ return pid;
+ }
+
+ if (fd != 3) {
+ if (dup2(fd, 3) == -1)
+ fatal("cannot setup imsg fd");
+ } else if (fcntl(F_SETFD, 0) == -1)
+ fatal("cannot setup imsg fd");
+
+ argv[argc++] = argv0;
+ switch (proc) {
+ case PROC_MAIN:
+ fatal("can not start main process");
+ case PROC_PLAYER:
+ argv[argc++] = "-Tp";
+ break;
+ }
+
+ if (debug)
+ argv[argc++] = "-d";
+ if (verbose)
+ argv[argc++] = "-v";
+ argv[argc++] = NULL;
+
+ /* obnoxious casts */
+ execvp(argv0, (char *const *)argv);
+ fatal("execvp %s", argv0);
+}
+
+/* daemon main routine */
+static __dead int
+amused_main(void)
+{
+ int pipe_main2player[2];
+ int control_fd;
+
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+ log_procinit("main");
+
+ if (!debug)
+ daemon(1, 0);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ PF_UNSPEC, pipe_main2player) == -1)
+ fatal("socketpair");
+
+ player_pid = start_child(PROC_PLAYER, pipe_main2player[1]);
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ iev_player = xmalloc(sizeof(*iev_player));
+ imsg_init(&iev_player->ibuf, pipe_main2player[0]);
+ iev_player->handler = main_dispatch_player;
+ iev_player->events = EV_READ;
+ event_set(&iev_player->ev, iev_player->ibuf.fd, iev_player->events,
+ iev_player->handler, iev_player);
+ event_add(&iev_player->ev, NULL);
+
+ if ((control_fd = control_init(csock)) == -1)
+ fatal("control socket setup failed %s", csock);
+ control_listen(control_fd);
+
+ if (pledge("stdio rpath unix sendfd", NULL) == -1)
+ fatal("pledge");
+
+ log_info("startup");
+ event_dispatch();
+ main_shutdown();
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, proc = PROC_MAIN;
+
+ log_init(1, LOG_DAEMON); /* Log to stderr until daemonized */
+ log_setverbose(1);
+
+ argv0 = argv[0];
+ if (argv0 == NULL)
+ argv0 = "amused";
+
+ while ((ch = getopt(argc, argv, "ds:T:vV")) != -1) {
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 's':
+ free(csock);
+ csock = xstrdup(optarg);
+ break;
+ case 'T':
+ switch (*optarg) {
+ case 'p':
+ proc = PROC_PLAYER;
+ break;
+ default:
+ usage();
+ }
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'V':
+ printf("%s version %s\n", getprogname(),
+ AMUSED_VERSION);
+ exit(0);
+ default:
+ usage();
+ }
+ }
+ argv += optind;
+ argc -= optind;
+
+ if (proc == PROC_PLAYER)
+ exit(player(debug, verbose));
+
+ if (csock == NULL)
+ xasprintf(&csock, "/tmp/amused-%d", getuid());
+
+ if (argc == 0)
+ amused_main();
+ else
+ ctl(argc, argv);
+ return 0;
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+ pid_t pid, int fd, const void *data, uint16_t datalen)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data,
+ datalen)) != -1)
+ imsg_event_add(iev);
+
+ return ret;
+}
+
+int
+main_play_song(const char *song)
+{
+ char path[PATH_MAX] = { 0 };
+ int fd;
+
+ strlcpy(path, song, sizeof(path));
+ if ((fd = open(path, O_RDONLY)) == -1) {
+#if todo
+ log_warn("open %s", path);
+ return -1;
+#else
+ fatal("open %s", path);
+#endif
+ }
+
+ imsg_compose_event(iev_player, IMSG_PLAY, 0, 0, fd,
+ path, sizeof(path));
+ return 0;
+}
+
+void
+main_enqueue(struct imsgev *iev, struct imsg *imsg)
+{
+ size_t datalen;
+ char path[PATH_MAX] = { 0 };
+ const char *err = NULL;
+
+ datalen = IMSG_DATA_SIZE(*imsg);
+ if (datalen != sizeof(path)) {
+ err = "data size mismatch";
+ goto err;
+ }
+
+ memcpy(path, imsg->data, sizeof(path));
+ if (path[datalen-1] != '\0') {
+ err = "malformed data";
+ goto err;
+ }
+
+ playlist_enqueue(path);
+ imsg_compose_event(iev, IMSG_CTL_ADD, 0, 0, -1, path, sizeof(path));
+ return;
+err:
+ imsg_compose_event(iev, IMSG_CTL_ERR, 0, 0, -1, err, strlen(err)+1);
+}
blob - /dev/null
blob + 5d76e295c518c59a068d76bdf4f4155a26ef40d9 (mode 644)
--- /dev/null
+++ amused.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef AMUSED_H
+#define AMUSED_H
+
+extern struct sio_hdl *hdl;
+extern char *csock;
+extern int debug;
+extern int verbose;
+extern int playing;
+extern struct imsgev *iev_player;
+
+#define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE)
+
+enum imsg_type {
+ IMSG_PLAY, /* fd + filename */
+ IMSG_RESUME,
+ IMSG_PAUSE,
+ IMSG_STOP,
+ IMSG_EOF,
+ IMSG_ERR,
+
+ IMSG_CTL_PLAY,
+ IMSG_CTL_TOGGLE_PLAY,
+ IMSG_CTL_PAUSE,
+ IMSG_CTL_STOP,
+ IMSG_CTL_RESTART,
+ IMSG_CTL_ADD,
+ IMSG_CTL_FLUSH,
+ IMSG_CTL_SHOW,
+
+ IMSG_CTL_ERR,
+};
+
+struct imsgev {
+ struct imsgbuf ibuf;
+ void (*handler)(int, short, void *);
+ struct event ev;
+ short events;
+};
+
+enum actions {
+ NONE,
+ PLAY,
+ PAUSE,
+ TOGGLE,
+ STOP,
+ RESTART,
+ ADD,
+ FLUSH,
+ SHOW,
+};
+
+struct ctl_command;
+
+struct parse_result {
+ enum actions action;
+ char **files;
+ struct ctl_command *ctl;
+};
+
+struct ctl_command {
+ const char *name;
+ enum actions action;
+ int (*main)(struct parse_result *, int, char **);
+ const char *usage;
+ int has_pledge;
+};
+
+/* amused.c */
+void imsg_event_add(struct imsgev *iev);
+int imsg_compose_event(struct imsgev *, uint16_t, uint32_t,
+ pid_t, int, const void *, uint16_t);
+int main_play_song(const char *);
+void main_enqueue(struct imsgev *, struct imsg *);
+
+/* ctl.c */
+__dead void usage(void);
+__dead void ctl(int, char **);
+
+/* player.c */
+int player_setrate(int);
+void player_senderr(void);
+void player_sendeof(void);
+int player_shouldstop(void);
+int player(int, int);
+
+void play_oggvorbis(int fd);
+void play_mp3(int fd);
+void play_flac(int fd);
+void play_opus(int fd);
+
+#endif
blob - /dev/null
blob + e24d6d2b6d90d0cd9c303969532e5eafc0c71fe5 (mode 644)
--- /dev/null
+++ control.c
@@ -0,0 +1,337 @@
+/* $OpenBSD: control.c,v 1.8 2021/03/02 04:10:07 jsg Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "amused.h"
+#include "log.h"
+#include "control.h"
+#include "playlist.h"
+
+#define CONTROL_BACKLOG 5
+
+struct {
+ struct event ev;
+ struct event evt;
+ int fd;
+} control_state = {.fd = -1};
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ struct imsgev iev;
+};
+
+TAILQ_HEAD(ctl_conns, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns);
+
+struct ctl_conn *control_connbyfd(int);
+struct ctl_conn *control_connbypid(pid_t);
+void control_close(int);
+
+int
+control_init(char *path)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ 0)) == -1) {
+ log_warn("%s: socket", __func__);
+ return (-1);
+ }
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
+
+ if (unlink(path) == -1)
+ if (errno != ENOENT) {
+ log_warn("%s: unlink %s", __func__, path);
+ close(fd);
+ return (-1);
+ }
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("%s: bind: %s", __func__, path);
+ close(fd);
+ umask(old_umask);
+ return (-1);
+ }
+ umask(old_umask);
+
+ if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ log_warn("%s: chmod", __func__);
+ close(fd);
+ (void)unlink(path);
+ return (-1);
+ }
+
+ return (fd);
+}
+
+int
+control_listen(int fd)
+{
+ if (control_state.fd != -1)
+ fatalx("%s: received unexpected controlsock", __func__);
+
+ control_state.fd = fd;
+ if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+ log_warn("%s: listen", __func__);
+ return (-1);
+ }
+
+ event_set(&control_state.ev, control_state.fd, EV_READ,
+ control_accept, NULL);
+ event_add(&control_state.ev, NULL);
+ evtimer_set(&control_state.evt, control_accept, NULL);
+
+ return (0);
+}
+
+void
+control_accept(int listenfd, short event, void *bula)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+
+ event_add(&control_state.ev, NULL);
+ if ((event & EV_TIMEOUT))
+ return;
+
+ len = sizeof(sun);
+ if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
+ SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) {
+ /*
+ * Pause accept if we are out of file descriptors, or
+ * libevent will haunt us here too.
+ */
+ if (errno == ENFILE || errno == EMFILE) {
+ struct timeval evtpause = { 1, 0 };
+
+ event_del(&control_state.ev);
+ evtimer_add(&control_state.evt, &evtpause);
+ } else if (errno != EWOULDBLOCK && errno != EINTR &&
+ errno != ECONNABORTED)
+ log_warn("%s: accept4", __func__);
+ return;
+ }
+
+ if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
+ log_warn("%s: calloc", __func__);
+ close(connfd);
+ return;
+ }
+
+ imsg_init(&c->iev.ibuf, connfd);
+ c->iev.handler = control_dispatch_imsg;
+ c->iev.events = EV_READ;
+ event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+ c->iev.handler, &c->iev);
+ event_add(&c->iev.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ TAILQ_FOREACH(c, &ctl_conns, entry) {
+ if (c->iev.ibuf.fd == fd)
+ break;
+ }
+
+ return (c);
+}
+
+struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+ struct ctl_conn *c;
+
+ TAILQ_FOREACH(c, &ctl_conns, entry) {
+ if (c->iev.ibuf.pid == pid)
+ break;
+ }
+
+ return (c);
+}
+
+void
+control_close(int fd)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warnx("%s: fd %d: not found", __func__, fd);
+ return;
+ }
+
+ msgbuf_clear(&c->iev.ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ event_del(&c->iev.ev);
+ close(c->iev.ibuf.fd);
+
+ /* Some file descriptors are available again. */
+ if (evtimer_pending(&control_state.evt, NULL)) {
+ evtimer_del(&control_state.evt);
+ event_add(&control_state.ev, NULL);
+ }
+
+ free(c);
+}
+
+void
+control_dispatch_imsg(int fd, short event, void *bula)
+{
+ struct ctl_conn *c;
+ struct imsg imsg;
+ ssize_t n;
+ const char *song;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warnx("%s: fd %d: not found", __func__, fd);
+ return;
+ }
+
+ if (event & EV_READ) {
+ if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
+ n == 0) {
+ control_close(fd);
+ return;
+ }
+ }
+ if (event & EV_WRITE) {
+ if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
+ control_close(fd);
+ return;
+ }
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+ control_close(fd);
+ return;
+ }
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_PLAY:
+ switch (play_state) {
+ case STATE_STOPPED:
+ song = playlist_advance();
+ if (song == NULL)
+ break;
+ /* XXX: watch out for failures! */
+ main_play_song(song);
+ break;
+ case STATE_PLAYING:
+ /* do nothing */
+ break;
+ case STATE_PAUSED:
+ imsg_compose_event(iev_player, IMSG_RESUME,
+ 0, 0, -1, NULL, 0);
+ break;
+ }
+ break;
+ case IMSG_CTL_TOGGLE_PLAY:
+ switch (play_state) {
+ case STATE_STOPPED:
+ song = playlist_advance();
+ if (song == NULL)
+ break;
+ /* XXX: watch out for failures! */
+ main_play_song(song);
+ break;
+ case STATE_PLAYING:
+ imsg_compose_event(iev_player, IMSG_PAUSE, 0, 0, -1,
+ NULL, 0);
+ break;
+ case STATE_PAUSED:
+ imsg_compose_event(iev_player, IMSG_RESUME, 0, 0, -1,
+ NULL, 0);
+ break;
+ }
+ break;
+ case IMSG_CTL_PAUSE:
+ if (play_state != STATE_PLAYING)
+ break;
+ imsg_compose_event(iev_player, IMSG_PAUSE, 0, 0, -1, NULL, 0);
+ break;
+ case IMSG_CTL_STOP:
+ if (play_state == STATE_STOPPED)
+ break;
+ imsg_compose_event(iev_player, IMSG_STOP, 0, 0, -1, NULL, 0);
+ break;
+ case IMSG_CTL_RESTART:
+ imsg_compose_event(iev_player, IMSG_STOP, 0, 0, -1, NULL, 0);
+ song = playlist_current();
+ if (song == NULL)
+ break;
+ /* XXX: watch out for failures */
+ main_play_song(song);
+ break;
+ case IMSG_CTL_ADD:
+ main_enqueue(&c->iev, &imsg);
+ break;
+ case IMSG_CTL_FLUSH:
+ playlist_truncate();
+ case IMSG_CTL_SHOW:
+ default:
+ log_debug("%s: error handling imsg %d", __func__,
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->iev);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+ return (0);
+
+ return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
+ -1, imsg->data, IMSG_DATA_SIZE(*imsg)));
+}
blob - /dev/null
blob + e75b3c1d5a53c935ef71364bbaabaa5c054b9acd (mode 644)
--- /dev/null
+++ control.h
@@ -0,0 +1,23 @@
+/* $OpenBSD: control.h,v 1.3 2021/01/19 16:54:00 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+int control_init(char *);
+int control_listen(int fd);
+void control_accept(int, short, void *);
+void control_dispatch_imsg(int, short, void *);
+int control_imsg_relay(struct imsg *);
blob - /dev/null
blob + 786a76debc3c4310400ca69fa83a257edc3ddddf (mode 644)
--- /dev/null
+++ ctl.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#include "amused.h"
+#include "log.h"
+#include "xmalloc.h"
+
+static struct imsgbuf *ibuf;
+
+int ctl_noarg(struct parse_result *, int, char **);
+int ctl_add(struct parse_result *, int, char **);
+
+struct ctl_command ctl_commands[] = {
+ { "play", PLAY, ctl_noarg, "" },
+ { "pause", PAUSE, ctl_noarg, "" },
+ { "toggle", TOGGLE, ctl_noarg, "" },
+ { "stop", STOP, ctl_noarg, "" },
+ { "restart", RESTART, ctl_noarg, "" },
+ { "add", ADD, ctl_add, "files...", 1 },
+ { "flush", FLUSH, ctl_noarg, "" },
+ { NULL },
+};
+
+__dead void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-dv] [-s socket]\n", getprogname());
+ fprintf(stderr, "%s version %s\n", getprogname(), AMUSED_VERSION);
+ exit(1);
+}
+
+static __dead void
+ctl_usage(struct ctl_command *ctl)
+{
+ fprintf(stderr, "usage: %s [-v] [-s socket] %s %s\n", getprogname(),
+ ctl->name, ctl->usage);
+ exit(1);
+}
+
+static int
+parse(int argc, char **argv)
+{
+ struct ctl_command *ctl = NULL;
+ struct parse_result res;
+ int i, status;
+
+ memset(&res, 0, sizeof(res));
+
+ for (i = 0; ctl_commands[i].name != NULL; ++i) {
+ if (strncmp(ctl_commands[i].name, argv[0], strlen(argv[0]))
+ == 0) {
+ if (ctl != NULL) {
+ fprintf(stderr,
+ "ambiguous argument: %s\n", argv[0]);
+ usage();
+ }
+ ctl = &ctl_commands[i];
+ }
+ }
+
+ if (ctl == NULL) {
+ fprintf(stderr, "unknown argument: %s\n", argv[0]);
+ usage();
+ }
+
+ res.action = ctl->action;
+ res.ctl = ctl;
+
+ if (!ctl->has_pledge) {
+ /* pledge(2) default if command doesn't have its own */
+ if (pledge("stdio", NULL) == -1)
+ fatal("pledge");
+ }
+
+ status = ctl->main(&res, argc, argv);
+ close(ibuf->fd);
+ free(ibuf);
+ return status;
+}
+
+static int
+enqueue_tracks(char **files)
+{
+ char res[PATH_MAX];
+ int enq = 0;
+
+ for (; *files != NULL; ++files) {
+ memset(&res, 0, sizeof(res));
+ if (realpath(*files, res) == NULL) {
+ log_warn("realpath %s", *files);
+ continue;
+ }
+
+ imsg_compose(ibuf, IMSG_CTL_ADD, 0, 0, -1,
+ res, sizeof(res));
+ enq++;
+ }
+
+ return enq == 0;
+}
+
+static int
+show_complete(struct imsg *imsg, int *ret)
+{
+ size_t datalen;
+ char path[PATH_MAX];
+
+ datalen = IMSG_DATA_SIZE(*imsg);
+ if (datalen == 0)
+ return 1;
+
+ if (datalen != sizeof(path))
+ fatalx("%s: data size mismatch", __func__);
+ memcpy(path, imsg->data, sizeof(path));
+ if (path[datalen-1] != '\0')
+ fatalx("%s: data corrupted?", __func__);
+
+ printf("%s\n", path);
+ return 0;
+}
+
+static int
+ctlaction(struct parse_result *res)
+{
+ struct imsg imsg;
+ ssize_t n;
+ int ret = 0, done = 1;
+
+ switch (res->action) {
+ case PLAY:
+ imsg_compose(ibuf, IMSG_CTL_PLAY, 0, 0, -1, NULL, 0);
+ break;
+ case PAUSE:
+ imsg_compose(ibuf, IMSG_CTL_PAUSE, 0, 0, -1, NULL, 0);
+ break;
+ case TOGGLE:
+ imsg_compose(ibuf, IMSG_CTL_TOGGLE_PLAY, 0, 0, -1, NULL, 0);
+ break;
+ case STOP:
+ imsg_compose(ibuf, IMSG_CTL_STOP, 0, 0, -1, NULL, 0);
+ break;
+ case RESTART:
+ imsg_compose(ibuf, IMSG_CTL_RESTART, 0, 0, -1, NULL, 0);
+ break;
+ case ADD:
+ ret = enqueue_tracks(res->files);
+ break;
+ case FLUSH:
+ imsg_compose(ibuf, IMSG_CTL_FLUSH, 0, 0, -1, NULL, 0);
+ break;
+ case SHOW:
+ done = 0;
+ imsg_compose(ibuf, IMSG_CTL_SHOW, 0, 0, -1, NULL, 0);
+ break;
+ case NONE:
+ /* action not expected */
+ fatalx("invalid action %u", res->action);
+ break;
+ }
+
+ if (ret != 0)
+ goto end;
+
+ imsg_flush(ibuf);
+
+ while (!done) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatalx("imsg_read error");
+ if (n == 0)
+ fatalx("pipe closed");
+
+ while (!done) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatalx("imsg_get error");
+ if (n == 0)
+ break;
+
+ switch (res->action) {
+ case SHOW:
+ done = show_complete(&imsg, &ret);
+ break;
+ default:
+ done = 1;
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+ }
+
+end:
+ return ret;
+}
+
+int
+ctl_noarg(struct parse_result *res, int argc, char **argv)
+{
+ if (argc != 1)
+ ctl_usage(res->ctl);
+ return ctlaction(res);
+}
+
+int
+ctl_add(struct parse_result *res, int argc, char **argv)
+{
+ if (argc < 2)
+ ctl_usage(res->ctl);
+ res->files = argv+1;
+ return ctlaction(res);
+}
+
+__dead void
+ctl(int argc, char **argv)
+{
+ struct sockaddr_un sun;
+ int ctl_sock;
+
+ log_init(1, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ fatal("socket");
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, csock, sizeof(sun.sun_path));
+
+ if (connect(ctl_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ fatal("connect %s", csock);
+
+ ibuf = xmalloc(sizeof(*ibuf));
+ imsg_init(ibuf, ctl_sock);
+
+ optreset = 1;
+ optind = 1;
+
+ exit(parse(argc, argv));
+}
blob - /dev/null
blob + 50b0a0e95ac84837dc120d024ba12c1206a04f4e (mode 644)
--- /dev/null
+++ log.c
@@ -0,0 +1,199 @@
+/* $OpenBSD: log.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <time.h>
+
+#include "log.h"
+
+static int debug;
+static int verbose;
+static const char *log_procname;
+
+void
+log_init(int n_debug, int facility)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+ verbose = n_debug;
+ log_procinit(__progname);
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, facility);
+
+ tzset();
+}
+
+void
+log_procinit(const char *procname)
+{
+ if (procname != NULL)
+ log_procname = procname;
+}
+
+void
+log_setverbose(int v)
+{
+ verbose = v;
+}
+
+int
+log_getverbose(void)
+{
+ return (verbose);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+ int saved_errno = errno;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+
+ errno = saved_errno;
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+ int saved_errno = errno;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg,
+ strerror(saved_errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_ERR, emsg, ap);
+ logit(LOG_ERR, "%s", strerror(saved_errno));
+ } else {
+ vlog(LOG_ERR, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+
+ errno = saved_errno;
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_ERR, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+static void
+vfatalc(int code, const char *emsg, va_list ap)
+{
+ static char s[BUFSIZ];
+ const char *sep;
+
+ if (emsg != NULL) {
+ (void)vsnprintf(s, sizeof(s), emsg, ap);
+ sep = ": ";
+ } else {
+ s[0] = '\0';
+ sep = "";
+ }
+ if (code)
+ logit(LOG_CRIT, "fatal in %s: %s%s%s",
+ log_procname, s, sep, strerror(code));
+ else
+ logit(LOG_CRIT, "fatal in %s%s%s", log_procname, sep, s);
+}
+
+void
+fatal(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(errno, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+fatalx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vfatalc(0, emsg, ap);
+ va_end(ap);
+ exit(1);
+}
blob - /dev/null
blob + 0fa046fc3afbe8c893d40f492481a4d3055dd2bc (mode 644)
--- /dev/null
+++ log.h
@@ -0,0 +1,45 @@
+/* $OpenBSD: log.h,v 1.2 2021/12/13 18:28:40 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdarg.h>
+
+void log_init(int, int);
+void log_procinit(const char *);
+void log_setverbose(int);
+int log_getverbose(void);
+void log_warn(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+__dead void fatalx(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+
+#endif /* LOG_H */
blob - /dev/null
blob + ddc342051acb4a5567bd9e2f630bfc28d0c4e9d0 (mode 644)
--- /dev/null
+++ player.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <limits.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <sndio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <imsg.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "amused.h"
+#include "log.h"
+#include "xmalloc.h"
+
+static struct imsgbuf *ibuf;
+
+static int nextfd = -1;
+static char nextpath[PATH_MAX];
+
+volatile sig_atomic_t halted;
+
+static void
+player_signal_handler(int signo)
+{
+ halted = 1;
+}
+
+static void
+audio_init(void)
+{
+ struct sio_par par;
+
+ if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0)) == NULL)
+ fatal("sio_open");
+
+ sio_initpar(&par);
+ par.bits = 16;
+ par.appbufsz = 50 * par.rate / 1000;
+ par.pchan = 2;
+ if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par))
+ fatal("couldn't set audio params");
+ if (par.bits != 16 || par.le != SIO_LE_NATIVE)
+ fatalx("unsupported audio params");
+ if (!sio_start(hdl))
+ fatal("sio_start");
+}
+
+int
+player_setrate(int rate)
+{
+ struct sio_par par;
+
+ log_debug("switching to sample rate %d", rate);
+
+ sio_stop(hdl);
+
+ sio_initpar(&par);
+ par.rate = rate;
+ if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) {
+ log_warnx("invalid params");
+ return -1;
+ }
+
+ /* TODO: check the sample rate? */
+
+ if (!sio_start(hdl)) {
+ log_warn("sio_start");
+ return -1;
+ }
+ return 0;
+}
+
+int
+player_pendingimsg(void)
+{
+ struct pollfd pfd;
+ int r;
+
+ if (halted != 0)
+ return 1;
+
+ pfd.fd = ibuf->fd;
+ pfd.revents = POLLIN;
+
+ r = poll(&pfd, 1, 0);
+ if (r == -1)
+ fatal("poll");
+ return r;
+}
+
+void
+player_enqueue(struct imsg *imsg)
+{
+ size_t datalen;
+
+ if (nextfd != -1)
+ fatalx("track already enqueued");
+
+ datalen = IMSG_DATA_SIZE(*imsg);
+ if (datalen != sizeof(nextpath))
+ fatalx("%s: size mismatch", __func__);
+ memcpy(nextpath, imsg->data, sizeof(nextpath));
+ if (nextpath[datalen-1] != '\0')
+ fatalx("%s: corrupted path", __func__);
+ if ((nextfd = imsg->fd) == -1)
+ fatalx("%s: got invalid file descriptor", __func__);
+ log_debug("enqueued %s", nextpath);
+}
+
+/* process only one message */
+int
+player_dispatch(void)
+{
+ struct imsg imsg;
+ ssize_t n;
+ int ret;
+
+ if (halted != 0)
+ return IMSG_STOP;
+
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatalx("imsg_read");
+ if (n == 0)
+ fatalx("pipe closed");
+
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("imsg_get");
+ if (n == 0) /* no more messages */
+ fatalx("expected at least a message");
+
+ ret = imsg.hdr.type;
+ switch (imsg.hdr.type) {
+ case IMSG_PLAY:
+ player_enqueue(&imsg);
+ ret = IMSG_STOP;
+ break;
+ case IMSG_RESUME:
+ case IMSG_PAUSE:
+ case IMSG_STOP:
+ break;
+ default:
+ fatalx("unknown imsg %d", imsg.hdr.type);
+ }
+
+ return ret;
+}
+
+void
+player_senderr(void)
+{
+ imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, NULL, 0);
+ imsg_flush(ibuf);
+}
+
+void
+player_sendeof(void)
+{
+ imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0);
+ imsg_flush(ibuf);
+}
+
+void
+player_playnext(void)
+{
+ int fd = nextfd;
+
+ assert(nextfd != -1);
+ nextfd = -1;
+
+ /* XXX: use magic(5) for this, not file extensions */
+ if (strstr(nextpath, ".ogg") != NULL)
+ play_oggvorbis(fd);
+ else if (strstr(nextpath, ".mp3") != NULL)
+ play_mp3(fd);
+ else if (strstr(nextpath, ".flac") != NULL)
+ play_flac(fd);
+ else if (strstr(nextpath, ".opus") != NULL)
+ play_opus(fd);
+ else {
+ log_warnx("unknown file type for %s", nextpath);
+ player_senderr();
+ return;
+ }
+
+ player_sendeof();
+}
+
+int
+player_pause(void)
+{
+ int r;
+
+ r = player_dispatch();
+ return r == IMSG_RESUME;
+}
+
+int
+player_shouldstop(void)
+{
+ if (!player_pendingimsg())
+ return 0;
+
+ switch (player_dispatch()) {
+ case IMSG_PAUSE:
+ if (player_pause())
+ break;
+ /* fallthrough */
+ case IMSG_STOP:
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+player(int debug, int verbose)
+{
+ int flags;
+ log_init(debug, LOG_DAEMON);
+ log_setverbose(verbose);
+
+ setproctitle("player");
+ log_procinit("player");
+
+#if 0
+ {
+ static int attached;
+
+ while (!attached)
+ sleep(1);
+ }
+#endif
+
+
+ audio_init();
+
+ /* mark fd as blocking i/o mode */
+ if ((flags = fcntl(3, F_GETFL)) == -1)
+ fatal("fcntl(F_GETFL)");
+ if (fcntl(3, F_SETFL, flags & ~O_NONBLOCK) == -1)
+ fatal("fcntl F_SETFL O_NONBLOCK");
+
+ ibuf = xmalloc(sizeof(*ibuf));
+ imsg_init(ibuf, 3);
+
+ signal(SIGINT, player_signal_handler);
+ signal(SIGTERM, player_signal_handler);
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (pledge("stdio recvfd", NULL) == -1)
+ fatal("pledge");
+
+ while (!halted) {
+ while (nextfd == -1)
+ assert(player_dispatch() == IMSG_STOP);
+
+ player_playnext();
+ }
+
+ return 0;
+}
blob - /dev/null
blob + ce442b965abad7a55c1e19642875989337955088 (mode 644)
--- /dev/null
+++ player_flac.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <event.h>
+#include <inttypes.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <imsg.h>
+
+#include <FLAC/stream_decoder.h>
+
+#include "amused.h"
+
+static FLAC__StreamDecoderWriteStatus
+writecb(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
+ const int32_t * const *buffer, void *data)
+{
+ static uint8_t buf[BUFSIZ];
+ int i;
+ size_t len;
+
+ if (player_shouldstop())
+ return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+
+ for (i = 0, len = 0; i < frame->header.blocksize; ++i) {
+ if (len+4 >= sizeof(buf)) {
+ sio_write(hdl, buf, len);
+ len = 0;
+ }
+
+ buf[len++] = buffer[0][i] & 0xff;
+ buf[len++] = (buffer[0][i] >> 8) & 0xff;
+
+ buf[len++] = buffer[1][i] & 0xff;
+ buf[len++] = (buffer[1][i] >> 8) & 0xff;
+ }
+
+ if (len != 0)
+ sio_write(hdl, buf, len);
+
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+static void
+metacb(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *meta,
+ void *d)
+{
+ uint32_t sample_rate;
+ struct sio_par par;
+
+ if (meta->type == FLAC__METADATA_TYPE_STREAMINFO) {
+ sample_rate = meta->data.stream_info.sample_rate;
+
+ printf("sample rate: %d\n", sample_rate);
+ printf("channels: %d\n", meta->data.stream_info.channels);
+ printf("bps: %d\n", meta->data.stream_info.bits_per_sample);
+ printf("total samples: %"PRIu64"\n", meta->data.stream_info.total_samples);
+
+ if (player_setrate(sample_rate) == -1)
+ err(1, "player_setrate");
+
+ sio_stop(hdl);
+
+ sio_initpar(&par);
+ par.rate = sample_rate;
+ if (!sio_setpar(hdl, &par))
+ err(1, "sio_setpar");
+ if (!sio_getpar(hdl, &par))
+ err(1, "sio_getpar");
+ /* TODO: check that there is a sane sample rate? */
+ if (!sio_start(hdl))
+ err(1, "sio_start");
+ }
+}
+
+static void
+errcb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status,
+ void *data)
+{
+ warnx("error: %s", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+void
+play_flac(int fd)
+{
+ FILE *f;
+ int ok = 1;
+ FLAC__StreamDecoder *decoder = NULL;
+ FLAC__StreamDecoderInitStatus init_status;
+
+ if ((f = fdopen(fd, "r")) == NULL)
+ err(1, "fdopen");
+
+ decoder = FLAC__stream_decoder_new();
+ if (decoder == NULL)
+ err(1, "flac stream decoder");
+
+ FLAC__stream_decoder_set_md5_checking(decoder, 1);
+
+ init_status = FLAC__stream_decoder_init_FILE(decoder, f, writecb,
+ metacb, errcb, NULL);
+ if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
+ errx(1, "flac decoder: %s",
+ FLAC__StreamDecoderInitStatusString[init_status]);
+
+ ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
+ warnx("decoding %s", ok ? "succeeded" : "failed");
+ warnx("state: %s",
+ FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)]);
+
+ FLAC__stream_decoder_delete(decoder);
+}
blob - /dev/null
blob + 5dda0c58eb3755eae21ae708c60579876220804e (mode 644)
--- /dev/null
+++ player_mad.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <event.h>
+#include <fcntl.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <imsg.h>
+#include <unistd.h>
+
+#include <mad.h>
+
+#include "amused.h"
+
+struct mad_stream mad_stream;
+struct mad_frame mad_frame;
+struct mad_synth mad_synth;
+
+struct buffer {
+ const void *start;
+ size_t length;
+ int sample_rate;
+};
+
+static enum mad_flow
+input(void *d, struct mad_stream *stream)
+{
+ struct buffer *buffer = d;
+
+ if (buffer->length == 0)
+ return MAD_FLOW_STOP;
+
+ printf("decode time! start=%p, len=%zu\n", buffer->start, buffer->length);
+ mad_stream_buffer(stream, buffer->start, buffer->length);
+ buffer->length = 0;
+ buffer->sample_rate = 0;
+ return MAD_FLOW_CONTINUE;
+}
+
+/* scale a mad sample to 16 bits */
+static inline int
+scale(mad_fixed_t sample)
+{
+ /* round */
+ sample += (1L << (MAD_F_FRACBITS - 16));
+
+ /* clip */
+ if (sample >= MAD_F_ONE)
+ sample = MAD_F_ONE - 1;
+ else if (sample < -MAD_F_ONE)
+ sample -= MAD_F_ONE;
+
+ /* quantize */
+ return sample >> (MAD_F_FRACBITS + 1 - 16);
+}
+
+static enum mad_flow
+output(void *data, const struct mad_header *header, struct mad_pcm *pcm)
+{
+ static uint8_t buf[BUFSIZ];
+ size_t len;
+ struct buffer *buffer = data;
+ int nsamples, i;
+ uint16_t sample;
+ const mad_fixed_t *leftch, *rightch;
+
+ if (player_shouldstop())
+ return MAD_FLOW_STOP;
+
+ nsamples = pcm->length;
+ leftch = pcm->samples[0];
+ rightch = pcm->samples[1];
+
+ if (buffer->sample_rate != pcm->samplerate) {
+ buffer->sample_rate = pcm->samplerate;
+ if (player_setrate(pcm->samplerate) == -1)
+ err(1, "player_setrate");
+ }
+
+ if (pcm->channels != 2) {
+ printf("mono not supported!\n");
+ return MAD_FLOW_STOP;
+ }
+
+ for (i = 0, len = 0; i < nsamples; ++i) {
+ if (len+4 >= sizeof(buf)) {
+ sio_write(hdl, buf, len);
+ /* fwrite(buf, 1, len, stdout); */
+ len = 0;
+ }
+
+ sample = scale(*leftch++);
+ buf[len++] = sample & 0xff;
+ buf[len++] = (sample >> 8) & 0xff;
+
+ sample = scale(*rightch++);
+ buf[len++] = sample & 0xff;
+ buf[len++] = (sample >> 8) & 0xff;
+ }
+
+ if (len != 0)
+ /* fwrite(buf, 1, len, stdout); */
+ sio_write(hdl, buf, len);
+
+ return MAD_FLOW_CONTINUE;
+}
+
+static enum mad_flow
+error(void *d, struct mad_stream *stream, struct mad_frame *frame)
+{
+ struct buffer *buffer = d;
+
+ warnx("decoding error 0x%04x (%s) at byte offset %zu",
+ stream->error, mad_stream_errorstr(stream),
+ stream->this_frame - (const unsigned char *)buffer->start);
+
+ return MAD_FLOW_CONTINUE;
+}
+
+static int
+decode(void *m, size_t len)
+{
+ struct buffer buffer;
+ struct mad_decoder decoder;
+ int result;
+
+ /* initialize our private message structure; */
+ buffer.start = m;
+ buffer.length = len;
+
+ /* configure input, output and error functions */
+ mad_decoder_init(&decoder, &buffer, input, 0 /* header */,
+ 0 /* filter */, output, error, 0 /* message */);
+
+ /* start decoding */
+ result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
+
+ /* release the decoder */
+ mad_decoder_finish(&decoder);
+
+ return result;
+}
+
+void
+play_mp3(int fd)
+{
+ struct stat stat;
+ void *m;
+
+ if (fstat(fd, &stat) == -1)
+ err(1, "fstat");
+ warnx("file size %lld", stat.st_size);
+
+ m = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (m == MAP_FAILED)
+ err(1, "mmap");
+
+ decode(m, stat.st_size);
+ munmap(m, stat.st_size);
+}
blob - /dev/null
blob + 2726b9419c7466e3933d2076a20fa4bc9af8cce9 (mode 644)
--- /dev/null
+++ player_oggvorbis.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <event.h>
+#include <fcntl.h>
+#include <math.h>
+#include <inttypes.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <imsg.h>
+#include <unistd.h>
+
+#include <vorbis/codec.h>
+#include <vorbis/vorbisfile.h>
+
+#include "amused.h"
+
+#ifndef nitems
+#define nitems(x) (sizeof(x)/sizeof(x[0]))
+#endif
+
+void
+play_oggvorbis(int fd)
+{
+ static uint8_t pcmout[4096];
+ FILE *f;
+ OggVorbis_File vf;
+ int current_section, eof = 0;
+
+ if ((f = fdopen(fd, "r")) == NULL)
+ err(1, "fdopen");
+
+ if (ov_open_callbacks(f, &vf, NULL, 0, OV_CALLBACKS_NOCLOSE) < 0)
+ errx(1, "input is not an Ogg bitstream");
+
+ {
+ char **ptr;
+ vorbis_info *vi;
+
+ vi = ov_info(&vf, -1);
+ for (ptr = ov_comment(&vf, -1)->user_comments; *ptr; ++ptr)
+ printf("%s\n", *ptr);
+
+ printf("bitstream is %d channel, %ldHz\n", vi->channels, vi->rate);
+
+ if (player_setrate(vi->rate) == -1)
+ err(1, "player_setrate");
+ }
+
+ while (!eof) {
+ long ret;
+
+ if (player_shouldstop())
+ break;
+
+ ret = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1,
+ &current_section);
+ if (ret == 0)
+ eof = 1;
+ else if (ret < 0)
+ warnx("non-fatal error in the stream %ld", ret);
+ else {
+ /* TODO: deal with sample rate changes */
+ sio_write(hdl, pcmout, ret);
+ }
+ }
+
+ ov_clear(&vf);
+}
blob - /dev/null
blob + 43812e46cda8499ed6cdd447707100c0396ddc55 (mode 644)
--- /dev/null
+++ player_opus.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <err.h>
+#include <event.h>
+#include <fcntl.h>
+#include <math.h>
+#include <inttypes.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <imsg.h>
+#include <unistd.h>
+
+#include <opusfile.h>
+
+#include "amused.h"
+
+#ifndef nitems
+#define nitems(x) (sizeof(x)/sizeof(x[0]))
+#endif
+
+void
+play_opus(int fd)
+{
+ static uint16_t pcm[BUFSIZ];
+ static uint8_t out[BUFSIZ * 2];
+ OggOpusFile *of;
+ void *f;
+ int ret;
+ OpusFileCallbacks cb = {NULL, NULL, NULL, NULL};
+ int i, li, prev_li = -1;
+
+ if ((f = op_fdopen(&cb, fd, "r")) == NULL)
+ err(1, "fdopen");
+
+ of = op_open_callbacks(f, &cb, NULL, 0, &ret);
+ if (of == NULL)
+ errx(1, "failed open opus file");
+
+ for (;;) {
+ if (player_shouldstop())
+ break;
+
+ ret = op_read_stereo(of, pcm, nitems(pcm));
+ if (ret == OP_HOLE) /* corrupt file segment? */
+ continue;
+ if (ret < 0)
+ errx(1, "error decoding file (%d)", ret);
+ if (ret == 0)
+ break; /* eof */
+
+ li = op_current_link(of);
+ if (li != prev_li) {
+ const OpusHead *head;
+
+ prev_li = li;
+
+ head = op_head(of, li);
+ if (head->input_sample_rate) {
+ if (player_setrate(head->input_sample_rate)
+ == -1)
+ err(1, "player_setrate");
+ }
+ }
+
+ for (i = 0; i < 2*ret; ++i) {
+ out[2*i+0] = pcm[i] & 0xFF;
+ out[2*i+1] = (pcm[i] >> 8) & 0xFF;
+ }
+ sio_write(hdl, out, 4*ret);
+ }
+
+ op_free(of);
+}
blob - /dev/null
blob + 46a6ac4889b64d4b782b42e6fe09adf838705cd4 (mode 644)
--- /dev/null
+++ playlist.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <syslog.h>
+
+#include "log.h"
+#include "xmalloc.h"
+#include "playlist.h"
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+struct playlist playlist;
+enum play_state play_state;
+int repeat_one;
+int repeat_all = 1;
+ssize_t play_off = -1;
+
+void
+playlist_enqueue(const char *path)
+{
+ size_t newcap;
+
+ if (playlist.len == playlist.cap) {
+ newcap = MAX(16, playlist.cap * 1.5);
+ playlist.songs = xrecallocarray(playlist.songs, playlist.cap,
+ newcap, sizeof(*playlist.songs));
+ playlist.cap = newcap;
+ }
+
+ playlist.songs[playlist.len++] = xstrdup(path);
+}
+
+const char *
+playlist_current(void)
+{
+ if (playlist.len == 0 || play_off == -1)
+ return NULL;
+
+ return playlist.songs[play_off];
+}
+
+const char *
+playlist_advance(void)
+{
+ if (playlist.len == 0 || play_off+1 == playlist.len)
+ return NULL;
+
+ play_off++;
+ if (play_off == playlist.len) {
+ if (repeat_all)
+ play_off = 0;
+ else {
+ play_state = STATE_STOPPED;
+ play_off = -1;
+ return NULL;
+ }
+ }
+
+ play_state = STATE_PLAYING;
+ return playlist.songs[play_off];
+}
+
+void
+playlist_reset(void)
+{
+ play_off = -1;
+}
+
+void
+playlist_truncate(void)
+{
+ size_t i;
+
+ for (i = 0; i < playlist.len; ++i)
+ free(playlist.songs[i]);
+ free(playlist.songs);
+ playlist.songs = NULL;
+
+ playlist.len = 0;
+ playlist.cap = 0;
+ play_off = -1;
+}
blob - /dev/null
blob + 3b2e2ed26d78dceb8e3fa7c637cb95218fedc322 (mode 644)
--- /dev/null
+++ playlist.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef PLAYLIST_H
+#define PLAYLIST_H
+
+struct playlist {
+ size_t len;
+ size_t cap;
+ char **songs;
+};
+
+enum play_state {
+ STATE_STOPPED,
+ STATE_PLAYING,
+ STATE_PAUSED,
+};
+
+extern struct playlist playlist;
+
+extern enum play_state play_state;
+extern int repeat_one;
+extern int repeat_all;
+extern ssize_t play_off;
+
+void playlist_enqueue(const char *);
+const char *playlist_current(void);
+const char *playlist_advance(void);
+void playlist_reset(void);
+void playlist_truncate(void);
+
+#endif
blob - /dev/null
blob + f05ceadf51129ffe6559594eeea12fa517db5f69 (mode 644)
--- /dev/null
+++ xmalloc.c
@@ -0,0 +1,100 @@
+/* $OpenBSD: xmalloc.c,v 1.4 2019/06/28 05:44:09 deraadt Exp $ */
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "log.h"
+#include "xmalloc.h"
+
+void *
+xmalloc(size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ fatal("xmalloc: zero size");
+ ptr = malloc(size);
+ if (ptr == NULL)
+ fatal("xmalloc: allocating %zu bytes", size);
+ return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ if (size == 0 || nmemb == 0)
+ fatal("xcalloc: zero size");
+ ptr = calloc(nmemb, size);
+ if (ptr == NULL)
+ fatal("xcalloc: allocating %zu * %zu bytes", nmemb, size);
+ return ptr;
+}
+
+void *
+xreallocarray(void *ptr, size_t nmemb, size_t size)
+{
+ void *new_ptr;
+
+ new_ptr = reallocarray(ptr, nmemb, size);
+ if (new_ptr == NULL)
+ fatal("xreallocarray: allocating %zu * %zu bytes",
+ nmemb, size);
+ return new_ptr;
+}
+
+void *
+xrecallocarray(void *ptr, size_t oldnmemb, size_t nmemb, size_t size)
+{
+ void *new_ptr;
+
+ new_ptr = recallocarray(ptr, oldnmemb, nmemb, size);
+ if (new_ptr == NULL)
+ fatal("xrecallocarray: allocating %zu * %zu bytes",
+ nmemb, size);
+ return new_ptr;
+}
+
+char *
+xstrdup(const char *str)
+{
+ char *cp;
+
+ if ((cp = strdup(str)) == NULL)
+ fatal("xstrdup");
+ return cp;
+}
+
+int
+xasprintf(char **ret, const char *fmt, ...)
+{
+ va_list ap;
+ int i;
+
+ va_start(ap, fmt);
+ i = vasprintf(ret, fmt, ap);
+ va_end(ap);
+
+ if (i == -1)
+ fatal("xasprintf");
+
+ return i;
+}
blob - /dev/null
blob + 5e11cb4e79d681af3c5b8787d9308302336a092f (mode 644)
--- /dev/null
+++ xmalloc.h
@@ -0,0 +1,31 @@
+/* $OpenBSD: xmalloc.h,v 1.3 2015/11/17 18:25:03 tobias Exp $ */
+
+/*
+ * Author: Tatu Ylonen <ylo@cs.hut.fi>
+ * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
+ * All rights reserved
+ * Created: Mon Mar 20 22:09:17 1995 ylo
+ *
+ * Versions of malloc and friends that check their results, and never return
+ * failure (they call fatal if they encounter an error).
+ *
+ * As far as I am concerned, the code I have written for this software
+ * can be used freely for any purpose. Any derived versions of this
+ * software must be clearly marked as such, and if the derived work is
+ * incompatible with the protocol description in the RFC file, it must be
+ * called by a name other than "ssh" or "Secure Shell".
+ */
+
+#ifndef XMALLOC_H
+#define XMALLOC_H
+
+void *xmalloc(size_t);
+void *xcalloc(size_t, size_t);
+void *xreallocarray(void *, size_t, size_t);
+void *xrecallocarray(void *, size_t, size_t, size_t);
+char *xstrdup(const char *);
+int xasprintf(char **, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)))
+ __attribute__((__nonnull__ (2)));
+
+#endif /* XMALLOC_H */
Omar Polo