From a99c4e9a49364934a3f74f003507477b9840acb2 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sat, 20 Oct 2007 11:27:00 +0100 Subject: [PATCH] disobedience can stop/start a background rtp player now Organization: Straylight/Edgeware From: Richard Kettlewell --- clients/playrtp.c | 88 +++++++++++++++++++- disobedience/Makefile.am | 2 +- disobedience/control.c | 51 +++++++++++- disobedience/disobedience.c | 52 ++++++++++++ disobedience/disobedience.h | 13 +++ disobedience/rtp.c | 156 ++++++++++++++++++++++++++++++++++++ images/Makefile.am | 2 +- images/speaker.png | Bin 0 -> 267 bytes images/speaker.xcf | Bin 0 -> 2399 bytes images/speakercross.png | Bin 0 -> 492 bytes lib/eclient.c | 29 +++++++ lib/eclient.h | 5 ++ lib/inputline.c | 15 ++++ 13 files changed, 407 insertions(+), 6 deletions(-) create mode 100644 disobedience/rtp.c create mode 100644 images/speaker.png create mode 100644 images/speaker.xcf create mode 100644 images/speakercross.png diff --git a/clients/playrtp.c b/clients/playrtp.c index 6e428aa..f92d872 100644 --- a/clients/playrtp.c +++ b/clients/playrtp.c @@ -67,6 +67,7 @@ #include #include #include +#include #include "log.h" #include "mem.h" @@ -80,6 +81,7 @@ #include "timeval.h" #include "client.h" #include "playrtp.h" +#include "inputline.h" #define readahead linux_headers_are_borked @@ -184,6 +186,9 @@ static void (*backend)(void) = &DEFAULT_BACKEND; 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' }, @@ -203,10 +208,77 @@ static const struct option options[] = { #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. @@ -396,11 +468,14 @@ struct packet *playrtp_next_packet(void) { */ 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(); } @@ -441,7 +516,7 @@ static void version(void) { } int main(int argc, char **argv) { - int n; + int n, err; struct addrinfo *res; struct stringlist sl; char *sockname; @@ -488,6 +563,7 @@ int main(int argc, char **argv) { case 'c': backend = playrtp_coreaudio; break; #endif case 'C': configfile = optarg; break; + case 's': control_socket = optarg; break; default: fatal(0, "invalid option"); } } @@ -561,6 +637,12 @@ int main(int argc, char **argv) { 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; } diff --git a/disobedience/Makefile.am b/disobedience/Makefile.am index 1f9bd88..86d1102 100644 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@ -25,7 +25,7 @@ AM_CFLAGS=$(GLIB_CFLAGS) $(GTK_CFLAGS) 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) diff --git a/disobedience/control.c b/disobedience/control.c index f4541f7..39bced1 100644 --- a/disobedience/control.c +++ b/disobedience/control.c @@ -42,8 +42,17 @@ static void update_random_enable(const struct icon *); 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); @@ -101,6 +110,10 @@ static struct icon icons[] = { 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 */ @@ -110,7 +123,7 @@ static GtkAdjustment *volume_adj; 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")); @@ -265,6 +278,18 @@ static void update_disable(const struct icon *icon) { 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; @@ -377,6 +402,30 @@ static double balance(double l, double r) { 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 diff --git a/disobedience/disobedience.c b/disobedience/disobedience.c index 6271560..d99ecef 100644 --- a/disobedience/disobedience.c +++ b/disobedience/disobedience.c @@ -75,9 +75,21 @@ double goesupto = 10; /* volume upper bound */ /** @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; @@ -316,9 +328,44 @@ static gboolean maybe_send_nop(gpointer attribute((unused)) data) { 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[] = { @@ -361,8 +408,11 @@ void reset(void) { /* 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 */ @@ -432,6 +482,8 @@ int main(int argc, char **argv) { 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); diff --git a/disobedience/disobedience.h b/disobedience/disobedience.h index 70eee10..6614d8b 100644 --- a/disobedience/disobedience.h +++ b/disobedience/disobedience.h @@ -111,6 +111,9 @@ extern int volume_l, volume_r; /* current volume */ 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; @@ -189,6 +192,8 @@ GtkWidget *control_widget(void); 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); @@ -226,8 +231,16 @@ GtkWidget *choose_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 diff --git a/disobedience/rtp.c b/disobedience/rtp.c new file mode 100644 index 0000000..a4efed5 --- /dev/null +++ b/disobedience/rtp.c @@ -0,0 +1,156 @@ +/* + * 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 +#include +#include +#include + +/** @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: +*/ diff --git a/images/Makefile.am b/images/Makefile.am index 90f3a22..a8e4ddf 100644 --- a/images/Makefile.am +++ b/images/Makefile.am @@ -21,7 +21,7 @@ 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 diff --git a/images/speaker.png b/images/speaker.png new file mode 100644 index 0000000000000000000000000000000000000000..c042397d68b2685035323ef84ea8880d0066bd0c GIT binary patch literal 267 zcmV+m0rdWfP)C0n5mB&AnmaHI z3G?HD%R9WEbKiaMT*~nj;~+QJRuT8up@$7l(8n6BDw%f?u)uBbC2p!7&IGRT68t(> z+Rt^v>A~41(_$Un3=o{e!!=^^M#QT4&JjY$rW*Xx2kPEA}bApR(aSx?3KoR_- RvL^rl002ovPDHLkV1mxxa6bS5 literal 0 HcmV?d00001 diff --git a/images/speaker.xcf b/images/speaker.xcf new file mode 100644 index 0000000000000000000000000000000000000000..3eb1d5226be8dd5ae297f2b17a3a9b98e2b61914 GIT binary patch literal 2399 zcmb_dJ!~UI6n^V}CovZw$DdNHL_rQI=t6^_0Pjg4Ar5qaC=dm5oV77$uYKP2#l8U9 z8tEix>8_yV1Qi0In3{%;f*KGKLZX2Sx$N+LvorPv9B~pn>CJra&3kX&zIij-?(_!k zxY=@BovtsSRl~%0gB=IMz2M4)4@AhIHE;pE0X_?U2wZC;)Mm5a>-j+hS?EZX`@poO za6hI4%%nVc>9rte^!%NNAJ?j!9uJ&>~U@wi3)A)I< z*Y@7f*dUIhy@PnZ#T-}6SHL87;>_w<=evNNuVUo-1rThq zxDG{qiUBrt342`|aL{OWg0{P5_kwb=NNIGNqo5NZ`Q}?b zvmWr438GHtqu`HDV7?))4{v)Z#vG9UO@nZm+Fl-BN znsuWq^eH>ys4_Swc~t}_q6#W6E6R})g%zwUtAbQjNy@M+W3ED8hQ3CFA}m+nQKXR5 zF>)rRBl{>6*^%RT7lk(gUM-+-~MUSr@ZLV^2eaj=~z zM#qtG#?jGZiBXf!Xkhy78oLyJ4f%62C0ZD$hym(H{| z17S)(Fc`?Di3*esQzp27u#_s-4X|0_oGLd?>MEa7#`%1kVZM6S=gg~qH~pcwFPe*iFq-Tq?n655 dk^Gkn!nzr%vZQub=&N}DG#Op>4NAyT{RMAZmv{gG literal 0 HcmV?d00001 diff --git a/images/speakercross.png b/images/speakercross.png new file mode 100644 index 0000000000000000000000000000000000000000..86c77f617394d165babf05e97b4a66dbeee7d5f2 GIT binary patch literal 492 zcmVvScfo`&ar>3TH}JUqY7_j|t2dCnoTz@@-NyKOd`k7jW=*XY?`c#@}E(O+5z&aE#{&LR(IXWxhT_=?*_Y6Ui5ax2iC?jhM2 zPs%ZJ{=Cv1zEn5^r&)SO2c$`il)uwBSF5!3i!w^_c;FnLYaH63Q0BuFEeUU%AYnET z#&}$%Ehp=5P}a<9h1N2U=IBN#jS7W?*{M44Ue?UoB-Mm@EaOyDy~A8K_x^|A1(gZb zvuzazb|vxWJ_`Oswe^|ReF!dda)e^GEnq8+Ig|uS*I3yv2b&xop%~)lwBu7= ziRUpm%ES>WIba$yI|+`p^A!VKv0@j~416Vby2-PHtW0A;OgCm0IbPsLk=in?L;Mc3 zrr6eyoKdbX{QDFM^Y>|M=M5~;aU!mN<)wNya5o#B4b2p?X8N{H?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 diff --git a/lib/eclient.h b/lib/eclient.h index 0ced5de..37ec9cb 100644 --- a/lib/eclient.h +++ b/lib/eclient.h @@ -321,6 +321,11 @@ int disorder_eclient_new_tracks(disorder_eclient *c, 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 /* diff --git a/lib/inputline.c b/lib/inputline.c index 416162c..3202eff 100644 --- a/lib/inputline.c +++ b/lib/inputline.c @@ -17,6 +17,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file lib/inputline.c + * @brief Line input + */ #include #include "types.h" @@ -31,6 +34,18 @@ #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; -- [mdw]