1 /* The routines in this file provide support for external program
2    execution under the Microsoft Windows environment on an IBM-PC or
3    compatible computer.
4 
5    Must be compiled with Borland C++ 2.0 or MSC 6.0 or later versions
6 
7    It should not be compiled if the WINDOW_MSWIN symbol is not set */
8 
9 #include    "estruct.h"
10 #include    <stdio.h>
11 #include    "eproto.h"
12 #include    "edef.h"
13 #include    "elang.h"
14 
15 #include    "mswin.h"
16 
17 #define CMDLENGTH   256+NFILEN
18 
19 /* local data */
20 #if WINDOW_MSWIN32
21 static STARTUPINFO suInfo = { 0 };
22 static char suTitle [] = PROGNAME "'s subshell";
23 static PROCESS_INFORMATION pInfo = { 0 };
24 #else
25 static HWND hPrgWnd;    /* window handle of the external program task */
26 #endif
27 
28 
29 /* HandleTimer: checks the existence of the external program window */
30 /* ===========                                                      */
31 
HandleTimer(HWND hDlg)32 static void PASCAL  HandleTimer (HWND hDlg)
33 
34 /* This function uses a 200ms timeout to check the existence of the window
35    indicated by hPrgWnd. When that window handle becomes invalid, it is
36    assumed that the external program has exited and the WAITFORPRG
37    dialog box is terminated with a TRUE result. If there is a problem
38    setting the timer, the dialog box is terminated with a FALSE result
39    after a message box announcing the problem has been displayed. */
40 {
41 #if WINDOW_MSWIN32
42     /* check for process completion */
43     if (WaitForSingleObject(pInfo.hProcess, 0) == WAIT_TIMEOUT) {
44 #else
45     /* look for the program window */
46     if (IsWindow (hPrgWnd)) {
47 #endif
48 	while (!SetTimer (hDlg, 1, 200, NULL)) {
49 	    /* bad: problem setting the timer */
50 	    if (MessageBox (hDlg, TEXT337, NULL, MB_RETRYCANCEL) == IDCANCEL) {
51 	        /* "cannot monitor external program" */
52 		EndDialog (hDlg, FALSE);    /* give up! */
53 		return;
54 	    }
55 	    /* else: attempt a retry */
56 	}
57     }
58     else {
59 	EndDialog (hDlg, TRUE);
60     }
61 } /* HandleTimer */
62 
63 /* WAITFORPRGDlgProc:   dialog proc for WAITFORPRG dialog box */
64 /* =================                                          */
65 int EXPORT FAR PASCAL  WAITFORPRGDlgProc (HWND hDlg, UINT wMsg,
66 					  UINT wParam, LONG lParam)
67 {
68     switch (wMsg) {
69     case WM_INITDIALOG:
70         SetWindowText (hDlg, PROGNAME);
71 	HandleTimer (hDlg);
72 	return TRUE;
73     case WM_TIMER:
74 	HandleTimer (hDlg);
75 	break;
76     case WM_COMMAND:
77 	if (LOWORD(wParam) == 2) {  /* Cancel */
78 	    KillTimer (hDlg, 1);
79 	    EndDialog (hDlg, FALSE);
80 	}
81 	break;
82     default:
83 	return FALSE;
84     }
85     return FALSE;
86 } /* WAITFORPRGDlgProc */
87 
88 /* LaunchPrgEnumProc:   used by LaunchPrg */
89 /* =================                      */
90 BOOL EXPORT FAR PASCAL LaunchPrgEnumProc (HWND hWnd, LONG lParam)
91 
92 /* this function sets hPrgWnd when it finds a window that matches the
93    module instance handle passed in lParam */
94 {
95 #if !WINDOW_MSWIN32
96     if (GetWindowWord (hWnd, GWW_HINSTANCE) == LOWORD(lParam)) {
97 	hPrgWnd = hWnd;
98 	return FALSE;   /* found it, stop enumerating */
99     }
100 #endif
101     return TRUE;
102 } /* LaunchPrgEnumProc */
103 
104 /* LaunchPrg:   launches and monitors an external program  */
105 /* =========                                               */
106 
107 static BOOL PASCAL  LaunchPrg (char *Cmd, BOOL DOSApp,
108                                char *InFile, char *OutFile)
109 
110 /* Returns TRUE if all went well, FALSE if wait cancelled and FAILD if
111    failed to launch.
112 
113    Cmd is the command string to launch.
114 
115    DOSApp is TRUE if the external program is a DOS program to be run
116    under a DOS shell. If DOSApp is FALSE, the program is launched
117    directly as a Windows application. In that case, the InFile parameter
118    is ignored, and the value of the OutFile parameter is used only to
119    determine if the program should be monitored. the text of the string
120    referenced by OutFile is irrelevant.
121 
122    InFile is the name of the file to pipe into stdin (if NULL, nothing
123    is piped in)
124 
125    OutFile is the name of the file where stdout is expected to be
126    redirected. If it is NULL or an empty string, stdout is not redirected
127 
128    If Outfile is NULL, LaunchPrg returns immediately after starting the
129    DOS box.
130 
131    If OutFile is not NULL, the external program is monitored.
132    LaunchPrg returns only when the external program has terminated or
133    the user has cancelled the wait (in which case LaunchPrg returns
134    FALSE). */
135 {
136     char    FullCmd [CMDLENGTH];
137     HANDLE  hModule;
138     int     nCmdShow;
139     BOOL    Synchronize;
140 #if !WINDOW_MSWIN32
141     FARPROC ProcInstance;
142 #endif
143 
144     if (OutFile) {
145         Synchronize = TRUE;
146         if (*OutFile == '\0') OutFile = NULL; /* stop worrying about that
147                                                  empty string */
148     }
149     else Synchronize = FALSE;
150 
151     if (SetWorkingDir () != 0) {
152         mlwrite (TEXT334);  /* "[No such directory]" */
153         return FALSE;
154     }
155     if (DOSApp) {
156 #if WINDOW_MSWIN32
157         GetPrivateProfileString (ProgName, "Shell", "cmd.exe", FullCmd,
158 			CMDLENGTH, IniFile);
159 
160 	/* the Shell profile string should contain the name of the shell
161            to be used for pipe-command, filter buffer, shell-command and
162            i-shell. The ShellExecOption profile string should contain
163            the option flags that cause the execution under that shell of
164            a single command (the text of which is appended to the profile
165            string) so that the shell terminates with that command. */
166 	if (Cmd) {
167 	    char ExecOption [10];
168 
169             GetPrivateProfileString (ProgName, "ShellExecOption", " /c ",
170                               ExecOption, 10, IniFile);
171 
172 	    if ((strlen (FullCmd) + strlen (ExecOption) + strlen (Cmd)) >=
173                 CMDLENGTH) return FALSE;
174 	    strcat (FullCmd, ExecOption);
175 	    strcat (FullCmd, Cmd);
176 	}
177 #else
178         if (Synchronize || !Cmd) {
179 	    GetPrivateProfileString (ProgName, "DOSExec", "",
180 			      FullCmd, CMDLENGTH, IniFile);
181 
182             if (FullCmd[0] == '\0') {   /* try to find it on the "path" */
183                 char    *s;
184 
185                 if ((s = flook ("DOSEXEC.PIF", TRUE)) != NULL) {
186                     strcpy (FullCmd, s);
187                 }
188             }
189         }
190 	else FullCmd[0] = '\0';
191 	if (FullCmd[0] == '\0') {
192 	    GetPrivateProfileString (ProgName, "DOSBox", "",
193 	                      FullCmd, CMDLENGTH, IniFile);
194 
195             if (FullCmd[0] == '\0') {   /* try to find it on the "path" */
196                 char    *s;
197 
198                 if ((s = flook ("DOSBOX.PIF", TRUE)) != NULL) {
199                     strcpy (FullCmd, s);
200                 }
201                 else strcpy (FullCmd, "command.com");
202             }
203         }
204 	/* the DOSBox profileString should be the name of a PIF file for
205 	   command.com that specifies no arguments and no starting dir.
206 	   The DOSExec should be similar but specify "Close window on
207 	   exit" so that synchronization can work */
208 	if (Cmd) {
209 	    if ((strlen (FullCmd) + strlen (Cmd) + 4) >=
210                 CMDLENGTH) return FALSE;
211 	    strcat (FullCmd, " /c ");
212 	    strcat (FullCmd, Cmd);
213 	}
214 #endif
215 	if (InFile) {
216 	    if ((strlen (FullCmd) + strlen (InFile) + 2) >=
217                 CMDLENGTH) return FALSE;
218 	    strcat (FullCmd, " <");
219 	    strcat (FullCmd, InFile);
220 	}
221 	if (OutFile) {
222 	    if ((strlen (FullCmd) + strlen (OutFile) + 2) >=
223                 CMDLENGTH) return FALSE;
224 	    strcat (FullCmd, " >");
225 	    strcat (FullCmd, OutFile);
226 	}
227     }
228 #if WINDOW_MSWIN32
229     /* set the startup window size */
230     suInfo.cb = sizeof(STARTUPINFO);
231     if (DOSApp && Cmd) suInfo.lpTitle = suTitle;
232     suInfo.wShowWindow = (DOSApp && Synchronize) ? SW_SHOWMINIMIZED :
233                                                    SW_SHOWNORMAL;
234     suInfo.dwFlags     = STARTF_USESHOWWINDOW;
235 
236     /* start the process and get a handle on it */
237     if (CreateProcess (NULL, DOSApp ? FullCmd : Cmd, NULL, NULL,
238                        DETACHED_PROCESS,
239                        FALSE, NULL, NULL, &suInfo, &pInfo)) {
240         int Result;
241 
242         if (Synchronize) {
243             /* put up a dialog box to wait for termination */
244             Result = DialogBox (hEmacsInstance, "WAITFORPRG",
245                                 hFrameWnd, WAITFORPRGDlgProc);
246 	}
247         else {
248             /* no need to synchronize */
249             Result = TRUE;
250 	}
251         CloseHandle(pInfo.hThread);
252         CloseHandle(pInfo.hProcess);
253 
254         return Result;
255     } else return FALSE;
256 #else
257     if (Win386Enhanced) {
258         if (DOSApp && Synchronize) nCmdShow = SW_SHOWMINIMIZED;
259         else nCmdShow = SW_SHOWNORMAL;
260     }
261     else nCmdShow = SW_SHOWNORMAL;
262 
263     hModule = WinExec (DOSApp ? FullCmd : Cmd, nCmdShow);
264         /* here we GOoooo */
265 
266     if (hModule < 32) {
267         mlwrite (TEXT3);    /* "[Execution failed]" */
268         return FAILD;
269     }
270     if (!Synchronize) return TRUE;  /* no synchronization */
271     hPrgWnd = 0;
272     ProcInstance = MakeProcInstance ((FARPROC)LaunchPrgEnumProc,
273                                      hEmacsInstance);
274     EnumWindows (ProcInstance, (DWORD)hModule);
275     FreeProcInstance (ProcInstance);
276     if (hPrgWnd != 0) {
277 	/*-put up a dialog box to wait for the external program termination */
278 	int     Result;
279 
280 	ProcInstance = MakeProcInstance ((FARPROC)WAITFORPRGDlgProc,
281                                          hEmacsInstance);
282 	Result = DialogBox (hEmacsInstance, "WAITFORPRG",
283 			    hFrameWnd, ProcInstance);
284 	FreeProcInstance (ProcInstance);
285 	return Result;
286     }
287     else return TRUE;   /* we assume it has zipped past us! */
288 #endif
289 } /* LaunchPrg */
290 
291 /* spawncli:    launch DOS shell. Bound to ^X-C */
292 /* ========                                     */
293 
294 PASCAL spawncli (int f, int n)
295 {
296     /*-don't allow this command if restricted */
297     if (restflag) return resterr();
298 
299     return LaunchPrg (NULL, TRUE, NULL, NULL);
300 } /* spawncli */
301 
302 /* spawn:   run a one-liner in a DOS box. Bound to ^X-! */
303 /* =====                                                */
304 
305 PASCAL spawn (int f, int n)
306 {
307     char    Line[NLINE];
308     int     Result;
309     char    *SynchOption;
310     static char empty[] = "";
311 
312     /*-don't allow this command if restricted */
313     if (restflag) return resterr();
314 
315     if ((Result = mlreply ("!", Line, NLINE)) != TRUE) return Result;
316     if (f) SynchOption = &empty[0];
317     else SynchOption = NULL;
318     return LaunchPrg (Line, TRUE, NULL, SynchOption);
319 } /* spawn */
320 
321 /* execprg: run another program with arguments. Bound to ^X-$ */
322 /* =======                                                    */
323 
324 PASCAL execprg (int f, int n)
325 {
326     char    Line[NLINE];
327     int     Result;
328     char    *SynchOption;
329 
330     /*-don't allow this command if restricted */
331     if (restflag) return resterr();
332 
333     /*-get the program command line */
334     if (mlreply ("$", Line, NLINE) != TRUE) return FALSE;
335 
336     if (f) SynchOption = &Line[0];  /* any not NULL will do */
337     else SynchOption = NULL;
338     Result = LaunchPrg (Line, FALSE, NULL, SynchOption);
339     if (Result == FAILD) {
340         mlwrite (TEXT3);    /* "[Execution failed]" */
341     }
342     return Result;
343 } /* execprg */
344 
345 /* pipecmd: pipe a one-liner into a window. Bound to ^X-@ */
346 /* =======                                                */
347 
348 PASCAL pipecmd (int f, int n)
349 
350 /* this function fills a buffer named "command" with the output of the
351    DOS one-liner. If the command buffer already exist, it is overwritten
352    only if it has not been changed */
353 {
354     char    Line[NLINE];
355     char    OutFile[NFILEN];
356     static  char bname[] = "command";
357     BUFFER  *bp;
358     EWINDOW  *wp;
359     int     Result;
360     int     bmode;
361     char    bflag;
362 #if WINDOW_MSWIN32
363     char    TempDir[NFILEN] = "\\";
364 #endif
365 
366     /*-don't allow this command if restricted */
367     if (restflag) return resterr();
368 
369     /*-get the command to pipe-in */
370     if (mlreply ("@", Line, NLINE) != TRUE) return FALSE;
371 
372     /*-find the "command" buffer */
373     if ((bp = bfind (bname, FALSE, 0)) != NULL) {
374 	/*-make sure the contents can safely be blown away */
375 	if (bp->b_flag & BFCHG) {
376 	    if (mlyesno (TEXT32) != TRUE) return FALSE;
377 	    /* discard changes */
378 	}
379     }
380     else if ((bp = bfind (bname, TRUE, 0)) == NULL) {
381 	mlwrite (TEXT137);
382         /* cannot create buffer */
383         return FALSE;
384     }
385 #if WINDOW_MSWIN32
386     GetTempPath (NFILEN, TempDir);
387     GetTempFileName (TempDir, "UE", 0, OutFile);
388 #else
389     GetTempFileName (0, "UE", 0, OutFile);
390 #endif
391     Result = LaunchPrg (Line, TRUE, NULL, OutFile);
392     if (Result == FAILD) {
393 	mlwrite (TEXT3);
394         /* [execution failed] */
395 	unlink (OutFile);
396     }
397     else {
398         if (Result == TRUE) {
399 	    BUFFER  *temp_bp;
400 
401 	    temp_bp = curbp;
402 	    swbuffer (bp);          /* make this buffer the current one */
403 	    bmode = bp->b_mode;
404 	    bp->b_mode &= ~MDVIEW;
405 	    bflag = bp->b_flag;
406 	    bp->b_flag &= ~BFCHG;
407 	    Result = readin (OutFile, FALSE);
408 	    bp->b_fname[0] = '\0';  /* clear file name */
409 	    if (Result == TRUE) {
410 		bp->b_mode |= MDVIEW;   /* force VIEW mode */
411 		lchange (WFMODE);       /* update all relevant mode lines */
412 		bp->b_flag &= ~BFCHG;   /* remove by-product BFCHG flag */
413 	    }
414 	    else {
415 		bp->b_mode = bmode;     /* restore mode */
416 	        bp->b_flag = bflag;
417 		swbuffer (temp_bp);
418 	    }
419 	    unlink (OutFile);
420 	    /* note that the file is not deleted if the wait was cancelled */
421 	}
422     }
423     return Result;
424 } /* pipecmd */
425 
426 /* filter:  filter a buffer through a DOS box. Bound to ^X-# */
427 /* ======                                                    */
428 
429 PASCAL filter (int f, int n)
430 {
431     char    Line[NLINE];
432     char    InFile[NFILEN];
433     char    OutFile[NFILEN];
434     char    fname[NFILEN];
435     BUFFER  *bp;
436     EWINDOW  *wp;
437     int     Result;
438 #if WINDOW_MSWIN32
439     char    TempDir[NFILEN] = "\\";
440 #endif
441 
442     /*-don't allow this command if restricted */
443     if (restflag) return resterr();
444 
445     /*-get the filter command line */
446     if (mlreply ("#", Line, NLINE) != TRUE) return FALSE;
447 
448     bp = curbp;
449     strcpy (fname, bp->b_fname);
450 #if WINDOW_MSWIN32
451     GetTempPath (NFILEN, TempDir);
452     GetTempFileName (TempDir, "UE", 0, InFile);
453 #else
454     GetTempFileName (0, "UE", 0, InFile);
455 #endif
456     Result = writeout (InFile, "w");
457     if (Result != TRUE) {
458 	mlwrite (TEXT2);
459         /* cannot write filter file */
460     }
461     else {
462 #if WINDOW_MSWIN32
463         GetTempFileName (TempDir, "UE", 0, OutFile);
464 #else
465         GetTempFileName (0, "UE", 0, OutFile);
466 #endif
467 	Result = LaunchPrg (Line, TRUE, InFile, OutFile);
468         if (Result == FAILD) {
469 	    mlwrite (TEXT3);
470 	    /* [execution failed] */
471             unlink (OutFile);
472             unlink (InFile);
473         }
474 	else {
475             if (Result == TRUE) {
476 		Result = readin (OutFile, FALSE);
477 		unlink (OutFile);
478 		unlink (InFile);
479 	    }
480 	    /* note that he files are not deleted if the wait was cancelled */
481 	    if (Result == TRUE) {
482 		lchange (WFMODE);   /* update all relevant mode lines */
483 	    }
484 	}
485     }
486     strcpy (bp->b_fname, fname);    /* restore original file name */
487     return Result;
488 } /* filter */
489