#include <errno.h>
#include <netinet/in.h>
#include <sys/time.h>
+#include <sys/un.h>
#include "log.h"
#include "mem.h"
#include "timeval.h"
#include "client.h"
#include "playrtp.h"
+#include "inputline.h"
#define readahead linux_headers_are_borked
HEAP_DEFINE(pheap, struct packet *, lt_packet);
+/** @brief Control socket or NULL */
+const char *control_socket;
+
static const struct option options[] = {
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'V' },
#if HAVE_COREAUDIO_AUDIOHARDWARE_H
{ "core-audio", no_argument, 0, 'c' },
#endif
+ { "socket", required_argument, 0, 's' },
{ "config", required_argument, 0, 'C' },
{ 0, 0, 0, 0 }
};
+/** @brief Control thread
+ *
+ * This thread is responsible for accepting control commands from Disobedience
+ * (or other controllers) over an AF_UNIX stream socket with a path specified
+ * by the @c --socket option. The protocol uses simple string commands and
+ * replies:
+ *
+ * - @c stop will shut the player down
+ * - @c query will send back the reply @c running
+ * - anything else is ignored
+ *
+ * Commands and response strings terminated by shutting down the connection or
+ * by a newline. No attempt is made to multiplex multiple clients so it is
+ * important that the command be sent as soon as the connection is made - it is
+ * assumed that both parties to the protocol are entirely cooperating with one
+ * another.
+ */
+static void *control_thread(void attribute((unused)) *arg) {
+ struct sockaddr_un sa;
+ int sfd, cfd;
+ char *line;
+ socklen_t salen;
+ FILE *fp;
+
+ assert(control_socket);
+ unlink(control_socket);
+ memset(&sa, 0, sizeof sa);
+ sa.sun_family = AF_UNIX;
+ strcpy(sa.sun_path, control_socket);
+ sfd = xsocket(PF_UNIX, SOCK_STREAM, 0);
+ if(bind(sfd, (const struct sockaddr *)&sa, sizeof sa) < 0)
+ fatal(errno, "error binding to %s", control_socket);
+ if(listen(sfd, 128) < 0)
+ fatal(errno, "error calling listen on %s", control_socket);
+ info("listening on %s", control_socket);
+ for(;;) {
+ salen = sizeof sa;
+ cfd = accept(sfd, (struct sockaddr *)&sa, &salen);
+ if(cfd < 0) {
+ switch(errno) {
+ case EINTR:
+ case EAGAIN:
+ break;
+ default:
+ fatal(errno, "error calling accept on %s", control_socket);
+ }
+ }
+ if(!(fp = fdopen(cfd, "r+"))) {
+ error(errno, "error calling fdopen for %s connection", control_socket);
+ close(cfd);
+ continue;
+ }
+ if(!inputline(control_socket, fp, &line, '\n')) {
+ if(!strcmp(line, "stop")) {
+ info("stopped via %s", control_socket);
+ exit(0); /* terminate immediately */
+ }
+ if(!strcmp(line, "query"))
+ fprintf(fp, "running");
+ xfree(line);
+ }
+ if(fclose(fp) < 0)
+ error(errno, "error closing %s connection", control_socket);
+ }
+}
+
/** @brief Drop the first packet
*
* Assumes that @ref lock is held.
*/
static void play_rtp(void) {
pthread_t ltid;
+ int err;
/* We receive and convert audio data in a background thread */
- pthread_create(<id, 0, listen_thread, 0);
+ if((err = pthread_create(<id, 0, listen_thread, 0)))
+ fatal(err, "pthread_create listen_thread");
/* We have a second thread to add received packets to the queue */
- pthread_create(<id, 0, queue_thread, 0);
+ if((err = pthread_create(<id, 0, queue_thread, 0)))
+ fatal(err, "pthread_create queue_thread");
/* The rest of the work is backend-specific */
backend();
}
}
int main(int argc, char **argv) {
- int n;
+ int n, err;
struct addrinfo *res;
struct stringlist sl;
char *sockname;
case 'c': backend = playrtp_coreaudio; break;
#endif
case 'C': configfile = optarg; break;
+ case 's': control_socket = optarg; break;
default: fatal(0, "invalid option");
}
}
info("default socket receive buffer %d", rcvbuf);
if(logfp)
info("WARNING: -L option can impact performance");
+ if(control_socket) {
+ pthread_t tid;
+
+ if((err = pthread_create(&tid, 0, control_thread, 0)))
+ fatal(err, "pthread_create control_thread");
+ }
play_rtp();
return 0;
}
disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c \
choose.c misc.c style.h control.c properties.c menu.c \
- log.c progress.c login.c \
+ log.c progress.c login.c rtp.c \
../lib/memgc.c
disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT)
disobedience_LDFLAGS=$(GTK_LIBS)
static void update_random_disable(const struct icon *);
static void update_enable(const struct icon *);
static void update_disable(const struct icon *);
+static void update_rtp(const struct icon *);
+static void update_nortp(const struct icon *);
static void clicked_icon(GtkButton *, gpointer);
+static int enable_rtp(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ void *v);
+static int disable_rtp(disorder_eclient *c,
+ disorder_eclient_no_response *completed,
+ void *v);
+
static double left(double v, double b);
static double right(double v, double b);
static double volume(double l, double r);
disorder_eclient_enable, 0 },
{ "notescross.png", "Disable play", clicked_icon, update_disable,
disorder_eclient_disable, 0 },
+ { "speaker.png", "Play network stream", clicked_icon, update_rtp,
+ enable_rtp, 0 },
+ { "speakercross.png", "Stop playing network stream", clicked_icon, update_nortp,
+ disable_rtp, 0 },
};
/** @brief Count of icons */
static GtkAdjustment *balance_adj;
/** @brief Called whenever last_state changes in any way */
-static void control_monitor(void attribute((unused)) *u) {
+void control_monitor(void attribute((unused)) *u) {
int n;
D(("control_monitor"));
update_icon(icon, visible, usable);
}
+static void update_rtp(const struct icon *icon) {
+ const int visible = !rtp_is_running;
+ const int usable = rtp_supported;
+ update_icon(icon, visible, usable);
+}
+
+static void update_nortp(const struct icon *icon) {
+ const int visible = rtp_is_running;
+ const int usable = rtp_supported;
+ update_icon(icon, visible, usable);
+}
+
static void clicked_icon(GtkButton attribute((unused)) *button,
gpointer userdata) {
const struct icon *icon = userdata;
return 0;
}
+/** @brief Called to enable RTP play
+ *
+ * Rather odd signature is to fit in with the other icons which all call @ref
+ * lib/eclient.h functions.
+ */
+static int enable_rtp(disorder_eclient attribute((unused)) *c,
+ disorder_eclient_no_response attribute((unused)) *completed,
+ void attribute((unused)) *v) {
+ start_rtp();
+ return 0;
+}
+
+/** @brief Called to disable RTP play
+ *
+ * Rather odd signature is to fit in with the other icons which all call @ref
+ * lib/eclient.h functions.
+ */
+static int disable_rtp(disorder_eclient attribute((unused)) *c,
+ disorder_eclient_no_response attribute((unused)) *completed,
+ void attribute((unused)) *v) {
+ stop_rtp();
+ return 0;
+}
+
/*
Local Variables:
c-basic-offset:2
/** @brief True if a NOP is in flight */
static int nop_in_flight;
+/** @brief True if an rtp-address command is in flight */
+static int rtp_address_in_flight;
+
/** @brief Global tooltip group */
GtkTooltips *tips;
+/** @brief True if RTP play is available
+ *
+ * This is a bit of a bodge...
+ */
+int rtp_supported;
+
+/** @brief True if RTP play is enabled */
+int rtp_is_running;
+
/** @brief Linked list of functions to call when we reset login parameters */
static struct reset_callback_node {
struct reset_callback_node *next;
nop_in_flight = 1;
disorder_eclient_nop(client, nop_completed, 0);
}
+ if(rtp_supported) {
+ const int old_state = rtp_is_running;
+ rtp_is_running = rtp_running();
+ if(old_state != rtp_is_running)
+ control_monitor(0);
+ }
return TRUE; /* keep call me please */
}
+/** @brief Called when a rtp-address command succeeds */
+static void got_rtp_address(void attribute((unused)) *v,
+ int attribute((unused)) nvec,
+ char attribute((unused)) **vec) {
+ rtp_address_in_flight = 0;
+ rtp_supported = 1;
+ rtp_is_running = rtp_running();
+ control_monitor(0);
+}
+
+/** @brief Called when a rtp-address command fails */
+static void no_rtp_address(struct callbackdata attribute((unused)) *cbd,
+ int attribute((unused)) code,
+ const char attribute((unused)) *msg) {
+ rtp_address_in_flight = 0;
+ rtp_supported = 0;
+ rtp_is_running = 0;
+ control_monitor(0);
+}
+
+/** @brief Called to check whether RTP play is available */
+static void check_rtp_address(void) {
+ if(!rtp_address_in_flight) {
+ struct callbackdata *const cbd = xmalloc(sizeof *cbd);
+ cbd->onerror = no_rtp_address;
+ disorder_eclient_rtp_address(client, got_rtp_address, cbd);
+ }
+}
+
/* main -------------------------------------------------------------------- */
static const struct option options[] = {
/* reset the clients */
disorder_eclient_close(client);
disorder_eclient_close(logclient);
+ rtp_supported = 0;
for(r = resets; r; r = r->next)
r->callback();
+ /* Might be a new server so re-check */
+ check_rtp_address();
}
/** @brief Register a reset callback */
register_reset(properties_reset);
/* Start monitoring the log */
disorder_eclient_log(logclient, &log_callbacks, 0);
+ /* See if RTP play supported */
+ check_rtp_address();
D(("enter main loop"));
MTAG("misc");
g_main_loop_run(mainloop);
extern double goesupto; /* volume upper bound */
extern int choosealpha; /* break up choose by letter */
extern GtkTooltips *tips;
+extern int rtp_supported;
+extern int rtp_is_running;
+
extern const disorder_eclient_log_callbacks log_callbacks;
void volume_update(void);
/* Called whenever we think the volume control has changed */
+void control_monitor(void *u);
+
/* Queue/Recent/Added */
GtkWidget *queue_widget(void);
void choose_update(void);
/* Called when we think the choose tree might need updating */
+/* Login details */
+
void login_box(void);
+/* RTP */
+
+int rtp_running(void);
+void start_rtp(void);
+void stop_rtp(void);
+
/* Widget leakage debugging rubbish ---------------------------------------- */
#if MDEBUG
--- /dev/null
+/*
+ * This file is part of Disobedience
+ * Copyright (C) 2007 Richard Kettlewell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+/** @file disobedience/rtp.c
+ * @brief RTP player support for Disobedience
+ */
+
+#include "disobedience.h"
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/un.h>
+
+/** @brief Path to RTP player's control socket */
+static char *rtp_socket;
+
+/** @brief Path to RTP player's logfile */
+static char *rtp_log;
+
+/** @brief Initialize @ref rtp_socket and @ref rtp_log if necessary */
+static void rtp_init(void) {
+ if(!rtp_socket) {
+ const char *home = getenv("HOME");
+ char *dir;
+
+ byte_xasprintf(&dir, "%s/.disorder", home);
+ mkdir(dir, 02700);
+ byte_xasprintf(&rtp_socket, "%s/rtp", dir);
+ byte_xasprintf(&rtp_log, "%s/rtp.log", dir);
+ }
+}
+
+/** @brief Return a connection to the RTP player's control socket */
+static FILE *rtp_connect(void) {
+ struct sockaddr_un un;
+ int fd;
+ FILE *fp;
+
+ rtp_init();
+ memset(&un, 0, sizeof un);
+ un.sun_family = AF_UNIX;
+ strcpy(un.sun_path, rtp_socket);
+ fd = xsocket(PF_UNIX, SOCK_STREAM, 0);
+ if(connect(fd, (const struct sockaddr *)&un, sizeof un) < 0) {
+ /* Connection refused just means that the player quit without deleting its
+ * socket */
+ switch(errno) {
+ case ECONNREFUSED:
+ case ENOENT:
+ break;
+ default:
+ /* Anything else may be a problem */
+ fpopup_msg(GTK_MESSAGE_ERROR, "connecting to %s: %s",
+ rtp_socket, strerror(errno));
+ break;
+ }
+ close(fd);
+ return 0;
+ }
+ if(!(fp = fdopen(fd, "r+"))) {
+ fpopup_msg(GTK_MESSAGE_ERROR, "error calling fdopen: %s", strerror(errno));
+ close(fd);
+ return 0;
+ }
+ return fp;
+}
+
+/** @brief Return non-0 iff the RTP player is running */
+int rtp_running(void) {
+ FILE *fp = rtp_connect();
+
+ if(!fp)
+ return 0; /* no connection -> not running */
+ fclose(fp); /* connection -> running */
+ return 1;
+}
+
+/** @brief Activate the RTP player if it is not running */
+void start_rtp(void) {
+ pid_t pid;
+ int w, fd;
+
+ if(rtp_running())
+ return; /* already running */
+ /* double-fork so we don't have to wait() later */
+ if(!(pid = xfork())) {
+ if(setsid() < 0)
+ fatal(errno, "error calling setsid");
+ if(!(pid = xfork())) {
+ /* grandchild */
+ exitfn = _exit;
+ /* log errors and output somewhere reasonably sane. rtp_running()
+ * will have made sure the directory exists. */
+ if((fd = open(rtp_log, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0)
+ fatal(errno, "creating %s", rtp_log);
+ if(dup2(fd, 1) < 0
+ || dup2(fd, 2) < 0)
+ fatal(errno, "dup2");
+ if(close(fd) < 0)
+ fatal(errno, "close");
+ /* We don't want to hang onto whatever stdin was */
+ if((fd = open("/dev/null", O_RDONLY)) < 0)
+ fatal(errno, "opening /dev/null");
+ if(dup2(fd, 0) < 0)
+ fatal(errno, "dup2");
+ if(close(fd) < 0)
+ fatal(errno, "close");
+ /* execute the player */
+ execlp("disorder-playrtp",
+ "disorder-playrtp", "--socket", rtp_socket, (char *)0);
+ fatal(errno, "disorder-playrtp");
+ } else {
+ /* child */
+ _exit(0);
+ }
+ } else {
+ /* parent */
+ while(waitpid(pid, &w, 0) < 0 && errno == EINTR)
+ ;
+ }
+}
+
+/** @brief Stop the RTP player if it is running */
+void stop_rtp(void) {
+ FILE *fp = rtp_connect();
+
+ if(!fp)
+ return; /* already stopped */
+ fprintf(fp, "stop\n");
+ fclose(fp);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
static_DATA=cross.png down.png downdown.png edit.png nocross.png \
nodown.png nodowndown.png noup.png noupup.png tick.png up.png upup.png \
notes.png play.png pause.png random.png randomcross.png notescross.png \
-propagate.png
+propagate.png speaker.png speakercross.png
staticdir=${pkgdatadir}/static
"new", limit, (char *)0);
}
+static void rtp_response_opcallback(disorder_eclient *c,
+ struct operation *op) {
+ D(("rtp_response_opcallback"));
+ if(c->rc / 100 == 2) {
+ if(op->completed) {
+ int nvec;
+ char **vec = split(c->line + 4, &nvec, SPLIT_QUOTES, 0, 0);
+
+ ((disorder_eclient_list_response *)op->completed)(op->v, nvec, vec);
+ }
+ } else
+ protocol_error(c, op, c->rc, "%s: %s", c->ident, c->line);
+}
+
+/** @brief Determine the RTP target address
+ * @param c Client
+ * @param completed Called with address details
+ * @param v Passed to @p completed
+ *
+ * The address details will be two elements, the first being the hostname and
+ * the second the service (port).
+ */
+int disorder_eclient_rtp_address(disorder_eclient *c,
+ disorder_eclient_list_response *completed,
+ void *v) {
+ return simple(c, rtp_response_opcallback, (void (*)())completed, v,
+ "rtp-address", (char *)0);
+}
+
/* Log clients ***************************************************************/
/** @brief Monitor the server log
disorder_eclient_list_response *completed,
int max,
void *v);
+
+int disorder_eclient_rtp_address(disorder_eclient *c,
+ disorder_eclient_list_response *completed,
+ void *v);
+
#endif
/*
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
+/** @file lib/inputline.c
+ * @brief Line input
+ */
#include <config.h>
#include "types.h"
#include "charset.h"
#include "inputline.h"
+/** @brief Read a line from @p fp
+ * @param tag Used in error messages
+ * @param fp Stream to read from
+ * @param lp Where to store newly allocated string
+ * @param newline Newline character
+ * @return 0 on success, -1 on error or eof.
+ *
+ * The newline is not included in the string. If the last line of a
+ * stream does not have a newline then that line is still returned.
+ *
+ * @p *lp is only set if the return value was 0.
+ */
int inputline(const char *tag, FILE *fp, char **lp, int newline) {
struct dynstr d;
int ch;