Including the "new folder" functionality. Tested with Explorer++.
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/shell32/new_menu.c | 47 +++++++++++++++++++++++++++++++++--- dlls/shell32/shell32.rc | 2 ++ dlls/shell32/shell32_main.c | 13 ++++++++++ dlls/shell32/shell32_main.h | 2 ++ dlls/shell32/shlview_cmenu.c | 10 +------- dlls/shell32/shresdef.h | 2 ++ 6 files changed, 63 insertions(+), 13 deletions(-)
diff --git a/dlls/shell32/new_menu.c b/dlls/shell32/new_menu.c index 87ea93f9964..6a4d54e1b73 100644 --- a/dlls/shell32/new_menu.c +++ b/dlls/shell32/new_menu.c @@ -20,6 +20,10 @@
#include "shobjidl.h"
+#include "shell32_main.h" +#include "shresdef.h" +#include "pidl.h" + #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(shell); @@ -30,6 +34,8 @@ struct new_menu IContextMenu3 IContextMenu3_iface; IObjectWithSite IObjectWithSite_iface; LONG refcount; + + ITEMIDLIST *pidl; };
static struct new_menu *impl_from_IShellExtInit(IShellExtInit *iface) @@ -80,7 +86,10 @@ static ULONG WINAPI ext_init_Release(IShellExtInit *iface) TRACE("%p decreasing refcount to %lu.\n", menu, refcount);
if (!refcount) + { + ILFree(menu->pidl); free(menu); + }
return refcount; } @@ -91,6 +100,10 @@ static HRESULT WINAPI ext_init_Initialize(IShellExtInit *iface, LPCITEMIDLIST pi
TRACE("menu %p, pidl %p, obj %p, key %p.\n", menu, pidl, obj, key);
+ if (!pidl) + return E_INVALIDARG; + + menu->pidl = ILClone(pidl); return S_OK; }
@@ -131,10 +144,36 @@ static ULONG WINAPI context_menu_Release(IContextMenu3 *iface) static HRESULT WINAPI context_menu_QueryContextMenu(IContextMenu3 *iface, HMENU hmenu, UINT index, UINT min_id, UINT max_id, UINT flags) { - FIXME("iface %p, hmenu %p, index %u, min_id %u, max_id %u, flags %#x, stub!\n", - iface, hmenu, index, min_id, max_id, flags); + struct new_menu *menu = impl_from_IContextMenu3(iface); + MENUITEMINFOW info; + WCHAR *new_string; + HMENU submenu;
- return E_NOTIMPL; + TRACE("menu %p, hmenu %p, index %u, min_id %u, max_id %u, flags %#x.\n", + menu, hmenu, index, min_id, max_id, flags); + + if (!_ILIsFolder(ILFindLastID(menu->pidl)) && !_ILIsDrive(ILFindLastID(menu->pidl))) + { + TRACE("Not returning a New item for this pidl type.\n"); + return S_OK; + } + + submenu = CreatePopupMenu(); + new_string = shell_get_resource_string(IDS_NEW_MENU); + + info.cbSize = sizeof(info); + info.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_SUBMENU; + info.dwTypeData = new_string; + info.fState = MFS_ENABLED; + info.wID = min_id; + info.fType = MFT_STRING; + info.hSubMenu = submenu; + InsertMenuItemW(hmenu, 0, MF_BYPOSITION, &info); + + free(new_string); + + /* Native doesn't return the highest ID; it instead just returns 0x40. */ + return MAKE_HRESULT(SEVERITY_SUCCESS, 0, 0x40); }
static HRESULT WINAPI context_menu_InvokeCommand(IContextMenu3 *iface, CMINVOKECOMMANDINFO *info) @@ -238,7 +277,7 @@ HRESULT WINAPI new_menu_create(IUnknown *outer, REFIID iid, void **out) if (outer) return CLASS_E_NOAGGREGATION;
- if (!(menu = malloc(sizeof(*menu)))) + if (!(menu = calloc(1, sizeof(*menu)))) return E_OUTOFMEMORY;
menu->IShellExtInit_iface.lpVtbl = &ext_init_vtbl; diff --git a/dlls/shell32/shell32.rc b/dlls/shell32/shell32.rc index a50fab6815d..d70e4cc3ad9 100644 --- a/dlls/shell32/shell32.rc +++ b/dlls/shell32/shell32.rc @@ -157,6 +157,8 @@ STRINGTABLE IDS_VIEW_LIST "&List" IDS_VIEW_DETAILS "&Details"
+ IDS_NEW_MENU "Ne&w" + IDS_VERB_EXPLORE "E&xplore" IDS_VERB_OPEN "&Open" IDS_VERB_PRINT "&Print" diff --git a/dlls/shell32/shell32_main.c b/dlls/shell32/shell32_main.c index 847587c70c9..c739e1723f7 100644 --- a/dlls/shell32/shell32_main.c +++ b/dlls/shell32/shell32_main.c @@ -1137,6 +1137,19 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID fImpLoad) return TRUE; }
+WCHAR *shell_get_resource_string(UINT id) +{ + const WCHAR *resource; + unsigned int size; + WCHAR *ret; + + size = LoadStringW(shell32_hInstance, id, (WCHAR *)&resource, 0); + ret = malloc((size + 1) * sizeof(WCHAR)); + memcpy(ret, resource, size * sizeof(WCHAR)); + ret[size] = 0; + return ret; +} + /************************************************************************* * DllInstall [SHELL32.@] * diff --git a/dlls/shell32/shell32_main.h b/dlls/shell32/shell32_main.h index 52c7d44f39f..f6fea65344d 100644 --- a/dlls/shell32/shell32_main.h +++ b/dlls/shell32/shell32_main.h @@ -273,4 +273,6 @@ typedef struct BOOL WINAPI StrRetToStrNA(char *, DWORD, STRRET *, const ITEMIDLIST *); BOOL WINAPI StrRetToStrNW(WCHAR *, DWORD, STRRET *, const ITEMIDLIST *);
+WCHAR *shell_get_resource_string(UINT id); + #endif diff --git a/dlls/shell32/shlview_cmenu.c b/dlls/shell32/shlview_cmenu.c index a5592d3c830..4df296ec042 100644 --- a/dlls/shell32/shlview_cmenu.c +++ b/dlls/shell32/shlview_cmenu.c @@ -1030,15 +1030,7 @@ static WCHAR *get_verb_desc(HKEY key, const WCHAR *verb) for (unsigned int i = 0; i < ARRAY_SIZE(builtin_verbs); ++i) { if (!wcscmp(verb, builtin_verbs[i].verb)) - { - const WCHAR *resource; - - size = LoadStringW(shell32_hInstance, builtin_verbs[i].id, (WCHAR *)&resource, 0); - desc = malloc((size + 1) * sizeof(WCHAR)); - memcpy(desc, resource, size * sizeof(WCHAR)); - desc[size] = 0; - return desc; - } + return shell_get_resource_string(builtin_verbs[i].id); }
return wcsdup(verb); diff --git a/dlls/shell32/shresdef.h b/dlls/shell32/shresdef.h index 6aac654568e..58a1c07fa05 100644 --- a/dlls/shell32/shresdef.h +++ b/dlls/shell32/shresdef.h @@ -54,6 +54,8 @@ #define IDS_VIEW_LIST 27 #define IDS_VIEW_DETAILS 28
+#define IDS_NEW_MENU 29 + #define IDS_VERB_EXPLORE 30 #define IDS_VERB_OPEN 31 #define IDS_VERB_PRINT 32
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/shell32/shell32.rc | 2 ++ dlls/shell32/shresdef.h | 1 + loader/wine.inf.in | 3 +++ 3 files changed, 6 insertions(+)
diff --git a/dlls/shell32/shell32.rc b/dlls/shell32/shell32.rc index d70e4cc3ad9..f95ba30585e 100644 --- a/dlls/shell32/shell32.rc +++ b/dlls/shell32/shell32.rc @@ -235,6 +235,8 @@ the folder?"
IDS_NEWFOLDER "New Folder"
+ IDS_NEW_MENU_FOLDER "New &Folder" + IDS_CPANEL_TITLE "Wine Control Panel" IDS_CPANEL_NAME "Name" IDS_CPANEL_DESCRIPTION "Description" diff --git a/dlls/shell32/shresdef.h b/dlls/shell32/shresdef.h index 58a1c07fa05..cee1069142c 100644 --- a/dlls/shell32/shresdef.h +++ b/dlls/shell32/shresdef.h @@ -137,6 +137,7 @@ #define IDS_RECYCLEBIN_OVERWRITEFOLDER 170 #define IDS_RECYCLEBIN_OVERWRITE_CAPTION 171
+#define IDS_NEW_MENU_FOLDER 180
#define IDS_LICENSE 256 #define IDS_LICENSE_CAPTION 257 diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 35644cbd285..11c0e20b3ff 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -293,6 +293,9 @@ HKCR,folder\shell\open\ddeexec,,2,"[ViewFolder("%l", %I, %S)]" HKCR,folder\shell\open\ddeexec,"NoActivateHandler",2,"" HKCR,folder\shell\open\ddeexec\application,,2,"Folders" HKCR,folder\shellex\ContextMenuHandlers,,16 +HKCR,folder\shellnew,"Directory",,"" +HKCR,folder\shellnew,"ItemName",,"@%11%\shell32.dll,-142" +HKCR,folder\shellnew,"MenuText",,"@%11%\shell32.dll,-180" HKCR,hlpfile,,2,"Help File" HKCR,hlpfile\shell\open\command,,2,"""%11%\winhlp32.exe"" ""%1""" HKCR,htmlfile\shell\open\command,,2,"""%11%\winebrowser.exe"" ""%1"""
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/shell32/new_menu.c | 97 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+)
diff --git a/dlls/shell32/new_menu.c b/dlls/shell32/new_menu.c index 6a4d54e1b73..fbbf42ee0a1 100644 --- a/dlls/shell32/new_menu.c +++ b/dlls/shell32/new_menu.c @@ -35,6 +35,12 @@ struct new_menu IObjectWithSite IObjectWithSite_iface; LONG refcount;
+ struct + { + WCHAR *name; + } *items; + size_t item_count; + ITEMIDLIST *pidl; };
@@ -87,6 +93,9 @@ static ULONG WINAPI ext_init_Release(IShellExtInit *iface)
if (!refcount) { + for (unsigned int i = 0; i < menu->item_count; ++i) + free(menu->items[i].name); + free(menu->items); ILFree(menu->pidl); free(menu); } @@ -141,6 +150,84 @@ static ULONG WINAPI context_menu_Release(IContextMenu3 *iface) return IShellExtInit_Release(&menu->IShellExtInit_iface); }
+static WCHAR *load_mui_string(HKEY key, const WCHAR *value) +{ + WCHAR *string; + DWORD size; + + if (RegLoadMUIStringW(key, value, NULL, 0, &size, 0, NULL) != ERROR_MORE_DATA) + return NULL; + string = malloc(size + sizeof(WCHAR)); + RegLoadMUIStringW(key, value, string, size + sizeof(WCHAR), NULL, 0, NULL); + string[size / sizeof(WCHAR)] = 0; + return string; +} + +static void add_menu_item(struct new_menu *menu, HMENU hmenu, const WCHAR *ext, UINT id) +{ + HKEY ext_key, shellnew_key, config_key; + WCHAR *menu_text, *item_name; + MENUITEMINFOW info; + DWORD ret; + + if ((ret = RegOpenKeyExW(HKEY_CLASSES_ROOT, ext, 0, KEY_READ, &ext_key))) + { + ERR("Failed to open %s, error %lu.\n", debugstr_w(ext), ret); + return; + } + + if ((ret = RegOpenKeyExW(ext_key, L"ShellNew", 0, KEY_READ, &shellnew_key))) + { + ERR("Failed to open ShellNew key, error %lu.\n", ret); + RegCloseKey(ext_key); + return; + } + RegCloseKey(ext_key); + + if (RegQueryValueExW(shellnew_key, L"Directory", NULL, NULL, NULL, NULL)) + { + FIXME("Ignoring non-directory item for extension %s.\n", debugstr_w(ext)); + RegCloseKey(shellnew_key); + return; + } + + if (!RegOpenKeyExW(shellnew_key, L"Config", 0, KEY_READ, &config_key)) + { + FIXME("Ignoring Config key for extension %s.\n", debugstr_w(ext)); + RegCloseKey(config_key); + } + + if (!(item_name = load_mui_string(shellnew_key, L"ItemName"))) + { + ERR("Missing ItemName value for extension %s.\n", debugstr_w(ext)); + RegCloseKey(shellnew_key); + return; + } + + if (!(menu_text = load_mui_string(shellnew_key, L"MenuText"))) + { + ERR("Missing MenuText value for extension %s.\n", debugstr_w(ext)); + free(item_name); + RegCloseKey(shellnew_key); + return; + } + + info.cbSize = sizeof(info); + info.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STATE | MIIM_STRING; + info.dwTypeData = menu_text; + info.fState = MFS_ENABLED; + info.wID = id; + info.fType = MFT_STRING; + InsertMenuItemW(hmenu, menu->item_count, MF_BYPOSITION, &info); + + menu->items = realloc(menu->items, (menu->item_count + 1) * sizeof(*menu->items)); + menu->items[menu->item_count].name = item_name; + ++menu->item_count; + + free(menu_text); + RegCloseKey(shellnew_key); +} + static HRESULT WINAPI context_menu_QueryContextMenu(IContextMenu3 *iface, HMENU hmenu, UINT index, UINT min_id, UINT max_id, UINT flags) { @@ -161,6 +248,16 @@ static HRESULT WINAPI context_menu_QueryContextMenu(IContextMenu3 *iface, submenu = CreatePopupMenu(); new_string = shell_get_resource_string(IDS_NEW_MENU);
+ /* Native apparently hardcodes "Folder" and "Briefcase". + * The remaining entries come from scanning all extension registry keys + * (not the file types). Then, for e.g. .txt, it looks up a ShellNew key in + * both .txt/txtfile [possibly an accident] and .txt itself. + * + * FIXME: For now only implement Folder. We don't currently have any other + * builtin verbs anyway. */ + + add_menu_item(menu, submenu, L"Folder", min_id + 1); + info.cbSize = sizeof(info); info.fMask = MIIM_ID | MIIM_FTYPE | MIIM_STATE | MIIM_STRING | MIIM_SUBMENU; info.dwTypeData = new_string;
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/shell32/new_menu.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/dlls/shell32/new_menu.c b/dlls/shell32/new_menu.c index fbbf42ee0a1..95d0bd90679 100644 --- a/dlls/shell32/new_menu.c +++ b/dlls/shell32/new_menu.c @@ -275,9 +275,41 @@ static HRESULT WINAPI context_menu_QueryContextMenu(IContextMenu3 *iface,
static HRESULT WINAPI context_menu_InvokeCommand(IContextMenu3 *iface, CMINVOKECOMMANDINFO *info) { - FIXME("iface %p, info %p, stub!\n", iface, info); + struct new_menu *menu = impl_from_IContextMenu3(iface); + WCHAR path[MAX_PATH], name[MAX_PATH]; + unsigned int id;
- return E_NOTIMPL; + TRACE("menu %p, info %p.\n", menu, info); + + id = (UINT_PTR)info->lpVerb - 1; + + if (id >= menu->item_count) + { + ERR("Invalid verb %p.\n", info->lpVerb); + return E_FAIL; + } + + if (!SHGetPathFromIDListW(menu->pidl, path)) + { + ERR("Failed to get path.\n"); + return E_FAIL; + } + + for (unsigned int i = 0;; ++i) + { + if (!i) + swprintf(name, ARRAY_SIZE(name), L"%s/%s", path, menu->items[id].name); + else + swprintf(name, ARRAY_SIZE(name), L"%s/%s (%u)", path, menu->items[id].name, i); + + if (CreateDirectoryW(name, NULL)) + return S_OK; + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + WARN("Failed to create %s, error %lu.\n", debugstr_w(name), GetLastError()); + return HRESULT_FROM_WIN32(GetLastError()); + } + } }
static HRESULT WINAPI context_menu_GetCommandString(IContextMenu3 *iface,
From: Zebediah Figura zfigura@codeweavers.com
--- dlls/shell32/tests/shlview.c | 89 ++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 10 deletions(-)
diff --git a/dlls/shell32/tests/shlview.c b/dlls/shell32/tests/shlview.c index 0de7f2768c1..2cacc3b8f87 100644 --- a/dlls/shell32/tests/shlview.c +++ b/dlls/shell32/tests/shlview.c @@ -1478,25 +1478,94 @@ if (0)
static void test_newmenu(void) { - IUnknown *unk, *unk2; + CMINVOKECOMMANDINFO invoke_info = {.cbSize = sizeof(CMINVOKECOMMANDINFO)}; + IObjectWithSite *ows; + WCHAR path[MAX_PATH]; + IShellExtInit *init; + IContextMenu3 *menu; + MENUITEMINFOA info; + ITEMIDLIST *pidl; + IUnknown *unk; + HMENU hmenu; HRESULT hr; + ULONG ref; + int count; + BOOL ret; + + hmenu = CreatePopupMenu();
hr = CoCreateInstance(&CLSID_NewMenu, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void **)&unk); ok(hr == S_OK, "Failed to create NewMenu object, hr %#lx.\n", hr); - - hr = IUnknown_QueryInterface(unk, &IID_IShellExtInit, (void **)&unk2); + hr = IUnknown_QueryInterface(unk, &IID_IShellExtInit, (void **)&init); ok(hr == S_OK, "Failed to get IShellExtInit, hr %#lx.\n", hr); - IUnknown_Release(unk2); + hr = IUnknown_QueryInterface(unk, &IID_IContextMenu3, (void **)&menu); + ok(hr == S_OK, "Failed to get IContextMenu3, hr %#lx.\n", hr); + hr = IUnknown_QueryInterface(unk, &IID_IObjectWithSite, (void **)&ows); + ok(hr == S_OK, "Failed to get IObjectWithSite, hr %#lx.\n", hr); + + GetTempPathW(ARRAY_SIZE(path), path); + hr = SHParseDisplayName(path, NULL, &pidl, 0, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IShellExtInit_Initialize(init, NULL, NULL, NULL); + ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr); + + hr = IShellExtInit_Initialize(init, pidl, NULL, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IContextMenu3_QueryContextMenu(menu, hmenu, 0, 1, 1000, CMF_EXPLORE); + ok(hr == 0x40, "Got hr %#lx.\n", hr); + + count = GetMenuItemCount(hmenu); + ok(count == 1, "Got count %d.\n", count); + + info.cbSize = sizeof(info); + info.fMask = MIIM_ID | MIIM_FTYPE | MIIM_SUBMENU; + ret = GetMenuItemInfoA(hmenu, 0, TRUE, &info); + ok(ret == TRUE, "Got error %lu.\n", GetLastError()); + ok(info.wID == 1, "Got ID %u.\n", info.wID); + ok(!info.fType, "Got type %#x.\n", info.fType); + ok(!!info.hSubMenu, "Got sub-menu %p.\n", info.hSubMenu); + + invoke_info.lpVerb = (const char *)1234; + hr = IContextMenu3_InvokeCommand(menu, &invoke_info); + ok(hr == E_FAIL, "Got hr %#lx.\n", hr);
- hr = IUnknown_QueryInterface(unk, &IID_IContextMenu3, (void **)&unk2); + ILFree(pidl); + IObjectWithSite_Release(ows); + IContextMenu3_Release(menu); + IShellExtInit_Release(init); + ref = IUnknown_Release(unk); + ok(!ref, "Got refcount %ld.\n", ref); + + /* Test with a virtual folder that can't create new items. */ + + hmenu = CreatePopupMenu(); + + hr = CoCreateInstance(&CLSID_NewMenu, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, (void **)&unk); + ok(hr == S_OK, "Failed to create NewMenu object, hr %#lx.\n", hr); + hr = IUnknown_QueryInterface(unk, &IID_IShellExtInit, (void **)&init); + ok(hr == S_OK, "Failed to get IShellExtInit, hr %#lx.\n", hr); + hr = IUnknown_QueryInterface(unk, &IID_IContextMenu3, (void **)&menu); ok(hr == S_OK, "Failed to get IContextMenu3, hr %#lx.\n", hr); - IUnknown_Release(unk2);
- hr = IUnknown_QueryInterface(unk, &IID_IObjectWithSite, (void **)&unk2); - ok(hr == S_OK, "Failed to get IObjectWithSite, hr %#lx.\n", hr); - IUnknown_Release(unk2); + hr = SHGetKnownFolderIDList(&FOLDERID_ComputerFolder, 0, NULL, &pidl); + ok(hr == S_OK, "Got hr %#lx.\n", hr);
- IUnknown_Release(unk); + hr = IShellExtInit_Initialize(init, pidl, NULL, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IContextMenu3_QueryContextMenu(menu, hmenu, 0, 1, 1000, CMF_EXPLORE); + ok(hr == 0, "Got hr %#lx.\n", hr); + + count = GetMenuItemCount(hmenu); + ok(!count, "Got count %d.\n", count); + + ILFree(pidl); + IContextMenu3_Release(menu); + IShellExtInit_Release(init); + ref = IUnknown_Release(unk); + ok(!ref, "Got refcount %ld.\n", ref); }
static void test_folder_flags(void)