|  | /* | 
|  | * Unit tests to document shdocvw's 'Shell Instance Objects' features | 
|  | * | 
|  | * Copyright 2005 Michael Jung | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library is distributed in the hope that it will be useful, | 
|  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, write to the Free Software | 
|  | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | */ | 
|  |  | 
|  | /* At least since Windows 2000 it's possible to add FolderShortcut objects | 
|  | * by creating some registry entries. Those objects, which refer to some | 
|  | * point in the filesystem, can be registered in the shell namespace like other | 
|  | * shell namespace extensions. Icons, names and filesystem location can be | 
|  | * configured. This is documented at http://www.virtualplastic.net/html/ui_shell.html | 
|  | * You can also google for a tool called "ShellObjectEditor" by "Tropical | 
|  | * Technologies". This mechanism would be cool for wine, since we could | 
|  | * map Gnome's virtual devices to FolderShortcuts and have them appear in the | 
|  | * file dialogs. These unit tests are meant to document how this mechanism | 
|  | * works on windows. | 
|  | * | 
|  | * Search MSDN for "Creating Shell Extensions with Shell Instance Objects" for | 
|  | * more documentation.*/ | 
|  |  | 
|  | #include <stdarg.h> | 
|  |  | 
|  | #define COBJMACROS | 
|  |  | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "winreg.h" | 
|  |  | 
|  | #include "shlobj.h" | 
|  | #include "shobjidl.h" | 
|  | #include "shlguid.h" | 
|  | #include "ole2.h" | 
|  |  | 
|  | #include "wine/test.h" | 
|  |  | 
|  | /* The following definitions and helper functions are meant to make the de-/registration | 
|  | * of the various necessary registry keys easier. */ | 
|  |  | 
|  | struct registry_value { | 
|  | const char  *szName; | 
|  | const DWORD dwType; | 
|  | const char  *szValue; | 
|  | const DWORD dwValue; | 
|  | }; | 
|  |  | 
|  | #define REG_VALUE_ADDR(x) ((x->dwType==REG_SZ)?(const BYTE *)x->szValue:(const BYTE *)&x->dwValue) | 
|  | #define REG_VALUE_SIZE(x) ((x->dwType==REG_SZ)?strlen(x->szValue)+1:sizeof(DWORD)) | 
|  |  | 
|  | struct registry_key { | 
|  | const char                  *szName; | 
|  | const struct registry_value *pValues; | 
|  | const unsigned int          cValues; | 
|  | const struct registry_key   *pSubKeys; | 
|  | const unsigned int          cSubKeys; | 
|  | }; | 
|  |  | 
|  | static const struct registry_value ShellFolder_values[] = { | 
|  | { "WantsFORPARSING", REG_SZ,    "",   0          }, | 
|  | { "Attributes",      REG_DWORD, NULL, 0xF8000100 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_value Instance_values[] = { | 
|  | { "CLSID", REG_SZ, "{0AFACED1-E828-11D1-9187-B532F1E9575D}", 0 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_value InitPropertyBag_values[] = { | 
|  | { "Attributes", REG_DWORD, NULL,   0x00000015 }, | 
|  | { "Target",     REG_SZ,    "C:\\", 0          } | 
|  | }; | 
|  |  | 
|  | static const struct registry_key Instance_keys[] = { | 
|  | { "InitPropertyBag", InitPropertyBag_values, 2, NULL, 0 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_value InProcServer32_values[] = { | 
|  | { NULL,             REG_SZ, "shdocvw.dll", 0 }, | 
|  | { "ThreadingModel", REG_SZ, "Apartment",   0 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_value DefaultIcon_values[] = { | 
|  | { NULL, REG_SZ,"shell32.dll,8", 0 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_key ShortcutCLSID_keys[] = { | 
|  | { "DefaultIcon",    DefaultIcon_values,    1, NULL,          0 }, | 
|  | { "InProcServer32", InProcServer32_values, 2, NULL,          0 }, | 
|  | { "Instance",       Instance_values,       1, Instance_keys, 1 }, | 
|  | { "ShellFolder",    ShellFolder_values,    2, NULL,          0 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_value ShortcutCLSID_values[] = { | 
|  | { NULL, REG_SZ, "WineTest", 0 } | 
|  | }; | 
|  |  | 
|  | static const struct registry_key HKEY_CLASSES_ROOT_keys[] = { | 
|  | { "CLSID\\{9B352EBF-2765-45C1-B4C6-85CC7F7ABC64}", ShortcutCLSID_values, 1, ShortcutCLSID_keys, 4} | 
|  | }; | 
|  |  | 
|  | /* register_keys - helper function, which recursively creates the registry keys and values in | 
|  | * parameter 'keys' in the registry under hRootKey. */ | 
|  | static BOOL register_keys(HKEY hRootKey, const struct registry_key *keys, unsigned int numKeys) { | 
|  | HKEY hKey; | 
|  | unsigned int iKey, iValue; | 
|  |  | 
|  | for (iKey = 0; iKey < numKeys; iKey++) { | 
|  | if (ERROR_SUCCESS == RegCreateKeyExA(hRootKey, keys[iKey].szName, 0, NULL, 0, | 
|  | KEY_WRITE, NULL, &hKey, NULL)) | 
|  | { | 
|  | for (iValue = 0; iValue < keys[iKey].cValues; iValue++) { | 
|  | const struct registry_value * value = &keys[iKey].pValues[iValue]; | 
|  | if (ERROR_SUCCESS != RegSetValueExA(hKey, value->szName, 0, value->dwType, | 
|  | REG_VALUE_ADDR(value), REG_VALUE_SIZE(value))) | 
|  | { | 
|  | RegCloseKey(hKey); | 
|  | return FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!register_keys(hKey, keys[iKey].pSubKeys, keys[iKey].cSubKeys)) { | 
|  | RegCloseKey(hKey); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | RegCloseKey(hKey); | 
|  | } | 
|  | } | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* unregister_keys - clean up after register_keys */ | 
|  | static void unregister_keys(HKEY hRootKey, const struct registry_key *keys, unsigned int numKeys) { | 
|  | HKEY hKey; | 
|  | unsigned int iKey; | 
|  |  | 
|  | for (iKey = 0; iKey < numKeys; iKey++) { | 
|  | if (ERROR_SUCCESS == RegOpenKeyExA(hRootKey, keys[iKey].szName, 0, DELETE, &hKey)) { | 
|  | unregister_keys(hKey, keys[iKey].pSubKeys, keys[iKey].cSubKeys); | 
|  | RegCloseKey(hKey); | 
|  | } | 
|  | RegDeleteKeyA(hRootKey, keys[iKey].szName); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void test_ShortcutFolder(void) { | 
|  | LPSHELLFOLDER pDesktopFolder, pWineTestFolder; | 
|  | IPersistFolder3 *pWineTestPersistFolder; | 
|  | LPITEMIDLIST pidlWineTestFolder, pidlCurFolder; | 
|  | HRESULT hr; | 
|  | CLSID clsid; | 
|  | const CLSID CLSID_WineTest = | 
|  | { 0x9b352ebf, 0x2765, 0x45c1, { 0xb4, 0xc6, 0x85, 0xcc, 0x7f, 0x7a, 0xbc, 0x64 } }; | 
|  | WCHAR wszWineTestFolder[] = { | 
|  | ':',':','{','9','B','3','5','2','E','B','F','-','2','7','6','5','-','4','5','C','1','-', | 
|  | 'B','4','C','6','-','8','5','C','C','7','F','7','A','B','C','6','4','}',0 }; | 
|  |  | 
|  | /* First, we register all the necessary registry keys/values for our 'WineTest' | 
|  | * shell object. */ | 
|  | register_keys(HKEY_CLASSES_ROOT, HKEY_CLASSES_ROOT_keys, 1); | 
|  |  | 
|  | hr = SHGetDesktopFolder(&pDesktopFolder); | 
|  | ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr); | 
|  | if (FAILED(hr)) goto cleanup; | 
|  |  | 
|  | /* Convert the wszWineTestFolder string to an ITEMIDLIST. */ | 
|  | hr = IShellFolder_ParseDisplayName(pDesktopFolder, NULL, NULL, wszWineTestFolder, NULL, | 
|  | &pidlWineTestFolder, NULL); | 
|  | todo_wine | 
|  | { | 
|  | ok (hr == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER), | 
|  | "Expected %08x, got %08x\n", HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER), hr); | 
|  | } | 
|  | if (FAILED(hr)) { | 
|  | IShellFolder_Release(pDesktopFolder); | 
|  | goto cleanup; | 
|  | } | 
|  |  | 
|  | /* FIXME: these tests are never run */ | 
|  |  | 
|  | /* Bind to a WineTest folder object. There has to be some support for this in shdocvw.dll. | 
|  | * This isn't implemented in wine yet.*/ | 
|  | hr = IShellFolder_BindToObject(pDesktopFolder, pidlWineTestFolder, NULL, &IID_IShellFolder, | 
|  | (LPVOID*)&pWineTestFolder); | 
|  | IShellFolder_Release(pDesktopFolder); | 
|  | ILFree(pidlWineTestFolder); | 
|  | ok (SUCCEEDED(hr), "IShellFolder::BindToObject(WineTestFolder) failed! hr = %08x\n", hr); | 
|  | if (FAILED(hr)) goto cleanup; | 
|  |  | 
|  | hr = IShellFolder_QueryInterface(pWineTestFolder, &IID_IPersistFolder3, (LPVOID*)&pWineTestPersistFolder); | 
|  | ok (SUCCEEDED(hr), "IShellFolder::QueryInterface(IPersistFolder3) failed! hr = %08x\n", hr); | 
|  | IShellFolder_Release(pWineTestFolder); | 
|  | if (FAILED(hr)) goto cleanup; | 
|  |  | 
|  | /* The resulting folder object has the FolderShortcut CLSID, instead of it's own. */ | 
|  | hr = IPersistFolder3_GetClassID(pWineTestPersistFolder, &clsid); | 
|  | ok (SUCCEEDED(hr), "IPersist::GetClassID failed! hr = %08x\n", hr); | 
|  | ok (IsEqualCLSID(&CLSID_FolderShortcut, &clsid), "GetClassId returned wrong CLSID!\n"); | 
|  |  | 
|  | pidlCurFolder = (LPITEMIDLIST)0xdeadbeef; | 
|  | hr = IPersistFolder3_GetCurFolder(pWineTestPersistFolder, &pidlCurFolder); | 
|  | ok (SUCCEEDED(hr), "IPersistFolder3::GetCurFolder failed! hr = %08x\n", hr); | 
|  | ok (pidlCurFolder->mkid.cb == 20 && ((LPSHITEMID)((BYTE*)pidlCurFolder+20))->cb == 0 && | 
|  | IsEqualCLSID(&CLSID_WineTest, (REFCLSID)((LPBYTE)pidlCurFolder+4)), | 
|  | "GetCurFolder returned unexpected pidl!\n"); | 
|  |  | 
|  | IPersistFolder3_Release(pWineTestPersistFolder); | 
|  |  | 
|  | cleanup: | 
|  | unregister_keys(HKEY_CLASSES_ROOT, HKEY_CLASSES_ROOT_keys, 1); | 
|  | } | 
|  |  | 
|  | START_TEST(shortcut) | 
|  | { | 
|  | OleInitialize(NULL); | 
|  | test_ShortcutFolder(); | 
|  | OleUninitialize(); | 
|  | } |