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