diff options
Diffstat (limited to 'pd/src/s_inter.c')
-rw-r--r-- | pd/src/s_inter.c | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/pd/src/s_inter.c b/pd/src/s_inter.c new file mode 100644 index 00000000..b9f52d68 --- /dev/null +++ b/pd/src/s_inter.c @@ -0,0 +1,794 @@ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* Pd side of the Pd/Pd-gui interface. */ + +#include "m_imp.h" +#ifdef UNIX +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <netdb.h> +#include <stdlib.h> +#include <sys/time.h> +#endif +#ifdef HAVE_BSTRING_H +#include <bstring.h> +#endif +#ifdef NT +#include <io.h> +#include <fcntl.h> +#include <process.h> +#include <winsock.h> +typedef int pid_t; +#define EADDRINUSE WSAEADDRINUSE +#endif +#include <stdarg.h> +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#ifdef MACOSX +#include <sys/types.h> +#include <sys/stat.h> +#else +#include <stdlib.h> +#endif + +extern char pd_version[]; + +typedef struct _fdpoll +{ + int fdp_fd; + t_fdpollfn fdp_fn; + void *fdp_ptr; +} t_fdpoll; + +#define INBUFSIZE 4096 + +struct _socketreceiver +{ + char *sr_inbuf; + int sr_inhead; + int sr_intail; + void *sr_owner; + int sr_udp; + t_socketnotifier sr_notifier; + t_socketreceivefn sr_socketreceivefn; +}; + +static int sys_nfdpoll; +static t_fdpoll *sys_fdpoll; +static int sys_maxfd; +static int sys_guisock; + +static t_binbuf *inbinbuf; +static t_socketreceiver *sys_socketreceiver; +extern int sys_addhist(int phase); + +void sys_sockerror(char *s) +{ +#ifdef NT + int err = WSAGetLastError(); + if (err == 10054) return; + else if (err == 10044) + { + fprintf(stderr, + "Warning: you might not have TCP/IP \"networking\" turned on\n"); + fprintf(stderr, "which is needed for Pd to talk to its GUI layer.\n"); + } +#endif +#ifdef UNIX + int err = errno; +#endif + fprintf(stderr, "%s: %s (%d)\n", s, strerror(err), err); +} + +void sys_addpollfn(int fd, t_fdpollfn fn, void *ptr) +{ + int nfd = sys_nfdpoll; + int size = nfd * sizeof(t_fdpoll); + t_fdpoll *fp; + sys_fdpoll = (t_fdpoll *)t_resizebytes(sys_fdpoll, size, + size + sizeof(t_fdpoll)); + fp = sys_fdpoll + nfd; + fp->fdp_fd = fd; + fp->fdp_fn = fn; + fp->fdp_ptr = ptr; + sys_nfdpoll = nfd + 1; + if (fd >= sys_maxfd) sys_maxfd = fd + 1; +} + +void sys_rmpollfn(int fd) +{ + int nfd = sys_nfdpoll; + int i, size = nfd * sizeof(t_fdpoll); + t_fdpoll *fp; + for (i = nfd, fp = sys_fdpoll; i--; fp++) + { + if (fp->fdp_fd == fd) + { + while (i--) + { + fp[0] = fp[1]; + fp++; + } + sys_fdpoll = (t_fdpoll *)t_resizebytes(sys_fdpoll, size, + size - sizeof(t_fdpoll)); + sys_nfdpoll = nfd - 1; + return; + } + } + post("warning: %d removed from poll list but not found", fd); +} + +static int sys_domicrosleep(int microsec, int pollem) +{ + struct timeval timout; + int i, didsomething = 0; + t_fdpoll *fp; + timout.tv_sec = 0; + timout.tv_usec = microsec; + if (pollem) + { + fd_set readset, writeset, exceptset; + FD_ZERO(&writeset); + FD_ZERO(&readset); + FD_ZERO(&exceptset); + for (fp = sys_fdpoll, i = sys_nfdpoll; i--; fp++) + FD_SET(fp->fdp_fd, &readset); + select(sys_maxfd+1, &readset, &writeset, &exceptset, &timout); + for (i = 0; i < sys_nfdpoll; i++) + if (FD_ISSET(sys_fdpoll[i].fdp_fd, &readset)) + { + (*sys_fdpoll[i].fdp_fn)(sys_fdpoll[i].fdp_ptr, sys_fdpoll[i].fdp_fd); + didsomething = 1; + } + return (didsomething); + } + else + { + select(0, 0, 0, 0, &timout); + return (0); + } +} + +void sys_microsleep(int microsec) +{ + sys_domicrosleep(microsec, 1); +} + +t_socketreceiver *socketreceiver_new(void *owner, t_socketnotifier notifier, + t_socketreceivefn socketreceivefn, int udp) +{ + t_socketreceiver *x = (t_socketreceiver *)getbytes(sizeof(*x)); + x->sr_inhead = x->sr_intail = 0; + x->sr_owner = owner; + x->sr_notifier = notifier; + x->sr_socketreceivefn = socketreceivefn; + x->sr_udp = udp; + if (!(x->sr_inbuf = malloc(INBUFSIZE))) bug("t_socketreceiver");; + return (x); +} + +void socketreceiver_free(t_socketreceiver *x) +{ + free(x->sr_inbuf); + freebytes(x, sizeof(*x)); +} + + /* this is in a separately called subroutine so that the buffer isn't + sitting on the stack while the messages are getting passed. */ +static int socketreceiver_doread(t_socketreceiver *x) +{ + char messbuf[INBUFSIZE], *bp = messbuf; + int indx; + int inhead = x->sr_inhead; + int intail = x->sr_intail; + char *inbuf = x->sr_inbuf; + if (intail == inhead) return (0); + for (indx = intail; indx != inhead; indx = (indx+1)&(INBUFSIZE-1)) + { + /* if we hit a semi that isn't preceeded by a \, it's a message + boundary. LATER we should deal with the possibility that the + preceeding \ might itself be escaped! */ + char c = *bp++ = inbuf[indx]; + if (c == ';' && (!indx || inbuf[indx-1] != '\\')) + { + intail = (indx+1)&(INBUFSIZE-1); + binbuf_text(inbinbuf, messbuf, bp - messbuf); + if (sys_debuglevel & DEBUG_MESSDOWN) + { + write(2, messbuf, bp - messbuf); + write(2, "\n", 1); + } + x->sr_inhead = inhead; + x->sr_intail = intail; + return (1); + } + } + return (0); +} + +static void socketreceiver_getudp(t_socketreceiver *x, int fd) +{ + char buf[INBUFSIZE+1]; + int ret = recv(fd, buf, INBUFSIZE, 0); + if (ret < 0) + { + sys_sockerror("recv"); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else if (ret > 0) + { + buf[ret] = 0; +#if 0 + post("%s", buf); +#endif + if (buf[ret-1] != '\n') + { +#if 0 + buf[ret] = 0; + error("dropped bad buffer %s\n", buf); +#endif + } + else + { + char *semi = strchr(buf, ';'); + if (semi) + *semi = 0; + binbuf_text(inbinbuf, buf, strlen(buf)); + outlet_setstacklim(); + if (x->sr_socketreceivefn) + (*x->sr_socketreceivefn)(x->sr_owner, inbinbuf); + else bug("socketreceiver_getudp"); + } + } +} + +void socketreceiver_read(t_socketreceiver *x, int fd) +{ + if (x->sr_udp) /* UDP ("datagram") socket protocol */ + socketreceiver_getudp(x, fd); + else /* TCP ("streaming") socket protocol */ + { + char *semi; + int readto = + (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1); + int ret; + + /* the input buffer might be full. If so, drop the whole thing */ + if (readto == x->sr_inhead) + { + fprintf(stderr, "pd: dropped message from gui\n"); + x->sr_inhead = x->sr_intail = 0; + readto = INBUFSIZE; + } + else + { + ret = recv(fd, x->sr_inbuf + x->sr_inhead, + readto - x->sr_inhead, 0); + if (ret < 0) + { + sys_sockerror("recv"); + if (x == sys_socketreceiver) sys_bail(1); + else + { + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + } + else if (ret == 0) + { + if (x == sys_socketreceiver) + { + fprintf(stderr, "pd: exiting\n"); + sys_bail(0); + } + else + { + post("EOF on socket %d\n", fd); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + } + else + { + x->sr_inhead += ret; + if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0; + while (socketreceiver_doread(x)) + { + outlet_setstacklim(); + if (x->sr_socketreceivefn) + (*x->sr_socketreceivefn)(x->sr_owner, inbinbuf); + else binbuf_eval(inbinbuf, 0, 0, 0); + } + } + } + } +} + +void sys_closesocket(int fd) +{ +#ifdef UNIX + close(fd); +#endif +#ifdef NT + closesocket(fd); +#endif +} + + +void sys_gui(char *s) +{ + int length = strlen(s), written = 0, res, histwas = sys_addhist(4); + if (sys_debuglevel & DEBUG_MESSUP) + fprintf(stderr, "%s", s); + if (sys_nogui) + return; + while (1) + { + res = send(sys_guisock, s + written, length, 0); + if (res < 0) + { + perror("pd output pipe"); + sys_bail(1); + } + else + { + written += res; + if (written >= length) + break; + } + } + sys_addhist(histwas); +} + +/* LATER should do a bounds check -- but how do you get printf to do that? + See also rtext_senditup() in this regard */ + +void sys_vgui(char *fmt, ...) +{ + int result, i; + char buf[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + sys_gui(buf); + va_end(ap); +} + + +#define FIRSTPORTNUM 5400 + +/* -------------- signal handling for UNIX -------------- */ + +#ifdef UNIX +typedef void (*sighandler_t)(int); + +static void sys_signal(int signo, sighandler_t sigfun) +{ + struct sigaction action; + action.sa_flags = 0; + action.sa_handler = sigfun; + memset(&action.sa_mask, 0, sizeof(action.sa_mask)); +#ifdef __linux__ + action.sa_restorer = 0; +#endif + if (sigaction(signo, &action, 0) < 0) + perror("sigaction"); +} + +static void sys_exithandler(int n) +{ + static int trouble = 0; + if (!trouble) + { + trouble = 1; + fprintf(stderr, "Pd: signal %d\n", n); + sys_bail(1); + + } + else _exit(1); +} + +static void sys_alarmhandler(int n) +{ + fprintf(stderr, "Pd: system call timed out\n"); +} + +static void sys_huphandler(int n) +{ + struct timeval timout; + timout.tv_sec = 0; + timout.tv_usec = 30000; + select(1, 0, 0, 0, &timout); +} + +void sys_setalarm(int microsec) +{ + struct itimerval gonzo; +#if 0 + fprintf(stderr, "timer %d\n", microsec); +#endif + gonzo.it_interval.tv_sec = 0; + gonzo.it_interval.tv_usec = 0; + gonzo.it_value.tv_sec = 0; + gonzo.it_value.tv_usec = microsec; + if (microsec) + sys_signal(SIGALRM, sys_alarmhandler); + else sys_signal(SIGALRM, SIG_IGN); + setitimer(ITIMER_REAL, &gonzo, 0); +} + +#endif + +static int sys_watchfd; + +void glob_ping(t_pd *dummy) +{ + if (write(sys_watchfd, "\n", 1) < 1) + { + fprintf(stderr, "pd: watchdog process died\n"); + sys_bail(1); + } +} + +static int defaultfontshit[] = { + 8, 5, 9, 10, 6, 10, 12, 7, 13, 14, 9, 17, 16, 10, 19, 24, 15, 28, + 24, 15, 28}; + +int sys_startgui(const char *guidir) +{ + pid_t childpid; + char cmdbuf[4*MAXPDSTRING]; + struct sockaddr_in server; + int msgsock; + char buf[15]; + int len = sizeof(server); + int ntry = 0, portno = FIRSTPORTNUM; + int xsock = -1; +#ifdef NT + short version = MAKEWORD(2, 0); + WSADATA nobby; +#endif +#ifdef UNIX + int stdinpipe[2]; +#endif + /* create an empty FD poll list */ + sys_fdpoll = (t_fdpoll *)t_getbytes(0); + sys_nfdpoll = 0; + inbinbuf = binbuf_new(); + +#ifdef UNIX + signal(SIGHUP, sys_huphandler); + signal(SIGINT, sys_exithandler); + signal(SIGQUIT, sys_exithandler); + signal(SIGILL, sys_exithandler); + signal(SIGIOT, sys_exithandler); + signal(SIGFPE, SIG_IGN); + /* signal(SIGILL, sys_exithandler); + signal(SIGBUS, sys_exithandler); + signal(SIGSEGV, sys_exithandler); */ + signal(SIGPIPE, sys_exithandler); + signal(SIGALRM, SIG_IGN); + signal(SIGTERM, SIG_IGN); +#ifdef __linux__ + signal(SIGSTKFLT, sys_exithandler); +#endif +#endif +#ifdef NT + if (WSAStartup(version, &nobby)) sys_sockerror("WSAstartup"); +#endif + + if (sys_nogui) + { + /* fake the GUI's message giving cwd and font sizes; then + skip starting the GUI up. */ + t_atom zz[19]; + int i; +#ifdef NT + if (GetCurrentDirectory(MAXPDSTRING, cmdbuf) == 0) + strcpy(cmdbuf, "."); +#endif +#ifdef UNIX + if (!getcwd(cmdbuf, MAXPDSTRING)) + strcpy(cmdbuf, "."); + +#endif + SETSYMBOL(zz, gensym(cmdbuf)); + for (i = 1; i < 22; i++) + SETFLOAT(zz + i, defaultfontshit[i-1]); + glob_initfromgui(0, 0, 22, zz); + } + else + { +#ifdef NT + char scriptbuf[MAXPDSTRING+30], wishbuf[MAXPDSTRING+30], portbuf[80]; + int spawnret; + +#endif + int intarg; + + /* create a socket */ + xsock = socket(AF_INET, SOCK_STREAM, 0); + if (xsock < 0) sys_sockerror("socket"); +#if 0 + intarg = 0; + if (setsockopt(xsock, SOL_SOCKET, SO_SNDBUF, + &intarg, sizeof(intarg)) < 0) + post("setsockopt (SO_RCVBUF) failed\n"); + intarg = 0; + if (setsockopt(xsock, SOL_SOCKET, SO_RCVBUF, + &intarg, sizeof(intarg)) < 0) + post("setsockopt (SO_RCVBUF) failed\n"); +#endif + intarg = 1; + if (setsockopt(xsock, IPPROTO_TCP, TCP_NODELAY, + &intarg, sizeof(intarg)) < 0) + post("setsockopt (TCP_NODELAY) failed\n"); + + + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + + /* assign server port number */ + server.sin_port = htons((unsigned short)portno); + + /* name the socket */ + while (bind(xsock, (struct sockaddr *)&server, sizeof(server)) < 0) + { +#ifdef NT + int err = WSAGetLastError(); +#endif +#ifdef UNIX + int err = errno; +#endif + if ((ntry++ > 20) || (err != EADDRINUSE)) + { + perror("bind"); + fprintf(stderr, + "Pd needs your machine to be configured with\n"); + fprintf(stderr, + "'networking' turned on (see Pd's html doc for details.)\n"); + exit(1); + } + portno++; + server.sin_port = htons((unsigned short)(portno)); + } + + if (sys_verbose) fprintf(stderr, "port %d\n", portno); + + sys_socketreceiver = socketreceiver_new(0, 0, 0, 0); + +#ifdef UNIX + childpid = fork(); + if (childpid < 0) + { + if (errno) perror("sys_startgui"); + else fprintf(stderr, "sys_startgui failed\n"); + return (1); + } + else if (!childpid) /* we're the child */ + { + seteuid(getuid()); /* lose setuid priveliges */ +#ifndef MACOSX + /* the wish process in Unix will make a wish shell and + read/write standard in and out unless we close the + file descriptors. Somehow this doesn't make the MAC OSX + version of Wish happy...*/ + if (pipe(stdinpipe) < 0) + sys_sockerror("pipe"); + else + { + if (stdinpipe[0] != 0) + { + close (0); + dup2(stdinpipe[0], 0); + close(stdinpipe[0]); + } + } +#endif + if (!sys_guicmd) + { +#ifdef MACOSX + char *homedir = getenv("HOME"), filename[250]; + struct stat statbuf; + if (!homedir || strlen(homedir) > 150) + goto nexttry; + sprintf(filename, + "%s/Applications/Wish shell.app/Contents/MacOS/Wish Shell", + homedir); + + if (stat(filename, &statbuf) >= 0) + goto foundit; + nexttry: + strcpy(filename, + "/Applications/Wish Shell.app/Contents/MacOS/Wish Shell"); + foundit: + sprintf(cmdbuf, "\"%s\" %s/pd.tk %d\n", filename, guidir, portno); +#else + sprintf(cmdbuf, +"TCL_LIBRARY=\"%s/tcl/library\" TK_LIBRARY=\"%s/tk/library\" \ + \"%s/pd-gui\" %d\n", + sys_libdir->s_name, sys_libdir->s_name, guidir, portno); +#endif + sys_guicmd = cmdbuf; + } + if (sys_verbose) fprintf(stderr, "%s", sys_guicmd); + execl("/bin/sh", "sh", "-c", sys_guicmd, 0); + perror("pd: exec"); + _exit(1); + } +#endif /* UNIX */ + +#ifdef NT + /* in NT land "guipath" is unused; we just do everything from + the libdir. */ + fprintf(stderr, "%s\n", sys_libdir->s_name); + + strcpy(scriptbuf, "\""); + strcat(scriptbuf, sys_libdir->s_name); + strcat(scriptbuf, "/bin/pd.tk\""); + sys_bashfilename(scriptbuf, scriptbuf); + + sprintf(portbuf, "%d", portno); + + strcpy(wishbuf, sys_libdir->s_name); + strcat(wishbuf, "/bin/wish83.exe"); + sys_bashfilename(wishbuf, wishbuf); + + spawnret = _spawnl(P_NOWAIT, wishbuf, "wish83", scriptbuf, portbuf, 0); + if (spawnret < 0) + { + perror("spawnl"); + fprintf(stderr, "%s: couldn't load TCL\n", wishbuf); + exit(1); + } + +#endif /* NT */ + } + +#ifdef UNIX + /* now that we've spun off the child process we can promote + our process's priority, if we happen to be root. */ + if (sys_hipriority) + { + if (!getuid() || !geteuid()) + { + /* To prevent lockup, we fork off a watchdog process with + higher real-time priority than ours. The GUI has to send + a stream of ping messages to the watchdog THROUGH the Pd + process which has to pick them up from the GUI and forward + them. If any of these things aren't happening the watchdog + starts sending "stop" and "cont" signals to the Pd process + to make it timeshare with the rest of the system. (Version + 0.33P2 : if there's no GUI, the watchdog pinging is done + from the scheduler idle routine in this process instead.) */ + + int pipe9[2], watchpid; + if (pipe(pipe9) < 0) + { + seteuid(getuid()); /* lose setuid priveliges */ + sys_sockerror("pipe"); + return (1); + } + watchpid = fork(); + if (watchpid < 0) + { + seteuid(getuid()); /* lose setuid priveliges */ + if (errno) + perror("sys_startgui"); + else fprintf(stderr, "sys_startgui failed\n"); + return (1); + } + else if (!watchpid) /* we're the child */ + { + sys_set_priority(1); + seteuid(getuid()); /* lose setuid priveliges */ + if (pipe9[1] != 0) + { + dup2(pipe9[0], 0); + close(pipe9[0]); + } + close(pipe9[1]); + + sprintf(cmdbuf, "%s/pd-watchdog\n", guidir); + if (sys_verbose) fprintf(stderr, "%s", cmdbuf); + execl("/bin/sh", "sh", "-c", cmdbuf, 0); + perror("pd: exec"); + _exit(1); + } + else /* we're the parent */ + { + sys_set_priority(0); + seteuid(getuid()); /* lose setuid priveliges */ + close(pipe9[0]); + sys_watchfd = pipe9[1]; + /* We also have to start the ping loop in the GUI; + this is done later when the socket is open. */ + } + } + else + { + post("realtime setting failed because not root\n"); + sys_hipriority = 0; + } + } + + seteuid(getuid()); /* lose setuid priveliges */ +#endif /* UNIX */ + +#ifdef NT + if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) + fprintf(stderr, "pd: couldn't set high priority class\n"); +#endif + + if (!sys_nogui) + { + if (sys_verbose) + fprintf(stderr, "Waiting for connection request... \n"); + if (listen(xsock, 5) < 0) sys_sockerror("listen"); + + sys_guisock = accept(xsock, (struct sockaddr *) &server, &len); +#ifdef OOPS + close(xsock); +#endif + if (sys_guisock < 0) sys_sockerror("accept"); + sys_addpollfn(sys_guisock, (t_fdpollfn)socketreceiver_read, + sys_socketreceiver); + + if (sys_verbose) + fprintf(stderr, "... connected\n"); + + /* here is where we start the pinging. */ + if (sys_hipriority) + sys_gui("pdtk_watchdog\n"); + + sys_vgui("pdtk_pd_startup {%s}\n", pd_version); + } + return (0); + +} + + +static int sys_poll_togui(void) +{ + /* LATER use this to flush output buffer to gui */ + return (0); +} + +int sys_pollgui(void) +{ + return (sys_domicrosleep(0, 1) || sys_poll_togui()); +} + +/* LATER try to save dirty documents */ +void sys_bail(int n) +{ + static int reentered = 0; + if (!reentered) + { + reentered = 1; + sys_close_audio(); + sys_close_midi(); + } + _exit(n); +} + +void glob_quit(void *dummy) +{ + sys_vgui("exit\n"); + if (!sys_nogui) + close(sys_guisock); + sys_bail(0); +} + |