2 * provides new shell item service
4 * Copyright 2007 Johannes Anderwald (johannes.anderwald@reactos.org)
5 * Copyright 2009 Andrew Hill
6 * Copyright 2012 Rafal Harabien
7 * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
28 CNewMenu::CNewMenu() :
49 void CNewMenu::UnloadItem(SHELLNEW_ITEM
*pItem
)
51 /* Note: free allows NULL as argument */
53 free(pItem
->pwszDesc
);
57 DestroyIcon(pItem
->hIcon
);
59 HeapFree(GetProcessHeap(), 0, pItem
);
62 void CNewMenu::UnloadAllItems()
64 SHELLNEW_ITEM
*pCurItem
;
66 /* Unload the handler items, including the link item */
70 m_pItems
= m_pItems
->pNext
;
77 CNewMenu::SHELLNEW_ITEM
*CNewMenu::LoadItem(LPCWSTR pwszExt
)
80 WCHAR wszBuf
[MAX_PATH
];
84 StringCbPrintfW(wszBuf
, sizeof(wszBuf
), L
"%s\\ShellNew", pwszExt
);
86 TRACE("LoadItem Keyname %s Name %s\n", debugstr_w(pwszExt
), debugstr_w(wszBuf
));
88 if (RegOpenKeyExW(HKEY_CLASSES_ROOT
, wszBuf
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
90 TRACE("Failed to open key\n");
94 /* Find first valid value */
102 {L
"FileName", SHELLNEW_TYPE_FILENAME
, TRUE
, TRUE
},
103 {L
"Command", SHELLNEW_TYPE_COMMAND
, TRUE
, TRUE
},
104 {L
"Data", SHELLNEW_TYPE_DATA
, TRUE
, FALSE
},
105 {L
"NullFile", SHELLNEW_TYPE_NULLFILE
, FALSE
},
110 for (i
= 0; Types
[i
].pszName
; ++i
)
112 /* Note: We are using ANSI function because strings can be treated as data */
114 DWORD dwFlags
= Types
[i
].bStr
? RRF_RT_REG_SZ
: RRF_RT_ANY
;
116 if (RegGetValueW(hKey
, NULL
, Types
[i
].pszName
, dwFlags
, NULL
, NULL
, &cbData
) == ERROR_SUCCESS
)
118 if (Types
[i
].bNeedData
&& cbData
> 0)
120 pData
= (PBYTE
)malloc(cbData
);
121 RegGetValueW(hKey
, NULL
, Types
[i
].pszName
, dwFlags
, &dwType
, pData
, &cbData
);
122 if (!Types
[i
].bStr
&& (dwType
== REG_SZ
|| dwType
== REG_EXPAND_SZ
))
124 PBYTE pData2
= (PBYTE
)malloc(cbData
);
125 cbData
= WideCharToMultiByte(CP_ACP
, 0, (LPWSTR
)pData
, -1, (LPSTR
)pData2
, cbData
, NULL
, NULL
);
135 /* Was any key found? */
136 if (!Types
[i
].pszName
)
143 if (!SHGetFileInfoW(pwszExt
, FILE_ATTRIBUTE_NORMAL
, &fi
, sizeof(fi
), SHGFI_USEFILEATTRIBUTES
| SHGFI_TYPENAME
| SHGFI_ICON
| SHGFI_SMALLICON
))
149 /* Create new item */
150 SHELLNEW_ITEM
*pNewItem
= static_cast<SHELLNEW_ITEM
*>(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(SHELLNEW_ITEM
)));
157 TRACE("new item %ls\n", fi
.szTypeName
);
158 pNewItem
->Type
= Types
[i
].Type
;
159 pNewItem
->pData
= pData
;
160 pNewItem
->cbData
= pData
? cbData
: 0;
161 pNewItem
->pwszExt
= _wcsdup(pwszExt
);
162 pNewItem
->pwszDesc
= _wcsdup(fi
.szTypeName
);
164 pNewItem
->hIcon
= fi
.hIcon
;
170 CNewMenu::CacheItems()
177 WCHAR wszName
[MAX_PATH
];
178 SHELLNEW_ITEM
*pNewItem
;
179 SHELLNEW_ITEM
*pCurItem
= NULL
;
181 /* Enumerate all extensions */
182 while (RegEnumKeyW(HKEY_CLASSES_ROOT
, dwIndex
++, wszName
, _countof(wszName
)) == ERROR_SUCCESS
)
184 if (wszName
[0] != L
'.')
187 pNewItem
= LoadItem(wszName
);
190 dwSize
+= wcslen(wszName
) + 1;
191 if (!m_pLinkItem
&& wcsicmp(pNewItem
->pwszExt
, L
".lnk") == 0)
193 /* The unique link handler */
194 m_pLinkItem
= pNewItem
;
197 /* Add at the end of the list */
200 pCurItem
->pNext
= pNewItem
;
205 pCurItem
= m_pItems
= pNewItem
;
212 lpValues
= (LPWSTR
) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, dwSize
* sizeof(WCHAR
));
216 for (pCurItem
= m_pItems
, lpValue
= lpValues
; pCurItem
; pCurItem
= pCurItem
->pNext
)
218 memcpy(lpValue
, pCurItem
->pwszExt
, (wcslen(pCurItem
->pwszExt
) + 1) * sizeof(WCHAR
));
219 lpValue
+= wcslen(pCurItem
->pwszExt
) + 1;
222 if (RegCreateKeyEx(HKEY_CURRENT_USER
, ShellNewKey
, 0, NULL
, REG_OPTION_NON_VOLATILE
, KEY_WRITE
, NULL
, &hKey
, NULL
) != ERROR_SUCCESS
)
224 HeapFree(GetProcessHeap(), 0, lpValues
);
228 if (RegSetValueExW(hKey
, L
"Classes", NULL
, REG_MULTI_SZ
, (LPBYTE
)lpValues
, dwSize
* sizeof(WCHAR
)) != ERROR_SUCCESS
)
230 HeapFree(GetProcessHeap(), 0, lpValues
);
235 HeapFree(GetProcessHeap(), 0, lpValues
);
242 CNewMenu::LoadCachedItems()
248 SHELLNEW_ITEM
*pNewItem
;
249 SHELLNEW_ITEM
*pCurItem
= NULL
;
251 if (RegOpenKeyExW(HKEY_CURRENT_USER
, ShellNewKey
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
254 if (RegQueryValueExW(hKey
, L
"Classes", NULL
, NULL
, NULL
, &dwSize
) != ERROR_SUCCESS
)
257 lpValues
= (LPWSTR
) HeapAlloc(GetProcessHeap(), 0, dwSize
);
261 if (RegQueryValueExW(hKey
, L
"Classes", NULL
, NULL
, (LPBYTE
)lpValues
, &dwSize
) != ERROR_SUCCESS
)
263 HeapFree(GetProcessHeap(), 0, lpValues
);
269 for (; *wszName
!= '\0'; wszName
+= wcslen(wszName
) + 1)
271 pNewItem
= LoadItem(wszName
);
274 if (!m_pLinkItem
&& wcsicmp(pNewItem
->pwszExt
, L
".lnk") == 0)
276 /* The unique link handler */
277 m_pLinkItem
= pNewItem
;
280 /* Add at the end of the list */
283 pCurItem
->pNext
= pNewItem
;
288 pCurItem
= m_pItems
= pNewItem
;
293 HeapFree(GetProcessHeap(), 0, lpValues
);
300 CNewMenu::LoadAllItems()
302 // TODO: We need to find a way to refresh the cache from time to time, when
303 // e.g. new extensions with ShellNew handlers have been added or removed.
305 /* If there are any unload them */
308 if (!LoadCachedItems())
313 return (m_pItems
!= NULL
);
317 CNewMenu::InsertShellNewItems(HMENU hMenu
, UINT idCmdFirst
, UINT Pos
)
320 UINT idCmd
= idCmdFirst
;
323 if (m_pItems
== NULL
)
329 ZeroMemory(&mii
, sizeof(mii
));
330 mii
.cbSize
= sizeof(mii
);
332 m_idCmdFirst
= idCmd
;
334 /* Insert the new folder action */
335 if (!LoadStringW(shell32_hInstance
, FCIDM_SHVIEW_NEWFOLDER
, wszBuf
, _countof(wszBuf
)))
337 mii
.fMask
= MIIM_ID
| MIIM_BITMAP
| MIIM_STRING
;
338 mii
.dwTypeData
= wszBuf
;
339 mii
.cch
= wcslen(mii
.dwTypeData
);
341 mii
.hbmpItem
= HBMMENU_CALLBACK
;
342 if (InsertMenuItemW(hMenu
, Pos
++, TRUE
, &mii
))
343 m_idCmdFolder
= idCmd
++;
345 /* Insert the new shortcut action */
348 if (!LoadStringW(shell32_hInstance
, FCIDM_SHVIEW_NEWLINK
, wszBuf
, _countof(wszBuf
)))
350 mii
.dwTypeData
= wszBuf
;
351 mii
.cch
= wcslen(mii
.dwTypeData
);
353 if (InsertMenuItemW(hMenu
, Pos
++, TRUE
, &mii
))
354 m_idCmdLink
= idCmd
++;
357 /* Insert a seperator for the custom new action */
358 mii
.fMask
= MIIM_TYPE
| MIIM_ID
;
359 mii
.fType
= MFT_SEPARATOR
;
361 InsertMenuItemW(hMenu
, Pos
++, TRUE
, &mii
);
363 /* Insert the rest of the items */
364 mii
.fMask
= MIIM_ID
| MIIM_BITMAP
| MIIM_STRING
| MIIM_DATA
;
367 for (SHELLNEW_ITEM
*pCurItem
= m_pItems
; pCurItem
; pCurItem
= pCurItem
->pNext
)
369 /* Skip shortcut item */
370 if (pCurItem
== m_pLinkItem
)
373 TRACE("szDesc %s\n", debugstr_w(pCurItem
->pwszDesc
));
374 mii
.dwItemData
= (ULONG_PTR
)pCurItem
;
375 mii
.dwTypeData
= pCurItem
->pwszDesc
;
376 mii
.cch
= wcslen(mii
.dwTypeData
);
378 if (InsertMenuItemW(hMenu
, Pos
++, TRUE
, &mii
))
382 return idCmd
- idCmdFirst
;
385 CNewMenu::SHELLNEW_ITEM
*CNewMenu::FindItemFromIdOffset(UINT IdOffset
)
388 if (m_idCmdFirst
+ IdOffset
== m_idCmdFolder
)
392 if (m_idCmdFirst
+ IdOffset
== m_idCmdLink
)
395 /* Find shell new item - Retrieve menu item info */
397 ZeroMemory(&mii
, sizeof(mii
));
398 mii
.cbSize
= sizeof(mii
);
399 mii
.fMask
= MIIM_DATA
;
401 if (GetMenuItemInfoW(m_hSubMenu
, m_idCmdFirst
+ IdOffset
, FALSE
, &mii
) && mii
.dwItemData
)
402 return (SHELLNEW_ITEM
*)mii
.dwItemData
;
407 HRESULT
CNewMenu::SelectNewItem(LONG wEventId
, UINT uFlags
, LPWSTR pszName
, BOOL bRename
)
409 CComPtr
<IShellBrowser
> lpSB
;
410 CComPtr
<IShellView
> lpSV
;
413 PITEMID_CHILD pidlNewItem
;
416 dwSelectFlags
= SVSI_DESELECTOTHERS
| SVSI_ENSUREVISIBLE
| SVSI_FOCUSED
| SVSI_SELECT
;
418 dwSelectFlags
|= SVSI_EDIT
;
420 /* Notify the view object about the new item */
421 SHChangeNotify(wEventId
, uFlags
, (LPCVOID
) pszName
, NULL
);
426 /* Get a pointer to the shell view */
427 hr
= IUnknown_QueryService(m_pSite
, SID_IFolderView
, IID_PPV_ARG(IShellView
, &lpSV
));
428 if (FAILED_UNEXPECTEDLY(hr
))
431 /* Attempt to get the pidl of the new item */
432 hr
= SHILCreateFromPathW(pszName
, &pidl
, NULL
);
433 if (FAILED_UNEXPECTEDLY(hr
))
436 pidlNewItem
= ILFindLastID(pidl
);
438 hr
= lpSV
->SelectItem(pidlNewItem
, dwSelectFlags
);
445 // Code is duplicated in CDefaultContextMenu
446 HRESULT
CNewMenu::CreateNewFolder(LPCMINVOKECOMMANDINFO lpici
)
448 WCHAR wszPath
[MAX_PATH
];
449 WCHAR wszName
[MAX_PATH
];
450 WCHAR wszNewFolder
[25];
453 /* Get folder path */
454 hr
= SHGetPathFromIDListW(m_pidlFolder
, wszPath
);
455 if (FAILED_UNEXPECTEDLY(hr
))
458 if (!LoadStringW(shell32_hInstance
, IDS_NEWFOLDER
, wszNewFolder
, _countof(wszNewFolder
)))
461 /* Create the name of the new directory */
462 if (!PathYetAnotherMakeUniqueName(wszName
, wszPath
, NULL
, wszNewFolder
))
465 /* Create the new directory and show the appropriate dialog in case of error */
466 if (SHCreateDirectory(lpici
->hwnd
, wszName
) != ERROR_SUCCESS
)
469 /* Show and select the new item in the def view */
470 SelectNewItem(SHCNE_MKDIR
, SHCNF_PATHW
, wszName
, TRUE
);
475 HRESULT
CNewMenu::NewItemByCommand(SHELLNEW_ITEM
*pItem
, LPCWSTR wszPath
)
477 WCHAR wszBuf
[MAX_PATH
];
479 WCHAR wszTemp
[MAX_PATH
];
481 PROCESS_INFORMATION pi
;
483 if (!ExpandEnvironmentStringsW((LPWSTR
)pItem
->pData
, wszBuf
, _countof(wszBuf
)))
485 TRACE("ExpandEnvironmentStrings failed\n");
489 /* Expand command parameter, FIXME: there can be more modifiers */
490 Ptr
= wcsstr(wszBuf
, L
"%1");
494 StringCbPrintfW(wszTemp
, sizeof(wszTemp
), wszBuf
, wszPath
);
503 ZeroMemory(&si
, sizeof(si
));
505 if (CreateProcessW(NULL
, pwszCmd
, NULL
, NULL
, FALSE
, 0, NULL
, NULL
, &si
, &pi
))
507 CloseHandle(pi
.hProcess
);
508 CloseHandle(pi
.hThread
);
513 ERR("Failed to create process\n");
518 HRESULT
CNewMenu::NewItemByNonCommand(SHELLNEW_ITEM
*pItem
, LPWSTR wszName
,
519 DWORD cchNameMax
, LPCWSTR wszPath
)
521 WCHAR wszBuf
[MAX_PATH
];
522 WCHAR wszNewFile
[MAX_PATH
];
523 BOOL bSuccess
= TRUE
;
525 if (!LoadStringW(shell32_hInstance
, FCIDM_SHVIEW_NEW
, wszBuf
, _countof(wszBuf
)))
528 StringCchPrintfW(wszNewFile
, _countof(wszNewFile
), L
"%s %s%s", wszBuf
, pItem
->pwszDesc
, pItem
->pwszExt
);
530 /* Create the name of the new file */
531 if (!PathYetAnotherMakeUniqueName(wszName
, wszPath
, NULL
, wszNewFile
))
534 /* Create new file */
535 HANDLE hFile
= CreateFileW(wszName
, GENERIC_WRITE
, 0, NULL
, CREATE_NEW
, FILE_ATTRIBUTE_NORMAL
, NULL
);
536 if (hFile
!= INVALID_HANDLE_VALUE
)
538 if (pItem
->Type
== SHELLNEW_TYPE_DATA
)
540 /* Write a content */
542 WriteFile(hFile
, pItem
->pData
, pItem
->cbData
, &cbWritten
, NULL
);
553 if (pItem
->Type
== SHELLNEW_TYPE_FILENAME
)
556 if (!CopyFileW((LPWSTR
)pItem
->pData
, wszName
, FALSE
))
557 ERR("Copy file failed: %ls\n", (LPWSTR
)pItem
->pData
);
560 /* Show message if we failed */
563 TRACE("Notifying fs %s\n", debugstr_w(wszName
));
564 SelectNewItem(SHCNE_CREATE
, SHCNF_PATHW
, wszName
, pItem
!= m_pLinkItem
);
568 StringCbPrintfW(wszBuf
, sizeof(wszBuf
), L
"Cannot create file: %s", wszName
);
569 MessageBoxW(NULL
, wszBuf
, L
"Cannot create file", MB_OK
| MB_ICONERROR
); // FIXME load localized error msg
575 HRESULT
CNewMenu::CreateNewItem(SHELLNEW_ITEM
*pItem
, LPCMINVOKECOMMANDINFO lpcmi
)
578 WCHAR wszPath
[MAX_PATH
], wszName
[MAX_PATH
];
580 /* Get folder path */
581 hr
= SHGetPathFromIDListW(m_pidlFolder
, wszPath
);
582 if (FAILED_UNEXPECTEDLY(hr
))
585 if (pItem
== m_pLinkItem
)
587 NewItemByNonCommand(pItem
, wszName
, _countof(wszName
), wszPath
);
588 NewItemByCommand(pItem
, wszName
);
594 case SHELLNEW_TYPE_COMMAND
:
595 NewItemByCommand(pItem
, wszPath
);
598 case SHELLNEW_TYPE_DATA
:
599 case SHELLNEW_TYPE_FILENAME
:
600 case SHELLNEW_TYPE_NULLFILE
:
601 NewItemByNonCommand(pItem
, wszName
, _countof(wszName
), wszPath
);
604 case SHELLNEW_TYPE_INVALID
:
605 ERR("Invalid type\n");
612 HRESULT STDMETHODCALLTYPE
CNewMenu::SetSite(IUnknown
*pUnkSite
)
618 HRESULT STDMETHODCALLTYPE
CNewMenu::GetSite(REFIID riid
, void **ppvSite
)
620 return m_pSite
->QueryInterface(riid
, ppvSite
);
625 CNewMenu::QueryContextMenu(HMENU hMenu
,
635 TRACE("%p %p %u %u %u %u\n", this,
636 hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
638 if (!LoadStringW(shell32_hInstance
, FCIDM_SHVIEW_NEW
, wszNew
, _countof(wszNew
)))
641 m_hSubMenu
= CreateMenu();
645 cItems
= InsertShellNewItems(m_hSubMenu
, idCmdFirst
, 0);
647 ZeroMemory(&mii
, sizeof(mii
));
648 mii
.cbSize
= sizeof(mii
);
649 mii
.fMask
= MIIM_TYPE
| MIIM_ID
| MIIM_STATE
| MIIM_SUBMENU
;
650 mii
.fType
= MFT_STRING
;
652 mii
.dwTypeData
= wszNew
;
653 mii
.cch
= wcslen(mii
.dwTypeData
);
654 mii
.fState
= MFS_ENABLED
;
655 mii
.hSubMenu
= m_hSubMenu
;
657 if (!InsertMenuItemW(hMenu
, indexMenu
, TRUE
, &mii
))
660 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, cItems
);
665 CNewMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici
)
669 if (m_idCmdFirst
+ LOWORD(lpici
->lpVerb
) == m_idCmdFolder
)
671 hr
= CreateNewFolder(lpici
);
675 SHELLNEW_ITEM
*pItem
= FindItemFromIdOffset(LOWORD(lpici
->lpVerb
));
677 hr
= CreateNewItem(pItem
, lpici
);
680 TRACE("CNewMenu::InvokeCommand %x\n", hr
);
686 CNewMenu::GetCommandString(UINT_PTR idCmd
,
692 FIXME("%p %lu %u %p %p %u\n", this,
693 idCmd
, uType
, pwReserved
, pszName
, cchMax
);
700 CNewMenu::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
707 CNewMenu::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
*plResult
)
713 MEASUREITEMSTRUCT
* lpmis
= reinterpret_cast<MEASUREITEMSTRUCT
*>(lParam
);
714 if (!lpmis
|| lpmis
->CtlType
!= ODT_MENU
)
717 if (lpmis
->itemWidth
< (UINT
)GetSystemMetrics(SM_CXMENUCHECK
))
718 lpmis
->itemWidth
= GetSystemMetrics(SM_CXMENUCHECK
);
719 if (lpmis
->itemHeight
< 16)
720 lpmis
->itemHeight
= 16;
728 DRAWITEMSTRUCT
* lpdis
= reinterpret_cast<DRAWITEMSTRUCT
*>(lParam
);
729 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
732 DWORD id
= LOWORD(lpdis
->itemID
);
734 if (m_idCmdFirst
+ id
== m_idCmdFolder
)
736 hIcon
= m_hIconFolder
;
738 else if (m_idCmdFirst
+ id
== m_idCmdLink
)
744 SHELLNEW_ITEM
*pItem
= FindItemFromIdOffset(id
);
746 hIcon
= pItem
->hIcon
;
752 DrawIconEx(lpdis
->hDC
,
754 lpdis
->rcItem
.top
+ (lpdis
->rcItem
.bottom
- lpdis
->rcItem
.top
- 16) / 2,
769 CNewMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder
,
770 IDataObject
*pdtobj
, HKEY hkeyProgID
)
772 m_pidlFolder
= ILClone(pidlFolder
);
774 /* Load folder and shortcut icons */
775 m_hIconFolder
= (HICON
)LoadImage(shell32_hInstance
, MAKEINTRESOURCE(IDI_SHELL_FOLDER
), IMAGE_ICON
, 16, 16, LR_SHARED
);
776 m_hIconLink
= (HICON
)LoadImage(shell32_hInstance
, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT
), IMAGE_ICON
, 16, 16, LR_SHARED
);