2 * Shell Library Functions
4 * Copyright 1998 Marcus Meissner
5 * Copyright 2002 Eric Pouech
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
38 #include "wine/winbase16.h"
39 #include "shell32_main.h"
41 #include "wine/debug.h"
43 WINE_DEFAULT_DEBUG_CHANNEL(exec);
45 /* this function is supposed to expand the escape sequences found in the registry
46 * some diving reported that the following were used:
47 * + %1, %2... seem to report to parameter of index N in ShellExecute pmts
48 + + %* seem to report to all parameter (or all remaining, ie after removing
49 * the already used %1 %2...)
50 * + %L seems to be %1 as long filename followed by the 8+3 variation
55 static void argify(char* res, int len, const char* fmt, const char* lpFile)
70 if (SearchPathA(NULL, lpFile, ".exe", sizeof(xlpFile), xlpFile, NULL))
73 res += strlen(xlpFile);
78 res += strlen(lpFile);
81 default: FIXME("Unknown escape sequence %%%c\n", *fmt);
91 /*************************************************************************
92 * SHELL_FindExecutable [Internal]
94 * Utility for code sharing between FindExecutable and ShellExecute
96 * lpFile the name of a file
97 * lpOperation the operation on it (open)
99 * lpResult a buffer, big enough :-(, to store the command to do the
100 * operation on the file
101 * key a buffer, big enough, to get the key name to do actually the
102 * command (it'll be used afterwards for more information
105 static HINSTANCE SHELL_FindExecutable(LPCSTR lpFile, LPCSTR lpOperation,
106 LPSTR lpResult, LPSTR key)
108 char *extension = NULL; /* pointer to file extension */
109 char tmpext[5]; /* local copy to mung as we please */
110 char filetype[256]; /* registry name for this filetype */
111 LONG filetypelen = 256; /* length of above */
112 char command[256]; /* command from registry */
113 LONG commandlen = 256; /* This is the most DOS can handle :) */
114 char buffer[256]; /* Used to GetProfileString */
115 HINSTANCE retval = 31; /* default - 'No association was found' */
116 char *tok; /* token pointer */
117 int i; /* random counter */
118 char xlpFile[256] = ""; /* result of SearchPath */
120 TRACE("%s\n", (lpFile != NULL) ? lpFile : "-");
122 lpResult[0] = '\0'; /* Start off with an empty return string */
124 /* trap NULL parameters on entry */
125 if ((lpFile == NULL) || (lpResult == NULL) || (lpOperation == NULL))
127 WARN("(lpFile=%s,lpResult=%s,lpOperation=%s): NULL parameter\n",
128 lpFile, lpOperation, lpResult);
129 return 2; /* File not found. Close enough, I guess. */
132 if (SearchPathA(NULL, lpFile, ".exe", sizeof(xlpFile), xlpFile, NULL))
134 TRACE("SearchPathA returned non-zero\n");
138 /* First thing we need is the file's extension */
139 extension = strrchr(xlpFile, '.'); /* Assume last "." is the one; */
140 /* File->Run in progman uses */
142 TRACE("xlpFile=%s,extension=%s\n", xlpFile, extension);
144 if ((extension == NULL) || (extension == &xlpFile[strlen(xlpFile)]))
146 WARN("Returning 31 - No association\n");
147 return 31; /* no association */
150 /* Make local copy & lowercase it for reg & 'programs=' lookup */
151 lstrcpynA(tmpext, extension, 5);
153 TRACE("%s file\n", tmpext);
155 /* Three places to check: */
156 /* 1. win.ini, [windows], programs (NB no leading '.') */
157 /* 2. Registry, HKEY_CLASS_ROOT\<filetype>\shell\open\command */
158 /* 3. win.ini, [extensions], extension (NB no leading '.' */
159 /* All I know of the order is that registry is checked before */
160 /* extensions; however, it'd make sense to check the programs */
161 /* section first, so that's what happens here. */
163 if (key) *key = '\0';
165 /* See if it's a program - if GetProfileString fails, we skip this
166 * section. Actually, if GetProfileString fails, we've probably
167 * got a lot more to worry about than running a program... */
168 if (GetProfileStringA("windows", "programs", "exe pif bat com",
169 buffer, sizeof(buffer)) > 0)
171 for (i = 0;i<strlen(buffer); i++) buffer[i] = tolower(buffer[i]);
173 tok = strtok(buffer, " \t"); /* ? */
176 if (strcmp(tok, &tmpext[1]) == 0) /* have to skip the leading "." */
178 strcpy(lpResult, xlpFile);
179 /* Need to perhaps check that the file has a path
181 TRACE("found %s\n", lpResult);
184 /* Greater than 32 to indicate success FIXME According to the
185 * docs, I should be returning a handle for the
186 * executable. Does this mean I'm supposed to open the
187 * executable file or something? More RTFM, I guess... */
189 tok = strtok(NULL, " \t");
194 if (RegQueryValueA(HKEY_CLASSES_ROOT, tmpext, filetype,
195 &filetypelen) == ERROR_SUCCESS)
197 filetype[filetypelen] = '\0';
198 TRACE("File type: %s\n", filetype);
200 /* Looking for ...buffer\shell\lpOperation\command */
201 strcat(filetype, "\\shell\\");
202 strcat(filetype, lpOperation);
203 strcat(filetype, "\\command");
205 if (RegQueryValueA(HKEY_CLASSES_ROOT, filetype, command,
206 &commandlen) == ERROR_SUCCESS)
208 if (key) strcpy(key, filetype);
214 /* FIXME: it seems all Windows version don't behave the same here.
215 * the doc states that this ddeexec information can be found after
217 * on Win98, it doesn't appear, but I think it does on Win2k
219 /* Get the parameters needed by the application
220 from the associated ddeexec key */
221 tmp = strstr(filetype, "command");
223 strcat(filetype, "ddeexec");
225 if (RegQueryValueA(HKEY_CLASSES_ROOT, filetype, param, ¶mlen) == ERROR_SUCCESS)
227 strcat(command, " ");
228 strcat(command, param);
229 commandlen += paramlen;
232 command[commandlen] = '\0';
233 argify(lpResult, sizeof(lpResult), command, xlpFile);
234 retval = 33; /* FIXME see above */
237 else /* Check win.ini */
239 /* Toss the leading dot */
241 if (GetProfileStringA("extensions", extension, "", command,
242 sizeof(command)) > 0)
244 if (strlen(command) != 0)
246 strcpy(lpResult, command);
247 tok = strstr(lpResult, "^"); /* should be ^.extension? */
251 strcat(lpResult, xlpFile); /* what if no dir in xlpFile? */
252 tok = strstr(command, "^"); /* see above */
253 if ((tok != NULL) && (strlen(tok)>5))
255 strcat(lpResult, &tok[5]);
258 retval = 33; /* FIXME - see above */
263 TRACE("returning %s\n", lpResult);
267 /******************************************************************
270 * callback for the DDE connection. not really usefull
272 static HDDEDATA CALLBACK dde_cb(UINT uType, UINT uFmt, HCONV hConv,
274 HDDEDATA hData, DWORD dwData1, DWORD dwData2)
279 /******************************************************************
282 * ShellExecute helper. Used to do an operation with a DDE connection
284 * Handles both the direct connection (try #1), and if it fails,
285 * launching an application and trying (#2) to connect to it
288 static unsigned dde_connect(char* key, char* start, char* ddeexec,
290 int iCmdShow, BOOL is32)
292 char* endkey = key + strlen(key);
293 char app[256], topic[256], ifexec[256], res[256];
294 LONG applen, topiclen, ifexeclen;
298 HSZ hszApp, hszTopic;
302 strcpy(endkey, "\\application");
303 applen = sizeof(app);
304 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, app, &applen) != ERROR_SUCCESS)
306 FIXME("default app name NIY %s\n", key);
310 strcpy(endkey, "\\topic");
311 topiclen = sizeof(topic);
312 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, topic, &topiclen) != ERROR_SUCCESS)
314 strcpy(topic, "System");
317 if (DdeInitializeA(&ddeInst, dde_cb, APPCMD_CLIENTONLY, 0L) != DMLERR_NO_ERROR)
322 hszApp = DdeCreateStringHandleA(ddeInst, app, CP_WINANSI);
323 hszTopic = DdeCreateStringHandleA(ddeInst, topic, CP_WINANSI);
325 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
329 TRACE("Launching '%s'\n", start);
330 ret = (is32) ? WinExec(start, iCmdShow) : WinExec16(start, iCmdShow);
333 TRACE("Couldn't launch\n");
336 hConv = DdeConnect(ddeInst, hszApp, hszTopic, NULL);
339 ret = 30; /* whatever */
342 strcpy(endkey, "\\ifexec");
343 ifexeclen = sizeof(ifexec);
344 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, ifexec, &ifexeclen) == ERROR_SUCCESS)
350 argify(res, sizeof(res), exec, lpFile);
351 TRACE("%s %s => %s\n", exec, lpFile, res);
353 ret = (DdeClientTransaction(res, strlen(res) + 1, hConv, 0L, 0,
354 XTYP_EXECUTE, 10000, &tid) != DMLERR_NO_ERROR) ? 31 : 32;
355 DdeDisconnect(hConv);
357 DdeUninitialize(ddeInst);
361 static HINSTANCE execute_from_key(LPSTR key, LPCSTR lpFile, INT iShowCmd, BOOL is32)
364 LONG cmdlen = sizeof(cmd);
365 HINSTANCE retval = 31;
367 /* Get the application for the registry */
368 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, cmd, &cmdlen) == ERROR_SUCCESS)
371 char param[256] = "";
374 /* Get the parameters needed by the application
375 from the associated ddeexec key */
376 tmp = strstr(key, "command");
378 strcpy(tmp, "ddeexec");
380 if (RegQueryValueA(HKEY_CLASSES_ROOT, key, param, ¶mlen) == ERROR_SUCCESS)
382 TRACE("Got ddeexec %s => %s\n", key, param);
383 retval = dde_connect(key, cmd, param, lpFile, iShowCmd, is32);
387 /* Is there a replace() function anywhere? */
389 argify(param, sizeof(param), cmd, lpFile);
391 retval = (is32) ? WinExec(param, iShowCmd) : WinExec16(param, iShowCmd);
394 else TRACE("ooch\n");
399 static HINSTANCE SHELL_Execute(HWND hWnd, LPCSTR lpOperation, LPCSTR lpFile,
400 LPCSTR lpParameters, LPCSTR lpDirectory,
401 INT iShowCmd, BOOL is32)
403 HINSTANCE retval = 31;
407 TRACE("(%04x,'%s','%s','%s','%s',%x)\n",
408 hWnd, lpOperation ? lpOperation:"<null>", lpFile ? lpFile:"<null>",
409 lpParameters ? lpParameters : "<null>",
410 lpDirectory ? lpDirectory : "<null>", iShowCmd);
412 if (lpFile == NULL) return 0; /* should not happen */
413 if (lpOperation == NULL) /* default is open */
414 lpOperation = "open";
418 GetCurrentDirectoryA(sizeof(old_dir), old_dir);
419 SetCurrentDirectoryA(lpDirectory);
422 /* First try to execute lpFile with lpParameters directly */
427 strcat(cmd, lpParameters);
430 retval = (is32) ? WinExec(cmd, iShowCmd) : WinExec16(cmd, iShowCmd);
432 /* Unable to execute lpFile directly
433 Check if we can match an application to lpFile */
436 char lpstrProtocol[256];
439 retval = SHELL_FindExecutable(lpFile, lpOperation, cmd, lpstrProtocol);
441 if (retval > 32) /* Found */
443 TRACE("%s/%s => %s/%s\n", lpFile, lpOperation, cmd, lpstrProtocol);
445 retval = execute_from_key(lpstrProtocol, lpFile, iShowCmd, is32);
447 retval = (is32) ? WinExec(cmd, iShowCmd) : WinExec16(cmd, iShowCmd);
449 else if (PathIsURLA((LPSTR)lpFile)) /* File not found, check for URL */
454 lpstrRes = strchr(lpFile, ':');
455 iSize = lpstrRes - lpFile;
457 TRACE("Got URL: %s\n", lpFile);
458 /* Looking for ...protocol\shell\lpOperation\command */
459 strncpy(lpstrProtocol, lpFile, iSize);
460 lpstrProtocol[iSize] = '\0';
461 strcat(lpstrProtocol, "\\shell\\");
462 strcat(lpstrProtocol, lpOperation);
463 strcat(lpstrProtocol, "\\command");
465 /* Remove File Protocol from lpFile */
466 /* In the case file://path/file */
467 if (!strncasecmp(lpFile, "file", iSize))
470 while (*lpFile == ':') lpFile++;
473 retval = execute_from_key(lpstrProtocol, lpFile, iShowCmd, is32);
475 /* Check if file specified is in the form www.??????.*** */
476 else if (!strncasecmp(lpFile, "www", 3))
478 /* if so, append lpFile http:// and call ShellExecute */
479 char lpstrTmpFile[256] = "http://" ;
480 strcat(lpstrTmpFile, lpFile);
481 retval = ShellExecuteA(hWnd, lpOperation, lpstrTmpFile, NULL, NULL, 0);
485 SetCurrentDirectoryA(old_dir);
489 /*************************************************************************
490 * FindExecutableA [SHELL32.@]
492 HINSTANCE WINAPI FindExecutableA(LPCSTR lpFile, LPCSTR lpDirectory, LPSTR lpResult)
494 HINSTANCE retval = 31; /* default - 'No association was found' */
497 TRACE("File %s, Dir %s\n",
498 (lpFile != NULL ? lpFile : "-"), (lpDirectory != NULL ? lpDirectory : "-"));
500 lpResult[0] = '\0'; /* Start off with an empty return string */
502 /* trap NULL parameters on entry */
503 if ((lpFile == NULL) || (lpResult == NULL))
505 /* FIXME - should throw a warning, perhaps! */
506 return 2; /* File not found. Close enough, I guess. */
511 GetCurrentDirectoryA(sizeof(old_dir), old_dir);
512 SetCurrentDirectoryA(lpDirectory);
515 retval = SHELL_FindExecutable(lpFile, "open", lpResult, NULL);
517 TRACE("returning %s\n", lpResult);
519 SetCurrentDirectoryA(old_dir);
523 /*************************************************************************
524 * FindExecutableW [SHELL32.@]
526 HINSTANCE WINAPI FindExecutableW(LPCWSTR lpFile, LPCWSTR lpDirectory, LPWSTR lpResult)
528 FIXME("(%p,%p,%p): stub\n", lpFile, lpDirectory, lpResult);
529 return 31; /* default - 'No association was found' */
532 /*************************************************************************
533 * ShellExecute [SHELL.20]
535 HINSTANCE16 WINAPI ShellExecute16( HWND16 hWnd, LPCSTR lpOperation,
536 LPCSTR lpFile, LPCSTR lpParameters,
537 LPCSTR lpDirectory, INT16 iShowCmd )
539 return (HINSTANCE16)SHELL_Execute(hWnd, lpOperation, lpFile,
540 lpParameters, lpDirectory, iShowCmd, FALSE );
543 /*************************************************************************
544 * ShellExecuteA [SHELL32.290]
546 HINSTANCE WINAPI ShellExecuteA(HWND hWnd, LPCSTR lpOperation,LPCSTR lpFile,
547 LPCSTR lpParameters,LPCSTR lpDirectory, INT iShowCmd)
550 return SHELL_Execute( hWnd, lpOperation, lpFile, lpParameters,
551 lpDirectory, iShowCmd, TRUE );
554 /*************************************************************************
555 * ShellExecuteW [SHELL32.294]
557 * WINSHELLAPI HINSTANCE APIENTRY ShellExecuteW(HWND hwnd, LPCWSTR lpOperation,
558 * LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd);
560 HINSTANCE WINAPI ShellExecuteW(HWND hwnd, LPCWSTR lpOperation, LPCWSTR lpFile,
561 LPCWSTR lpParameters, LPCWSTR lpDirectory, INT nShowCmd)