update.
[chise/xemacs-chise.git] / lib-src / winclient.c
1 /* DDE client for XEmacs.
2    Copyright (C) 2002 Alastair J. Houghton
3
4    This file is part of XEmacs.
5
6    XEmacs is free software; you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by the
8    Free Software Foundation; either version 2, or (at your option) any
9    later version.
10
11    XEmacs is distributed in the hope that it will be useful, but WITHOUT
12    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14    for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with XEmacs; see the file COPYING.  If not, write to
18    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 /* Synched up with: Not in FSF. */
22
23 /* -- Pre-Include Defines --------------------------------------------------- */
24
25 #define STRICT
26
27 /* -- Includes -------------------------------------------------------------- */
28
29 #include <windows.h>
30 #ifdef HAVE_CONFIG_H
31 # include <config.h>
32 #endif
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <ctype.h>
36 #include <errno.h>
37
38 static void error (const char* s1, const char* s2);
39 static void fatal (const char *s1, const char *s2);
40 static void * xmalloc (size_t size);
41 static char * getNextArg (const char **ptr, unsigned *len);
42
43 /* -- Post-Include Defines -------------------------------------------------- */
44
45 /* Timeouts & delays */
46 #define CONNECT_DELAY           500             /* ms */
47 #define TRANSACTION_TIMEOUT     5000            /* ms */
48 #define MAX_INPUT_IDLE_WAIT     INFINITE        /* ms */
49
50 /* DDE Strings */
51 #define SERVICE_NAME    "XEmacs"
52 #define TOPIC_NAME      "System"
53 #define COMMAND_FORMAT  "[open(\"%s%s\")]"
54
55 /* XEmacs program name */
56 #define PROGRAM_TO_RUN  "xemacs.exe"
57
58 /* -- Constants ------------------------------------------------------------- */
59
60 /* -- Global Variables ------------------------------------------------------ */
61
62 HINSTANCE hInstance;
63 DWORD     idInst = 0;
64
65 /* -- Function Declarations ------------------------------------------------- */
66
67 HDDEDATA CALLBACK ddeCallback (UINT uType, UINT uFmt, HCONV hconv,
68                                HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
69                                DWORD dwData1, DWORD dwData2);
70
71 int WINAPI WinMain (HINSTANCE hInst,
72                     HINSTANCE hPrev,
73                     LPSTR     lpCmdLine,
74                     int       nCmdShow);
75
76 static HCONV openConversation (void);
77 static void closeConversation (HCONV hConv);
78 static int doFile (HCONV hConv, LPSTR lpszFileName1, LPSTR lpszFileName2);
79 static int parseCommandLine (HCONV hConv, LPSTR lpszCommandLine);
80
81 /* -- Function Definitions -------------------------------------------------- */
82
83 /*
84  * Name    : ddeCallback
85  * Function: Gets called by DDEML.
86  *
87  */
88
89 HDDEDATA CALLBACK
90 ddeCallback (UINT uType, UINT uFmt, HCONV hconv,
91              HSZ hsz1, HSZ hsz2, HDDEDATA hdata,
92              DWORD dwData1, DWORD dwData2)
93 {
94   return (HDDEDATA) NULL;
95 }
96
97 /*
98  * Name    : WinMain
99  * Function: The program's entry point function.
100  *
101  */
102
103 int WINAPI
104 WinMain (HINSTANCE hInst,
105          HINSTANCE hPrev,
106          LPSTR     lpCmdLine,
107          int       nCmdShow)
108 {
109   HCONV hConv;
110   int   ret = 0;
111   UINT  uiRet;
112   
113   /* Initialise the DDEML library */
114   uiRet = DdeInitialize (&idInst,
115                          (PFNCALLBACK) ddeCallback,
116                          APPCMD_CLIENTONLY
117                          |CBF_FAIL_ALLSVRXACTIONS,
118                          0);
119
120   if (uiRet != DMLERR_NO_ERROR)
121     {
122       MessageBox (NULL, "Could not initialise DDE management library.",
123                   "winclient", MB_ICONEXCLAMATION | MB_OK);
124
125       return 1;
126     }
127
128   /* Open a conversation */
129   hConv = openConversation ();
130
131   if (hConv)
132     {
133       /* OK. Next, we need to parse the command line. */
134       ret = parseCommandLine (hConv, lpCmdLine);
135
136       /* Close the conversation */
137       closeConversation (hConv);
138     }
139   
140   DdeUninitialize (idInst);
141
142   return ret;
143 }
144
145 /*
146  * Name    : openConversation
147  * Function: Start a conversation.
148  *
149  */
150
151 static HCONV
152 openConversation (void)
153 {
154   HSZ             hszService = NULL, hszTopic = NULL;
155   HCONV           hConv = NULL;
156
157   /* Get the application (service) name */
158   hszService = DdeCreateStringHandle (idInst,
159                                       SERVICE_NAME,
160                                       CP_WINANSI);
161
162   if (!hszService)
163     {
164       MessageBox (NULL, "Could not create string handle for service.",
165                   "winclient", MB_ICONEXCLAMATION | MB_OK);
166
167       goto error;
168     }
169   
170   /* Get the topic name */
171   hszTopic = DdeCreateStringHandle (idInst,
172                                     TOPIC_NAME,
173                                     CP_WINANSI);
174
175   if (!hszTopic)
176     {
177       MessageBox (NULL, "Could not create string handle for topic.",
178                   "winclient", MB_ICONEXCLAMATION | MB_OK);
179
180       goto error;
181     }
182
183   /* Try to connect */
184   hConv = DdeConnect (idInst, hszService, hszTopic, NULL);
185
186   if (!hConv)
187     {
188       STARTUPINFO         sti;
189       PROCESS_INFORMATION pi;
190       int                 n;
191       
192       /* Try to start the program */
193       ZeroMemory (&sti, sizeof (sti));
194       sti.cb = sizeof (sti);
195       if (!CreateProcess (NULL, PROGRAM_TO_RUN, NULL, NULL, FALSE, 0,
196                           NULL, NULL, &sti, &pi))
197         {
198           MessageBox (NULL, "Could not start process.",
199                       "winclient", MB_ICONEXCLAMATION | MB_OK);
200
201           goto error;
202         }
203
204       /* Wait for the process to enter an idle state */
205       WaitForInputIdle (pi.hProcess, MAX_INPUT_IDLE_WAIT);
206
207       /* Close the handles */
208       CloseHandle (pi.hThread);
209       CloseHandle (pi.hProcess);
210       
211       /* Try to connect */
212       for (n = 0; n < 5; n++)
213         {
214           Sleep (CONNECT_DELAY);
215           
216           hConv = DdeConnect (idInst, hszService, hszTopic, NULL);
217
218           if (hConv)
219             break;
220         }
221
222       if (!hConv)
223         {
224           /* Still couldn't connect. */
225           MessageBox (NULL, "Could not connect to DDE server.",
226                       "winclient", MB_ICONEXCLAMATION | MB_OK);
227
228           goto error;
229         }
230     }
231
232   /* Release the string handles */
233   DdeFreeStringHandle (idInst, hszService);
234   DdeFreeStringHandle (idInst, hszTopic);
235
236   return hConv;
237   
238  error:
239   if (hConv)
240     DdeDisconnect (hConv);
241   if (hszService)
242     DdeFreeStringHandle (idInst, hszService);
243   if (hszTopic)
244     DdeFreeStringHandle (idInst, hszTopic);
245
246   return NULL;
247 }
248
249 /*
250  * Name    : closeConversation
251  * Function: Close a conversation.
252  *
253  */
254
255 static void
256 closeConversation (HCONV hConv)
257 {
258   /* Shut down */
259   DdeDisconnect (hConv);
260 }
261
262 /*
263  * Name    : doFile
264  * Function: Process a file.
265  *
266  */
267
268 int
269 doFile (HCONV hConv, LPSTR lpszFileName1, LPSTR lpszFileName2)
270 {
271   char            *buf = NULL;
272   unsigned        len;
273   
274   /* Calculate the buffer length */
275   len = strlen (lpszFileName1) + strlen (lpszFileName2)
276     + strlen (COMMAND_FORMAT);
277   
278   /* Allocate a buffer */
279   buf = (char *) xmalloc (len);
280
281   if (!buf)
282     {
283       MessageBox (NULL, "Not enough memory.",
284                   "winclient", MB_ICONEXCLAMATION | MB_OK);
285
286       return 1;
287     }
288
289   /* Build the command */
290   len = wsprintf (buf, COMMAND_FORMAT, lpszFileName1, lpszFileName2);
291
292   len++;
293   
294   /* OK. We're connected. Send the message. */
295   DdeClientTransaction (buf, len, hConv, NULL,
296                         0, XTYP_EXECUTE, TRANSACTION_TIMEOUT, NULL);
297
298   free (buf);
299   
300   return 0;
301 }
302
303 /*
304  * Name    : getNextArg
305  * Function: Retrieve the next command line argument.
306  *
307  */
308
309 static char *
310 getNextArg (const char **ptr, unsigned *len)
311 {
312   int        in_quotes = 0, quit = 0, all_in_quotes = 0;
313   const char *p = *ptr, *start;
314   char       *buf = NULL;
315   unsigned   length = 0;
316
317   /* Skip whitespace */
318   while (*p && isspace (*p))
319     p++;
320
321   /* If this is the end, return NULL */
322   if (!*p)
323     return NULL;
324   
325   /* Remember where we are */
326   start = p;
327   
328   /* Find the next whitespace character outside quotes */
329   if (*p == '"')
330     all_in_quotes = 1;
331   
332   while (*p && !quit)
333     {
334       switch (*p)
335         {
336         case '"':
337           in_quotes = 1 - in_quotes;
338           p++;
339           break;
340
341         case '\\':
342           if (!in_quotes)
343             all_in_quotes = 0;
344           
345           p++;
346
347           if (!*p)
348             break;
349
350           p++;
351           break;
352
353         default:
354           if (isspace (*p) && !in_quotes)
355             quit = 1;
356           else if (!in_quotes)
357             all_in_quotes = 0;
358
359           if (!quit)
360             p++;
361         }
362     }
363
364   /* Work out the length */
365   length = p - start;
366
367   /* Strip quotes if the argument is completely quoted */
368   if (all_in_quotes)
369     {
370       start++;
371       length -= 2;
372     }
373   
374   /* Copy */
375   buf = (char *) xmalloc (length + 1);
376
377   if (!buf)
378     return NULL;
379   
380   strncpy (buf, start, length);
381   buf[length] = '\0';
382
383   /* Return the pointer and length */
384   *ptr = p;
385   *len = length;
386
387   return buf;
388 }
389
390 /*
391  * Name    : parseCommandLine
392  * Function: Process the command line. This program accepts a list of strings
393  *         : (which may contain wildcards) representing filenames.
394  *
395  */
396
397 int
398 parseCommandLine (HCONV hConv, LPSTR lpszCommandLine)
399 {
400   char            *fullpath, *filepart;
401   char            *arg;
402   unsigned        len, pathlen;
403   int             ret = 0;
404   HANDLE          hFindFile = NULL;
405   WIN32_FIND_DATA wfd;
406
407   /* Retrieve arguments */
408   while ((arg = getNextArg ((const char**)&lpszCommandLine, &len)) != NULL)
409     {
410       /* First find the canonical path name */
411       fullpath = filepart = NULL;
412       pathlen = GetFullPathName (arg, 0, fullpath, &filepart);
413
414       fullpath = (char *) xmalloc (pathlen);
415
416       if (!fullpath)
417         {
418           MessageBox (NULL, "Not enough memory.", "winclient",
419                       MB_ICONEXCLAMATION | MB_OK);
420           
421           ret = 1;
422           free (arg);
423           
424           break;
425         }
426
427       GetFullPathName (arg, pathlen, fullpath, &filepart);
428
429       /* Find the first matching file */
430       hFindFile = FindFirstFile (arg, &wfd);
431
432       if (hFindFile == INVALID_HANDLE_VALUE)
433         ret = doFile (hConv, fullpath, "");
434       else
435         {
436           /* Chop off the file part from the full path name */
437           if (filepart)
438             *filepart = '\0';
439
440           /* For each matching file */
441           do
442             {
443               /* Process it */
444               ret = doFile (hConv, fullpath, wfd.cFileName);
445
446               if (ret)
447                 break;
448             }
449           while (FindNextFile (hFindFile, &wfd));
450
451           FindClose (hFindFile);
452         }
453
454       /* Release the path name buffers */
455       free (fullpath);
456       free (arg);
457
458       if (ret)
459         break;
460     }
461
462   return ret;
463 }
464
465 static void
466 fatal (const char *s1, const char *s2)
467 {
468   error (s1, s2);
469   exit (1);
470 }
471
472 /* Print error message.  `s1' is printf control string, `s2' is arg for it. */
473 static void
474 error (const char* s1, const char* s2)
475 {
476   fprintf (stderr, "winclient: ");
477   fprintf (stderr, s1, s2);
478   fprintf (stderr, "\n");
479 }
480
481 /* Like malloc but get fatal error if memory is exhausted.  */
482
483 static void *
484 xmalloc (size_t size)
485 {
486   void *result = malloc (size);
487   if (result == NULL)
488     fatal ("virtual memory exhausted", (char *) 0);
489   return result;
490 }