/* 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. */ #ifndef MSW /* in unix this only works first; in MSW it only works last. */ #include "tk.h" #endif #include "t_tk.h" #include #include #include #include #include #ifndef MSW #include #include #include #include #include #ifdef HAVE_BSTRING_H #include #endif #include #include #include #endif #ifdef MSW #include #include #endif #ifdef MSW #pragma warning( disable : 4305 ) /* uncast const double to float */ #pragma warning( disable : 4244 ) /* uncast double to float */ #pragma warning( disable : 4101 ) /* unused local variables */ #endif #ifdef MSW #include "tk.h" #endif #ifdef MACOSX #define STARTGUI #endif #ifdef __linux__ #define STARTGUI #endif #define FIRSTPORTNUM 5600 void tcl_mess(char *s); static Tcl_Interp *tk_pdinterp; static int pd_portno = 0; /***************** the socket setup code ********************/ /* If this is reset by pdgui_setsock(), it's the port number Pd will try to connect to; but if zero, that means we should set it and start Pd ourselves. */ /* some installations of linux don't know about "localhost" so give the loopback address; NT, on the other hand, can't understand the hostname "127.0.0.1". */ char hostname[100] = #ifdef __linux__ "127.0.0.1"; #else "localhost"; #endif void pdgui_setsock(int port) { pd_portno = port; } /* why is this here??? probably never used (see t_main.c). */ void pdgui_sethost(char *name) { strncpy(hostname, name, 100); hostname[99] = 0; } static void pdgui_sockerror(char *s) { #ifdef MSW int err = WSAGetLastError(); #endif #ifndef MSW int err = errno; #endif fprintf(stderr, "%s: %s (%d)\n", s, strerror(err), err); tcl_mess("exit\n"); exit(1); } static int sockfd; /* The "pd_suck" command, which polls the socket. */ #define CHUNKSIZE 20000 /* chunks to allocate memory for reading socket */ #define READSIZE 10000 /* size of read to issue */ static char *pd_tkbuf = 0; /* buffer for reading */ static int pd_tkbufsize = 0; /* current buffer size */ static int pd_tkgotbytes = 0; /* number of bytes already in buffer */ static void pd_readsocket(ClientData cd, int mask) { int ngot; fd_set readset, writeset, exceptset; struct timeval timout; timout.tv_sec = 0; timout.tv_usec = 0; FD_ZERO(&writeset); FD_ZERO(&readset); FD_ZERO(&exceptset); FD_SET(sockfd, &readset); FD_SET(sockfd, &exceptset); if (!pd_tkbuf) { if (!(pd_tkbuf = malloc(CHUNKSIZE))) { fprintf(stderr, "pd-gui: out of memory\n"); tcl_mess("exit\n"); } pd_tkbufsize = CHUNKSIZE; } if (pd_tkgotbytes + READSIZE + 1 > pd_tkbufsize) { int newsize = pd_tkbufsize + CHUNKSIZE; char *newbuf = realloc(pd_tkbuf, newsize); if (!newbuf) { fprintf(stderr, "pd-gui: out of memory\n"); tcl_mess("exit\n"); } pd_tkbuf = newbuf; pd_tkbufsize = newsize; } if (select(sockfd+1, &readset, &writeset, &exceptset, &timout) < 0) perror("select"); if (FD_ISSET(sockfd, &exceptset) || FD_ISSET(sockfd, &readset)) { int ret; ret = recv(sockfd, pd_tkbuf + pd_tkgotbytes, READSIZE, 0); if (ret < 0) pdgui_sockerror("socket receive error"); else if (ret == 0) { /* fprintf(stderr, "read %d\n", SOCKSIZE - pd_tkgotbytes); */ fprintf(stderr, "pd_gui: pd process exited\n"); tcl_mess("exit\n"); } else { char *lastcr = 0, *bp = pd_tkbuf, *ep = bp + (pd_tkgotbytes + ret); int brace = 0; char lastc = 0; /* search for locations that terminate a complete TK command. These are carriage returns which are not inside any braces. Braces can be escaped with backslashes (but backslashes themselves can't.) */ while (bp < ep) { char c = *bp; if (c == '}' && brace) brace--; else if (c == '{') brace++; else if (!brace && c == '\n' && lastc != '\\') lastcr = bp; lastc = c; bp++; } /* if lastcr is set there is at least one complete TK command in the buffer. Execute it or them, and slide any extra bytes to beginning of the buffer. */ if (lastcr) { int xtra = pd_tkbuf + pd_tkgotbytes + ret - (lastcr+1); char bashwas = lastcr[1]; lastcr[1] = 0; tcl_mess(pd_tkbuf); lastcr[1] = bashwas; if (xtra) { /* fprintf(stderr, "x %d\n", xtra); */ memmove(pd_tkbuf, lastcr+1, xtra); } pd_tkgotbytes = xtra; } else { pd_tkgotbytes += ret; } } } } #ifdef MSW /* if we're in Gatesland, we add a tcl command to poll the socket for data. */ static int pd_pollsocketCmd(ClientData cd, Tcl_Interp *interp, int argc, char **argv) { pd_readsocket(cd, 0); return (TCL_OK); } #endif static void pd_sockerror(char *s) { #ifdef MSW 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"); } #else int err = errno; #endif fprintf(stderr, "%s: %s (%d)\n", s, strerror(err), err); } static void pdgui_connecttosocket(void) { struct sockaddr_in server; struct hostent *hp; #ifndef MSW int retry = 10; #else int retry = 1; #endif #ifdef MSW short version = MAKEWORD(2, 0); WSADATA nobby; if (WSAStartup(version, &nobby)) pdgui_sockerror("setup"); #endif /* create a socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) pdgui_sockerror("socket"); /* connect socket using hostname provided in command line */ server.sin_family = AF_INET; hp = gethostbyname(hostname); if (hp == 0) { fprintf(stderr, "localhost not found (inet protocol not installed?)\n"); exit(1); } memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); /* assign client port number */ server.sin_port = htons((unsigned short)pd_portno); /* try to connect */ while (1) { if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) >= 0) goto gotit; retry--; if (retry <= 0) break; /* In unix there's a race condition; the child won't be able to connect before the parent (pd) has shed its setuid-ness. In case this is the problem, sleep and retry. */ else { #ifndef MSW fd_set readset, writeset, exceptset; struct timeval timout; timout.tv_sec = 0; timout.tv_usec = 100000; FD_ZERO(&writeset); FD_ZERO(&readset); FD_ZERO(&exceptset); fprintf(stderr, "retrying connect...\n"); if (select(1, &readset, &writeset, &exceptset, &timout) < 0) perror("select"); #endif /* !MSW */ } } pdgui_sockerror("connecting stream socket"); gotit: ; #ifndef MSW /* normally we ask TK to call us back; but in MSW we have to poll. */ Tk_CreateFileHandler(sockfd, TK_READABLE | TK_EXCEPTION, pd_readsocket, 0); #endif /* !MSW */ } #ifdef STARTGUI /* #define DEBUGCONNECT */ #ifdef DEBUGCONNECT static FILE *debugfd; #endif static void pd_startfromgui( void) { pid_t childpid; char cmdbuf[1000], pdbuf[1000], *lastchar; const char *arg0; struct sockaddr_in server; int msgsock; int len = sizeof(server), nchar; int ntry = 0, portno = FIRSTPORTNUM; int xsock = -1; char morebuf[256]; #ifdef MSW short version = MAKEWORD(2, 0); WSADATA nobby; char scriptbuf[1000], wishbuf[1000], portbuf[80]; int spawnret; char intarg; #else int intarg; #endif arg0 = Tcl_GetVar(tk_pdinterp, "argv0", 0); if (!arg0) { fprintf(stderr, "Pd-gui: can't get arg 0\n"); return; } lastchar = strrchr(arg0, '/'); if (lastchar) snprintf(pdbuf, lastchar - arg0 + 1, "%s", arg0); else strcpy(pdbuf, "."); strcat(pdbuf, "/../bin/pd"); #ifdef DEBUGCONNECT fprintf(stderr, "pdbuf is %s\n", pdbuf); #endif #ifdef MSW if (WSAStartup(version, &nobby)) pd_sockerror("WSAstartup"); #endif /* create a socket */ xsock = socket(AF_INET, SOCK_STREAM, 0); if (xsock < 0) pd_sockerror("socket"); intarg = 1; if (setsockopt(xsock, IPPROTO_TCP, TCP_NODELAY, &intarg, sizeof(intarg)) < 0) fprintf(stderr, "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 MSW int err = WSAGetLastError(); #else int err = errno; #endif if ((ntry++ > 20) || (err != EADDRINUSE)) { perror("bind"); fprintf(stderr, "couldn't open GUI-to-pd network connection\n"); return; } portno++; server.sin_port = htons((unsigned short)(portno)); } #ifdef DEBUGCONNECT fprintf(debugfd, "port %d\n", portno); fflush(debugfd); #endif #ifdef UNISTD childpid = fork(); if (childpid < 0) { if (errno) perror("sys_startgui"); else fprintf(stderr, "sys_startgui failed\n"); return; } else if (!childpid) /* we're the child */ { sprintf(cmdbuf, "\"%s\" -guiport %d\n", pdbuf, portno); #ifdef DEBUGCONNECT fprintf(debugfd, "%s", cmdbuf); fflush(debugfd); #endif execl("/bin/sh", "sh", "-c", cmdbuf, 0); perror("pd: exec"); _exit(1); } #endif /* UNISTD */ #ifdef MSW #error not yet used.... sys_bashfilename() not filled in here strcpy(cmdbuf, pdcmd); strcat(cmdbuf, "/pd.exe"); sys_bashfilename(scriptbuf, scriptbuf); sprintf(portbuf, "%d", portno); spawnret = _spawnl(P_NOWAIT, cmdbuf, "pd.exe", "-port", portbuf, 0); if (spawnret < 0) { perror("spawnl"); fprintf(stderr, "%s: couldn't start\n", cmdbuf); return; } #endif /* MSW */ #ifdef DEBUGCONNECT fprintf(stderr, "Waiting for connection request... \n"); #endif if (listen(xsock, 5) < 0) pd_sockerror("listen"); sockfd = accept(xsock, (struct sockaddr *) &server, (unsigned int *)&len); if (sockfd < 0) pd_sockerror("accept"); #ifdef DEBUGCONNECT fprintf(stderr, "... connected\n"); #endif #ifndef MSW /* normally we ask TK to call us back; but in MSW we have to poll. */ Tk_CreateFileHandler(sockfd, TK_READABLE | TK_EXCEPTION, pd_readsocket, 0); #endif /* !MSW */ } #endif /* STARTGUI */ static void pdgui_setupsocket(void) { #ifdef DEBUGCONNECT debugfd = fopen("/Users/msp/bratwurst", "w"); fprintf(debugfd, "turning stderr back on\n"); fflush(debugfd); dup2(fileno(debugfd), 2); fprintf(stderr, "duped to stderr?\n"); #endif #ifdef MSW pdgui_connecttosocket(); #else if (pd_portno) pdgui_connecttosocket(); else pd_startfromgui() ; #endif } /**************************** commands ************************/ static char *pdgui_path; /* The "pd" command, which cats its args together and throws the result * at the Pd interpreter. */ #define MAXWRITE 1024 static int pdCmd(ClientData cd, Tcl_Interp *interp, int argc, char **argv) { if (argc == 2) { int n = strlen(argv[1]); if (send(sockfd, argv[1], n, 0) < n) { perror("stdout"); tcl_mess("exit\n"); } } else { int i; char buf[MAXWRITE]; buf[0] = 0; for (i = 1; i < argc; i++) { if (strlen(argv[i]) + strlen(buf) + 2 > MAXWRITE) { interp->result = "pd: arg list too long"; return (TCL_ERROR); } if (i > 1) strcat(buf, " "); strcat(buf, argv[i]); } if (send(sockfd, buf, strlen(buf), 0) < 0) { perror("stdout"); tcl_mess("exit\n"); } } return (TCL_OK); } /*********** "c" level access to tk functions. ******************/ void tcl_mess(char *s) { int result; result = Tcl_Eval(tk_pdinterp, s); if (result != TCL_OK) { if (*tk_pdinterp->result) printf("%s\n", tk_pdinterp->result); } } /* in linux, we load the tk code from here (in MSW and MACOS, this is done by passing the name of the file as a startup argument to the wish shell.) */ #if !defined(MSW) && !defined(MACOSX) void pdgui_doevalfile(Tcl_Interp *interp, char *s) { char buf[GUISTRING]; sprintf(buf, "set pd_guidir \"%s\"\n", pdgui_path); tcl_mess(buf); strcpy(buf, pdgui_path); strcat(buf, "/bin/"); strcat(buf, s); if (Tcl_EvalFile(interp, buf) != TCL_OK) { char buf2[1000]; sprintf(buf2, "puts [concat tcl: %s: can't open script]\n", buf); tcl_mess(buf2); } } void pdgui_evalfile(char *s) { pdgui_doevalfile(tk_pdinterp, s); } #endif void pdgui_startup(Tcl_Interp *interp) { /* save pointer to the main interpreter */ tk_pdinterp = interp; /* add our own TK commands */ Tcl_CreateCommand(interp, "pd", (Tcl_CmdProc*)pdCmd, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL); #ifdef MSW Tcl_CreateCommand(interp, "pd_pollsocket",(Tcl_CmdProc*) pd_pollsocketCmd, (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL); #endif pdgui_setupsocket(); /* read in the startup file */ #if !defined(MSW) && !defined(MACOSX) pdgui_evalfile("pd.tk"); #endif } #ifndef MSW void pdgui_setname(char *s) { char *t; char *str; int n; if (t = strrchr(s, '/')) str = s, n = (t-s) + 1; else str = "./", n = 2; if (n > GUISTRING-100) n = GUISTRING-100; pdgui_path = malloc(n+9); strncpy(pdgui_path, str, n); while (strlen(pdgui_path) > 0 && pdgui_path[strlen(pdgui_path)-1] == '/') pdgui_path[strlen(pdgui_path)-1] = 0; if (t = strrchr(pdgui_path, '/')) *t = 0; } #endif /* this is called when an off-the-shelf "wish" has to "load" this module at runtime. In Linux, this module is linked in and Pdtcl_Init() is not called; instead, the code in t_main.c calls pdgui_setsock() and pdgui_startup(). */ int Pdtcl_Init(Tcl_Interp *interp) { const char *argv = Tcl_GetVar(interp, "argv", 0); int portno, argno = 0; if (argv && (portno = atoi(argv)) > 1) pdgui_setsock(portno); tk_pdinterp = interp; pdgui_startup(interp); interp->result = "loaded pdtcl_init"; return (TCL_OK); } #if 0 int Pdtcl_SafeInit(Tcl_Interp *interp) { fprintf(stderr, "Pdtcl_Safeinit 51\n"); return (TCL_OK); } #endif