1 /*
2  * Copyright 2003, 2004, 2005 Martin Fuchs
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 
20  //
21  // Explorer clone
22  //
23  // shellclasses.cpp
24  //
25  // C++ wrapper classes for COM interfaces and shell objects
26  //
27  // Martin Fuchs, 20.07.2003
28  //
29 
30 
31 #include <precomp.h>
32 
33 
34 #ifdef _MS_VER
35 #pragma comment(lib, "shell32")	// link to shell32.dll
36 #endif
37 
38 
39  // work around GCC's wide string constant bug
40 #ifdef __GNUC__
41 const LPCTSTR sCFSTR_SHELLIDLIST = TEXT("Shell IDList Array");
42 #endif
43 
44 
45  // helper functions for string copying
46 
47 /*LPSTR strcpyn(LPSTR dest, LPCSTR source, size_t count)
48 {
49 	LPCSTR s;
50 	LPSTR d = dest;
51 
52 	for(s=source; count&&(*d++=*s++); )
53 		count--;
54 
55 	return dest;
56 }
57 
58 LPWSTR wcscpyn(LPWSTR dest, LPCWSTR source, size_t count)
59 {
60 	LPCWSTR s;
61 	LPWSTR d = dest;
62 
63 	for(s=source; count&&(*d++=*s++); )
64 		count--;
65 
66 	return dest;
67 }*/
68 
69 
toString() const70 String COMException::toString() const
71 {
72 	TCHAR msg[4*BUFFER_LEN];
73 #ifdef __STDC_WANT_SECURE_LIB__
74 	int l = 4*BUFFER_LEN;
75 #endif
76 	LPTSTR p = msg;
77 
78 	int n = _stprintf_s2(p, l, TEXT("%s\nContext: %s"), super::ErrorMessage(), (LPCTSTR)_context.toString());
79 	p += n;
80 #ifdef __STDC_WANT_SECURE_LIB__
81 	l -= n;
82 #endif
83 
84 	if (_file)
85 		p += _stprintf_s2(p, l, TEXT("\nLocation: %hs:%d"), _file, _line);
86 
87 	return msg;
88 }
89 
90 
91  /// Exception Handler for COM exceptions
92 
HandleException(COMException & e,HWND hwnd)93 void HandleException(COMException& e, HWND hwnd)
94 {
95 	String msg = e.toString();
96 
97 	SetLastError(0);
98 
99 	if (hwnd && !IsWindowVisible(hwnd))
100 		hwnd = 0;
101 
102 	MessageBox(hwnd, msg, TEXT("ShellClasses Exception"), MB_ICONHAND|MB_OK);
103 
104 	 // If displaying the error message box _with_ parent was not successfull, display it now without a parent window.
105 	if (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
106 		MessageBox(0, msg, TEXT("ShellClasses Exception"), MB_ICONHAND|MB_OK);
107 }
108 
109 
110  // common IMalloc object
111 
112 CommonShellMalloc ShellMalloc::s_cmn_shell_malloc;
113 
114 
115  // common desktop object
116 
GetDesktopFolder()117 ShellFolder& GetDesktopFolder()
118 {
119 	static CommonDesktop s_desktop;
120 
121 	 // initialize s_desktop
122 	s_desktop.init();
123 
124 	return s_desktop;
125 }
126 
127 
init()128 void CommonDesktop::init()
129 {
130 	CONTEXT("CommonDesktop::init()");
131 
132 	if (!_desktop)
133 		_desktop = new ShellFolder;
134 }
135 
~CommonDesktop()136 CommonDesktop::~CommonDesktop()
137 {
138 	if (_desktop)
139 		delete _desktop;
140 }
141 
142 
path_from_pidlA(IShellFolder * folder,LPCITEMIDLIST pidl,LPSTR buffer,int len)143 HRESULT path_from_pidlA(IShellFolder* folder, LPCITEMIDLIST pidl, LPSTR buffer, int len)
144 {
145 	CONTEXT("path_from_pidlA()");
146 
147 	StrRetA str;
148 
149 	HRESULT hr = folder->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str);
150 
151 	if (SUCCEEDED(hr))
152 		str.GetString(pidl->mkid, buffer, len);
153 	else
154 		buffer[0] = '\0';
155 
156 	return hr;
157 }
158 
path_from_pidlW(IShellFolder * folder,LPCITEMIDLIST pidl,LPWSTR buffer,int len)159 HRESULT path_from_pidlW(IShellFolder* folder, LPCITEMIDLIST pidl, LPWSTR buffer, int len)
160 {
161 	CONTEXT("path_from_pidlW()");
162 
163 	StrRetW str;
164 
165 	HRESULT hr = folder->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &str);
166 
167 	if (SUCCEEDED(hr))
168 		str.GetString(pidl->mkid, buffer, len);
169 	else
170 		buffer[0] = '\0';
171 
172 	return hr;
173 }
174 
name_from_pidl(IShellFolder * folder,LPCITEMIDLIST pidl,LPTSTR buffer,int len,SHGDNF flags)175 HRESULT name_from_pidl(IShellFolder* folder, LPCITEMIDLIST pidl, LPTSTR buffer, int len, SHGDNF flags)
176 {
177 	CONTEXT("name_from_pidl()");
178 
179 	StrRet str;
180 
181 	HRESULT hr = folder->GetDisplayNameOf(pidl, flags, &str);
182 
183 	if (SUCCEEDED(hr))
184 		str.GetString(pidl->mkid, buffer, len);
185 	else
186 		buffer[0] = '\0';
187 
188 	return hr;
189 }
190 
191 
192 #ifndef _NO_COMUTIL
193 
ShellFolder()194 ShellFolder::ShellFolder()
195 {
196 	CONTEXT("ShellFolder::ShellFolder()");
197 
198 	IShellFolder* desktop;
199 
200 	CHECKERROR(SHGetDesktopFolder(&desktop));
201 
202 	super::Attach(desktop);
203 	desktop->AddRef();
204 }
205 
ShellFolder(IShellFolder * p)206 ShellFolder::ShellFolder(IShellFolder* p)
207  :	super(p)
208 {
209 	CONTEXT("ShellFolder::ShellFolder(IShellFolder*)");
210 
211 	p->AddRef();
212 }
213 
ShellFolder(IShellFolder * parent,LPCITEMIDLIST pidl)214 ShellFolder::ShellFolder(IShellFolder* parent, LPCITEMIDLIST pidl)
215 {
216 	CONTEXT("ShellFolder::ShellFolder(IShellFolder*, LPCITEMIDLIST)");
217 
218 	IShellFolder* ptr;
219 
220 	if (!pidl)
221 		CHECKERROR(E_INVALIDARG);
222 
223 	if (pidl && pidl->mkid.cb)
224 		CHECKERROR(parent->BindToObject(pidl, 0, IID_IShellFolder, (LPVOID*)&ptr));
225 	else
226 		ptr = parent;
227 
228 	super::Attach(ptr);
229 	ptr->AddRef();
230 }
231 
ShellFolder(LPCITEMIDLIST pidl)232 ShellFolder::ShellFolder(LPCITEMIDLIST pidl)
233 {
234 	CONTEXT("ShellFolder::ShellFolder(LPCITEMIDLIST)");
235 
236 	IShellFolder* ptr;
237 	IShellFolder* parent = GetDesktopFolder();
238 
239 	if (pidl && pidl->mkid.cb)
240 		CHECKERROR(parent->BindToObject(pidl, 0, IID_IShellFolder, (LPVOID*)&ptr));
241 	else
242 		ptr = parent;
243 
244 	super::Attach(ptr);
245 	ptr->AddRef();
246 }
247 
attach(IShellFolder * parent,LPCITEMIDLIST pidl)248 void ShellFolder::attach(IShellFolder* parent, LPCITEMIDLIST pidl)
249 {
250 	CONTEXT("ShellFolder::attach(IShellFolder*, LPCITEMIDLIST)");
251 
252 	IShellFolder* ptr;
253 
254 	if (pidl && pidl->mkid.cb)
255 		CHECKERROR(parent->BindToObject(pidl, 0, IID_IShellFolder, (LPVOID*)&ptr));
256 	else
257 		ptr = parent;
258 
259 	super::Attach(ptr);
260 	ptr->AddRef();
261 }
262 
263 #else // _com_ptr not available -> use SIfacePtr
264 
ShellFolder()265 ShellFolder::ShellFolder()
266 {
267 	CONTEXT("ShellFolder::ShellFolder()");
268 
269 	CHECKERROR(SHGetDesktopFolder(&_p));
270 
271 	_p->AddRef();
272 }
273 
ShellFolder(IShellFolder * p)274 ShellFolder::ShellFolder(IShellFolder* p)
275  :	super(p)
276 {
277 	CONTEXT("ShellFolder::ShellFolder(IShellFolder*)");
278 
279 	_p->AddRef();
280 }
281 
ShellFolder(IShellFolder * parent,LPCITEMIDLIST pidl)282 ShellFolder::ShellFolder(IShellFolder* parent, LPCITEMIDLIST pidl)
283 {
284 	CONTEXT("ShellFolder::ShellFolder(IShellFolder*, LPCITEMIDLIST)");
285 
286 	if (pidl && pidl->mkid.cb)
287 		CHECKERROR(parent->BindToObject(pidl, 0, IID_IShellFolder, (LPVOID*)&_p));
288 	else
289 		_p = GetDesktopFolder();
290 
291 	_p->AddRef();
292 }
293 
ShellFolder(LPCITEMIDLIST pidl)294 ShellFolder::ShellFolder(LPCITEMIDLIST pidl)
295 {
296 	CONTEXT("ShellFolder::ShellFolder(LPCITEMIDLIST)");
297 
298 	if (pidl && pidl->mkid.cb)
299 		CHECKERROR(GetDesktopFolder()->BindToObject(pidl, 0, IID_IShellFolder, (LPVOID*)&_p));
300 	else
301 		_p = GetDesktopFolder();
302 
303 	_p->AddRef();
304 }
305 
attach(IShellFolder * parent,LPCITEMIDLIST pidl)306 void ShellFolder::attach(IShellFolder* parent, LPCITEMIDLIST pidl)
307 {
308 	CONTEXT("ShellFolder::ShellFolder(IShellFolder*, LPCITEMIDLIST)");
309 
310 	IShellFolder* h = _p;
311 
312 	CHECKERROR(parent->BindToObject(pidl, 0, IID_IShellFolder, (LPVOID*)&_p));
313 
314 	_p->AddRef();
315 	h->Release();
316 }
317 
318 #endif
319 
get_name(LPCITEMIDLIST pidl,SHGDNF flags) const320 String ShellFolder::get_name(LPCITEMIDLIST pidl, SHGDNF flags) const
321 {
322 	CONTEXT("ShellFolder::get_name()");
323 
324 	TCHAR buffer[MAX_PATH];
325 	StrRet strret;
326 
327 	HRESULT hr = ((IShellFolder*)*const_cast<ShellFolder*>(this))->GetDisplayNameOf(pidl, flags, &strret);
328 
329 	if (hr == S_OK)
330 		strret.GetString(pidl->mkid, buffer, COUNTOF(buffer));
331 	else {
332 		CHECKERROR(hr);
333 		*buffer = TEXT('\0');
334 	}
335 
336 	return buffer;
337 }
338 
339 
split(ShellPath & parent,ShellPath & obj) const340 void ShellPath::split(ShellPath& parent, ShellPath& obj) const
341 {
342 	SHITEMID *piid, *piidLast;
343 	int size = 0;
344 
345 	 // find last item-id and calculate total size of pidl
346 	for(piid=piidLast=&_p->mkid; piid->cb; ) {
347 		piidLast = piid;
348 		size += (piid->cb);
349 		piid = (SHITEMID*)((LPBYTE)piid + (piid->cb));
350 	}
351 
352 	 // copy parent folder portion
353 	size -= piidLast->cb;  // don't count "object" item-id
354 
355 	if (size > 0)
356 		parent.assign(_p, size);
357 
358 	 // copy "object" portion
359 	obj.assign((ITEMIDLIST*)piidLast, piidLast->cb);
360 }
361 
GetUIObjectOf(REFIID riid,LPVOID * ppvOut,HWND hWnd,ShellFolder & sf)362 void ShellPath::GetUIObjectOf(REFIID riid, LPVOID* ppvOut, HWND hWnd, ShellFolder& sf)
363 {
364 	CONTEXT("ShellPath::GetUIObjectOf()");
365 
366 	ShellPath parent, obj;
367 
368 	split(parent, obj);
369 
370 	LPCITEMIDLIST idl = obj;
371 
372 	if (parent && parent->mkid.cb)
373 		 // use the IShellFolder of the parent
374 		CHECKERROR(ShellFolder((IShellFolder*)sf,parent)->GetUIObjectOf(hWnd, 1, &idl, riid, 0, ppvOut));
375 	else // else use desktop folder
376 		CHECKERROR(sf->GetUIObjectOf(hWnd, 1, &idl, riid, 0, ppvOut));
377 }
378 
379 #if 0	// ILCombine() was missing in previous versions of MinGW and is not exported from shell32.dll on Windows 2000.
380 
381  // convert an item id list from relative to absolute (=relative to the desktop) format
382 ShellPath ShellPath::create_absolute_pidl(LPCITEMIDLIST parent_pidl) const
383 {
384 	CONTEXT("ShellPath::create_absolute_pidl()");
385 
386 	return ILCombine(parent_pidl, _p);
387 
388 /* seems to work only for NT upwards
389 	 // create a new item id list with _p append behind parent_pidl
390 	int l1 = ILGetSize(parent_pidl) - sizeof(USHORT/ SHITEMID::cb /);
391 	int l2 = ILGetSize(_p);
392 
393 	LPITEMIDLIST p = (LPITEMIDLIST) _malloc->Alloc(l1+l2);
394 
395 	memcpy(p, parent_pidl, l1);
396 	memcpy((LPBYTE)p+l1, _p, l2);
397 
398 	return p;
399 */
400 }
401 
402 #else
403 
create_absolute_pidl(LPCITEMIDLIST parent_pidl) const404 ShellPath ShellPath::create_absolute_pidl(LPCITEMIDLIST parent_pidl) const
405 {
406 	CONTEXT("ShellPath::create_absolute_pidl()");
407 
408 	static DynamicFct<LPITEMIDLIST(WINAPI*)(LPCITEMIDLIST, LPCITEMIDLIST)> ILCombine(TEXT("SHELL32"), 25);
409 
410 	if (ILCombine)
411 		return (*ILCombine)(parent_pidl, _p);
412 
413 	 // create a new item id list with _p append behind parent_pidl
414 	int l1 = ILGetSize(parent_pidl) - sizeof(USHORT/*SHITEMID::cb*/);
415 	int l2 = ILGetSize(_p);
416 
417 	LPITEMIDLIST p = (LPITEMIDLIST) _malloc->Alloc(l1+l2);
418 
419 	memcpy(p, parent_pidl, l1);
420 	memcpy((LPBYTE)p+l1, _p, l2);
421 
422 	return p;
423 }
424 
425 #endif
426 
427  // local implementation of ILGetSize() to replace missing export on Windows 2000
ILGetSize_local(LPCITEMIDLIST pidl)428 UINT ILGetSize_local(LPCITEMIDLIST pidl)
429 {
430 	if (!pidl)
431 		return 0;
432 
433 	int l = sizeof(USHORT/*SHITEMID::cb*/);
434 
435 	while(pidl->mkid.cb) {
436 		l += pidl->mkid.cb;
437 		pidl = LPCITEMIDLIST((LPBYTE)pidl+pidl->mkid.cb);
438 	}
439 
440 	return l;
441 }
442 
443 
444 #ifndef	_SHFOLDER_H_
445 #define	CSIDL_FLAG_CREATE	0x8000
446 #endif
447 
448  /// file system path of special folder
SpecialFolderFSPath(int folder,HWND hwnd)449 SpecialFolderFSPath::SpecialFolderFSPath(int folder, HWND hwnd)
450 {
451 	_fullpath[0] = '\0';
452 
453 #ifdef UNICODE
454 	static DynamicFct<BOOL (__stdcall*)(HWND hwnd, LPTSTR pszPath, int csidl, BOOL fCreate)> s_pSHGetSpecialFolderPath(TEXT("shell32"), "SHGetSpecialFolderPathW");
455 #else
456 	static DynamicFct<BOOL (__stdcall*)(HWND hwnd, LPTSTR pszPath, int csidl, BOOL fCreate)> s_pSHGetSpecialFolderPath(TEXT("shell32"), "SHGetSpecialFolderPathA");
457 #endif
458 	if (*s_pSHGetSpecialFolderPath)
459 		(*s_pSHGetSpecialFolderPath)(hwnd, _fullpath, folder, TRUE);
460 	else {
461 		 // SHGetSpecialFolderPath() is not compatible to WIN95/NT4
462 #ifdef UNICODE
463 		static DynamicFct<HRESULT (__stdcall*)(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)> s_pSHGetFolderPath_shell32(TEXT("shell32"), "SHGetFolderPathW");
464 #else
465 		static DynamicFct<HRESULT (__stdcall*)(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)> s_pSHGetFolderPath_shell32(TEXT("shell32"), "SHGetFolderPathA");
466 #endif
467 		if (*s_pSHGetFolderPath_shell32)
468 			(*s_pSHGetFolderPath_shell32)(hwnd, folder|CSIDL_FLAG_CREATE, 0, 0, _fullpath);
469 		else {
470 			 // SHGetFolderPath() is only present in shfolder.dll on some platforms.
471 #ifdef UNICODE
472 			static DynamicLoadLibFct<HRESULT (__stdcall*)(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)> s_pSHGetFolderPath_shfolder(TEXT("shfolder"), "SHGetFolderPathW");
473 #else
474 			static DynamicLoadLibFct<HRESULT (__stdcall*)(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath)> s_pSHGetFolderPath_shfolder(TEXT("shfolder"), "SHGetFolderPathA");
475 #endif
476 			if (*s_pSHGetFolderPath_shfolder)
477 				(*s_pSHGetFolderPath_shfolder)(hwnd, folder|CSIDL_FLAG_CREATE, 0, 0, _fullpath);
478 		}
479 	}
480 }
481 
482 
reset()483 void CtxMenuInterfaces::reset()
484 {
485 	_pctxmenu2 = NULL;
486 	_pctxmenu3 = NULL;
487 }
488 
HandleMenuMsg(UINT nmsg,WPARAM wparam,LPARAM lparam)489 bool CtxMenuInterfaces::HandleMenuMsg(UINT nmsg, WPARAM wparam, LPARAM lparam)
490 {
491 	if (_pctxmenu3) {
492 		if (SUCCEEDED(_pctxmenu3->HandleMenuMsg(nmsg, wparam, lparam)))
493 			return true;
494 	}
495 
496 	if (_pctxmenu2)
497 		if (SUCCEEDED(_pctxmenu2->HandleMenuMsg(nmsg, wparam, lparam)))
498 			return true;
499 
500 	return false;
501 }
502 
query_interfaces(IContextMenu * pcm1)503 IContextMenu* CtxMenuInterfaces::query_interfaces(IContextMenu* pcm1)
504 {
505 	IContextMenu* pcm = NULL;
506 
507 	reset();
508 
509 	 // Get the higher version context menu interfaces.
510 	if (pcm1->QueryInterface(IID_IContextMenu3, (void**)&pcm) == NOERROR)
511 		_pctxmenu3 = (LPCONTEXTMENU3)pcm;
512 	else
513 	if (pcm1->QueryInterface(IID_IContextMenu2, (void**)&pcm) == NOERROR)
514 		_pctxmenu2 = (LPCONTEXTMENU2)pcm;
515 
516 	if (pcm) {
517 		pcm1->Release();
518 		return pcm;
519 	} else
520 		return pcm1;
521 }
522 
523 
ShellFolderContextMenu(IShellFolder * shell_folder,HWND hwndParent,int cidl,LPCITEMIDLIST * apidl,int x,int y,CtxMenuInterfaces & cm_ifs)524 HRESULT ShellFolderContextMenu(IShellFolder* shell_folder, HWND hwndParent, int cidl,
525 								LPCITEMIDLIST* apidl, int x, int y, CtxMenuInterfaces& cm_ifs)
526 {
527 	IContextMenu* pcm;
528 
529 	HRESULT hr = shell_folder->GetUIObjectOf(hwndParent, cidl, apidl, IID_IContextMenu, NULL, (LPVOID*)&pcm);
530 //	HRESULT hr = CDefFolderMenu_Create2(dir?dir->_pidl:DesktopFolder(), hwndParent, 1, &pidl, shell_folder, NULL, 0, NULL, &pcm);
531 
532 	if (SUCCEEDED(hr)) {
533 		pcm = cm_ifs.query_interfaces(pcm);
534 
535 		HMENU hmenu = CreatePopupMenu();
536 
537 		if (hmenu) {
538 			hr = pcm->QueryContextMenu(hmenu, 0, FCIDM_SHVIEWFIRST, FCIDM_SHVIEWLAST, CMF_NORMAL|CMF_EXPLORE);
539 
540 			if (SUCCEEDED(hr)) {
541 				UINT idCmd = TrackPopupMenu(hmenu, TPM_LEFTALIGN|TPM_RETURNCMD|TPM_RIGHTBUTTON, x, y, 0, hwndParent, NULL);
542 
543 				cm_ifs.reset();
544 
545 				if (idCmd) {
546 				  CMINVOKECOMMANDINFO cmi;
547 
548 				  cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
549 				  cmi.fMask = 0;
550 				  cmi.hwnd = hwndParent;
551 				  cmi.lpVerb = (LPCSTR)(INT_PTR)(idCmd - FCIDM_SHVIEWFIRST);
552 				  cmi.lpParameters = NULL;
553 				  cmi.lpDirectory = NULL;
554 				  cmi.nShow = SW_SHOWNORMAL;
555 				  cmi.dwHotKey = 0;
556 				  cmi.hIcon = 0;
557 
558 				  hr = pcm->InvokeCommand(&cmi);
559 				}
560 			} else
561 				cm_ifs.reset();
562 			DestroyMenu(hmenu);
563 		}
564 
565 		pcm->Release();
566 	}
567 
568 	return hr;
569 }
570