1 /*
2  * Unit tests to document shdocvw's 'Shell Instance Objects' features
3  *
4  * Copyright 2005 Michael Jung
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 /* At least since Windows 2000 it's possible to add FolderShortcut objects
22  * by creating some registry entries. Those objects, which refer to some
23  * point in the filesystem, can be registered in the shell namespace like other
24  * shell namespace extensions. Icons, names and filesystem location can be
25  * configured. This is documented at http://www.virtualplastic.net/html/ui_shell.html
26  * You can also google for a tool called "ShellObjectEditor" by "Tropical
27  * Technologies". This mechanism would be cool for wine, since we could
28  * map GNOME's virtual devices to FolderShortcuts and have them appear in the
29  * file dialogs. These unit tests are meant to document how this mechanism
30  * works on windows.
31  *
32  * Search MSDN for "Creating Shell Extensions with Shell Instance Objects" for
33  * more documentation.*/
34 
35 #include <stdarg.h>
36 
37 #define COBJMACROS
38 
39 #include "windef.h"
40 #include "winbase.h"
41 #include "winreg.h"
42 
43 #include "initguid.h"
44 #include "shlobj.h"
45 #include "shobjidl.h"
46 #include "shlguid.h"
47 #include "ole2.h"
48 
49 #include "wine/test.h"
50 
51 /* The following definitions and helper functions are meant to make the de-/registration
52  * of the various necessary registry keys easier. */
53 
54 struct registry_value {
55     const char  *szName;
56     const DWORD dwType;
57     const char  *szValue;
58     const DWORD dwValue;
59 };
60 
61 #define REG_VALUE_ADDR(x) ((x->dwType==REG_SZ)?(const BYTE *)x->szValue:(const BYTE *)&x->dwValue)
62 #define REG_VALUE_SIZE(x) ((x->dwType==REG_SZ)?strlen(x->szValue)+1:sizeof(DWORD))
63 
64 struct registry_key {
65     const char                  *szName;
66     const struct registry_value *pValues;
67     const unsigned int          cValues;
68     const struct registry_key   *pSubKeys;
69     const unsigned int          cSubKeys;
70 };
71 
72 static const struct registry_value ShellFolder_values[] = {
73     { "WantsFORPARSING", REG_SZ,    "",   0          },
74     { "Attributes",      REG_DWORD, NULL, 0xF8000100 }
75 };
76 
77 static const struct registry_value Instance_values[] = {
78     { "CLSID", REG_SZ, "{0AFACED1-E828-11D1-9187-B532F1E9575D}", 0 }
79 };
80 
81 static const struct registry_value InitPropertyBag_values[] = {
82     { "Attributes", REG_DWORD, NULL,   0x00000015 },
83     { "Target",     REG_SZ,    "C:\\", 0          }
84 };
85 
86 static const struct registry_key Instance_keys[] = {
87     { "InitPropertyBag", InitPropertyBag_values, 2, NULL, 0 }
88 };
89 
90 static const struct registry_value InProcServer32_values[] = {
91     { NULL,             REG_SZ, "shdocvw.dll", 0 },
92     { "ThreadingModel", REG_SZ, "Apartment",   0 }
93 };
94 
95 static const struct registry_value DefaultIcon_values[] = {
96     { NULL, REG_SZ,"shell32.dll,8", 0 }
97 };
98 
99 static const struct registry_key ShortcutCLSID_keys[] = {
100     { "DefaultIcon",    DefaultIcon_values,    1, NULL,          0 },
101     { "InProcServer32", InProcServer32_values, 2, NULL,          0 },
102     { "Instance",       Instance_values,       1, Instance_keys, 1 },
103     { "ShellFolder",    ShellFolder_values,    2, NULL,          0 }
104 };
105 
106 static const struct registry_value ShortcutCLSID_values[] = {
107     { NULL, REG_SZ, "WineTest", 0 }
108 };
109 
110 static const struct registry_key HKEY_CLASSES_ROOT_keys[] = {
111     { "CLSID\\{9B352EBF-2765-45C1-B4C6-85CC7F7ABC64}", ShortcutCLSID_values, 1, ShortcutCLSID_keys, 4}
112 };
113 
114 /* register_keys - helper function, which recursively creates the registry keys and values in
115  * parameter 'keys' in the registry under hRootKey. */
116 static BOOL register_keys(HKEY hRootKey, const struct registry_key *keys, unsigned int numKeys) {
117     HKEY hKey;
118     unsigned int iKey, iValue;
119 
120     for (iKey = 0; iKey < numKeys; iKey++) {
121         if (ERROR_SUCCESS == RegCreateKeyExA(hRootKey, keys[iKey].szName, 0, NULL, 0,
122                                              KEY_WRITE, NULL, &hKey, NULL))
123         {
124             for (iValue = 0; iValue < keys[iKey].cValues; iValue++) {
125                 const struct registry_value * value = &keys[iKey].pValues[iValue];
126                 if (ERROR_SUCCESS != RegSetValueExA(hKey, value->szName, 0, value->dwType,
127                                                     REG_VALUE_ADDR(value), REG_VALUE_SIZE(value)))
128                 {
129                     RegCloseKey(hKey);
130                     return FALSE;
131                 }
132             }
133 
134             if (!register_keys(hKey, keys[iKey].pSubKeys, keys[iKey].cSubKeys)) {
135                 RegCloseKey(hKey);
136                 return FALSE;
137             }
138 
139             RegCloseKey(hKey);
140         }
141     }
142 
143     return TRUE;
144 }
145 
146 /* unregister_keys - clean up after register_keys */
147 static void unregister_keys(HKEY hRootKey, const struct registry_key *keys, unsigned int numKeys) {
148     HKEY hKey;
149     unsigned int iKey;
150 
151     for (iKey = 0; iKey < numKeys; iKey++) {
152         if (ERROR_SUCCESS == RegOpenKeyExA(hRootKey, keys[iKey].szName, 0, DELETE, &hKey)) {
153             unregister_keys(hKey, keys[iKey].pSubKeys, keys[iKey].cSubKeys);
154             RegCloseKey(hKey);
155         }
156         RegDeleteKeyA(hRootKey, keys[iKey].szName);
157     }
158 }
159 
160 static void test_ShortcutFolder(void) {
161     LPSHELLFOLDER pDesktopFolder, pWineTestFolder;
162     IPersistFolder3 *pWineTestPersistFolder;
163     LPITEMIDLIST pidlWineTestFolder, pidlCurFolder;
164     HRESULT hr;
165     CLSID clsid;
166     const CLSID CLSID_WineTest =
167         { 0x9b352ebf, 0x2765, 0x45c1, { 0xb4, 0xc6, 0x85, 0xcc, 0x7f, 0x7a, 0xbc, 0x64 } };
168     WCHAR wszWineTestFolder[] = {
169         ':',':','{','9','B','3','5','2','E','B','F','-','2','7','6','5','-','4','5','C','1','-',
170         'B','4','C','6','-','8','5','C','C','7','F','7','A','B','C','6','4','}',0 };
171 
172     /* First, we register all the necessary registry keys/values for our 'WineTest'
173      * shell object. */
174     register_keys(HKEY_CLASSES_ROOT, HKEY_CLASSES_ROOT_keys, 1);
175 
176     hr = SHGetDesktopFolder(&pDesktopFolder);
177     ok (SUCCEEDED(hr), "SHGetDesktopFolder failed! hr = %08x\n", hr);
178     if (FAILED(hr)) goto cleanup;
179 
180     /* Convert the wszWineTestFolder string to an ITEMIDLIST. */
181     hr = IShellFolder_ParseDisplayName(pDesktopFolder, NULL, NULL, wszWineTestFolder, NULL,
182                                        &pidlWineTestFolder, NULL);
183     todo_wine
184     {
185         ok (hr == HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER),
186             "Expected %08x, got %08x\n", HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER), hr);
187     }
188     if (FAILED(hr)) {
189         IShellFolder_Release(pDesktopFolder);
190         goto cleanup;
191     }
192 
193     /* FIXME: these tests are never run */
194 
195     /* Bind to a WineTest folder object. There has to be some support for this in shdocvw.dll.
196      * This isn't implemented in wine yet.*/
197     hr = IShellFolder_BindToObject(pDesktopFolder, pidlWineTestFolder, NULL, &IID_IShellFolder,
198                                    (LPVOID*)&pWineTestFolder);
199     IShellFolder_Release(pDesktopFolder);
200     ILFree(pidlWineTestFolder);
201     ok (SUCCEEDED(hr), "IShellFolder::BindToObject(WineTestFolder) failed! hr = %08x\n", hr);
202     if (FAILED(hr)) goto cleanup;
203 
204     hr = IShellFolder_QueryInterface(pWineTestFolder, &IID_IPersistFolder3, (LPVOID*)&pWineTestPersistFolder);
205     ok (SUCCEEDED(hr), "IShellFolder::QueryInterface(IPersistFolder3) failed! hr = %08x\n", hr);
206     IShellFolder_Release(pWineTestFolder);
207     if (FAILED(hr)) goto cleanup;
208 
209     /* The resulting folder object has the FolderShortcut CLSID, instead of its own. */
210     hr = IPersistFolder3_GetClassID(pWineTestPersistFolder, &clsid);
211     ok (SUCCEEDED(hr), "IPersist::GetClassID failed! hr = %08x\n", hr);
212     ok (IsEqualCLSID(&CLSID_FolderShortcut, &clsid), "GetClassId returned wrong CLSID!\n");
213 
214     pidlCurFolder = (LPITEMIDLIST)0xdeadbeef;
215     hr = IPersistFolder3_GetCurFolder(pWineTestPersistFolder, &pidlCurFolder);
216     ok (SUCCEEDED(hr), "IPersistFolder3::GetCurFolder failed! hr = %08x\n", hr);
217     ok (pidlCurFolder->mkid.cb == 20 && ((LPSHITEMID)((BYTE*)pidlCurFolder+20))->cb == 0 &&
218         IsEqualCLSID(&CLSID_WineTest, (REFCLSID)((LPBYTE)pidlCurFolder+4)),
219         "GetCurFolder returned unexpected pidl!\n");
220 
221     ILFree(pidlCurFolder);
222     IPersistFolder3_Release(pWineTestPersistFolder);
223 
224 cleanup:
225     unregister_keys(HKEY_CLASSES_ROOT, HKEY_CLASSES_ROOT_keys, 1);
226 }
227 
228 START_TEST(shortcut)
229 {
230     OleInitialize(NULL);
231     test_ShortcutFolder();
232     OleUninitialize();
233 }
234