xref: /reactos/dll/win32/shell32/COpenWithMenu.cpp (revision 9c5efed7)
1 /*
2  *    Open With  Context Menu extension
3  *
4  * Copyright 2007 Johannes Anderwald <johannes.anderwald@reactos.org>
5  * Copyright 2009 Andrew Hill
6  * Copyright 2012 Rafal Harabien
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22 
23 #include "precomp.h"
24 
25 WINE_DEFAULT_DEBUG_CHANNEL(shell);
26 
27 //
28 // [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system]
29 // "NoInternetOpenWith"=dword:00000001
30 //
31 
32 EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath);
33 
34 static SIZE_T PathGetAppFromCommandLine(LPCWSTR pszIn, LPWSTR pszOut, SIZE_T cchMax)
35 {
36     SIZE_T count = 0;
37     WCHAR stop = ' ';
38     if (pszIn[0] == '"')
39         stop = *(pszIn++);
40 
41     for (LPCWSTR pwszSrc = pszIn; *pwszSrc && *pwszSrc != stop; ++pwszSrc)
42     {
43         if (++count >= cchMax)
44             return 0;
45         *(pszOut++) = *pwszSrc;
46     }
47     *pszOut = UNICODE_NULL;
48     return count;
49 }
50 
51 HRESULT SHELL32_GetDllFromRundll32CommandLine(LPCWSTR pszCmd, LPWSTR pszOut, SIZE_T cchMax)
52 {
53     WCHAR szDll[MAX_PATH + 100];
54     if (!PathGetAppFromCommandLine(pszCmd, szDll, _countof(szDll)))
55         return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
56 
57     PWSTR pszName = PathFindFileNameW(szDll);
58     if (_wcsicmp(pszName, L"rundll32") && _wcsicmp(pszName, L"rundll32.exe"))
59         return E_UNEXPECTED;
60 
61     PCWSTR pszDllStart = pszCmd + (pszName - szDll) + lstrlenW(pszName);
62 
63     if (*pszDllStart == '\"')
64         ++pszDllStart; // Skip possible end quote of ..\rundll32.exe" foo.dll,func
65     while (*pszDllStart <= ' ' && *pszDllStart)
66         ++pszDllStart;
67     if (PathGetAppFromCommandLine(pszDllStart, szDll, _countof(szDll)))
68     {
69         BOOL quoted = *pszDllStart == '\"';
70         PWSTR pszComma = szDll + lstrlenW(szDll);
71         while (!quoted && pszComma > szDll && *pszComma != ',' && *pszComma != '\\' && *pszComma != '/')
72             --pszComma;
73         SIZE_T cch = pszComma - szDll;
74         if (cch <= cchMax && (quoted || *pszComma == ','))
75         {
76             *pszComma = UNICODE_NULL;
77             lstrcpynW(pszOut, szDll, cchMax);
78             return S_OK;
79         }
80     }
81     return HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
82 }
83 
84 class COpenWithList
85 {
86     public:
87         struct SApp
88         {
89             WCHAR wszFilename[MAX_PATH];
90             WCHAR wszCmd[MAX_PATH];
91             //WCHAR wszManufacturer[256];
92             WCHAR wszName[256];
93             BOOL bHidden;
94             BOOL bRecommended;
95             BOOL bMRUList;
96             HICON hIcon;
97         };
98 
99         COpenWithList();
100         ~COpenWithList();
101 
102         BOOL Load();
103         SApp *Add(LPCWSTR pwszPath);
104         static BOOL SaveApp(SApp *pApp);
105         SApp *Find(LPCWSTR pwszFilename);
106         static LPCWSTR GetName(SApp *pApp);
107         static HICON GetIcon(SApp *pApp);
108         static BOOL Execute(SApp *pApp, LPCWSTR pwszFilePath);
109         static BOOL IsHidden(SApp *pApp);
110         inline BOOL IsNoOpen(VOID) { return m_bNoOpen; }
111         BOOL LoadRecommended(LPCWSTR pwszFilePath);
112         BOOL SetDefaultHandler(SApp *pApp, LPCWSTR pwszFilename);
113 
114         inline SApp *GetList() { return m_pApp; }
115         inline UINT GetCount() { return m_cApp; }
116         inline UINT GetRecommendedCount() { return m_cRecommended; }
117 
118     private:
119         typedef struct _LANGANDCODEPAGE
120         {
121             WORD lang;
122             WORD code;
123         } LANGANDCODEPAGE, *LPLANGANDCODEPAGE;
124 
125         SApp *m_pApp;
126         UINT m_cApp, m_cRecommended;
127         BOOL m_bNoOpen;
128 
129         SApp *AddInternal(LPCWSTR pwszFilename);
130         static BOOL LoadInfo(SApp *pApp);
131         static BOOL GetPathFromCmd(LPWSTR pwszAppPath, LPCWSTR pwszCmd);
132         BOOL LoadProgIdList(HKEY hKey, LPCWSTR pwszExt);
133         static HANDLE OpenMRUList(HKEY hKey);
134         BOOL LoadMRUList(HKEY hKey);
135         BOOL LoadAppList(HKEY hKey);
136         VOID LoadFromProgIdKey(HKEY hKey, LPCWSTR pwszExt);
137         VOID LoadRecommendedFromHKCR(LPCWSTR pwszExt);
138         VOID LoadRecommendedFromHKCU(LPCWSTR pwszExt);
139         static BOOL AddAppToMRUList(SApp *pApp, LPCWSTR pwszFilename);
140 
141         inline VOID SetRecommended(SApp *pApp)
142         {
143             if (!pApp->bRecommended)
144                 ++m_cRecommended;
145             pApp->bRecommended = TRUE;
146         }
147 };
148 
149 COpenWithList::COpenWithList():
150     m_pApp(NULL), m_cApp(0), m_cRecommended(0), m_bNoOpen(FALSE) {}
151 
152 COpenWithList::~COpenWithList()
153 {
154     for (UINT i = 0; i < m_cApp; ++i)
155         if (m_pApp[i].hIcon)
156             DestroyIcon(m_pApp[i].hIcon);
157 
158     HeapFree(GetProcessHeap(), 0, m_pApp);
159 }
160 
161 BOOL COpenWithList::Load()
162 {
163     HKEY hKey, hKeyApp;
164     WCHAR wszName[256], wszBuf[100];
165     DWORD i = 0, cchName, dwSize;
166     SApp *pApp;
167 
168     if (RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Applications", 0, KEY_READ, &hKey) != ERROR_SUCCESS)
169     {
170         ERR("RegOpenKeyEx HKCR\\Applications failed!\n");
171         return FALSE;
172     }
173 
174     while (TRUE)
175     {
176         cchName = _countof(wszName);
177         if (RegEnumKeyEx(hKey, i++, wszName, &cchName, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
178             break;
179 
180         pApp = AddInternal(wszName);
181 
182         if (pApp)
183         {
184             if (RegOpenKeyW(hKey, wszName, &hKeyApp) == ERROR_SUCCESS)
185             {
186                 if ((RegQueryValueExW(hKeyApp, L"NoOpenWith", NULL,  NULL, NULL, NULL) != ERROR_SUCCESS) &&
187                     (RegQueryValueExW(hKeyApp, L"NoStartPage", NULL,  NULL, NULL, NULL) != ERROR_SUCCESS))
188                 {
189                     StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s\\shell\\open\\command", wszName);
190                     dwSize = sizeof(pApp->wszCmd);
191                     if (RegGetValueW(hKey, wszBuf, L"", RRF_RT_REG_SZ, NULL, pApp->wszCmd, &dwSize) != ERROR_SUCCESS)
192                     {
193                         ERR("Failed to add app %ls\n", wszName);
194                         pApp->bHidden = TRUE;
195                     }
196                     else
197                     {
198                         TRACE("App added %ls\n", pApp->wszCmd);
199                     }
200                 }
201                 else
202                 {
203                     pApp->bHidden = TRUE;
204                 }
205                 RegCloseKey(hKeyApp);
206             }
207             else
208             {
209                 pApp->bHidden = TRUE;
210             }
211         }
212         else
213         {
214             ERR("AddInternal failed\n");
215         }
216     }
217 
218     RegCloseKey(hKey);
219     return TRUE;
220 }
221 
222 COpenWithList::SApp *COpenWithList::Add(LPCWSTR pwszPath)
223 {
224     SApp *pApp = AddInternal(PathFindFileNameW(pwszPath));
225 
226     if (pApp)
227     {
228         StringCbPrintfW(pApp->wszCmd, sizeof(pApp->wszCmd), L"\"%s\" \"%%1\"", pwszPath);
229         SaveApp(pApp);
230     }
231 
232     return pApp;
233 }
234 
235 BOOL COpenWithList::SaveApp(SApp *pApp)
236 {
237     WCHAR wszBuf[256];
238     HKEY hKey;
239 
240     StringCbPrintfW(wszBuf, sizeof(wszBuf), L"Applications\\%s\\shell\\open\\command", pApp->wszFilename);
241     if (RegCreateKeyEx(HKEY_CLASSES_ROOT, wszBuf, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
242     {
243         ERR("RegOpenKeyEx failed\n");
244         return FALSE;
245     }
246 
247     if (RegSetValueEx(hKey, L"", 0, REG_SZ, (PBYTE)pApp->wszCmd, (wcslen(pApp->wszCmd)+1)*sizeof(WCHAR)) != ERROR_SUCCESS)
248         ERR("Cannot add app to registry\n");
249 
250     RegCloseKey(hKey);
251     return TRUE;
252 }
253 
254 COpenWithList::SApp *COpenWithList::Find(LPCWSTR pwszFilename)
255 {
256     for (UINT i = 0; i < m_cApp; ++i)
257         if (_wcsicmp(m_pApp[i].wszFilename, pwszFilename) == 0)
258             return &m_pApp[i];
259     return NULL;
260 }
261 
262 LPCWSTR COpenWithList::GetName(SApp *pApp)
263 {
264     if (!pApp->wszName[0])
265     {
266         if (!LoadInfo(pApp))
267         {
268             WARN("Failed to load %ls info\n", pApp->wszFilename);
269             StringCbCopyW(pApp->wszName, sizeof(pApp->wszName), pApp->wszFilename);
270 
271             WCHAR wszPath[MAX_PATH];
272             if (!GetPathFromCmd(wszPath, pApp->wszCmd))
273             {
274                 return NULL;
275             }
276         }
277     }
278 
279     TRACE("%ls name: %ls\n", pApp->wszFilename, pApp->wszName);
280     return pApp->wszName;
281 }
282 
283 HICON COpenWithList::GetIcon(SApp *pApp)
284 {
285     if (!pApp->hIcon)
286     {
287         WCHAR wszPath[MAX_PATH];
288 
289         GetPathFromCmd(wszPath, pApp->wszCmd);
290         if (!ExtractIconExW(wszPath, 0, NULL, &pApp->hIcon, 1))
291         {
292             SHFILEINFO fi;
293             /* FIXME: Ideally we should include SHGFI_USEFILEATTRIBUTES because we already
294             ** know the file has no icons but SHGetFileInfo is broken in that case (CORE-19122).
295             ** Without SHGFI_USEFILEATTRIBUTES we needlessly hit the disk again but it will
296             ** return the correct default .exe icon.
297             */
298             SHGetFileInfoW(wszPath, 0, &fi, sizeof(fi), SHGFI_ICON|SHGFI_SMALLICON|SHGFI_SHELLICONSIZE);
299             pApp->hIcon = fi.hIcon;
300         }
301     }
302 
303     TRACE("%ls icon: %p\n", pApp->wszFilename, pApp->hIcon);
304 
305     return pApp->hIcon;
306 }
307 
308 BOOL COpenWithList::Execute(COpenWithList::SApp *pApp, LPCWSTR pwszFilePath)
309 {
310     WCHAR wszBuf[256];
311     HKEY hKey;
312 
313     /* Add app to registry if it wasnt there before */
314     SaveApp(pApp);
315     if (!pApp->bMRUList)
316         AddAppToMRUList(pApp, pwszFilePath);
317 
318     /* Get a handle to the reg key */
319     StringCbPrintfW(wszBuf, sizeof(wszBuf), L"Applications\\%s", pApp->wszFilename);
320     if (RegCreateKeyEx(HKEY_CLASSES_ROOT, wszBuf, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
321     {
322         ERR("RegOpenKeyEx failed\n");
323         return FALSE;
324     }
325 
326     /* Let ShellExecuteExW do the work */
327     SHELLEXECUTEINFOW sei = {sizeof(SHELLEXECUTEINFOW), SEE_MASK_CLASSKEY};
328     sei.nShow = SW_SHOWNORMAL;
329     sei.hkeyClass = hKey;
330     sei.lpFile = pwszFilePath;
331 
332     ShellExecuteExW(&sei);
333 
334     return TRUE;
335 }
336 
337 BOOL COpenWithList::IsHidden(SApp *pApp)
338 {
339     WCHAR wszBuf[100];
340     DWORD dwSize = 0;
341 
342     if (pApp->bHidden)
343         return pApp->bHidden;
344 
345     if (FAILED(StringCbPrintfW(wszBuf, sizeof(wszBuf), L"Applications\\%s", pApp->wszFilename)))
346     {
347         ERR("insufficient buffer\n");
348         return FALSE;
349     }
350 
351     if (RegGetValueW(HKEY_CLASSES_ROOT, wszBuf, L"NoOpenWith", RRF_RT_REG_SZ, NULL, NULL, &dwSize) != ERROR_SUCCESS)
352         return FALSE;
353 
354     pApp->bHidden = TRUE;
355     return TRUE;
356 }
357 
358 COpenWithList::SApp *COpenWithList::AddInternal(LPCWSTR pwszFilename)
359 {
360     /* Check for duplicate */
361     SApp *pApp = Find(pwszFilename);
362     if (pApp)
363         return pApp;
364 
365     /* Create new item */
366     if (!m_pApp)
367         m_pApp = static_cast<SApp *>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(m_pApp[0])));
368     else
369         m_pApp = static_cast<SApp *>(HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, m_pApp, (m_cApp + 1)*sizeof(m_pApp[0])));
370     if (!m_pApp)
371     {
372         ERR("Allocation failed\n");
373         return NULL;
374     }
375 
376     pApp = &m_pApp[m_cApp++];
377     wcscpy(pApp->wszFilename, pwszFilename);
378     return pApp;
379 }
380 
381 BOOL COpenWithList::LoadInfo(COpenWithList::SApp *pApp)
382 {
383     UINT cbSize, cchLen;
384     LPVOID pBuf;
385     WORD wLang = 0, wCode = 0;
386     LPLANGANDCODEPAGE lpLangCode;
387     WCHAR wszBuf[100];
388     WCHAR *pResult;
389     WCHAR wszPath[MAX_PATH];
390     BOOL success = FALSE;
391 
392     GetPathFromCmd(wszPath, pApp->wszCmd);
393     TRACE("LoadInfo %ls\n", wszPath);
394 
395     /* query version info size */
396     cbSize = GetFileVersionInfoSizeW(wszPath, NULL);
397     if (!cbSize)
398     {
399         ERR("GetFileVersionInfoSizeW %ls failed: %lu\n", wszPath, GetLastError());
400         return FALSE;
401     }
402 
403     /* allocate buffer */
404     pBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbSize + 200);
405     if (!pBuf)
406     {
407         ERR("HeapAlloc failed\n");
408         return FALSE;
409     }
410 
411     /* query version info */
412     if (!GetFileVersionInfoW(wszPath, 0, cbSize, pBuf))
413     {
414         ERR("GetFileVersionInfoW %ls failed: %lu\n", wszPath, GetLastError());
415         HeapFree(GetProcessHeap(), 0, pBuf);
416         return FALSE;
417     }
418 
419     /* query lang code */
420     if (VerQueryValueW(pBuf, L"VarFileInfo\\Translation", (LPVOID*)&lpLangCode, &cbSize))
421     {
422                 /* FIXME: find language from current locale / if not available,
423                  * default to english
424                  * for now default to first available language
425                  */
426                 wLang = lpLangCode->lang;
427                 wCode = lpLangCode->code;
428     }
429 
430     /* Query name */
431     swprintf(wszBuf, L"\\StringFileInfo\\%04x%04x\\FileDescription", wLang, wCode);
432     success = VerQueryValueW(pBuf, wszBuf, (LPVOID *)&pResult, &cchLen) && (cchLen > 1);
433     if (success)
434         StringCchCopyNW(pApp->wszName, _countof(pApp->wszName), pResult, cchLen);
435     else
436         ERR("Cannot get app name\n");
437 
438     /* Query manufacturer */
439     /*swprintf(wszBuf, L"\\StringFileInfo\\%04x%04x\\CompanyName", wLang, wCode);
440 
441     if (VerQueryValueW(pBuf, wszBuf, (LPVOID *)&pResult, &cchLen))
442         StringCchCopyNW(pApp->wszManufacturer, _countof(pApp->wszManufacturer), pResult, cchLen);*/
443     HeapFree(GetProcessHeap(), 0, pBuf);
444     return success;
445 }
446 
447 BOOL COpenWithList::GetPathFromCmd(LPWSTR pwszAppPath, LPCWSTR pwszCmd)
448 {
449     WCHAR wszBuf[MAX_PATH];
450 
451     /* Remove arguments */
452     if (!PathGetAppFromCommandLine(pwszCmd, wszBuf, _countof(wszBuf)))
453         return FALSE;
454 
455     /* Replace rundll32.exe with the dll path */
456     SHELL32_GetDllFromRundll32CommandLine(pwszCmd, wszBuf, _countof(wszBuf));
457 
458     /* Expand env. vars and optionally search for path */
459     ExpandEnvironmentStrings(wszBuf, pwszAppPath, MAX_PATH);
460     if (!PathFileExists(pwszAppPath))
461     {
462         UINT cch = SearchPathW(NULL, pwszAppPath, NULL, MAX_PATH, pwszAppPath, NULL);
463         if (!cch || cch >= MAX_PATH)
464             return FALSE;
465     }
466     return TRUE;
467 }
468 
469 BOOL COpenWithList::LoadRecommended(LPCWSTR pwszFilePath)
470 {
471     LPCWSTR pwszExt;
472 
473     pwszExt = PathFindExtensionW(pwszFilePath);
474     if (!pwszExt[0])
475         return FALSE;
476 
477     /* load programs directly associated from HKCU */
478     LoadRecommendedFromHKCU(pwszExt);
479 
480     /* load programs associated from HKCR\Extension */
481     LoadRecommendedFromHKCR(pwszExt);
482 
483     return TRUE;
484 }
485 
486 BOOL COpenWithList::LoadProgIdList(HKEY hKey, LPCWSTR pwszExt)
487 {
488     HKEY hSubkey, hSubkey2;
489     WCHAR wszProgId[256];
490     DWORD i = 0, cchProgId;
491 
492     if (RegOpenKeyExW(hKey, L"OpenWithProgIDs", 0, KEY_READ, &hSubkey) != ERROR_SUCCESS)
493         return FALSE;
494 
495     while (TRUE)
496     {
497         /* Enumerate values - value name is ProgId */
498         cchProgId = _countof(wszProgId);
499         if (RegEnumValue(hSubkey, i++, wszProgId, &cchProgId, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
500             break;
501 
502         /* If ProgId exists load it */
503         if (RegOpenKeyExW(HKEY_CLASSES_ROOT, wszProgId, 0, KEY_READ, &hSubkey2) == ERROR_SUCCESS)
504         {
505             LoadFromProgIdKey(hSubkey2, pwszExt);
506             RegCloseKey(hSubkey2);
507         }
508     }
509 
510     RegCloseKey(hSubkey);
511     return TRUE;
512 }
513 
514 HANDLE COpenWithList::OpenMRUList(HKEY hKey)
515 {
516     MRUINFOW Info;
517 
518     /* Initialize mru list info */
519     Info.cbSize = sizeof(Info);
520     Info.uMax = 32;
521     Info.fFlags = MRU_STRING;
522     Info.hKey = hKey;
523     Info.lpszSubKey = L"OpenWithList";
524     Info.lpfnCompare = NULL;
525 
526     return CreateMRUListW(&Info);
527 }
528 
529 BOOL COpenWithList::LoadMRUList(HKEY hKey)
530 {
531     HANDLE hList;
532     int nItem, nCount, nResult;
533     WCHAR wszAppFilename[MAX_PATH];
534 
535     /* Open MRU list */
536     hList = OpenMRUList(hKey);
537     if (!hList)
538     {
539         TRACE("OpenMRUList failed\n");
540         return FALSE;
541     }
542 
543     /* Get list count */
544     nCount = EnumMRUListW(hList, -1, NULL, 0);
545 
546     for(nItem = 0; nItem < nCount; nItem++)
547     {
548         nResult = EnumMRUListW(hList, nItem, wszAppFilename, _countof(wszAppFilename));
549         if (nResult <= 0)
550             continue;
551 
552         /* Insert item */
553         SApp *pApp = Find(wszAppFilename);
554 
555         TRACE("Recommended app %ls: %p\n", wszAppFilename, pApp);
556         if (pApp)
557         {
558             pApp->bMRUList = TRUE;
559             SetRecommended(pApp);
560         }
561     }
562 
563     /* Free the MRU list */
564     FreeMRUList(hList);
565     return TRUE;
566 }
567 
568 BOOL COpenWithList::LoadAppList(HKEY hKey)
569 {
570     WCHAR wszAppFilename[MAX_PATH];
571     HKEY hSubkey;
572     DWORD i = 0, cchAppFilename;
573 
574     if (RegOpenKeyExW(hKey, L"OpenWithList", 0, KEY_READ, &hSubkey) != ERROR_SUCCESS)
575         return FALSE;
576 
577     while (TRUE)
578     {
579         /* Enum registry keys - each of them is app name */
580         cchAppFilename = _countof(wszAppFilename);
581         if (RegEnumKeyExW(hSubkey, i++, wszAppFilename, &cchAppFilename, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
582             break;
583 
584         /* Set application as recommended */
585         SApp *pApp = Find(wszAppFilename);
586 
587         TRACE("Recommended app %ls: %p\n", wszAppFilename, pApp);
588         if (pApp)
589             SetRecommended(pApp);
590     }
591 
592     RegCloseKey(hSubkey);
593     return TRUE;
594 }
595 
596 VOID COpenWithList::LoadFromProgIdKey(HKEY hKey, LPCWSTR pwszExt)
597 {
598     WCHAR wszCmd[MAX_PATH], wszPath[MAX_PATH];
599     DWORD dwSize = 0;
600 
601     /* Check if NoOpen value exists */
602     if (RegGetValueW(hKey, NULL, L"NoOpen", RRF_RT_REG_SZ, NULL, NULL, &dwSize) == ERROR_SUCCESS)
603     {
604         /* Display warning dialog */
605         m_bNoOpen = TRUE;
606     }
607 
608     /* Check if there is a directly available execute key */
609     dwSize = sizeof(wszCmd);
610     if (RegGetValueW(hKey, L"shell\\open\\command", NULL, RRF_RT_REG_SZ, NULL, (PVOID)wszCmd, &dwSize) == ERROR_SUCCESS)
611     {
612         /* Erase extra arguments */
613         GetPathFromCmd(wszPath, wszCmd);
614 
615         /* Add application */
616         SApp *pApp = AddInternal(PathFindFileNameW(wszPath));
617         TRACE("Add app %ls: %p\n", wszPath, pApp);
618 
619         if (pApp)
620         {
621             StringCbCopyW(pApp->wszCmd, sizeof(pApp->wszCmd), wszCmd);
622             SetRecommended(pApp);
623         }
624     }
625 }
626 
627 VOID COpenWithList::LoadRecommendedFromHKCR(LPCWSTR pwszExt)
628 {
629     HKEY hKey, hSubkey;
630     WCHAR wszBuf[MAX_PATH], wszBuf2[MAX_PATH];
631     DWORD dwSize;
632 
633     /* Check if extension exists */
634     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, pwszExt, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
635     {
636         /* Load items from SystemFileAssociations\Ext key */
637         StringCbPrintfW(wszBuf, sizeof(wszBuf), L"SystemFileAssociations\\%s", pwszExt);
638         if (RegOpenKeyExW(HKEY_CLASSES_ROOT, wszBuf, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
639             return;
640     }
641 
642     /* Load programs referenced from HKCR\ProgId */
643     dwSize = sizeof(wszBuf);
644     if (RegGetValueW(hKey, NULL, L"", RRF_RT_REG_SZ, NULL, wszBuf, &dwSize) == ERROR_SUCCESS &&
645         RegOpenKeyExW(HKEY_CLASSES_ROOT, wszBuf, 0, KEY_READ, &hSubkey) == ERROR_SUCCESS)
646     {
647         LoadFromProgIdKey(hSubkey, pwszExt);
648         RegCloseKey(hSubkey);
649     }
650     else
651         LoadFromProgIdKey(hKey, pwszExt);
652 
653     /* Load items from HKCR\Ext\OpenWithList */
654     LoadAppList(hKey);
655 
656     /* Load items from HKCR\Ext\OpenWithProgIDs */
657     if (RegOpenKeyExW(hKey, L"OpenWithProgIDs", 0, KEY_READ, &hSubkey) == ERROR_SUCCESS)
658     {
659         LoadProgIdList(hSubkey, pwszExt);
660         RegCloseKey(hSubkey);
661     }
662 
663     /* Load additional items from referenced PerceivedType */
664     dwSize = sizeof(wszBuf);
665     if (RegGetValueW(hKey, NULL, L"PerceivedType", RRF_RT_REG_SZ, NULL, wszBuf, &dwSize) == ERROR_SUCCESS)
666     {
667         StringCbPrintfW(wszBuf2, sizeof(wszBuf2), L"SystemFileAssociations\\%s", wszBuf);
668         if (RegOpenKeyExW(HKEY_CLASSES_ROOT, wszBuf2, 0, KEY_READ | KEY_WRITE, &hSubkey) == ERROR_SUCCESS)
669         {
670             /* Load from OpenWithList key */
671             LoadAppList(hSubkey);
672             RegCloseKey(hSubkey);
673         }
674     }
675 
676     /* Close the key */
677     RegCloseKey(hKey);
678 }
679 
680 VOID COpenWithList::LoadRecommendedFromHKCU(LPCWSTR pwszExt)
681 {
682     WCHAR wszBuf[MAX_PATH];
683     HKEY hKey;
684 
685     StringCbPrintfW(wszBuf, sizeof(wszBuf),
686                     L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s",
687                     pwszExt);
688     if (RegOpenKeyExW(HKEY_CURRENT_USER, wszBuf, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
689     {
690         /* Load MRU and ProgId lists */
691         LoadMRUList(hKey);
692         LoadProgIdList(hKey, pwszExt);
693 
694         /* Handle "Application" value */
695         DWORD cbBuf = sizeof(wszBuf);
696         if (RegGetValueW(hKey, NULL, L"Application", RRF_RT_REG_SZ, NULL, wszBuf, &cbBuf) == ERROR_SUCCESS)
697         {
698             SApp *pApp = Find(wszBuf);
699             if (pApp)
700                 SetRecommended(pApp);
701         }
702 
703         /* Close the key */
704         RegCloseKey(hKey);
705     }
706 }
707 
708 BOOL COpenWithList::AddAppToMRUList(SApp *pApp, LPCWSTR pwszFilename)
709 {
710     WCHAR wszBuf[100];
711     LPCWSTR pwszExt;
712     HKEY hKey;
713     HANDLE hList;
714 
715     /* Get file extension */
716     pwszExt = PathFindExtensionW(pwszFilename);
717     if (!pwszExt[0])
718         return FALSE;
719 
720     /* Build registry key */
721     if (FAILED(StringCbPrintfW(wszBuf, sizeof(wszBuf),
722                                L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s",
723                                pwszExt)))
724     {
725         ERR("insufficient buffer\n");
726         return FALSE;
727     }
728 
729     /* Open base key for this file extension */
730     if (RegCreateKeyExW(HKEY_CURRENT_USER, wszBuf, 0, NULL, 0, KEY_WRITE | KEY_READ, NULL, &hKey, NULL) != ERROR_SUCCESS)
731         return FALSE;
732 
733     /* Open MRU list */
734     hList = OpenMRUList(hKey);
735     if (hList)
736     {
737         /* Insert the entry */
738         AddMRUStringW(hList, pApp->wszFilename);
739 
740         /* Set MRU presence */
741         pApp->bMRUList = TRUE;
742 
743         /* Close MRU list */
744         FreeMRUList(hList);
745     }
746 
747     RegCloseKey(hKey);
748     return TRUE;
749 }
750 
751 BOOL COpenWithList::SetDefaultHandler(SApp *pApp, LPCWSTR pwszFilename)
752 {
753     HKEY hKey, hSrcKey, hDestKey;
754     WCHAR wszBuf[256];
755 
756     TRACE("SetDefaultHandler %ls %ls\n", pApp->wszFilename, pwszFilename);
757 
758     /* Extract file extension */
759     LPCWSTR pwszExt = PathFindExtensionW(pwszFilename);
760     if (!pwszExt[0] || !pwszExt[1])
761         return FALSE;
762 
763     /* Create file extension key */
764     if (RegCreateKeyExW(HKEY_CLASSES_ROOT, pwszExt, 0, NULL, 0, KEY_READ|KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
765     {
766         ERR("Can't open ext key\n");
767         return FALSE;
768     }
769 
770     DWORD dwSize = sizeof(wszBuf);
771     LONG lResult = RegGetValueW(hKey, NULL, L"", RRF_RT_REG_SZ, NULL, wszBuf, &dwSize);
772 
773     if (lResult == ERROR_FILE_NOT_FOUND)
774     {
775         /* A new entry was created or the default key is not set: set the prog key id */
776         StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s_auto_file", pwszExt + 1);
777         if (RegSetValueExW(hKey, L"", 0, REG_SZ, (const BYTE*)wszBuf, (wcslen(wszBuf) + 1) * sizeof(WCHAR)) != ERROR_SUCCESS)
778         {
779             RegCloseKey(hKey);
780             ERR("RegSetValueExW failed\n");
781             return FALSE;
782         }
783     }
784     else if (lResult != ERROR_SUCCESS)
785     {
786         RegCloseKey(hKey);
787         ERR("RegGetValueExW failed: 0x%08x\n", lResult);
788         return FALSE;
789     }
790 
791     /* Close file extension key */
792     RegCloseKey(hKey);
793 
794     /* Create prog id key */
795     if (RegCreateKeyExW(HKEY_CLASSES_ROOT, wszBuf, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
796     {
797         ERR("RegCreateKeyExW failed\n");
798         return FALSE;
799     }
800 
801     /* Check if there already verbs existing for that app */
802     StringCbPrintfW(wszBuf, sizeof(wszBuf), L"Applications\\%s\\shell", pApp->wszFilename);
803     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, wszBuf, 0, KEY_READ, &hSrcKey) != ERROR_SUCCESS)
804     {
805         ERR("RegOpenKeyExW %ls failed\n", wszBuf);
806         RegCloseKey(hKey);
807         return FALSE;
808     }
809 
810     /* Open destination key */
811     if (RegCreateKeyExW(hKey, L"shell", 0, NULL, 0, KEY_WRITE, NULL, &hDestKey, NULL) != ERROR_SUCCESS)
812     {
813         ERR("RegCreateKeyExW failed\n");
814         RegCloseKey(hSrcKey);
815         RegCloseKey(hKey);
816         return FALSE;
817     }
818 
819     /* Copy static verbs from Classes\Applications key */
820     /* FIXME: SHCopyKey does not copy the security attributes of the keys */
821     /* FIXME: Windows does not actually copy the verb keys */
822     /* FIXME: Should probably delete any existing DelegateExecute/DropTarget/DDE verb information first */
823     LSTATUS Result = SHCopyKeyW(hSrcKey, NULL, hDestKey, 0);
824 #ifdef __REACTOS__
825     // FIXME: When OpenWith is used to set a new default on Windows, the FileExts key
826     // is changed to force this association. ROS does not support this. The best
827     // we can do is to try to set the verb we (incorrectly) copied as the new default.
828     HKEY hAppKey;
829     StringCbPrintfW(wszBuf, sizeof(wszBuf), L"Applications\\%s", pApp->wszFilename);
830     if (Result == ERROR_SUCCESS && !RegOpenKeyExW(HKEY_CLASSES_ROOT, wszBuf, 0, KEY_READ, &hAppKey))
831     {
832         if (HCR_GetDefaultVerbW(hAppKey, NULL, wszBuf, _countof(wszBuf)) && *wszBuf)
833             RegSetString(hDestKey, NULL, wszBuf, REG_SZ);
834         RegCloseKey(hAppKey);
835     }
836 #endif // __REACTOS__
837     RegCloseKey(hDestKey);
838     RegCloseKey(hSrcKey);
839     RegCloseKey(hKey);
840 
841     if (Result != ERROR_SUCCESS)
842     {
843         ERR("SHCopyKeyW failed\n");
844         return FALSE;
845     }
846 
847     return TRUE;
848 }
849 
850 class COpenWithDialog
851 {
852     public:
853         COpenWithDialog(const OPENASINFO *pInfo, COpenWithList *pAppList);
854         ~COpenWithDialog();
855         static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
856         BOOL IsNoOpen(HWND hwnd);
857 
858     private:
859         VOID Init(HWND hwnd);
860         VOID AddApp(COpenWithList::SApp *pApp, BOOL bSelected);
861         VOID Browse();
862         VOID Accept();
863         static INT_PTR CALLBACK NoOpenDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
864         COpenWithList::SApp *GetCurrentApp();
865 
866         const OPENASINFO *m_pInfo;
867         COpenWithList *m_pAppList;
868         BOOL m_bListAllocated;
869         HWND m_hDialog, m_hTreeView;
870         HTREEITEM m_hRecommend;
871         HTREEITEM m_hOther;
872         HIMAGELIST m_hImgList;
873         BOOL m_bNoOpen;
874 };
875 
876 COpenWithDialog::COpenWithDialog(const OPENASINFO *pInfo, COpenWithList *pAppList = NULL):
877     m_pInfo(pInfo), m_pAppList(pAppList), m_hImgList(NULL), m_bNoOpen(FALSE)
878 {
879     if (!m_pAppList)
880     {
881         m_pAppList = new COpenWithList;
882         m_bListAllocated = TRUE;
883     }
884     else
885         m_bListAllocated = FALSE;
886 }
887 
888 COpenWithDialog::~COpenWithDialog()
889 {
890     if (m_bListAllocated && m_pAppList)
891         delete m_pAppList;
892     if (m_hImgList)
893         ImageList_Destroy(m_hImgList);
894 }
895 
896 INT_PTR CALLBACK COpenWithDialog::NoOpenDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
897 {
898     switch(Message)
899     {
900         case WM_INITDIALOG:
901         {
902             return TRUE;
903         }
904         case WM_CLOSE:
905             EndDialog(hwnd, IDNO);
906             break;
907         case WM_COMMAND:
908             switch(LOWORD(wParam))
909             {
910                 case IDYES:
911                     EndDialog(hwnd, IDYES);
912                 break;
913                 case IDNO:
914                     EndDialog(hwnd, IDNO);
915                 break;
916             }
917         break;
918         default:
919             return FALSE;
920     }
921     return TRUE;
922 }
923 
924 BOOL COpenWithDialog::IsNoOpen(HWND hwnd)
925 {
926     /* Only do the actual check if the file type has the 'NoOpen' flag. */
927     if (m_bNoOpen)
928     {
929         int dReturnValue = DialogBox(shell32_hInstance, MAKEINTRESOURCE(IDD_NOOPEN), hwnd, NoOpenDlgProc);
930 
931         if (dReturnValue == IDNO)
932             return TRUE;
933         else if (dReturnValue == -1)
934         {
935             ERR("IsNoOpen failed to load dialog box\n");
936             return TRUE;
937         }
938     }
939 
940     return FALSE;
941 }
942 
943 VOID COpenWithDialog::AddApp(COpenWithList::SApp *pApp, BOOL bSelected)
944 {
945     LPCWSTR pwszName = m_pAppList->GetName(pApp);
946     if (!pwszName) return;
947     HICON hIcon = m_pAppList->GetIcon(pApp);
948 
949     TRACE("AddApp Cmd %ls Name %ls\n", pApp->wszCmd, pwszName);
950 
951     /* Add item to the list */
952     TVINSERTSTRUCT tvins;
953 
954     if (pApp->bRecommended)
955         tvins.hParent = tvins.hInsertAfter = m_hRecommend;
956     else
957         tvins.hParent = tvins.hInsertAfter = m_hOther;
958 
959     tvins.item.mask = TVIF_TEXT|TVIF_PARAM;
960     tvins.item.pszText = const_cast<LPWSTR>(pwszName);
961     tvins.item.lParam = (LPARAM)pApp;
962 
963     if (hIcon)
964     {
965         tvins.item.mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE;
966         tvins.item.iImage = tvins.item.iSelectedImage = ImageList_AddIcon(m_hImgList, hIcon);
967     }
968 
969     HTREEITEM hItem = TreeView_InsertItem(m_hTreeView, &tvins);
970 
971     if (bSelected)
972         TreeView_SelectItem(m_hTreeView, hItem);
973 }
974 
975 VOID COpenWithDialog::Browse()
976 {
977     WCHAR wszTitle[64];
978     WCHAR wszFilter[256];
979     WCHAR wszPath[MAX_PATH];
980     OPENFILENAMEW ofn;
981 
982     /* Initialize OPENFILENAMEW structure */
983     ZeroMemory(&ofn, sizeof(OPENFILENAMEW));
984     ofn.lStructSize  = sizeof(OPENFILENAMEW);
985     ofn.hInstance = shell32_hInstance;
986     ofn.hwndOwner = m_hDialog;
987     ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
988     ofn.nMaxFile = (sizeof(wszPath) / sizeof(WCHAR));
989     ofn.lpstrFile = wszPath;
990     ofn.lpstrInitialDir = L"%programfiles%";
991 
992     /* Init title */
993     if (LoadStringW(shell32_hInstance, IDS_OPEN_WITH, wszTitle, sizeof(wszTitle) / sizeof(WCHAR)))
994     {
995         ofn.lpstrTitle = wszTitle;
996         ofn.nMaxFileTitle = wcslen(wszTitle);
997     }
998 
999     /* Init the filter string */
1000     if (LoadStringW(shell32_hInstance, IDS_OPEN_WITH_FILTER, wszFilter, sizeof(wszFilter) / sizeof(WCHAR)))
1001         ofn.lpstrFilter = wszFilter;
1002     ZeroMemory(wszPath, sizeof(wszPath));
1003 
1004     /* Create OpenFile dialog */
1005     if (!GetOpenFileNameW(&ofn))
1006         return;
1007 
1008     /* Setup context for insert proc */
1009     COpenWithList::SApp *pApp = m_pAppList->Add(wszPath);
1010     AddApp(pApp, TRUE);
1011 }
1012 
1013 COpenWithList::SApp *COpenWithDialog::GetCurrentApp()
1014 {
1015     TVITEM tvi;
1016     tvi.hItem = TreeView_GetSelection(m_hTreeView);
1017     if (!tvi.hItem)
1018         return NULL;
1019 
1020     tvi.mask = TVIF_PARAM;
1021     if (!TreeView_GetItem(m_hTreeView, &tvi))
1022         return NULL;
1023 
1024     return (COpenWithList::SApp*)tvi.lParam;
1025 }
1026 
1027 VOID COpenWithDialog::Init(HWND hwnd)
1028 {
1029     TRACE("COpenWithDialog::Init hwnd %p\n", hwnd);
1030 
1031     m_hDialog = hwnd;
1032     SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)this);
1033 
1034     /* Handle register checkbox */
1035     HWND hRegisterCheckbox = GetDlgItem(hwnd, 14003);
1036     if (!(m_pInfo->oaifInFlags & OAIF_ALLOW_REGISTRATION))
1037         EnableWindow(hRegisterCheckbox, FALSE);
1038     if (m_pInfo->oaifInFlags & OAIF_FORCE_REGISTRATION)
1039         SendMessage(hRegisterCheckbox, BM_SETCHECK, BST_CHECKED, 0);
1040     if (m_pInfo->oaifInFlags & OAIF_HIDE_REGISTRATION)
1041         ShowWindow(hRegisterCheckbox, SW_HIDE);
1042 
1043     if (m_pInfo->pcszFile)
1044     {
1045         WCHAR wszBuf[MAX_PATH];
1046         UINT cchBuf;
1047 
1048         /* Add filename to label */
1049         cchBuf = GetDlgItemTextW(hwnd, 14001, wszBuf, _countof(wszBuf));
1050         StringCchCopyW(wszBuf + cchBuf, _countof(wszBuf) - cchBuf, PathFindFileNameW(m_pInfo->pcszFile));
1051         SetDlgItemTextW(hwnd, 14001, wszBuf);
1052 
1053         /* Load applications from registry */
1054         m_pAppList->Load();
1055         m_pAppList->LoadRecommended(m_pInfo->pcszFile);
1056 
1057         /* Determine if the type of file can be opened directly from the shell */
1058         if (m_pAppList->IsNoOpen() != FALSE)
1059             m_bNoOpen = TRUE;
1060 
1061         /* Init treeview */
1062         m_hTreeView = GetDlgItem(hwnd, 14002);
1063         m_hImgList = ImageList_Create(16, 16,  ILC_COLOR32 | ILC_MASK, m_pAppList->GetCount() + 1, m_pAppList->GetCount() + 1);
1064         (void)TreeView_SetImageList(m_hTreeView, m_hImgList, TVSIL_NORMAL);
1065 
1066         /* If there are some recommendations add parent nodes: Recommended and Others */
1067         UINT cRecommended = m_pAppList->GetRecommendedCount();
1068         if (cRecommended > 0)
1069         {
1070             TVINSERTSTRUCT tvins;
1071             HICON hFolderIcon;
1072 
1073             tvins.hParent = tvins.hInsertAfter = TVI_ROOT;
1074             tvins.item.mask = TVIF_TEXT|TVIF_STATE|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
1075             tvins.item.pszText = (LPWSTR)wszBuf;
1076             tvins.item.state = tvins.item.stateMask = TVIS_EXPANDED;
1077             hFolderIcon = (HICON)LoadImage(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_PROGRAMS_FOLDER), IMAGE_ICON, 0, 0, 0);
1078             tvins.item.iImage = tvins.item.iSelectedImage = ImageList_AddIcon(m_hImgList, hFolderIcon);
1079 
1080             LoadStringW(shell32_hInstance, IDS_OPEN_WITH_RECOMMENDED, wszBuf, _countof(wszBuf));
1081             m_hRecommend = TreeView_InsertItem(m_hTreeView, &tvins);
1082 
1083             LoadStringW(shell32_hInstance, IDS_OPEN_WITH_OTHER, wszBuf, _countof(wszBuf));
1084             m_hOther = TreeView_InsertItem(m_hTreeView, &tvins);
1085         }
1086         else
1087             m_hRecommend = m_hOther = TVI_ROOT;
1088 
1089         /* Add all applications */
1090         BOOL bNoAppSelected = TRUE;
1091         COpenWithList::SApp *pAppList = m_pAppList->GetList();
1092         for (UINT i = 0; i < m_pAppList->GetCount(); ++i)
1093         {
1094             if (!COpenWithList::IsHidden(&pAppList[i]))
1095             {
1096                 if (bNoAppSelected && (pAppList[i].bRecommended || !cRecommended))
1097                 {
1098                     AddApp(&pAppList[i], TRUE);
1099                     bNoAppSelected = FALSE;
1100                 }
1101                 else
1102                     AddApp(&pAppList[i], FALSE);
1103             }
1104         }
1105     }
1106 }
1107 
1108 VOID COpenWithDialog::Accept()
1109 {
1110     COpenWithList::SApp *pApp = GetCurrentApp();
1111     if (pApp)
1112     {
1113         /* Set programm as default handler */
1114         if (IsDlgButtonChecked(m_hDialog, 14003) == BST_CHECKED)
1115         {
1116             m_pAppList->SetDefaultHandler(pApp, m_pInfo->pcszFile);
1117             // FIXME: Update DefaultIcon registry
1118             SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSHNOWAIT, NULL, NULL);
1119         }
1120 
1121         /* Execute program */
1122         if (m_pInfo->oaifInFlags & OAIF_EXEC)
1123             m_pAppList->Execute(pApp, m_pInfo->pcszFile);
1124 
1125         EndDialog(m_hDialog, 1);
1126     }
1127 }
1128 
1129 INT_PTR CALLBACK COpenWithDialog::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1130 {
1131     COpenWithDialog *pThis = reinterpret_cast<COpenWithDialog *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
1132 
1133     switch(uMsg)
1134     {
1135         case WM_INITDIALOG:
1136         {
1137             COpenWithDialog *pThis = reinterpret_cast<COpenWithDialog *>(lParam);
1138 
1139             pThis->Init(hwndDlg);
1140             return TRUE;
1141         }
1142         case WM_COMMAND:
1143             switch(LOWORD(wParam))
1144             {
1145                 case 14004: /* browse */
1146                 {
1147                     pThis->Browse();
1148                     return TRUE;
1149                 }
1150                 case IDOK: /* ok */
1151                 {
1152                     pThis->Accept();
1153                     return TRUE;
1154                 }
1155                 case IDCANCEL: /* cancel */
1156                     EndDialog(hwndDlg, 0);
1157                     return TRUE;
1158                 default:
1159                     break;
1160             }
1161             break;
1162         case WM_NOTIFY:
1163              switch (((LPNMHDR)lParam)->code)
1164              {
1165                 case TVN_SELCHANGED:
1166                     EnableWindow(GetDlgItem(hwndDlg, IDOK), pThis->GetCurrentApp() ? TRUE : FALSE);
1167                     break;
1168                 case NM_DBLCLK:
1169                 case NM_RETURN:
1170                     pThis->Accept();
1171                     break;
1172              }
1173             break;
1174         case WM_CLOSE:
1175             EndDialog(hwndDlg, 0);
1176             return TRUE;
1177         default:
1178             break;
1179     }
1180     return FALSE;
1181 }
1182 
1183 COpenWithMenu::COpenWithMenu()
1184 {
1185     m_idCmdFirst = 0;
1186     m_idCmdLast = 0;
1187     m_pAppList = new COpenWithList;
1188 }
1189 
1190 COpenWithMenu::~COpenWithMenu()
1191 {
1192     TRACE("Destroying COpenWithMenu(%p)\n", this);
1193 
1194     if (m_hSubMenu)
1195     {
1196         INT Count, Index;
1197         MENUITEMINFOW mii;
1198 
1199         /* get item count */
1200         Count = GetMenuItemCount(m_hSubMenu);
1201         if (Count == -1)
1202             return;
1203 
1204         /* setup menuitem info */
1205         ZeroMemory(&mii, sizeof(mii));
1206         mii.cbSize = sizeof(mii);
1207         mii.fMask = MIIM_DATA | MIIM_FTYPE | MIIM_CHECKMARKS;
1208 
1209         for(Index = 0; Index < Count; Index++)
1210         {
1211             if (GetMenuItemInfoW(m_hSubMenu, Index, TRUE, &mii))
1212             {
1213                 if (mii.hbmpChecked)
1214                     DeleteObject(mii.hbmpChecked);
1215             }
1216         }
1217     }
1218 
1219     if (m_pAppList)
1220         delete m_pAppList;
1221 }
1222 
1223 HBITMAP COpenWithMenu::IconToBitmap(HICON hIcon)
1224 {
1225     HDC hdc, hdcScr;
1226     HBITMAP hbm, hbmOld;
1227     RECT rc;
1228 
1229     hdcScr = GetDC(NULL);
1230     hdc = CreateCompatibleDC(hdcScr);
1231     SetRect(&rc, 0, 0, GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK));
1232     hbm = CreateCompatibleBitmap(hdcScr, rc.right, rc.bottom);
1233     ReleaseDC(NULL, hdcScr);
1234 
1235     hbmOld = (HBITMAP)SelectObject(hdc, hbm);
1236     FillRect(hdc, &rc, (HBRUSH)(COLOR_MENU + 1));
1237     if (!DrawIconEx(hdc, 0, 0, hIcon, rc.right, rc.bottom, 0, NULL, DI_NORMAL))
1238         ERR("DrawIcon failed: %x\n", GetLastError());
1239     SelectObject(hdc, hbmOld);
1240 
1241     DeleteDC(hdc);
1242 
1243     return hbm;
1244 }
1245 
1246 VOID COpenWithMenu::AddChooseProgramItem()
1247 {
1248     MENUITEMINFOW mii;
1249     WCHAR wszBuf[128];
1250 
1251     ZeroMemory(&mii, sizeof(mii));
1252     mii.cbSize = sizeof(mii);
1253     mii.fMask = MIIM_TYPE | MIIM_ID;
1254     mii.fType = MFT_SEPARATOR;
1255     mii.wID = -1;
1256     InsertMenuItemW(m_hSubMenu, -1, TRUE, &mii);
1257 
1258     if (!LoadStringW(shell32_hInstance, IDS_OPEN_WITH_CHOOSE, wszBuf, _countof(wszBuf)))
1259     {
1260         ERR("Failed to load string\n");
1261         return;
1262     }
1263 
1264     mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
1265     mii.fType = MFT_STRING;
1266     mii.fState = MFS_ENABLED;
1267     mii.wID = m_idCmdLast;
1268     mii.dwTypeData = (LPWSTR)wszBuf;
1269     mii.cch = wcslen(wszBuf);
1270 
1271     InsertMenuItemW(m_hSubMenu, -1, TRUE, &mii);
1272 }
1273 
1274 VOID COpenWithMenu::AddApp(PVOID pApp)
1275 {
1276     MENUITEMINFOW mii;
1277     LPCWSTR pwszName = m_pAppList->GetName((COpenWithList::SApp*)pApp);
1278     if (!pwszName) return;
1279 
1280     ZeroMemory(&mii, sizeof(mii));
1281     mii.cbSize = sizeof(mii);
1282     mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA;
1283     mii.fType = MFT_STRING;
1284     mii.fState = MFS_ENABLED;
1285     mii.wID = m_idCmdLast;
1286     mii.dwTypeData = const_cast<LPWSTR>(pwszName);
1287     mii.dwItemData = (ULONG_PTR)pApp;
1288 
1289     HICON hIcon = m_pAppList->GetIcon((COpenWithList::SApp*)pApp);
1290     if (hIcon)
1291     {
1292         mii.fMask |= MIIM_CHECKMARKS;
1293         mii.hbmpChecked = mii.hbmpUnchecked = IconToBitmap(hIcon);
1294     }
1295 
1296     if (InsertMenuItemW(m_hSubMenu, -1, TRUE, &mii))
1297         m_idCmdLast++;
1298 }
1299 
1300 static const CMVERBMAP g_VerbMap[] =
1301 {
1302     { "openas", 0 },
1303     { NULL }
1304 };
1305 
1306 HRESULT WINAPI COpenWithMenu::QueryContextMenu(
1307     HMENU hMenu,
1308     UINT indexMenu,
1309     UINT idCmdFirst,
1310     UINT idCmdLast,
1311     UINT uFlags)
1312 {
1313     TRACE("hMenu %p indexMenu %u idFirst %u idLast %u uFlags %u\n", hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
1314 
1315     INT DefaultPos = GetMenuDefaultItem(hMenu, TRUE, 0);
1316 
1317     WCHAR wszName[100];
1318     UINT NameId = (DefaultPos == -1 ? IDS_OPEN : IDS_OPEN_WITH);
1319     if (!LoadStringW(shell32_hInstance, NameId, wszName, _countof(wszName)))
1320     {
1321         ERR("Failed to load string\n");
1322         return E_FAIL;
1323     }
1324 
1325     /* Init first cmd id and submenu */
1326     m_idCmdFirst = m_idCmdLast = idCmdFirst;
1327     m_hSubMenu = NULL;
1328 
1329     /* We can only be a submenu if we are not the default */
1330     if (DefaultPos != -1)
1331     {
1332         /* Load applications list */
1333         m_pAppList->Load();
1334         m_pAppList->LoadRecommended(m_wszPath);
1335 
1336         /* Create submenu only if there is more than one application and menu has a default item */
1337         if (m_pAppList->GetRecommendedCount() > 1)
1338         {
1339             m_hSubMenu = CreatePopupMenu();
1340 
1341             for(UINT i = 0; i < m_pAppList->GetCount(); ++i)
1342             {
1343                 COpenWithList::SApp *pApp = m_pAppList->GetList() + i;
1344                 if (pApp->bRecommended)
1345                     AddApp(pApp);
1346             }
1347 
1348             AddChooseProgramItem();
1349         }
1350     }
1351 
1352     /* Insert menu item */
1353     MENUITEMINFOW mii;
1354     ZeroMemory(&mii, sizeof(mii));
1355     mii.cbSize = sizeof(mii);
1356     mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE;
1357     if (m_hSubMenu)
1358     {
1359         mii.fMask |= MIIM_SUBMENU;
1360         mii.hSubMenu = m_hSubMenu;
1361         mii.wID = -1;
1362     }
1363     else
1364         mii.wID = m_idCmdLast;
1365 
1366     mii.fType = MFT_STRING;
1367     mii.dwTypeData = (LPWSTR)wszName;
1368     mii.fState = MFS_ENABLED;
1369     if (DefaultPos == -1)
1370     {
1371         mii.fState |= MFS_DEFAULT;
1372         indexMenu = 0;
1373     }
1374 
1375     if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
1376         return E_FAIL;
1377 
1378     return MAKE_HRESULT(SEVERITY_SUCCESS, 0, m_idCmdLast - m_idCmdFirst + 1);
1379 }
1380 
1381 HRESULT WINAPI
1382 COpenWithMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
1383 {
1384     const SIZE_T idChooseApp = m_idCmdLast;
1385     HRESULT hr = E_FAIL;
1386 
1387     TRACE("This %p idFirst %u idLast %u idCmd %u\n", this, m_idCmdFirst, m_idCmdLast, m_idCmdFirst + LOWORD(lpici->lpVerb));
1388 
1389     if (!IS_INTRESOURCE(lpici->lpVerb) && SHELL_MapContextMenuVerbToCmdId(lpici, g_VerbMap) == 0)
1390         goto DoChooseApp;
1391 
1392     if (IS_INTRESOURCE(lpici->lpVerb) && m_idCmdFirst + LOWORD(lpici->lpVerb) <= m_idCmdLast)
1393     {
1394         if (m_idCmdFirst + LOWORD(lpici->lpVerb) == idChooseApp)
1395         {
1396 DoChooseApp:
1397             OPENASINFO info;
1398             LPCWSTR pwszExt = PathFindExtensionW(m_wszPath);
1399 
1400             info.pcszFile = m_wszPath;
1401             info.oaifInFlags = OAIF_EXEC;
1402             if (pwszExt[0])
1403                 info.oaifInFlags |= OAIF_REGISTER_EXT | OAIF_ALLOW_REGISTRATION;
1404             info.pcszClass = NULL;
1405             hr = SHOpenWithDialog(lpici->hwnd, &info);
1406         }
1407         else
1408         {
1409             /* retrieve menu item info */
1410             MENUITEMINFOW mii;
1411             ZeroMemory(&mii, sizeof(mii));
1412             mii.cbSize = sizeof(mii);
1413             mii.fMask = MIIM_DATA | MIIM_FTYPE;
1414 
1415             if (GetMenuItemInfoW(m_hSubMenu, LOWORD(lpici->lpVerb), TRUE, &mii) && mii.dwItemData)
1416             {
1417                 /* launch item with specified app */
1418                 COpenWithList::SApp *pApp = (COpenWithList::SApp*)mii.dwItemData;
1419                 COpenWithList::Execute(pApp, m_wszPath);
1420                 hr = S_OK;
1421             }
1422         }
1423     }
1424 
1425     return hr;
1426 }
1427 
1428 HRESULT WINAPI
1429 COpenWithMenu::GetCommandString(UINT_PTR idCmd, UINT uType,
1430                                 UINT* pwReserved, LPSTR pszName, UINT cchMax )
1431 {
1432     TRACE("%p %lu %u %p %p %u\n", this,
1433           idCmd, uType, pwReserved, pszName, cchMax );
1434 
1435     const SIZE_T idChooseApp = m_idCmdLast;
1436     if (m_idCmdFirst + idCmd == idChooseApp)
1437         return SHELL_GetCommandStringImpl(0, uType, pszName, cchMax, g_VerbMap);
1438 
1439     return E_NOTIMPL;
1440 }
1441 
1442 HRESULT WINAPI COpenWithMenu::HandleMenuMsg(
1443     UINT uMsg,
1444     WPARAM wParam,
1445     LPARAM lParam)
1446 {
1447     TRACE("This %p uMsg %x\n", this, uMsg);
1448 
1449     return E_NOTIMPL;
1450 }
1451 
1452 HRESULT WINAPI
1453 COpenWithMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
1454                           IDataObject *pdtobj,
1455                           HKEY hkeyProgID)
1456 {
1457     LPCITEMIDLIST pidlFolder2;
1458     LPCITEMIDLIST pidlChild;
1459 
1460     TRACE("This %p\n", this);
1461 
1462     if (pdtobj == NULL)
1463         return E_INVALIDARG;
1464 
1465     CDataObjectHIDA pida(pdtobj);
1466     if (FAILED(pida.hr()))
1467     {
1468         ERR("pdtobj->GetData failed with 0x%x\n", pida.hr());
1469         return pida.hr();
1470     }
1471 
1472     ASSERT(pida->cidl >= 1);
1473 
1474     pidlFolder2 = HIDA_GetPIDLFolder(pida);
1475     pidlChild = HIDA_GetPIDLItem(pida, 0);
1476 
1477     if (!_ILIsValue(pidlChild))
1478     {
1479         TRACE("pidl is not a file\n");
1480         return E_FAIL;
1481     }
1482 
1483     CComHeapPtr<ITEMIDLIST> pidl(ILCombine(pidlFolder2, pidlChild));
1484     if (!pidl)
1485     {
1486         ERR("no mem\n");
1487         return E_OUTOFMEMORY;
1488     }
1489 
1490     if (!SHGetPathFromIDListW(pidl, m_wszPath))
1491     {
1492         ERR("SHGetPathFromIDListW failed\n");
1493         return E_FAIL;
1494     }
1495 
1496     TRACE("szPath %s\n", debugstr_w(m_wszPath));
1497 
1498     LPCWSTR pwszExt = PathFindExtensionW(m_wszPath);
1499     if (PathIsExeW(pwszExt) || !_wcsicmp(pwszExt, L".lnk"))
1500     {
1501         TRACE("file is a executable or shortcut\n");
1502         return E_FAIL;
1503     }
1504 
1505     return S_OK;
1506 }
1507 
1508 HRESULT WINAPI
1509 SHOpenWithDialog(HWND hwndParent, const OPENASINFO *poainfo)
1510 {
1511     INT_PTR ret;
1512 
1513     TRACE("SHOpenWithDialog hwndParent %p poainfo %p\n", hwndParent, poainfo);
1514 
1515     InitCommonControls();
1516 
1517     if (poainfo->pcszClass == NULL && poainfo->pcszFile == NULL)
1518         return E_FAIL;
1519 
1520     COpenWithDialog pDialog(poainfo);
1521 
1522     if (pDialog.IsNoOpen(hwndParent))
1523         return S_OK;
1524 
1525     ret = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCE(IDD_OPEN_WITH), hwndParent,
1526                           COpenWithDialog::DialogProc, (LPARAM)&pDialog);
1527 
1528     if (ret == (INT_PTR)-1)
1529     {
1530         ERR("Failed to create dialog: %u\n", GetLastError());
1531         return E_FAIL;
1532     }
1533 
1534     return S_OK;
1535 }
1536