2 * PROJECT: ReactOS Software Control Panel
3 * FILE: dll/cpl/appwiz/createlink.c
4 * PURPOSE: ReactOS Software Control Panel
5 * PROGRAMMER: Gero Kuehn (reactos.filter@gkware.com)
6 * Dmitry Chapyshev (lentind@yandex.ru)
8 * Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
17 #include <shlwapi_undoc.h> // for PathFindOnPathExW
18 #include "../../win32/shell32/shresdef.h" // for IDS_NEWITEMFORMAT and IDS_LNK_FILE
28 if (RegQueryValueExW(hKey
, L
"IsShortcut", NULL
, &Type
, (LPBYTE
)Value
, &Size
) != ERROR_SUCCESS
)
34 return (wcsicmp(Value
, L
"yes") == 0);
38 IsExtensionAShortcut(LPWSTR lpExtension
)
45 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, lpExtension
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
54 Size
= sizeof(Buffer
);
55 if (RegQueryValueEx(hKey
, NULL
, NULL
, &Type
, (LPBYTE
)Buffer
, &Size
) != ERROR_SUCCESS
|| Type
!= REG_SZ
)
63 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, Buffer
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
77 CreateShortcut(PCREATE_LINK_CONTEXT pContext
)
79 IShellLinkW
*pShellLink
;
80 IPersistFile
*pPersistFile
;
83 hr
= CoCreateInstance(&CLSID_ShellLink
, NULL
, CLSCTX_ALL
, &IID_IShellLinkW
, (void**)&pShellLink
);
87 hr
= IUnknown_QueryInterface(pShellLink
, &IID_IPersistFile
, (void**)&pPersistFile
);
90 IUnknown_Release(pShellLink
);
94 if (IsExtensionAShortcut(PathFindExtensionW(pContext
->szTarget
)))
96 hr
= pPersistFile
->lpVtbl
->Load(pPersistFile
, (LPCOLESTR
)pContext
->szTarget
, STGM_READ
);
100 if (pContext
->pidlTarget
)
101 pShellLink
->lpVtbl
->SetIDList(pShellLink
, pContext
->pidlTarget
);
103 pShellLink
->lpVtbl
->SetPath(pShellLink
, pContext
->szTarget
);
105 if (pContext
->szArguments
[0])
106 pShellLink
->lpVtbl
->SetArguments(pShellLink
, pContext
->szArguments
);
108 if (pContext
->szDescription
[0])
109 pShellLink
->lpVtbl
->SetDescription(pShellLink
, pContext
->szDescription
);
113 hr
= pPersistFile
->lpVtbl
->Save(pPersistFile
, pContext
->szLinkName
, TRUE
);
115 IUnknown_Release(pPersistFile
);
116 IUnknown_Release(pShellLink
);
117 return SUCCEEDED(hr
);
121 CreateInternetShortcut(PCREATE_LINK_CONTEXT pContext
)
123 IUniformResourceLocatorW
*pURL
= NULL
;
124 IPersistFile
*pPersistFile
= NULL
;
126 WCHAR szPath
[MAX_PATH
];
127 GetFullPathNameW(pContext
->szLinkName
, _countof(szPath
), szPath
, NULL
);
129 hr
= CoCreateInstance(&CLSID_InternetShortcut
, NULL
, CLSCTX_ALL
,
130 &IID_IUniformResourceLocatorW
, (void **)&pURL
);
134 hr
= IUnknown_QueryInterface(pURL
, &IID_IPersistFile
, (void **)&pPersistFile
);
137 IUnknown_Release(pURL
);
141 pURL
->lpVtbl
->SetURL(pURL
, pContext
->szTarget
, 0);
143 hr
= pPersistFile
->lpVtbl
->Save(pPersistFile
, szPath
, TRUE
);
145 IUnknown_Release(pPersistFile
);
146 IUnknown_Release(pURL
);
148 return SUCCEEDED(hr
);
151 BOOL
IsInternetLocation(LPCWSTR pszLocation
)
153 return (PathIsURLW(pszLocation
) || wcsstr(pszLocation
, L
"www.") == pszLocation
);
156 /* Remove all invalid characters from the name */
158 DoConvertNameForFileSystem(LPWSTR szName
)
161 for (pch1
= pch2
= szName
; *pch1
; ++pch1
)
163 if (wcschr(L
"\\/:*?\"<>|", *pch1
) != NULL
)
165 /* *pch1 is an invalid character */
175 DoValidateShortcutName(PCREATE_LINK_CONTEXT pContext
)
178 LPCWSTR pch
, pszName
= pContext
->szDescription
;
180 if (!pszName
|| !pszName
[0])
183 cch
= wcslen(pContext
->szOrigin
) + wcslen(pszName
) + 1;
188 for (pch
= pszName
; *pch
; ++pch
)
190 if (wcschr(L
"\\/:*?\"<>|", *pch
) != NULL
)
192 /* *pch is an invalid character */
202 WelcomeDlgProc(HWND hwndDlg
,
207 LPPROPSHEETPAGEW ppsp
;
208 PCREATE_LINK_CONTEXT pContext
= (PCREATE_LINK_CONTEXT
)GetWindowLongPtr(hwndDlg
, DWLP_USER
);
210 WCHAR szPath
[MAX_PATH
* 2], szDisplayName
[MAX_PATH
];
214 LPITEMIDLIST pidllist
;
215 SHFILEINFOW FileInfo
;
220 ppsp
= (LPPROPSHEETPAGEW
)lParam
;
221 pContext
= (PCREATE_LINK_CONTEXT
) ppsp
->lParam
;
222 SetWindowLongPtr(hwndDlg
, DWLP_USER
, (LONG_PTR
)pContext
);
223 PropSheet_SetWizButtons(GetParent(hwndDlg
), 0);
224 SHAutoComplete(GetDlgItem(hwndDlg
, IDC_LINK_LOCATION
), SHACF_DEFAULT
);
228 switch (LOWORD(wParam
))
230 case IDC_LINK_LOCATION
:
232 if (HIWORD(wParam
) == EN_CHANGE
)
234 /* The text was changed by user. Invalidate pidlTarget. */
235 if (pContext
->pidlTarget
)
237 CoTaskMemFree(pContext
->pidlTarget
);
238 pContext
->pidlTarget
= NULL
;
241 if (SendDlgItemMessage(hwndDlg
, IDC_LINK_LOCATION
, WM_GETTEXTLENGTH
, 0, 0))
242 PropSheet_SetWizButtons(GetParent(hwndDlg
), PSWIZB_NEXT
);
244 PropSheet_SetWizButtons(GetParent(hwndDlg
), 0);
248 case IDC_SHORTCUT_BROWSE
:
250 LoadStringW(hApplet
, IDS_BROWSE_FOR_TARGET
, szTitle
, _countof(szTitle
));
251 ZeroMemory(&brws
, sizeof(brws
));
252 brws
.hwndOwner
= hwndDlg
;
253 brws
.pidlRoot
= NULL
;
254 brws
.pszDisplayName
= szDisplayName
;
255 brws
.lpszTitle
= szTitle
;
256 brws
.ulFlags
= BIF_BROWSEINCLUDEFILES
| BIF_NEWDIALOGSTYLE
| BIF_SHAREABLE
;
258 pidllist
= SHBrowseForFolderW(&brws
);
262 StringCchCopyW(pContext
->szDescription
, _countof(pContext
->szDescription
),
265 SHGetPathFromIDListW(pidllist
, szPath
);
267 if (PathFileExistsW(szPath
) && !PathIsRelativeW(szPath
))
268 SetDlgItemTextW(hwndDlg
, IDC_LINK_LOCATION
, szPath
);
270 SetDlgItemTextW(hwndDlg
, IDC_LINK_LOCATION
, szDisplayName
);
272 SendDlgItemMessageW(hwndDlg
, IDC_LINK_LOCATION
, EM_SETSEL
, 0, -1);
274 if (pContext
->pidlTarget
)
275 CoTaskMemFree(pContext
->pidlTarget
);
276 pContext
->pidlTarget
= pidllist
;
282 lppsn
= (LPPSHNOTIFY
) lParam
;
283 if (lppsn
->hdr
.code
== PSN_SETACTIVE
)
285 SetDlgItemTextW(hwndDlg
, IDC_LINK_LOCATION
, pContext
->szTarget
);
287 else if (lppsn
->hdr
.code
== PSN_WIZNEXT
)
289 GetDlgItemTextW(hwndDlg
, IDC_LINK_LOCATION
, pContext
->szTarget
, _countof(pContext
->szTarget
));
290 StrTrimW(pContext
->szTarget
, L
" \t");
291 ExpandEnvironmentStringsW(pContext
->szTarget
, szPath
, _countof(szPath
));
293 if (IsInternetLocation(szPath
)) /* The internet location */
296 LoadStringW(hApplet
, IDS_NEW_INTERNET_SHORTCUT
, szName
, _countof(szName
));
297 StringCchCopyW(pContext
->szDescription
, _countof(pContext
->szDescription
), szName
);
298 pContext
->szArguments
[0] = 0;
302 if (pContext
->pidlTarget
) /* The result of SHBrowseForFolderW */
304 SHGetPathFromIDListW(pContext
->pidlTarget
, pContext
->szTarget
);
305 pContext
->szArguments
[0] = 0;
309 /* Otherwise, the target is a command line or pathname */
311 /* Split and build args */
312 LPWSTR pszArgs
= PathGetArgsW(szPath
);
313 if (pszArgs
&& pszArgs
> szPath
)
315 PathRemoveArgsW(szPath
);
316 StringCchCopyW(pContext
->szArguments
, _countof(pContext
->szArguments
), pszArgs
);
320 pContext
->szArguments
[0] = 0;
324 if (!PathFindOnPathExW(szPath
, NULL
, WHICH_DEFAULT
) &&
325 !PathFileExistsW(szPath
))
328 SendDlgItemMessageW(hwndDlg
, IDC_LINK_LOCATION
, EM_SETSEL
, 0, -1);
330 LoadStringW(hApplet
, IDS_CREATE_SHORTCUT
, szDesc
, _countof(szDesc
));
331 LoadStringW(hApplet
, IDS_ERROR_NOT_FOUND
, szPath
, _countof(szPath
));
333 WCHAR szError
[MAX_PATH
+ 100];
334 StringCchPrintfW(szError
, _countof(szError
), szPath
, pContext
->szTarget
);
335 MessageBoxW(hwndDlg
, szError
, szDesc
, MB_ICONERROR
);
337 /* Prevent the wizard to go next */
338 SetWindowLongPtr(hwndDlg
, DWLP_MSGRESULT
, -1);
343 if (PathIsRelativeW(szPath
))
344 GetFullPathNameW(szPath
, _countof(pContext
->szTarget
), pContext
->szTarget
, NULL
);
346 StringCchCopyW(pContext
->szTarget
, _countof(pContext
->szTarget
), szPath
);
348 /* Get display name */
349 FileInfo
.szDisplayName
[0] = UNICODE_NULL
;
350 if (SHGetFileInfoW(pContext
->szTarget
, 0, &FileInfo
, sizeof(FileInfo
), SHGFI_DISPLAYNAME
))
351 StringCchCopyW(pContext
->szDescription
, _countof(pContext
->szDescription
), FileInfo
.szDisplayName
);
355 else if (lppsn
->hdr
.code
== PSN_RESET
&& !lppsn
->lParam
)
357 /* The user has clicked [Cancel] */
358 DeleteFileW(pContext
->szOldFile
);
359 SHChangeNotify(SHCNE_DELETE
, SHCNF_PATHW
, pContext
->szOldFile
, NULL
);
369 FinishDlgProc(HWND hwndDlg
,
374 LPPROPSHEETPAGEW ppsp
;
375 PCREATE_LINK_CONTEXT pContext
;
377 WCHAR szText
[MAX_PATH
], szPath
[MAX_PATH
];
378 WCHAR szMessage
[128];
383 ppsp
= (LPPROPSHEETPAGEW
)lParam
;
384 pContext
= (PCREATE_LINK_CONTEXT
) ppsp
->lParam
;
385 SetWindowLongPtr(hwndDlg
, DWLP_USER
, (LONG_PTR
)pContext
);
386 PropSheet_SetWizButtons(GetParent(hwndDlg
), PSWIZB_BACK
| PSWIZB_FINISH
);
389 switch(HIWORD(wParam
))
392 if (SendDlgItemMessage(hwndDlg
, IDC_SHORTCUT_NAME
, WM_GETTEXTLENGTH
, 0, 0))
394 GetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_NAME
, szText
, _countof(szText
));
395 StrTrimW(szText
, L
" \t");
397 PropSheet_SetWizButtons(GetParent(hwndDlg
), PSWIZB_BACK
| PSWIZB_FINISH
);
399 PropSheet_SetWizButtons(GetParent(hwndDlg
), PSWIZB_BACK
);
403 PropSheet_SetWizButtons(GetParent(hwndDlg
), PSWIZB_BACK
);
409 lppsn
= (LPPSHNOTIFY
) lParam
;
410 pContext
= (PCREATE_LINK_CONTEXT
) GetWindowLongPtr(hwndDlg
, DWLP_USER
);
411 if (lppsn
->hdr
.code
== PSN_SETACTIVE
)
413 /* Remove invalid characters */
414 DoConvertNameForFileSystem(pContext
->szDescription
);
415 PathCleanupSpec(NULL
, pContext
->szDescription
);
417 /* Is it empty? (rare case) */
418 if (!pContext
->szDescription
[0])
420 HMODULE hShell32
= GetModuleHandleW(L
"shell32.dll");
421 LoadStringW(hShell32
, IDS_NEWITEMFORMAT
, szText
, _countof(szText
));
422 LoadStringW(hShell32
, IDS_LNK_FILE
, szMessage
, _countof(szMessage
));
423 StringCchPrintfW(pContext
->szDescription
, _countof(pContext
->szDescription
),
427 /* Build a path from szOldFile */
428 StringCchCopyW(szText
, _countof(szText
), pContext
->szOldFile
);
429 PathRemoveFileSpecW(szText
);
431 /* Rename duplicate if necessary */
432 PathAddExtensionW(pContext
->szDescription
,
433 (IsInternetLocation(pContext
->szTarget
) ? L
".url" : L
".lnk"));
434 PathYetAnotherMakeUniqueName(szPath
, szText
, NULL
, pContext
->szDescription
);
435 StringCchCopyW(pContext
->szDescription
, _countof(pContext
->szDescription
),
436 PathFindFileNameW(szPath
));
437 PathRemoveExtensionW(pContext
->szDescription
);
439 SetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_NAME
, pContext
->szDescription
);
440 SendDlgItemMessageW(hwndDlg
, IDC_SHORTCUT_NAME
, EM_SETSEL
, 0, -1);
441 SetFocus(GetDlgItem(hwndDlg
, IDC_SHORTCUT_NAME
));
443 else if (lppsn
->hdr
.code
== PSN_WIZFINISH
)
445 GetDlgItemTextW(hwndDlg
, IDC_SHORTCUT_NAME
, pContext
->szDescription
, _countof(pContext
->szDescription
));
446 StrTrimW(pContext
->szDescription
, L
" \t");
448 if (!DoValidateShortcutName(pContext
))
450 SendDlgItemMessageW(hwndDlg
, IDC_SHORTCUT_NAME
, EM_SETSEL
, 0, -1);
452 LoadStringW(hApplet
, IDS_INVALID_NAME
, szMessage
, _countof(szMessage
));
453 MessageBoxW(hwndDlg
, szMessage
, NULL
, MB_ICONERROR
);
455 /* prevent the wizard to go next */
456 SetWindowLongPtr(hwndDlg
, DWLP_MSGRESULT
, -1);
460 /* if old shortcut file exists, then delete it now */
461 DeleteFileW(pContext
->szOldFile
);
462 SHChangeNotify(SHCNE_DELETE
, SHCNF_PATHW
, pContext
->szOldFile
, NULL
);
464 if (IsInternetLocation(pContext
->szTarget
))
467 StringCchCopyW(pContext
->szLinkName
, _countof(pContext
->szLinkName
),
469 PathAppendW(pContext
->szLinkName
, pContext
->szDescription
);
471 /* change extension if any */
472 PathRemoveExtensionW(pContext
->szLinkName
);
473 PathAddExtensionW(pContext
->szLinkName
, L
".url");
475 if (!CreateInternetShortcut(pContext
))
477 LoadStringW(hApplet
, IDS_CANTMAKEINETSHORTCUT
, szMessage
, _countof(szMessage
));
478 MessageBoxW(hwndDlg
, szMessage
, NULL
, MB_ICONERROR
);
484 StringCchCopyW(pContext
->szLinkName
, _countof(pContext
->szLinkName
),
486 PathAppendW(pContext
->szLinkName
, pContext
->szDescription
);
488 /* change extension if any */
489 PathRemoveExtensionW(pContext
->szLinkName
);
490 PathAddExtensionW(pContext
->szLinkName
, L
".lnk");
492 if (!CreateShortcut(pContext
))
494 WCHAR szMessage
[128];
495 LoadStringW(hApplet
, IDS_CANTMAKESHORTCUT
, szMessage
, _countof(szMessage
));
496 MessageBoxW(hwndDlg
, szMessage
, NULL
, MB_ICONERROR
);
500 else if (lppsn
->hdr
.code
== PSN_RESET
&& !lppsn
->lParam
)
502 /* The user has clicked [Cancel] */
503 DeleteFileW(pContext
->szOldFile
);
504 SHChangeNotify(SHCNE_DELETE
, SHCNF_PATHW
, pContext
->szOldFile
, NULL
);
512 PropSheetProc(HWND hwndDlg
, UINT uMsg
, LPARAM lParam
)
514 // NOTE: This callback is needed to set large icon correctly.
518 case PSCB_INITIALIZED
:
520 hIcon
= LoadIconW(hApplet
, MAKEINTRESOURCEW(IDI_APPINETICO
));
521 SendMessageW(hwndDlg
, WM_SETICON
, ICON_BIG
, (LPARAM
)hIcon
);
529 ShowCreateShortcutWizard(HWND hwndCPl
, LPCWSTR szPath
)
531 PROPSHEETHEADERW psh
;
532 HPROPSHEETPAGE ahpsp
[2];
536 PCREATE_LINK_CONTEXT pContext
;
537 WCHAR szMessage
[128];
540 pContext
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(*pContext
));
544 LoadStringW(hApplet
, IDS_NO_MEMORY
, szMessage
, _countof(szMessage
));
545 MessageBoxW(hwndCPl
, szMessage
, NULL
, MB_ICONERROR
);
549 nLength
= wcslen(szPath
);
552 HeapFree(GetProcessHeap(), 0, pContext
);
554 /* no directory given */
555 LoadStringW(hApplet
, IDS_NO_DIRECTORY
, szMessage
, _countof(szMessage
));
556 MessageBoxW(hwndCPl
, szMessage
, NULL
, MB_ICONERROR
);
560 if (!PathFileExistsW(szPath
))
562 HeapFree(GetProcessHeap(), 0, pContext
);
565 LoadStringW(hApplet
, IDS_INVALID_PATH
, szMessage
, _countof(szMessage
));
566 MessageBoxW(hwndCPl
, szMessage
, NULL
, MB_ICONERROR
);
570 /* build the pContext->szOrigin and pContext->szOldFile */
571 if (PathIsDirectoryW(szPath
))
573 StringCchCopyW(pContext
->szOrigin
, _countof(pContext
->szOrigin
), szPath
);
574 pContext
->szOldFile
[0] = 0;
578 StringCchCopyW(pContext
->szOrigin
, _countof(pContext
->szOrigin
), szPath
);
579 pch
= PathFindFileNameW(pContext
->szOrigin
);
583 StringCchCopyW(pContext
->szOldFile
, _countof(pContext
->szOldFile
), szPath
);
585 pch
= PathFindFileNameW(szPath
);
588 /* build szDescription */
589 StringCchCopyW(pContext
->szDescription
, _countof(pContext
->szDescription
), pch
);
592 pch
= PathFindExtensionW(pContext
->szDescription
);
596 PathAddBackslashW(pContext
->szOrigin
);
598 /* Create the Welcome page */
599 psp
.dwSize
= sizeof(PROPSHEETPAGE
);
600 psp
.dwFlags
= PSP_DEFAULT
| PSP_HIDEHEADER
;
601 psp
.hInstance
= hApplet
;
602 psp
.pfnDlgProc
= WelcomeDlgProc
;
603 psp
.pszTemplate
= MAKEINTRESOURCEW(IDD_SHORTCUT_LOCATION
);
604 psp
.lParam
= (LPARAM
)pContext
;
605 ahpsp
[nPages
++] = CreatePropertySheetPage(&psp
);
607 /* Create the Finish page */
608 psp
.dwFlags
= PSP_DEFAULT
| PSP_HIDEHEADER
;
609 psp
.pfnDlgProc
= FinishDlgProc
;
610 psp
.pszTemplate
= MAKEINTRESOURCEW(IDD_SHORTCUT_FINISH
);
611 ahpsp
[nPages
++] = CreatePropertySheetPage(&psp
);
613 /* Create the property sheet */
614 psh
.dwSize
= sizeof(PROPSHEETHEADER
);
615 psh
.dwFlags
= PSH_WIZARD97
| PSH_WATERMARK
| PSH_USEICONID
| PSH_USECALLBACK
;
616 psh
.hInstance
= hApplet
;
617 psh
.pszIcon
= MAKEINTRESOURCEW(IDI_APPINETICO
);
618 psh
.hwndParent
= NULL
;
622 psh
.pszbmWatermark
= MAKEINTRESOURCEW(IDB_SHORTCUT
);
623 psh
.pfnCallback
= PropSheetProc
;
625 /* Display the wizard */
628 CoTaskMemFree(pContext
->pidlTarget
);
629 HeapFree(GetProcessHeap(), 0, pContext
);
635 NewLinkHereW(HWND hwndCPl
, UINT uMsg
, LPARAM lParam1
, LPARAM lParam2
)
637 InitCommonControls();
638 return ShowCreateShortcutWizard(hwndCPl
, (LPWSTR
)lParam1
);
643 NewLinkHereA(HWND hwndCPl
, UINT uMsg
, LPARAM lParam1
, LPARAM lParam2
)
645 WCHAR szFile
[MAX_PATH
];
647 if (MultiByteToWideChar(CP_ACP
, 0, (LPSTR
)lParam1
, -1, szFile
, _countof(szFile
)))
649 InitCommonControls();
650 return ShowCreateShortcutWizard(hwndCPl
, szFile
);