1 #ifndef lint
RCSid()2 static char *RCSid() { return RCSid("$Id: pgnuplot.c,v 1.17 2011/08/15 17:30:40 markisch Exp $"); }
3 #endif
4 
5 /*
6  * pgnuplot.c -- pipe stdin to wgnuplot
7  *
8  * Version 0.4 -- October 2002
9  *
10  * This program is based on pgnuplot.c Copyright (C) 1999 Hans-Bernhard Broeker
11  * with substantial modifications Copyright (C) 1999 Craig R. Schardt.
12  *
13  * The code is released to the public domain.
14  *
15  */
16 
17 /* Changes by Petr Mikulik, October 2002:
18  * Added command line options --version and --help, and consequently dependency
19  * on gnuplot's version.h and version.c.
20  * Compile pgnuplot by:
21  *     gcc -O2 -s -o pgnuplot.exe pgnuplot.c ../version.c -I.. -luser32
22  */
23 
24 /* Comments from original pgnuplot.c */
25 /*
26  * pgnuplot.c -- 'Pipe to gnuplot'  Version 990608
27  *
28  * A small program, to be compiled as a Win32 console mode application.
29  * (NB: will not work on 16bit Windows, not even with Win32s installed).
30  *
31  * This program will accept commands on STDIN, and pipe them to an
32  * active (or newly created) wgnuplot text window. Command line options
33  * are passed on to wgnuplot.
34  *
35  * Effectively, this means `pgnuplot' is an almost complete substitute
36  * for `wgnuplot', on the command line, with the added benefit that it
37  * does accept commands from redirected stdin. (Being a Windows GUI
38  * application, `wgnuplot' itself cannot read stdin at all.)
39  *
40  * Copyright (C) 1999 by Hans-Bernhard Broeker
41  *                       (broeker@physik.rwth-aachen.de)
42  * This file is in the public domain. It might become part of a future
43  * distribution of gnuplot.
44  *
45  * based on a program posted to comp.graphics.apps.gnuplot in May 1998 by
46  * jl Hamel <jlhamel@cea.fr>
47  *
48  * Changes relative to that original version:
49  * -- doesn't start a new wgnuplot if one already is running.
50  * -- doesn't end up in an endless loop if STDIN is not redirected.
51  *    (refuses to read from STDIN at all, in that case).
52  * -- doesn't stop the wgnuplot session at the end of
53  *    stdin, if it didn't start wgnuplot itself.
54  * -- avoids the usual buffer overrun problem with gets().
55  *
56  * For the record, I usually use MinGW32 to compile this, with a
57  * command line looking like this:
58  *
59  *     gcc -o pgnuplot.exe pgnuplot.c -luser32 -s
60  *
61  * Note that if you're using Cygwin GCC, you'll want to add the option
62  * -mno-cygwin to that command line to avoid getting a pgnuplot.exe
63  * that depends on their GPL'ed cygwin1.dll.
64  */
65 
66 /*	Modifications by Craig R. Schardt (17 Jun 1999)
67 
68 	Copyright (C) 1999 by Craig R. Schardt (craig@silica.mse.ufl.edu)
69 
70 	Major changes: (See the explanation below for more information)
71 		+ Always starts a new instance of wgnuplot.
72 		+ If stdin isn't redirected then start wgnuplot and give it focus.
73 		+ Uses CreateProcess() instead of WinExec() to start wgnuplot when stdin
74 		  is redirected.
75 
76 	Other changes:
77 		+ New technique for building the command line to pass to wgnuplot.exe
78 		  which is less complicated and seems to work more reliably than the old
79 		  technique.
80 		+ Simplified message passing section of the code.
81 		+ All printf(...) statements are now fprintf(stderr,...) so that errors
82 		  are sent to the console, even if stdout is redirected.
83 
84 	The previous version of pgnuplot would fail when more than one program
85 	tried to access wgnuplot simultaneously or when one program tried to start
86 	more than one wgnuplot session. Only a single instance of wgnuplot would be
87 	started and all input would be sent to that instance. When two or more programs
88 	tried to pipe input to wgnuplot, the two seperate input streams would be sent
89 	to one wgnuplot window resulting in one very confused copy of wgnuplot. The only
90 	way to avoid this problem was to change pgnuplot so that it would start a
91 	new instance of wgnuplot every time.
92 
93 	Just starting a new instance of wgnuplot isn't enough. pgnuplot must also
94 	make sure that the data on each stdin pipe is sent to the proper wgnuplot
95 	instance. This is achieved by using CreateProcess() which returns a handle
96 	to the newly created process. Once the process has initialized, it can be
97 	searched for the text window and then data can be routed correctly. The search
98 	is carried out by the EnumThreadWindows() call and the data passing is carried
99 	out by a rewritten version of the original code. With these changes, pgnuplot
100 	now behaves in a manner consistent with the behavior of gnuplot on UNIX
101 	computers.
102 
103 	This program has been compiled using Microsoft Visual C++ 4.0 with the
104 	following command line:
105 
106 		cl /O2 pgnuplot.c /link user32.lib
107 
108 	The resulting program has been tested on WinNT and Win98 both by calling
109 	it directly from the command line with and without redirected input. The
110 	program also works on WinNT with a modified version of Gnuplot.py (a script
111 	for interactive control of Gnuplot from Python).
112 
113 	22 JUN 1999:
114 	+ Fixed command line code to behave properly when the first
115 	  item is quoted in the original command line.
116 
117 	29 JUN 1999:
118 	+ Added some code to print the command line. This is for testing
119 	  only and should be removed before the general release. To enable,
120 	  compile with SHOWCMDLINE defined.
121 
122 	30 JUN 1999:
123 	+ New function FindUnquotedSpace() which replaces the earlier technique for
124 	  finding the command line arguments to send on to wgnuplot. Prior to this
125 	  the arguments were assumed to start after argv[0], however argv[0] is not
126 	  set the same by all combinitaions of compiler, command processor, and OS.
127 	  The new method ignores argv completely and manually search the command line
128 	  string for the first space which isn't enclosed in double-quotes.
129 
130   */
131 
132 #include <io.h>
133 #include <conio.h>
134 #include <fcntl.h>
135 #include <stdio.h>
136 #include <string.h>
137 #include <stdlib.h>
138 #include <windows.h>
139 #include "version.h"
140 
141 #ifndef _O_BINARY
142 # define _O_BINARY O_BINARY
143 #endif
144 #if (__BORLANDC__ >= 0x450) /* about BCBuilder 1.0 */
145 # define _setmode setmode
146 #endif
147 #ifdef __WATCOMC__
148 # define _setmode setmode
149 #endif
150 
151 /* Customize this path if needed */
152 #define PROGNAME "wgnuplot.exe"
153 /* CRS: The value given above will work correctly as long as pgnuplot.exe
154  * is in the same directory as wgnuplot.exe or the directory containing
155  * wgnuplot.exe is included in the path. I would recommend placing the
156  * pgnuplot.exe executable in the same directory as wgnuplot.exe and
157  * leaving this definition alone.
158  */
159 
160 #define WINDOWNAME "gnuplot"
161 #define PARENTCLASS "wgnuplot_parent"
162 #define TEXTCLASS "wgnuplot_text"
163 #define GRAPHWINDOW "gnuplot graph"
164 #define GRAPHCLASS "wgnuplot_graph"
165 #define BUFFER_SIZE 80
166 
167 /* GLOBAL Variables */
168 HWND hwndParent = NULL;
169 HWND hwndText = NULL;
170 
171 PROCESS_INFORMATION piProcInfo;
172 STARTUPINFO         siStartInfo;
173 
174 /* CRS: Callback for the EnumThreadWindows function */
175 BOOL CALLBACK
cbGetTextWindow(HWND hwnd,LPARAM lParam)176 cbGetTextWindow(HWND  hwnd, LPARAM  lParam)
177 {
178     /* save the value of the parent window */
179     hwndParent = hwnd;
180     /* check to see if it has a child text window */
181     hwndText = FindWindowEx(hwnd, NULL, TEXTCLASS, NULL);
182 
183     /* if the text window was found, stop looking */
184     return (hwndText == NULL);
185 }
186 
187 /* sends a string to the specified window */
188 /* CRS: made this into a function call */
189 void
PostString(HWND hwnd,char * pc)190 PostString(HWND hwnd, char *pc)
191 {
192     while(*pc) {
193 	PostMessage(hwnd, WM_CHAR, (unsigned char) *pc, 1L);
194 	/* CRS: should add a check of return code on PostMessage. If 0, the
195 	   message que was full and the message wasn't posted. */
196 	pc++;
197     }
198 }
199 
200 /* FindUnquotedSpace(): Search a string for the first space not enclosed in quotes.
201  *   Returns a pointer to the space, or the empty string if no space is found.
202  *   -CRS 30061999
203  */
204 char*
FindUnquotedSpace(char * pc)205 FindUnquotedSpace(char *pc)
206 {
207     while ((*pc) && (*pc != ' ') && (*pc != '\t')) {
208 	if (*pc == '"') {
209 	    do {
210 		pc++;
211 	    } while (pc[1] && (*pc != '"'));
212 	}
213 	pc++;
214     }
215     return pc;
216 }
217 
218 BOOL
ProcessAlive(HANDLE hProcess)219 ProcessAlive(HANDLE hProcess)
220 {
221     DWORD code = 0;
222     if (GetExitCodeProcess(hProcess, &code))
223 	return (code == STILL_ACTIVE);
224     return FALSE;
225 }
226 
227 int
main(int argc,char * argv[])228 main (int argc, char *argv[])
229 {
230     char    psBuffer[BUFFER_SIZE];
231     char    psGnuplotCommandLine[MAX_PATH] = PROGNAME;
232     LPTSTR  psCmdLine;
233     BOOL    bSuccess;
234     BOOL    bPersist = FALSE;
235     int	i;
236 
237 #if !defined(_O_BINARY) && defined(O_BINARY)
238 # define _O_BINARY O_BINARY
239 # define _setmode setmode /* this is for BC4.5 ... */
240 #endif
241     _setmode(fileno(stdout), _O_BINARY);
242 
243     for (i = 1; i < argc; i++) {
244 	if (!argv[i])
245 	    continue;
246 	if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) {
247 	    printf("gnuplot %s patchlevel %s\n",
248 		   gnuplot_version, gnuplot_patchlevel);
249 	    return 0;
250 	} else if ((!stricmp(argv[i], "-noend")) || (!stricmp(argv[i], "/noend")) ||
251 		   (!stricmp(argv[i], "-persist"))) {
252 	    bPersist = TRUE;
253 	} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
254 	    printf("Usage: gnuplot [OPTION] [FILE] [-]\n"
255 		    "  -V, --version       show gnuplot version\n"
256 		    "  -h, --help          show this help\n"
257 		    "  -e \"cmd; cmd; ...\"  prepand additional commands\n"
258 		    "  -persist            don't close the plot after executing FILE\n"
259 		    "  -noend, /noend      like -persist (non-portable Windows-only options)\n"
260 		    "  -                   allow work in interactive mode after executing FILE\n"
261 		    "Only on Windows, -persist and - have the same effect.\n"
262 		    "This is gnuplot %s patchlevel %s\n"
263 		    "Report bugs to <info-gnuplot-beta@lists.sourceforge.net>\n",
264 		    gnuplot_version, gnuplot_patchlevel);
265 	    return 0;
266 	}
267     } /* for(argc) */
268 
269     /* CRS: create the new command line, passing all of the command
270      * line options to wgnuplot so that it can process them:
271      * first, get the command line,
272      * then move past the name of the program (e.g., 'pgnuplot'),
273      * finally, add what's left of the line onto the gnuplot command line. */
274     psCmdLine = GetCommandLine();
275 
276 #ifdef SHOWCMDLINE
277     fprintf(stderr,"CmdLine: %s\n", psCmdLine);
278     fprintf(stderr,"argv[0]: %s\n",argv[0]);
279 #endif
280 
281     /* CRS 30061999: Search for the first unquoted space. This should
282        separate the program name from the arguments. */
283     psCmdLine = FindUnquotedSpace(psCmdLine);
284 
285     strncat(psGnuplotCommandLine, psCmdLine, sizeof(psGnuplotCommandLine) - strlen(psGnuplotCommandLine)-1);
286 
287 #ifdef SHOWCMDLINE
288     fprintf(stderr,"Arguments: %s\n", psCmdLine);
289     fprintf(stderr,"GnuplotCommandLine: %s\n",psGnuplotCommandLine);
290 #endif
291 
292     /* CRS: if stdin isn't redirected then just launch wgnuplot normally
293      * and exit. */
294     if (isatty(fileno(stdin))) {
295 	if (WinExec(psGnuplotCommandLine, SW_SHOWDEFAULT) > 31) {
296 	    exit(EXIT_SUCCESS);
297 	}
298 	fprintf(stderr,"ERROR %u: Couldn't execute: \"%s\"\n",
299 		GetLastError(), psGnuplotCommandLine);
300 	exit(EXIT_FAILURE);
301     }
302 
303     /* CRS: initialize the STARTUPINFO and call CreateProcess(). */
304     siStartInfo.cb = sizeof(STARTUPINFO);
305     siStartInfo.lpReserved = NULL;
306     siStartInfo.lpReserved2 = NULL;
307     siStartInfo.cbReserved2 = 0;
308     siStartInfo.lpDesktop = NULL;
309     siStartInfo.dwFlags = STARTF_USESHOWWINDOW;
310     siStartInfo.wShowWindow = SW_SHOWMINIMIZED;
311 
312     bSuccess = CreateProcess(
313 			     NULL,                   /* pointer to name of executable module   */
314 			     psGnuplotCommandLine,   /* pointer to command line string         */
315 			     NULL,                   /* pointer to process security attributes */
316 			     NULL,                   /* pointer to thread security attributes  */
317 			     FALSE,                  /* handle inheritance flag                */
318 			     0,                      /* creation flags                         */
319 			     NULL,                   /* pointer to new environment block       */
320 			     NULL,                   /* pointer to current directory name      */
321 			     &siStartInfo,           /* pointer to STARTUPINFO                 */
322 			     &piProcInfo             /* pointer to PROCESS_INFORMATION         */
323 			     );
324 
325     /* if CreateProcess() failed, print a warning and exit. */
326     if (! bSuccess) {
327 	fprintf(stderr,"ERROR %u: Couldn't execute: \"%s\"\n",
328 		GetLastError(), psGnuplotCommandLine);
329 	exit(EXIT_FAILURE);
330     }
331 
332     /* CRS: give gnuplot enough time to start (1 sec.) */
333     if (WaitForInputIdle(piProcInfo.hProcess, 1000)) {
334 	fprintf(stderr, "Timeout: gnuplot is not ready\n");
335 	exit(EXIT_FAILURE);
336     }
337 
338     /* CRS: get the HWND of the parent window and text windows */
339     EnumThreadWindows(piProcInfo.dwThreadId, cbGetTextWindow, 0);
340 
341     if (! hwndParent || ! hwndText) {
342 	/* Still no gnuplot window? Problem! */
343 	fprintf(stderr, "Can't find the gnuplot window");
344 	/* CRS: free the process and thread handles */
345 	CloseHandle(piProcInfo.hProcess);
346 	CloseHandle(piProcInfo.hThread);
347 	exit(EXIT_FAILURE);
348     }
349 
350     /* wait for commands on stdin, and pass them on to the wgnuplot text
351      * window */
352     while (fgets(psBuffer, BUFFER_SIZE, stdin) != NULL) {
353 	/* RWH: Check if wgnuplot is still alive */
354 	if (!ProcessAlive(piProcInfo.hProcess))
355 	    break;
356 	PostString(hwndText, psBuffer);
357     }
358 
359     /* exit gracefully, unless -persist is requested */
360     if (!bPersist && ProcessAlive(piProcInfo.hProcess)) {
361 	PostString(hwndText, "\nexit\n");
362     }
363 
364     /* CRS: free the process and thread handles */
365     CloseHandle(piProcInfo.hProcess);
366     CloseHandle(piProcInfo.hThread);
367 
368     return EXIT_SUCCESS;
369 }
370