xref: /reactos/dll/win32/shell32/dialogs/dialogs.cpp (revision f04935d8)
1 /*
2  *    common shell dialogs
3  *
4  * Copyright 2000 Juergen Schmied
5  * Copyright 2018 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  *
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.
11  *
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.
16  *
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include "precomp.h"
23 
24 typedef struct
25 {
26     HWND hwndOwner;
27     HICON hIcon;
28     LPCWSTR lpstrDirectory;
29     LPCWSTR lpstrTitle;
30     LPCWSTR lpstrDescription;
31     UINT uFlags;
32 } RUNFILEDLGPARAMS;
33 
34 typedef BOOL (WINAPI * LPFNOFN) (OPENFILENAMEW *);
35 
36 WINE_DEFAULT_DEBUG_CHANNEL(shell);
37 static INT_PTR CALLBACK RunDlgProc(HWND, UINT, WPARAM, LPARAM);
38 static void FillList(HWND, LPWSTR, UINT, BOOL);
39 
40 
41 /*************************************************************************
42  * PickIconDlg                    [SHELL32.62]
43  *
44  */
45 
46 typedef struct
47 {
48     HMODULE hLibrary;
49     HWND hDlgCtrl;
50     WCHAR szPath[MAX_PATH];
51     INT Index;
52     INT nIcons;
53     HICON *phIcons;
54 } PICK_ICON_CONTEXT, *PPICK_ICON_CONTEXT;
55 
56 BOOL CALLBACK EnumPickIconResourceProc(HMODULE hModule,
57     LPCWSTR lpszType,
58     LPWSTR lpszName,
59     LONG_PTR lParam)
60 {
61     PPICK_ICON_CONTEXT pIconContext = PPICK_ICON_CONTEXT(lParam);
62     HWND hDlgCtrl = pIconContext->hDlgCtrl;
63 
64     if (IS_INTRESOURCE(lpszName))
65         lParam = LOWORD(lpszName);
66     else
67         lParam = -1;
68 
69     SendMessageW(hDlgCtrl, LB_ADDSTRING, 0, lParam);
70 
71     return TRUE;
72 }
73 
74 static void
75 DestroyIconList(HWND hDlgCtrl, PPICK_ICON_CONTEXT pIconContext)
76 {
77     int count;
78     int index;
79 
80     count = SendMessageW(hDlgCtrl, LB_GETCOUNT, 0, 0);
81     if (count == LB_ERR)
82         return;
83 
84     for(index = 0; index < count; index++)
85     {
86         DestroyIcon(pIconContext->phIcons[index]);
87         pIconContext->phIcons[index] = NULL;
88     }
89 }
90 
91 static BOOL
92 DoLoadIcons(HWND hwndDlg, PPICK_ICON_CONTEXT pIconContext, LPCWSTR pszFile)
93 {
94     WCHAR szExpandedPath[MAX_PATH];
95 
96     // Destroy previous icons
97     DestroyIconList(pIconContext->hDlgCtrl, pIconContext);
98     SendMessageW(pIconContext->hDlgCtrl, LB_RESETCONTENT, 0, 0);
99     delete[] pIconContext->phIcons;
100 
101     // Store the path
102     StringCchCopyW(pIconContext->szPath, _countof(pIconContext->szPath), pszFile);
103     ExpandEnvironmentStringsW(pszFile, szExpandedPath, _countof(szExpandedPath));
104 
105     // Load the module if possible
106     HMODULE hLibrary = LoadLibraryExW(szExpandedPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
107     if (pIconContext->hLibrary)
108         FreeLibrary(pIconContext->hLibrary);
109     pIconContext->hLibrary = hLibrary;
110 
111     if (pIconContext->hLibrary)
112     {
113         // Load the icons from the module
114         pIconContext->nIcons = ExtractIconExW(szExpandedPath, -1, NULL, NULL, 0);
115         pIconContext->phIcons = new HICON[pIconContext->nIcons];
116 
117         if (ExtractIconExW(szExpandedPath, 0, pIconContext->phIcons, NULL, pIconContext->nIcons))
118         {
119             EnumResourceNamesW(pIconContext->hLibrary, RT_GROUP_ICON, EnumPickIconResourceProc, (LPARAM)pIconContext);
120         }
121         else
122         {
123             pIconContext->nIcons = 0;
124         }
125     }
126     else
127     {
128         // .ico file
129         pIconContext->nIcons = 1;
130         pIconContext->phIcons = new HICON[1];
131 
132         if (ExtractIconExW(szExpandedPath, 0, pIconContext->phIcons, NULL, pIconContext->nIcons))
133         {
134             SendMessageW(pIconContext->hDlgCtrl, LB_ADDSTRING, 0, 0);
135         }
136         else
137         {
138             pIconContext->nIcons = 0;
139         }
140     }
141 
142     // Set the text and reset the edit control's modification flag
143     SetDlgItemTextW(hwndDlg, IDC_EDIT_PATH, pIconContext->szPath);
144     SendDlgItemMessage(hwndDlg, IDC_EDIT_PATH, EM_SETMODIFY, FALSE, 0);
145 
146     if (pIconContext->nIcons == 0)
147     {
148         delete[] pIconContext->phIcons;
149         pIconContext->phIcons = NULL;
150     }
151 
152     return (pIconContext->nIcons > 0);
153 }
154 
155 static const LPCWSTR s_pszDefaultPath = L"%SystemRoot%\\system32\\shell32.dll";
156 
157 static void NoIconsInFile(HWND hwndDlg, PPICK_ICON_CONTEXT pIconContext)
158 {
159     // Show an error message
160     CStringW strText, strTitle(MAKEINTRESOURCEW(IDS_PICK_ICON_TITLE));
161     strText.Format(IDS_NO_ICONS, pIconContext->szPath);
162     MessageBoxW(hwndDlg, strText, strTitle, MB_ICONWARNING);
163 
164     // Load the default icons
165     DoLoadIcons(hwndDlg, pIconContext, s_pszDefaultPath);
166 }
167 
168 // Icon size
169 #define CX_ICON     GetSystemMetrics(SM_CXICON)
170 #define CY_ICON     GetSystemMetrics(SM_CYICON)
171 
172 // Item size
173 #define CX_ITEM     (CX_ICON + 4)
174 #define CY_ITEM     (CY_ICON + 12)
175 
176 INT_PTR CALLBACK PickIconProc(
177     HWND hwndDlg,
178     UINT uMsg,
179     WPARAM wParam,
180     LPARAM lParam)
181 {
182     LPMEASUREITEMSTRUCT lpmis;
183     LPDRAWITEMSTRUCT lpdis;
184     HICON hIcon;
185     INT index, count;
186     WCHAR szText[MAX_PATH], szFilter[100];
187     CStringW strTitle;
188     OPENFILENAMEW ofn;
189 
190     PPICK_ICON_CONTEXT pIconContext = (PPICK_ICON_CONTEXT)GetWindowLongPtr(hwndDlg, DWLP_USER);
191 
192     switch(uMsg)
193     {
194         case WM_INITDIALOG:
195         {
196             pIconContext = (PPICK_ICON_CONTEXT)lParam;
197             SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pIconContext);
198             pIconContext->hDlgCtrl = GetDlgItem(hwndDlg, IDC_PICKICON_LIST);
199 
200             SendMessageW(pIconContext->hDlgCtrl, LB_SETCOLUMNWIDTH, CX_ITEM, 0);
201 
202             // Load the icons
203             if (!DoLoadIcons(hwndDlg, pIconContext, pIconContext->szPath))
204                 NoIconsInFile(hwndDlg, pIconContext);
205 
206             // Set the selection
207             count = SendMessageW(pIconContext->hDlgCtrl, LB_GETCOUNT, 0, 0);
208             if (count != LB_ERR)
209             {
210                 if (pIconContext->Index < 0)
211                 {
212                     // A negative value will be interpreted as a negated resource ID.
213                     LPARAM lParam = -pIconContext->Index;
214                     pIconContext->Index = (INT)SendMessageW(pIconContext->hDlgCtrl, LB_FINDSTRINGEXACT, -1, lParam);
215                 }
216 
217                 if (pIconContext->Index < 0 || count <= pIconContext->Index)
218                     pIconContext->Index = 0;
219 
220                 SendMessageW(pIconContext->hDlgCtrl, LB_SETCURSEL, pIconContext->Index, 0);
221                 SendMessageW(pIconContext->hDlgCtrl, LB_SETTOPINDEX, pIconContext->Index, 0);
222             }
223             return TRUE;
224         }
225 
226         case WM_DESTROY:
227         {
228             DestroyIconList(pIconContext->hDlgCtrl, pIconContext);
229             delete[] pIconContext->phIcons;
230 
231             if (pIconContext->hLibrary)
232                 FreeLibrary(pIconContext->hLibrary);
233             break;
234         }
235 
236         case WM_COMMAND:
237             switch(LOWORD(wParam))
238             {
239             case IDOK:
240             {
241                 /* Check whether the path edit control has been modified; if so load the icons instead of validating */
242                 if (SendDlgItemMessage(hwndDlg, IDC_EDIT_PATH, EM_GETMODIFY, 0, 0))
243                 {
244                     /* Reset the edit control's modification flag and retrieve the text */
245                     SendDlgItemMessage(hwndDlg, IDC_EDIT_PATH, EM_SETMODIFY, FALSE, 0);
246                     GetDlgItemTextW(hwndDlg, IDC_EDIT_PATH, szText, _countof(szText));
247 
248                     // Load the icons
249                     if (!DoLoadIcons(hwndDlg, pIconContext, szText))
250                         NoIconsInFile(hwndDlg, pIconContext);
251 
252                     // Set the selection
253                     SendMessageW(pIconContext->hDlgCtrl, LB_SETCURSEL, 0, 0);
254                     break;
255                 }
256 
257                 /* The path edit control has not been modified, return the selection */
258                 pIconContext->Index = (INT)SendMessageW(pIconContext->hDlgCtrl, LB_GETCURSEL, 0, 0);
259                 GetDlgItemTextW(hwndDlg, IDC_EDIT_PATH, pIconContext->szPath, _countof(pIconContext->szPath));
260                 EndDialog(hwndDlg, 1);
261                 break;
262             }
263 
264             case IDCANCEL:
265                 EndDialog(hwndDlg, 0);
266                 break;
267 
268             case IDC_PICKICON_LIST:
269                 switch (HIWORD(wParam))
270                 {
271                     case LBN_SELCHANGE:
272                         InvalidateRect((HWND)lParam, NULL, TRUE);
273                         break;
274 
275                     case LBN_DBLCLK:
276                         SendMessage(hwndDlg, WM_COMMAND, MAKEWPARAM(IDOK, 0), 0);
277                         break;
278                 }
279                 break;
280 
281             case IDC_BUTTON_PATH:
282             {
283                 // Choose the module path
284                 szText[0] = 0;
285                 szFilter[0] = 0;
286                 ZeroMemory(&ofn, sizeof(ofn));
287                 ofn.lStructSize = sizeof(ofn);
288                 ofn.hwndOwner = hwndDlg;
289                 ofn.lpstrFile = szText;
290                 ofn.nMaxFile = _countof(szText);
291                 strTitle.LoadString(IDS_PICK_ICON_TITLE);
292                 ofn.lpstrTitle = strTitle;
293                 LoadStringW(shell32_hInstance, IDS_PICK_ICON_FILTER, szFilter, _countof(szFilter));
294                 ofn.lpstrFilter = szFilter;
295                 if (!GetOpenFileNameW(&ofn))
296                     break;
297 
298                 // Load the icons
299                 if (!DoLoadIcons(hwndDlg, pIconContext, szText))
300                     NoIconsInFile(hwndDlg, pIconContext);
301 
302                 // Set the selection
303                 SendMessageW(pIconContext->hDlgCtrl, LB_SETCURSEL, 0, 0);
304                 break;
305             }
306 
307             default:
308                 break;
309             }
310             break;
311 
312         case WM_MEASUREITEM:
313             lpmis = (LPMEASUREITEMSTRUCT)lParam;
314             lpmis->itemHeight = CY_ITEM;
315             return TRUE;
316 
317         case WM_DRAWITEM:
318         {
319             lpdis = (LPDRAWITEMSTRUCT)lParam;
320             if (lpdis->itemID == (UINT)-1)
321                 break;
322             switch (lpdis->itemAction)
323             {
324                 case ODA_SELECT:
325                 case ODA_DRAWENTIRE:
326                 {
327                     index = SendMessageW(pIconContext->hDlgCtrl, LB_GETCURSEL, 0, 0);
328                     hIcon = pIconContext->phIcons[lpdis->itemID];
329 
330                     if (lpdis->itemID == (UINT)index)
331                         FillRect(lpdis->hDC, &lpdis->rcItem, (HBRUSH)(COLOR_HIGHLIGHT + 1));
332                     else
333                         FillRect(lpdis->hDC, &lpdis->rcItem, (HBRUSH)(COLOR_WINDOW + 1));
334 
335                     // Centering
336                     INT x = lpdis->rcItem.left + (CX_ITEM - CX_ICON) / 2;
337                     INT y = lpdis->rcItem.top + (CY_ITEM - CY_ICON) / 2;
338 
339                     DrawIconEx(lpdis->hDC, x, y, hIcon, 0, 0, 0, NULL, DI_NORMAL);
340                     break;
341                 }
342             }
343             return TRUE;
344         }
345     }
346 
347     return FALSE;
348 }
349 
350 BOOL WINAPI PickIconDlg(
351     HWND hWndOwner,
352     LPWSTR lpstrFile,
353     UINT nMaxFile,
354     INT* lpdwIconIndex)
355 {
356     int res;
357     WCHAR szExpandedPath[MAX_PATH];
358 
359     // Initialize the dialog
360     PICK_ICON_CONTEXT IconContext = { NULL };
361     IconContext.Index = *lpdwIconIndex;
362     StringCchCopyW(IconContext.szPath, _countof(IconContext.szPath), lpstrFile);
363     ExpandEnvironmentStringsW(lpstrFile, szExpandedPath, _countof(szExpandedPath));
364 
365     if (!szExpandedPath[0] ||
366         GetFileAttributesW(szExpandedPath) == INVALID_FILE_ATTRIBUTES)
367     {
368         if (szExpandedPath[0])
369         {
370             // No such file
371             CStringW strText, strTitle(MAKEINTRESOURCEW(IDS_PICK_ICON_TITLE));
372             strText.Format(IDS_FILE_NOT_FOUND, lpstrFile);
373             MessageBoxW(hWndOwner, strText, strTitle, MB_ICONWARNING);
374         }
375 
376         // Set the default value
377         StringCchCopyW(IconContext.szPath, _countof(IconContext.szPath), s_pszDefaultPath);
378     }
379 
380     // Show the dialog
381     res = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_PICK_ICON), hWndOwner, PickIconProc, (LPARAM)&IconContext);
382     if (res)
383     {
384         // Store the selected icon
385         StringCchCopyW(lpstrFile, nMaxFile, IconContext.szPath);
386         *lpdwIconIndex = IconContext.Index;
387     }
388 
389     return res;
390 }
391 
392 /*************************************************************************
393  * RunFileDlg                    [internal]
394  *
395  * The Unicode function that is available as ordinal 61 on Windows NT/2000/XP/...
396  */
397 void WINAPI RunFileDlg(
398     HWND hWndOwner,
399     HICON hIcon,
400     LPCWSTR lpstrDirectory,
401     LPCWSTR lpstrTitle,
402     LPCWSTR lpstrDescription,
403     UINT uFlags)
404 {
405     TRACE("\n");
406 
407     RUNFILEDLGPARAMS rfdp;
408     rfdp.hwndOwner        = hWndOwner;
409     rfdp.hIcon            = hIcon;
410     rfdp.lpstrDirectory   = lpstrDirectory;
411     rfdp.lpstrTitle       = lpstrTitle;
412     rfdp.lpstrDescription = lpstrDescription;
413     rfdp.uFlags           = uFlags;
414 
415     DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_RUN), hWndOwner, RunDlgProc, (LPARAM)&rfdp);
416 }
417 
418 
419 /* find the directory that contains the file being run */
420 static LPWSTR RunDlg_GetParentDir(LPCWSTR cmdline)
421 {
422     const WCHAR *src;
423     WCHAR *dest, *result, *result_end=NULL;
424     static const WCHAR dotexeW[] = L".exe";
425 
426     result = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*(strlenW(cmdline)+5));
427 
428     if (NULL == result)
429     {
430         TRACE("HeapAlloc couldn't allocate %d bytes\n", sizeof(WCHAR)*(strlenW(cmdline)+5));
431         return NULL;
432     }
433 
434     src = cmdline;
435     dest = result;
436 
437     if (*src == '"')
438     {
439         src++;
440         while (*src && *src != '"')
441         {
442             if (*src == '\\')
443                 result_end = dest;
444             *dest++ = *src++;
445         }
446     }
447     else {
448         while (*src)
449         {
450             if (isspaceW(*src))
451             {
452                 *dest = 0;
453                 if (INVALID_FILE_ATTRIBUTES != GetFileAttributesW(result))
454                     break;
455                 strcatW(dest, dotexeW);
456                 if (INVALID_FILE_ATTRIBUTES != GetFileAttributesW(result))
457                     break;
458             }
459             else if (*src == '\\')
460                 result_end = dest;
461             *dest++ = *src++;
462         }
463     }
464 
465     if (result_end)
466     {
467         *result_end = 0;
468         return result;
469     }
470     else
471     {
472         HeapFree(GetProcessHeap(), 0, result);
473         return NULL;
474     }
475 }
476 
477 static void EnableOkButtonFromEditContents(HWND hwnd)
478 {
479     BOOL Enable = FALSE;
480     INT Length, n;
481     HWND Edit = GetDlgItem(hwnd, IDC_RUNDLG_EDITPATH);
482     Length = GetWindowTextLengthW(Edit);
483     if (Length > 0)
484     {
485         PWCHAR psz = (PWCHAR)HeapAlloc(GetProcessHeap(), 0, (Length + 1) * sizeof(WCHAR));
486         if (psz)
487         {
488             GetWindowTextW(Edit, psz, Length + 1);
489             for (n = 0; n < Length && !Enable; ++n)
490                 Enable = psz[n] != ' ';
491             HeapFree(GetProcessHeap(), 0, psz);
492         }
493         else
494             Enable = TRUE;
495     }
496     EnableWindow(GetDlgItem(hwnd, IDOK), Enable);
497 }
498 
499 /* Dialog procedure for RunFileDlg */
500 static INT_PTR CALLBACK RunDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
501 {
502     RUNFILEDLGPARAMS *prfdp = (RUNFILEDLGPARAMS *)GetWindowLongPtrW(hwnd, DWLP_USER);
503 
504     switch (message)
505     {
506         case WM_INITDIALOG:
507             prfdp = (RUNFILEDLGPARAMS *)lParam;
508             SetWindowLongPtrW(hwnd, DWLP_USER, (LONG_PTR)prfdp);
509 
510             if (prfdp->lpstrTitle)
511                 SetWindowTextW(hwnd, prfdp->lpstrTitle);
512             if (prfdp->lpstrDescription)
513                 SetWindowTextW(GetDlgItem(hwnd, IDC_RUNDLG_DESCRIPTION), prfdp->lpstrDescription);
514             if (prfdp->uFlags & RFF_NOBROWSE)
515             {
516                 HWND browse = GetDlgItem(hwnd, IDC_RUNDLG_BROWSE);
517                 ShowWindow(browse, SW_HIDE);
518                 EnableWindow(browse, FALSE);
519             }
520             if (prfdp->uFlags & RFF_NOLABEL)
521                 ShowWindow(GetDlgItem(hwnd, IDC_RUNDLG_LABEL), SW_HIDE);
522             if (prfdp->uFlags & RFF_NOSEPARATEMEM)
523             {
524                 FIXME("RFF_NOSEPARATEMEM not supported\n");
525             }
526 
527             /* Use the default Shell Run icon if no one is specified */
528             if (prfdp->hIcon == NULL)
529                 prfdp->hIcon = LoadIconW(shell32_hInstance, MAKEINTRESOURCEW(IDI_SHELL_RUN));
530             /*
531              * NOTE: Starting Windows Vista, the "Run File" dialog gets a
532              * title icon that remains the same as the default one, even if
533              * the user specifies a custom icon.
534              * Since we currently imitate Windows 2003, therefore do not show
535              * any title icon.
536              */
537             // SendMessageW(hwnd, WM_SETICON, ICON_BIG, (LPARAM)prfdp->hIcon);
538             // SendMessageW(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)prfdp->hIcon);
539             SendMessageW(GetDlgItem(hwnd, IDC_RUNDLG_ICON), STM_SETICON, (WPARAM)prfdp->hIcon, 0);
540 
541             FillList(GetDlgItem(hwnd, IDC_RUNDLG_EDITPATH), NULL, 0, (prfdp->uFlags & RFF_NODEFAULT) == 0);
542             EnableOkButtonFromEditContents(hwnd);
543             SetFocus(GetDlgItem(hwnd, IDC_RUNDLG_EDITPATH));
544             return TRUE;
545 
546         case WM_COMMAND:
547             switch (LOWORD(wParam))
548             {
549                 case IDOK:
550                 {
551                     LRESULT lRet;
552                     HWND htxt = GetDlgItem(hwnd, IDC_RUNDLG_EDITPATH);
553                     INT ic;
554                     WCHAR *psz, *pszExpanded, *parent = NULL;
555                     DWORD cchExpand;
556                     NMRUNFILEDLGW nmrfd;
557 
558                     ic = GetWindowTextLengthW(htxt);
559                     if (ic == 0)
560                     {
561                         EndDialog(hwnd, IDCANCEL);
562                         return TRUE;
563                     }
564 
565                     /*
566                      * Allocate a new MRU entry, we need to add two characters
567                      * for the terminating "\\1" part, then the NULL character.
568                      */
569                     psz = (WCHAR*)HeapAlloc(GetProcessHeap(), 0, (ic + 2 + 1)*sizeof(WCHAR));
570                     if (!psz)
571                     {
572                         EndDialog(hwnd, IDCANCEL);
573                         return TRUE;
574                     }
575 
576                     GetWindowTextW(htxt, psz, ic + 1);
577                     StrTrimW(psz, L" \t");
578 
579                     if (wcschr(psz, L'%') != NULL)
580                     {
581                         cchExpand = ExpandEnvironmentStringsW(psz, NULL, 0);
582                         pszExpanded = (WCHAR*)HeapAlloc(GetProcessHeap(), 0, cchExpand * sizeof(WCHAR));
583                         if (!pszExpanded)
584                         {
585                             HeapFree(GetProcessHeap(), 0, psz);
586                             EndDialog(hwnd, IDCANCEL);
587                             return TRUE;
588                         }
589                         ExpandEnvironmentStringsW(psz, pszExpanded, cchExpand);
590                         StrTrimW(pszExpanded, L" \t");
591                     }
592                     else
593                     {
594                         pszExpanded = psz;
595                     }
596 
597                     /*
598                      * The precedence is the following: first the user-given
599                      * current directory is used; if there is none, a current
600                      * directory is computed if the RFF_CALCDIRECTORY is set,
601                      * otherwise no current directory is defined.
602                      */
603                     LPCWSTR pszStartDir;
604                     if (prfdp->lpstrDirectory)
605                         pszStartDir = prfdp->lpstrDirectory;
606                     else if (prfdp->uFlags & RFF_CALCDIRECTORY)
607                         pszStartDir = parent = RunDlg_GetParentDir(pszExpanded);
608                     else
609                         pszStartDir = NULL;
610 
611                     /* Hide the dialog for now on, we will show it up in case of retry */
612                     ShowWindow(hwnd, SW_HIDE);
613 
614                     /*
615                      * As shown by manual tests on Windows, modifying the contents
616                      * of the notification structure will not modify what the
617                      * Run-Dialog will use for the nShow parameter. However the
618                      * lpFile and lpDirectory pointers are set to the buffers used
619                      * by the Run-Dialog, as a consequence they can be modified by
620                      * the notification receiver, as long as it respects the lengths
621                      * of the buffers (to avoid buffer overflows).
622                      */
623                     nmrfd.hdr.code = RFN_VALIDATE;
624                     nmrfd.hdr.hwndFrom = hwnd;
625                     nmrfd.hdr.idFrom = 0;
626                     nmrfd.lpFile = pszExpanded;
627                     nmrfd.lpDirectory = pszStartDir;
628                     nmrfd.nShow = SW_SHOWNORMAL;
629 
630                     lRet = SendMessageW(prfdp->hwndOwner, WM_NOTIFY, 0, (LPARAM)&nmrfd.hdr);
631 
632                     switch (lRet)
633                     {
634                         case RF_CANCEL:
635                             EndDialog(hwnd, IDCANCEL);
636                             break;
637 
638                         case RF_OK:
639                             if (SUCCEEDED(ShellExecCmdLine(hwnd, pszExpanded, pszStartDir, SW_SHOWNORMAL, NULL,
640                                                            SECL_ALLOW_NONEXE)))
641                             {
642                                 /* Call again GetWindowText in case the contents of the edit box has changed? */
643                                 GetWindowTextW(htxt, psz, ic + 1);
644                                 FillList(htxt, psz, ic + 2 + 1, FALSE);
645                                 EndDialog(hwnd, IDOK);
646                                 break;
647                             }
648 
649                         /* Fall-back */
650                         case RF_RETRY:
651                         default:
652                             SendMessageW(htxt, CB_SETEDITSEL, 0, MAKELPARAM (0, -1));
653                             /* Show back the dialog */
654                             ShowWindow(hwnd, SW_SHOW);
655                             break;
656                     }
657 
658                     HeapFree(GetProcessHeap(), 0, parent);
659                     HeapFree(GetProcessHeap(), 0, psz);
660                     if (psz != pszExpanded)
661                         HeapFree(GetProcessHeap(), 0, pszExpanded);
662                     return TRUE;
663                 }
664 
665                 case IDCANCEL:
666                     EndDialog(hwnd, IDCANCEL);
667                     return TRUE;
668 
669                 case IDC_RUNDLG_BROWSE:
670                 {
671                     HMODULE hComdlg = NULL;
672                     LPFNOFN ofnProc = NULL;
673                     WCHAR szFName[1024] = {0};
674                     WCHAR filter[MAX_PATH], szCaption[MAX_PATH];
675                     OPENFILENAMEW ofn;
676 
677                     LoadStringW(shell32_hInstance, IDS_RUNDLG_BROWSE_FILTER, filter, _countof(filter));
678                     LoadStringW(shell32_hInstance, IDS_RUNDLG_BROWSE_CAPTION, szCaption, _countof(szCaption));
679 
680                     ZeroMemory(&ofn, sizeof(ofn));
681                     ofn.lStructSize = sizeof(ofn);
682                     ofn.hwndOwner = hwnd;
683                     ofn.lpstrFilter = filter;
684                     ofn.lpstrFile = szFName;
685                     ofn.nMaxFile = _countof(szFName) - 1;
686                     ofn.lpstrTitle = szCaption;
687                     ofn.Flags = OFN_ENABLESIZING | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
688                     ofn.lpstrInitialDir = prfdp->lpstrDirectory;
689 
690                     if (NULL == (hComdlg = LoadLibraryExW(L"comdlg32", NULL, 0)) ||
691                         NULL == (ofnProc = (LPFNOFN)GetProcAddress(hComdlg, "GetOpenFileNameW")))
692                     {
693                         ERR("Couldn't get GetOpenFileName function entry (lib=%p, proc=%p)\n", hComdlg, ofnProc);
694                         ShellMessageBoxW(shell32_hInstance, hwnd, MAKEINTRESOURCEW(IDS_RUNDLG_BROWSE_ERROR), NULL, MB_OK | MB_ICONERROR);
695                         return TRUE;
696                     }
697 
698                     if (ofnProc(&ofn))
699                     {
700                         SetFocus(GetDlgItem(hwnd, IDOK));
701                         SetWindowTextW(GetDlgItem(hwnd, IDC_RUNDLG_EDITPATH), szFName);
702                         SendMessageW(GetDlgItem(hwnd, IDC_RUNDLG_EDITPATH), CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
703                         EnableOkButtonFromEditContents(hwnd);
704                         SetFocus(GetDlgItem(hwnd, IDOK));
705                     }
706 
707                     FreeLibrary(hComdlg);
708 
709                     return TRUE;
710                 }
711                 case IDC_RUNDLG_EDITPATH:
712                 {
713                     if (HIWORD(wParam) == CBN_EDITCHANGE)
714                     {
715                         EnableOkButtonFromEditContents(hwnd);
716                     }
717                     return TRUE;
718                 }
719             }
720             return TRUE;
721     }
722     return FALSE;
723 }
724 
725 /*
726  * This function grabs the MRU list from the registry and fills the combo-list
727  * for the "Run" dialog above. fShowDefault is ignored if pszLatest != NULL.
728  */
729 // FIXME: Part of this code should be part of some MRUList API,
730 // that is scattered amongst shell32, comctl32 (?!) and comdlg32.
731 static void FillList(HWND hCb, LPWSTR pszLatest, UINT cchStr, BOOL fShowDefault)
732 {
733     HKEY hkey;
734     WCHAR *pszList = NULL, *pszCmd = NULL, *pszTmp = NULL, cMatch = 0, cMax = 0x60;
735     WCHAR szIndex[2] = L"-";
736     UINT cchLatest;
737     DWORD dwType, icList = 0, icCmd = 0;
738     LRESULT lRet;
739     UINT Nix;
740 
741     /*
742      * Retrieve the string length of pszLatest and check whether its buffer size
743      * (cchStr in number of characters) is large enough to add the terminating "\\1"
744      * (and the NULL character).
745      */
746     if (pszLatest)
747     {
748         cchLatest = wcslen(pszLatest);
749         if (cchStr < cchLatest + 2 + 1)
750         {
751             TRACE("pszLatest buffer is not large enough (%d) to hold the MRU terminator.\n", cchStr);
752             return;
753         }
754     }
755     else
756     {
757         cchStr = 0;
758     }
759 
760     SendMessageW(hCb, CB_RESETCONTENT, 0, 0);
761 
762     lRet = RegCreateKeyExW(HKEY_CURRENT_USER,
763                            L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU",
764                            0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
765     if (lRet != ERROR_SUCCESS)
766     {
767         TRACE("Unable to open or create the RunMRU key, error %d\n", GetLastError());
768         return;
769     }
770 
771     lRet = RegQueryValueExW(hkey, L"MRUList", NULL, &dwType, NULL, &icList);
772     if (lRet == ERROR_SUCCESS && dwType == REG_SZ && icList > sizeof(WCHAR))
773     {
774         pszList = (WCHAR*)HeapAlloc(GetProcessHeap(), 0, icList);
775         if (!pszList)
776         {
777             TRACE("HeapAlloc failed to allocate %d bytes\n", icList);
778             goto Continue;
779         }
780         pszList[0] = L'\0';
781 
782         lRet = RegQueryValueExW(hkey, L"MRUList", NULL, NULL, (LPBYTE)pszList, &icList);
783         if (lRet != ERROR_SUCCESS)
784         {
785             TRACE("Unable to grab MRUList, error %d\n", GetLastError());
786             pszList[0] = L'\0';
787         }
788     }
789     else
790     {
791 Continue:
792         icList = sizeof(WCHAR);
793         pszList = (WCHAR*)HeapAlloc(GetProcessHeap(), 0, icList);
794         if (!pszList)
795         {
796             TRACE("HeapAlloc failed to allocate %d bytes\n", icList);
797             RegCloseKey(hkey);
798             return;
799         }
800         pszList[0] = L'\0';
801     }
802 
803     /* Convert the number of bytes from MRUList into number of characters (== number of indices) */
804     icList /= sizeof(WCHAR);
805 
806     for (Nix = 0; Nix < icList - 1; Nix++)
807     {
808         if (pszList[Nix] > cMax)
809             cMax = pszList[Nix];
810 
811         szIndex[0] = pszList[Nix];
812 
813         lRet = RegQueryValueExW(hkey, szIndex, NULL, &dwType, NULL, &icCmd);
814         if (lRet != ERROR_SUCCESS || dwType != REG_SZ)
815         {
816             TRACE("Unable to grab size of index, error %d\n", GetLastError());
817             continue;
818         }
819 
820         if (pszCmd)
821         {
822             pszTmp = (WCHAR*)HeapReAlloc(GetProcessHeap(), 0, pszCmd, icCmd);
823             if (!pszTmp)
824             {
825                 TRACE("HeapReAlloc failed to reallocate %d bytes\n", icCmd);
826                 continue;
827             }
828             pszCmd = pszTmp;
829         }
830         else
831         {
832             pszCmd = (WCHAR*)HeapAlloc(GetProcessHeap(), 0, icCmd);
833             if (!pszCmd)
834             {
835                 TRACE("HeapAlloc failed to allocate %d bytes\n", icCmd);
836                 continue;
837             }
838         }
839 
840         lRet = RegQueryValueExW(hkey, szIndex, NULL, NULL, (LPBYTE)pszCmd, &icCmd);
841         if (lRet != ERROR_SUCCESS)
842         {
843             TRACE("Unable to grab index, error %d\n", GetLastError());
844             continue;
845         }
846 
847         /*
848          * Generally the command string will end up with "\\1".
849          * Find the last backslash in the string and NULL-terminate.
850          * Windows does not seem to check for what comes next, so that
851          * a command of the form:
852          *     c:\\my_dir\\myfile.exe
853          * will be cut just after "my_dir", whereas a command of the form:
854          *     c:\\my_dir\\myfile.exe\\1
855          * will be cut just after "myfile.exe".
856          */
857         pszTmp = wcsrchr(pszCmd, L'\\');
858         if (pszTmp)
859             *pszTmp = L'\0';
860 
861         /*
862          * In the following we try to add pszLatest to the MRU list.
863          * We suppose that our caller has already correctly allocated
864          * the string with enough space for us to append a "\\1".
865          *
866          * FIXME: TODO! (At the moment we don't append it!)
867          */
868 
869         if (pszLatest)
870         {
871             if (wcsicmp(pszCmd, pszLatest) == 0)
872             {
873                 SendMessageW(hCb, CB_INSERTSTRING, 0, (LPARAM)pszCmd);
874                 SetWindowTextW(hCb, pszCmd);
875                 SendMessageW(hCb, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
876 
877                 cMatch = pszList[Nix];
878                 memmove(&pszList[1], pszList, Nix * sizeof(WCHAR));
879                 pszList[0] = cMatch;
880                 continue;
881             }
882         }
883 
884         if (icList - 1 != 26 || icList - 2 != Nix || cMatch || pszLatest == NULL)
885         {
886             SendMessageW(hCb, CB_ADDSTRING, 0, (LPARAM)pszCmd);
887             if (!Nix && fShowDefault)
888             {
889                 SetWindowTextW(hCb, pszCmd);
890                 SendMessageW(hCb, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
891             }
892         }
893         else
894         {
895             SendMessageW(hCb, CB_INSERTSTRING, 0, (LPARAM)pszLatest);
896             SetWindowTextW(hCb, pszLatest);
897             SendMessageW(hCb, CB_SETEDITSEL, 0, MAKELPARAM(0, -1));
898 
899             cMatch = pszList[Nix];
900             memmove(&pszList[1], pszList, Nix * sizeof(WCHAR));
901             pszList[0] = cMatch;
902             szIndex[0] = cMatch;
903 
904             wcscpy(&pszLatest[cchLatest], L"\\1");
905             RegSetValueExW(hkey, szIndex, 0, REG_SZ, (LPBYTE)pszLatest, (cchLatest + 2 + 1) * sizeof(WCHAR));
906             pszLatest[cchLatest] = L'\0';
907         }
908     }
909 
910     if (!cMatch && pszLatest != NULL)
911     {
912         SendMessageW(hCb, CB_INSERTSTRING, 0, (LPARAM)pszLatest);
913         SetWindowTextW(hCb, pszLatest);
914         SendMessageW(hCb, CB_SETEDITSEL, 0, MAKELPARAM (0, -1));
915 
916         cMatch = ++cMax;
917 
918         if (pszList)
919         {
920             pszTmp = (WCHAR*)HeapReAlloc(GetProcessHeap(), 0, pszList, (++icList) * sizeof(WCHAR));
921             if (!pszTmp)
922             {
923                 TRACE("HeapReAlloc failed to reallocate enough bytes\n");
924                 goto Cleanup;
925             }
926             pszList = pszTmp;
927         }
928         else
929         {
930             pszList = (WCHAR*)HeapAlloc(GetProcessHeap(), 0, (++icList) * sizeof(WCHAR));
931             if (!pszList)
932             {
933                 TRACE("HeapAlloc failed to allocate enough bytes\n");
934                 goto Cleanup;
935             }
936         }
937 
938         memmove(&pszList[1], pszList, (icList - 1) * sizeof(WCHAR));
939         pszList[0] = cMatch;
940         szIndex[0] = cMatch;
941 
942         wcscpy(&pszLatest[cchLatest], L"\\1");
943         RegSetValueExW(hkey, szIndex, 0, REG_SZ, (LPBYTE)pszLatest, (cchLatest + 2 + 1) * sizeof(WCHAR));
944         pszLatest[cchLatest] = L'\0';
945     }
946 
947 Cleanup:
948     RegSetValueExW(hkey, L"MRUList", 0, REG_SZ, (LPBYTE)pszList, (wcslen(pszList) + 1) * sizeof(WCHAR));
949 
950     HeapFree(GetProcessHeap(), 0, pszCmd);
951     HeapFree(GetProcessHeap(), 0, pszList);
952 
953     RegCloseKey(hkey);
954 }
955 
956 
957 /*************************************************************************
958  * ConfirmDialog                [internal]
959  *
960  * Put up a confirm box, return TRUE if the user confirmed
961  */
962 static BOOL ConfirmDialog(HWND hWndOwner, UINT PromptId, UINT TitleId)
963 {
964     WCHAR Prompt[256];
965     WCHAR Title[256];
966 
967     LoadStringW(shell32_hInstance, PromptId, Prompt, _countof(Prompt));
968     LoadStringW(shell32_hInstance, TitleId, Title, _countof(Title));
969     return MessageBoxW(hWndOwner, Prompt, Title, MB_YESNO | MB_ICONQUESTION) == IDYES;
970 }
971 
972 typedef HRESULT (WINAPI *tShellDimScreen)(IUnknown** Unknown, HWND* hWindow);
973 
974 BOOL
975 CallShellDimScreen(IUnknown** pUnknown, HWND* hWindow)
976 {
977     static tShellDimScreen ShellDimScreen;
978     static BOOL Initialized = FALSE;
979     if (!Initialized)
980     {
981         HMODULE mod = LoadLibraryW(L"msgina.dll");
982         ShellDimScreen = (tShellDimScreen)GetProcAddress(mod, (LPCSTR)16);
983         Initialized = TRUE;
984     }
985 
986     HRESULT hr = E_FAIL;
987     if (ShellDimScreen)
988         hr = ShellDimScreen(pUnknown, hWindow);
989     return SUCCEEDED(hr);
990 }
991 
992 
993 /* Used to get the shutdown privilege */
994 static BOOL
995 EnablePrivilege(LPCWSTR lpszPrivilegeName, BOOL bEnablePrivilege)
996 {
997     BOOL   Success;
998     HANDLE hToken;
999     TOKEN_PRIVILEGES tp;
1000 
1001     Success = OpenProcessToken(GetCurrentProcess(),
1002                                TOKEN_ADJUST_PRIVILEGES,
1003                                &hToken);
1004     if (!Success) return Success;
1005 
1006     Success = LookupPrivilegeValueW(NULL,
1007                                     lpszPrivilegeName,
1008                                     &tp.Privileges[0].Luid);
1009     if (!Success) goto Quit;
1010 
1011     tp.PrivilegeCount = 1;
1012     tp.Privileges[0].Attributes = (bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0);
1013 
1014     Success = AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL);
1015 
1016 Quit:
1017     CloseHandle(hToken);
1018     return Success;
1019 }
1020 
1021 /*************************************************************************
1022  * RestartDialogEx                [SHELL32.730]
1023  */
1024 
1025 int WINAPI RestartDialogEx(HWND hWndOwner, LPCWSTR lpwstrReason, DWORD uFlags, DWORD uReason)
1026 {
1027     TRACE("(%p)\n", hWndOwner);
1028 
1029     CComPtr<IUnknown> fadeHandler;
1030     HWND parent;
1031 
1032     if (!CallShellDimScreen(&fadeHandler, &parent))
1033         parent = hWndOwner;
1034 
1035     /* FIXME: use lpwstrReason */
1036     if (ConfirmDialog(parent, IDS_RESTART_PROMPT, IDS_RESTART_TITLE))
1037     {
1038         EnablePrivilege(L"SeShutdownPrivilege", TRUE);
1039         ExitWindowsEx(EWX_REBOOT, uReason);
1040         EnablePrivilege(L"SeShutdownPrivilege", FALSE);
1041     }
1042 
1043     return 0;
1044 }
1045 
1046 /*************************************************************************
1047  * LogOffDialogProc
1048  *
1049  * NOTES: Used to make the Log Off dialog work
1050  */
1051 INT_PTR CALLBACK LogOffDialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1052 {
1053     switch (uMsg)
1054     {
1055         case WM_INITDIALOG:
1056             return TRUE;
1057 
1058         case WM_CLOSE:
1059             EndDialog(hwnd, IDCANCEL);
1060             break;
1061 
1062 #if 0
1063         case WM_ACTIVATE:
1064         {
1065             if (LOWORD(wParam) == WA_INACTIVE)
1066                 EndDialog(hwnd, 0);
1067             return FALSE;
1068         }
1069 #endif
1070 
1071         case WM_COMMAND:
1072             switch (LOWORD(wParam))
1073             {
1074                 case IDOK:
1075                     ExitWindowsEx(EWX_LOGOFF, 0);
1076                     break;
1077 
1078                 case IDCANCEL:
1079                     EndDialog(hwnd, IDCANCEL);
1080                     break;
1081             }
1082             break;
1083 
1084         default:
1085             break;
1086     }
1087     return FALSE;
1088 }
1089 
1090 /*************************************************************************
1091  * LogoffWindowsDialog  [SHELL32.54]
1092  */
1093 
1094 EXTERN_C int WINAPI LogoffWindowsDialog(HWND hWndOwner)
1095 {
1096     CComPtr<IUnknown> fadeHandler;
1097     HWND parent;
1098 
1099     if (!CallShellDimScreen(&fadeHandler, &parent))
1100         parent = hWndOwner;
1101 
1102     DialogBoxW(shell32_hInstance, MAKEINTRESOURCEW(IDD_LOG_OFF), parent, LogOffDialogProc);
1103     return 0;
1104 }
1105 
1106 /*************************************************************************
1107  * RestartDialog                [SHELL32.59]
1108  */
1109 
1110 int WINAPI RestartDialog(HWND hWndOwner, LPCWSTR lpstrReason, DWORD uFlags)
1111 {
1112     return RestartDialogEx(hWndOwner, lpstrReason, uFlags, 0);
1113 }
1114 
1115 /*************************************************************************
1116  * ExitWindowsDialog_backup
1117  *
1118  * NOTES
1119  *     Used as a backup solution to shutdown the OS in case msgina.dll
1120  *     somehow cannot be found.
1121  */
1122 VOID ExitWindowsDialog_backup(HWND hWndOwner)
1123 {
1124     TRACE("(%p)\n", hWndOwner);
1125 
1126     if (ConfirmDialog(hWndOwner, IDS_SHUTDOWN_PROMPT, IDS_SHUTDOWN_TITLE))
1127     {
1128         EnablePrivilege(L"SeShutdownPrivilege", TRUE);
1129         ExitWindowsEx(EWX_SHUTDOWN, 0);
1130         EnablePrivilege(L"SeShutdownPrivilege", FALSE);
1131     }
1132 }
1133 
1134 /*************************************************************************
1135  * ExitWindowsDialog                [SHELL32.60]
1136  *
1137  * NOTES
1138  *     exported by ordinal
1139  */
1140 /*
1141  * TODO:
1142  * - Implement the ability to show either the Welcome Screen or the classic dialog boxes based upon the
1143  *   registry value: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\LogonType.
1144  */
1145 void WINAPI ExitWindowsDialog(HWND hWndOwner)
1146 {
1147     typedef DWORD (WINAPI *ShellShFunc)(HWND hParent, WCHAR *Username, BOOL bHideLogoff);
1148     HINSTANCE msginaDll = LoadLibraryW(L"msgina.dll");
1149 
1150     TRACE("(%p)\n", hWndOwner);
1151 
1152     CComPtr<IUnknown> fadeHandler;
1153     HWND parent;
1154     if (!CallShellDimScreen(&fadeHandler, &parent))
1155         parent = hWndOwner;
1156 
1157     /* If the DLL cannot be found for any reason, then it simply uses a
1158        dialog box to ask if the user wants to shut down the computer. */
1159     if (!msginaDll)
1160     {
1161         TRACE("Unable to load msgina.dll.\n");
1162         ExitWindowsDialog_backup(parent);
1163         return;
1164     }
1165 
1166     ShellShFunc pShellShutdownDialog = (ShellShFunc)GetProcAddress(msginaDll, "ShellShutdownDialog");
1167 
1168     if (pShellShutdownDialog)
1169     {
1170         /* Actually call the function */
1171         DWORD returnValue = pShellShutdownDialog(parent, NULL, FALSE);
1172 
1173         switch (returnValue)
1174         {
1175         case 0x01: /* Log off user */
1176         {
1177             ExitWindowsEx(EWX_LOGOFF, 0);
1178             break;
1179         }
1180         case 0x02: /* Shut down */
1181         {
1182             EnablePrivilege(L"SeShutdownPrivilege", TRUE);
1183             ExitWindowsEx(EWX_SHUTDOWN, 0);
1184             EnablePrivilege(L"SeShutdownPrivilege", FALSE);
1185             break;
1186         }
1187         case 0x03: /* Install Updates/Shutdown (?) */
1188         {
1189             break;
1190         }
1191         case 0x04: /* Reboot */
1192         {
1193             EnablePrivilege(L"SeShutdownPrivilege", TRUE);
1194             ExitWindowsEx(EWX_REBOOT, 0);
1195             EnablePrivilege(L"SeShutdownPrivilege", FALSE);
1196             break;
1197         }
1198         case 0x10: /* Sleep */
1199         {
1200             if (IsPwrSuspendAllowed())
1201             {
1202                 EnablePrivilege(L"SeShutdownPrivilege", TRUE);
1203                 SetSuspendState(FALSE, FALSE, FALSE);
1204                 EnablePrivilege(L"SeShutdownPrivilege", FALSE);
1205             }
1206             break;
1207         }
1208         case 0x40: /* Hibernate */
1209         {
1210             if (IsPwrHibernateAllowed())
1211             {
1212                 EnablePrivilege(L"SeShutdownPrivilege", TRUE);
1213                 SetSuspendState(TRUE, FALSE, TRUE);
1214                 EnablePrivilege(L"SeShutdownPrivilege", FALSE);
1215             }
1216             break;
1217         }
1218         /* If the option is any other value */
1219         default:
1220             break;
1221         }
1222     }
1223     else
1224     {
1225         /* If the function cannot be found, then revert to using the backup solution */
1226         TRACE("Unable to find the 'ShellShutdownDialog' function");
1227         ExitWindowsDialog_backup(parent);
1228     }
1229 
1230     FreeLibrary(msginaDll);
1231 }
1232