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