1 /*
2  * PROJECT:     ReactOS API tests
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Test for SHCreateFileExtractIconW
5  * COPYRIGHT:   Copyright 2017,2018 Mark Jansen (mark.jansen@reactos.org)
6  */
7 
8 #include "shelltest.h"
9 
10 #include <wincon.h>
11 #include <wingdi.h>
12 
13 ULONG DbgPrint(PCH Format,...);
14 #include <shellutils.h>
15 
16 HRESULT (STDAPICALLTYPE *pSHCreateFileExtractIconW)(LPCWSTR pszFile, DWORD dwFileAttributes, REFIID riid, void **ppv);
17 
18 struct TestData
19 {
20     const WCHAR* Name;
21     DWORD dwFlags;
22 };
23 
24 static TestData IconTests[] =
25 {
26     { L"xxx.zip", FILE_ATTRIBUTE_NORMAL },
27     { L"xxx.zip", FILE_ATTRIBUTE_DIRECTORY },
28     { L"xxx.exe", FILE_ATTRIBUTE_NORMAL },
29     { L"xxx.exe", FILE_ATTRIBUTE_DIRECTORY },
30     { L"xxx.dll", FILE_ATTRIBUTE_NORMAL },
31     { L"xxx.dll", FILE_ATTRIBUTE_DIRECTORY },
32     { L"xxx.txt", FILE_ATTRIBUTE_NORMAL },
33     { L"xxx.txt", FILE_ATTRIBUTE_DIRECTORY },
34     { NULL, FILE_ATTRIBUTE_NORMAL },
35     { NULL, FILE_ATTRIBUTE_DIRECTORY },
36 };
37 
38 struct TestIID
39 {
40     const GUID* IID;
41     HRESULT ExpectedCreate;
42     HRESULT ExpectedQueryInterface;
43 };
44 
45 static TestIID InterfaceTests[] =
46 {
47     { &IID_IDefaultExtractIconInit, E_NOINTERFACE, E_NOINTERFACE },
48     { &IID_IExtractIconW, S_OK, S_OK },
49     { &IID_IExtractIconA, S_OK, S_OK },
50     { &IID_IPersist, E_NOINTERFACE, E_NOINTERFACE },
51     { &IID_IPersistFile, E_NOINTERFACE, E_NOINTERFACE },
52 };
53 
54 
55 static void ExtractOneBitmap(HBITMAP hbm, CComHeapPtr<BYTE>& data, DWORD& size)
56 {
57     HDC hdc = CreateCompatibleDC(NULL);
58     HGDIOBJ obj = SelectObject(hdc, hbm);
59 
60     CComHeapPtr<BITMAPINFO> pInfoBM;
61 
62     pInfoBM.AllocateBytes(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));
63     memset(pInfoBM, 0, sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD));
64     pInfoBM->bmiHeader.biSize = sizeof(pInfoBM->bmiHeader);
65     if (!GetDIBits(hdc, hbm, 0, 0, NULL, pInfoBM, DIB_RGB_COLORS))
66         return;
67 
68     size = pInfoBM->bmiHeader.biSizeImage;
69     data.Allocate(size);
70     GetDIBits(hdc, hbm, 0, pInfoBM->bmiHeader.biHeight, data, pInfoBM, DIB_RGB_COLORS);
71 
72     SelectObject(hdc, obj);
73     DeleteDC(hdc);
74 }
75 
76 static bool GetIconData(HICON icon, CComHeapPtr<BYTE>& colorData, DWORD& colorSize, CComHeapPtr<BYTE>& maskData, DWORD& maskSize)
77 {
78     ICONINFO iconinfo;
79 
80     if (!GetIconInfo(icon, &iconinfo))
81         return false;
82 
83     ExtractOneBitmap(iconinfo.hbmColor, colorData, colorSize);
84     ExtractOneBitmap(iconinfo.hbmMask, maskData, maskSize);
85 
86     DeleteObject(iconinfo.hbmColor);
87     DeleteObject(iconinfo.hbmMask);
88 
89     return true;
90 }
91 
92 
93 START_TEST(SHCreateFileExtractIconW)
94 {
95     WCHAR CurrentModule[MAX_PATH];
96     HMODULE shell32 = LoadLibraryA("shell32.dll");
97     HICON myIcon;
98     pSHCreateFileExtractIconW = (HRESULT (__stdcall *)(LPCWSTR, DWORD, REFIID, void **))GetProcAddress(shell32, "SHCreateFileExtractIconW");
99 
100     /* Show that icons returned are always the same */
101     UINT tryFlags[4] = { 0, GIL_FORSHORTCUT, GIL_OPENICON };
102 
103     CoInitialize(NULL);
104 
105     GetModuleFileNameW(NULL, CurrentModule, _countof(CurrentModule));
106     {
107         SHFILEINFOW shfi;
108         ULONG_PTR firet = SHGetFileInfoW(CurrentModule, 0, &shfi, sizeof(shfi), SHGFI_ICON);
109         myIcon = shfi.hIcon;
110         if (!firet)
111         {
112             skip("Unable to get my own icon\n");
113             return;
114         }
115     }
116 
117     if (!pSHCreateFileExtractIconW)
118     {
119         skip("SHCreateFileExtractIconW not available\n");
120         return;
121     }
122 
123     for (size_t n = 0; n < _countof(InterfaceTests); ++n)
124     {
125         {
126             CComPtr<IUnknown> spUnknown;
127             HRESULT hr = pSHCreateFileExtractIconW(L"test.txt", FILE_ATTRIBUTE_NORMAL, *InterfaceTests[n].IID, (void**)&spUnknown);
128             ok(hr == InterfaceTests[n].ExpectedCreate, "Expected hr to be 0x%lx, was 0x%lx for %u\n", InterfaceTests[n].ExpectedCreate, hr, n);
129         }
130 
131         {
132             CComPtr<IUnknown> spUnknown, spUnknown2;
133             HRESULT hr = pSHCreateFileExtractIconW(L"test.txt", FILE_ATTRIBUTE_NORMAL, IID_PPV_ARG(IUnknown, &spUnknown));
134             ok(hr == S_OK, "Expected hr to be S_OK, was 0x%lx for %u\n", hr, n);
135 
136             hr = spUnknown->QueryInterface(*InterfaceTests[n].IID, (void**)&spUnknown2);
137             ok(hr == InterfaceTests[n].ExpectedQueryInterface, "Expected hr to be 0x%lx, was 0x%lx for %u\n", InterfaceTests[n].ExpectedQueryInterface, hr, n);
138         }
139     }
140 
141     for (size_t n = 0; n < _countof(IconTests); ++n)
142     {
143         TestData& cur = IconTests[n];
144         bool useMyIcon = false;
145 
146         if (cur.Name == NULL)
147         {
148             cur.Name = CurrentModule;
149             useMyIcon = true;
150         }
151 
152         CComPtr<IExtractIconW> spExtract;
153         HRESULT hr = pSHCreateFileExtractIconW(cur.Name, cur.dwFlags, IID_PPV_ARG(IExtractIconW, &spExtract));
154         ok(hr == S_OK, "Expected hr to be S_OK, was 0x%lx for %S(%lx)\n", hr, cur.Name, cur.dwFlags);
155 
156         if (!SUCCEEDED(hr))
157             continue;
158 
159         /* Show that GIL_DEFAULTICON does not work. */
160         {
161             int ilIndex = -1;
162             UINT wFlags = 0xdeaddead;
163             WCHAR Buffer[MAX_PATH];
164 
165             hr = spExtract->GetIconLocation(GIL_DEFAULTICON, Buffer, _countof(Buffer), &ilIndex, &wFlags);
166             ok(hr == S_FALSE, "Expected hr to be S_FALSE, was 0x%lx for %S(0x%lx)\n", hr, cur.Name, cur.dwFlags);
167         }
168 
169 
170         for (UINT idFlags = 0; idFlags < _countof(tryFlags); ++idFlags)
171         {
172             int ilIndex = -1;
173             UINT wFlags = 0xdeaddead;
174             WCHAR Buffer[MAX_PATH];
175 
176             hr = spExtract->GetIconLocation(tryFlags[idFlags], Buffer, _countof(Buffer), &ilIndex, &wFlags);
177             ok(hr == S_OK, "Expected hr to be S_OK, was 0x%lx for %S(0x%lx,0x%x)\n", hr, cur.Name, cur.dwFlags, tryFlags[idFlags]);
178             if (!SUCCEEDED(hr))
179                 continue;
180 
181             ok(wFlags & (GIL_NOTFILENAME|GIL_PERCLASS), "Expected GIL_NOTFILENAME|GIL_PERCLASS to be set for %S(0x%lx,0x%x)\n", cur.Name, cur.dwFlags, tryFlags[idFlags]);
182             ok(!wcscmp(Buffer, L"*"), "Expected '*', was '%S' for %S(0x%lx,0x%x)\n", Buffer, cur.Name, cur.dwFlags, tryFlags[idFlags]);
183 
184             HICON ico;
185             hr = spExtract->Extract(Buffer, ilIndex, &ico, NULL, 0);
186 
187             /* Visualize the icon extracted for whoever is stepping through this code. */
188             HWND console = GetConsoleWindow();
189             SendMessage(console, WM_SETICON, ICON_BIG, (LPARAM)ico);
190             SendMessage(console, WM_SETICON, ICON_SMALL, (LPARAM)ico);
191 
192             CComHeapPtr<BYTE> colorData, maskData;
193             DWORD colorSize = 0, maskSize = 0;
194 
195             GetIconData(ico, colorData, colorSize, maskData, maskSize);
196 
197             if (!colorSize || !maskSize)
198                 continue;
199 
200             SHFILEINFOW shfi;
201             ULONG_PTR firet = SHGetFileInfoW(cur.Name, cur.dwFlags, &shfi, sizeof(shfi), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SYSICONINDEX);
202 
203             if (!firet)
204                 continue;
205 
206             ok(shfi.iIcon == ilIndex, "Expected ilIndex to be 0%x, was 0x%x for %S(0x%lx,0x%x)\n", shfi.iIcon, ilIndex, cur.Name, cur.dwFlags, tryFlags[idFlags]);
207 
208 
209             CComHeapPtr<BYTE> colorDataRef, maskDataRef;
210             DWORD colorSizeRef = 0, maskSizeRef = 0;
211             GetIconData(shfi.hIcon, colorDataRef, colorSizeRef, maskDataRef, maskSizeRef);
212 
213             ok(colorSizeRef == colorSize, "Expected %lu, was %lu for %S(0x%lx,0x%x)\n", colorSizeRef, colorSize, cur.Name, cur.dwFlags, tryFlags[idFlags]);
214             ok(maskSizeRef == maskSize, "Expected %lu, was %lu for %S(0x%lx,0x%x)\n", maskSizeRef, maskSize, cur.Name, cur.dwFlags, tryFlags[idFlags]);
215 
216             if (colorSizeRef == colorSize)
217             {
218                 ok(!memcmp(colorData, colorDataRef, colorSize), "Expected equal colorData for %S(0x%lx,0x%x)\n", cur.Name, cur.dwFlags, tryFlags[idFlags]);
219             }
220 
221             if (maskSizeRef == maskSize)
222             {
223                 ok(!memcmp(maskData, maskDataRef, maskSize), "Expected equal maskData for %S(0x%lx,0x%x)\n", cur.Name, cur.dwFlags, tryFlags[idFlags]);
224             }
225 
226             if (useMyIcon)
227             {
228                 colorDataRef.Free();
229                 maskDataRef.Free();
230                 colorSizeRef = maskSizeRef = 0;
231                 GetIconData(myIcon, colorDataRef, colorSizeRef, maskDataRef, maskSizeRef);
232 
233                 ok(colorSizeRef == colorSize, "Expected %lu, was %lu for %S(0x%lx,0x%x)\n", colorSizeRef, colorSize, cur.Name, cur.dwFlags, tryFlags[idFlags]);
234                 ok(maskSizeRef == maskSize, "Expected %lu, was %lu for %S(0x%lx,0x%x)\n", maskSizeRef, maskSize, cur.Name, cur.dwFlags, tryFlags[idFlags]);
235 
236                 if (colorSizeRef == colorSize)
237                 {
238                     /* In case requested filetype does not match, the exe icon is not used! */
239                     if (cur.dwFlags == FILE_ATTRIBUTE_DIRECTORY)
240                     {
241                         ok(memcmp(colorData, colorDataRef, colorSize), "Expected colorData to be changed for %S(0x%lx,0x%x)\n", cur.Name, cur.dwFlags, tryFlags[idFlags]);
242                     }
243                     else
244                     {
245                         ok(!memcmp(colorData, colorDataRef, colorSize), "Expected equal colorData for %S(0x%lx,0x%x)\n", cur.Name, cur.dwFlags, tryFlags[idFlags]);
246                     }
247                 }
248 
249                 // Mask is not reliable for some reason
250                 //if (maskSizeRef == maskSize)
251                 //{
252                 //    ok(!memcmp(maskData, maskDataRef, maskSize), "Expected equal maskData for %S(0x%lx,0x%lx)\n", cur.Name, cur.dwFlags);
253                 //}
254             }
255         }
256     }
257 }
258