blob: 04fe61e89618bd78a16353b70654e804f94002a0 [file] [log] [blame]
Steve Dowerdf450d12016-07-16 16:17:33 -07001// Support back to Vista
2#define _WIN32_WINNT _WIN32_WINNT_VISTA
3#include <sdkddkver.h>
4
5// Use WRL to define a classic COM class
6#define __WRL_CLASSIC_COM__
7#include <wrl.h>
8
9#include <windows.h>
10#include <shlobj.h>
11#include <shlwapi.h>
12#include <olectl.h>
13#include <strsafe.h>
14
15#include "pyshellext_h.h"
16
17#define DDWM_UPDATEWINDOW (WM_USER+3)
18
19static HINSTANCE hModule;
20static CLIPFORMAT cfDropDescription;
21static CLIPFORMAT cfDragWindow;
22
23static const LPCWSTR CLASS_SUBKEY = L"Software\\Classes\\CLSID\\{BEA218D2-6950-497B-9434-61683EC065FE}";
24static const LPCWSTR DRAG_MESSAGE = L"Open with %1";
25
26using namespace Microsoft::WRL;
27
28HRESULT FilenameListCchLengthA(LPCSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) {
29 HRESULT hr = S_OK;
30 size_t count = 0;
31 size_t length = 0;
32
33 while (pszSource && pszSource[0]) {
34 size_t oneLength;
35 hr = StringCchLengthA(pszSource, cchMax - length, &oneLength);
36 if (FAILED(hr)) {
37 return hr;
38 }
39 count += 1;
40 length += oneLength + (strchr(pszSource, ' ') ? 3 : 1);
41 pszSource = &pszSource[oneLength + 1];
42 }
43
44 *pcchCount = count;
45 *pcchLength = length;
46 return hr;
47}
48
49HRESULT FilenameListCchLengthW(LPCWSTR pszSource, size_t cchMax, size_t *pcchLength, size_t *pcchCount) {
50 HRESULT hr = S_OK;
51 size_t count = 0;
52 size_t length = 0;
53
54 while (pszSource && pszSource[0]) {
55 size_t oneLength;
56 hr = StringCchLengthW(pszSource, cchMax - length, &oneLength);
57 if (FAILED(hr)) {
58 return hr;
59 }
60 count += 1;
61 length += oneLength + (wcschr(pszSource, ' ') ? 3 : 1);
62 pszSource = &pszSource[oneLength + 1];
63 }
64
65 *pcchCount = count;
66 *pcchLength = length;
67 return hr;
68}
69
70HRESULT FilenameListCchCopyA(STRSAFE_LPSTR pszDest, size_t cchDest, LPCSTR pszSource, LPCSTR pszSeparator) {
71 HRESULT hr = S_OK;
72 size_t count = 0;
73 size_t length = 0;
74
75 while (pszSource[0]) {
76 STRSAFE_LPSTR newDest;
77
78 hr = StringCchCopyExA(pszDest, cchDest, pszSource, &newDest, &cchDest, 0);
79 if (FAILED(hr)) {
80 return hr;
81 }
82 pszSource += (newDest - pszDest) + 1;
83 pszDest = PathQuoteSpacesA(pszDest) ? newDest + 2 : newDest;
84
85 if (pszSource[0]) {
86 hr = StringCchCopyExA(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0);
87 if (FAILED(hr)) {
88 return hr;
89 }
90 pszDest = newDest;
91 }
92 }
93
94 return hr;
95}
96
97HRESULT FilenameListCchCopyW(STRSAFE_LPWSTR pszDest, size_t cchDest, LPCWSTR pszSource, LPCWSTR pszSeparator) {
98 HRESULT hr = S_OK;
99 size_t count = 0;
100 size_t length = 0;
101
102 while (pszSource[0]) {
103 STRSAFE_LPWSTR newDest;
104
105 hr = StringCchCopyExW(pszDest, cchDest, pszSource, &newDest, &cchDest, 0);
106 if (FAILED(hr)) {
107 return hr;
108 }
109 pszSource += (newDest - pszDest) + 1;
110 pszDest = PathQuoteSpacesW(pszDest) ? newDest + 2 : newDest;
111
112 if (pszSource[0]) {
113 hr = StringCchCopyExW(pszDest, cchDest, pszSeparator, &newDest, &cchDest, 0);
114 if (FAILED(hr)) {
115 return hr;
116 }
117 pszDest = newDest;
118 }
119 }
120
121 return hr;
122}
123
124
125class PyShellExt : public RuntimeClass<
126 RuntimeClassFlags<ClassicCom>,
127 IDropTarget,
128 IPersistFile
129>
130{
131 LPOLESTR target, target_dir;
132 DWORD target_mode;
133
134 IDataObject *data_obj;
135
136public:
137 PyShellExt() : target(NULL), target_dir(NULL), target_mode(0), data_obj(NULL) {
138 OutputDebugString(L"PyShellExt::PyShellExt");
139 }
140
141 ~PyShellExt() {
142 if (target) {
143 CoTaskMemFree(target);
144 }
145 if (target_dir) {
146 CoTaskMemFree(target_dir);
147 }
148 if (data_obj) {
149 data_obj->Release();
150 }
151 }
152
153private:
154 HRESULT UpdateDropDescription(IDataObject *pDataObj) {
155 STGMEDIUM medium;
156 FORMATETC fmt = {
157 cfDropDescription,
158 NULL,
159 DVASPECT_CONTENT,
160 -1,
161 TYMED_HGLOBAL
162 };
163
164 auto hr = pDataObj->GetData(&fmt, &medium);
165 if (FAILED(hr)) {
166 OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to get DROPDESCRIPTION format");
167 return hr;
168 }
169 if (!medium.hGlobal) {
170 OutputDebugString(L"PyShellExt::UpdateDropDescription - DROPDESCRIPTION format had NULL hGlobal");
171 ReleaseStgMedium(&medium);
172 return E_FAIL;
173 }
174 auto dd = (DROPDESCRIPTION*)GlobalLock(medium.hGlobal);
175 StringCchCopy(dd->szMessage, sizeof(dd->szMessage) / sizeof(dd->szMessage[0]), DRAG_MESSAGE);
176 StringCchCopy(dd->szInsert, sizeof(dd->szInsert) / sizeof(dd->szInsert[0]), PathFindFileNameW(target));
177 dd->type = DROPIMAGE_MOVE;
178
179 GlobalUnlock(medium.hGlobal);
180 ReleaseStgMedium(&medium);
181
182 return S_OK;
183 }
184
185 HRESULT GetDragWindow(IDataObject *pDataObj, HWND *phWnd) {
186 HRESULT hr;
187 HWND *pMem;
188 STGMEDIUM medium;
189 FORMATETC fmt = {
190 cfDragWindow,
191 NULL,
192 DVASPECT_CONTENT,
193 -1,
194 TYMED_HGLOBAL
195 };
196
197 hr = pDataObj->GetData(&fmt, &medium);
198 if (FAILED(hr)) {
199 OutputDebugString(L"PyShellExt::GetDragWindow - failed to get DragWindow format");
200 return hr;
201 }
202 if (!medium.hGlobal) {
203 OutputDebugString(L"PyShellExt::GetDragWindow - DragWindow format had NULL hGlobal");
204 ReleaseStgMedium(&medium);
205 return E_FAIL;
206 }
207
208 pMem = (HWND*)GlobalLock(medium.hGlobal);
209 if (!pMem) {
210 OutputDebugString(L"PyShellExt::GetDragWindow - failed to lock DragWindow hGlobal");
211 ReleaseStgMedium(&medium);
212 return E_FAIL;
213 }
214
215 *phWnd = *pMem;
216
217 GlobalUnlock(medium.hGlobal);
218 ReleaseStgMedium(&medium);
219
220 return S_OK;
221 }
222
223 HRESULT GetArguments(IDataObject *pDataObj, LPCWSTR *pArguments) {
224 HRESULT hr;
225 DROPFILES *pdropfiles;
226
227 STGMEDIUM medium;
228 FORMATETC fmt = {
229 CF_HDROP,
230 NULL,
231 DVASPECT_CONTENT,
232 -1,
233 TYMED_HGLOBAL
234 };
235
236 hr = pDataObj->GetData(&fmt, &medium);
237 if (FAILED(hr)) {
238 OutputDebugString(L"PyShellExt::GetArguments - failed to get CF_HDROP format");
239 return hr;
240 }
241 if (!medium.hGlobal) {
242 OutputDebugString(L"PyShellExt::GetArguments - CF_HDROP format had NULL hGlobal");
243 ReleaseStgMedium(&medium);
244 return E_FAIL;
245 }
246
247 pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal);
248 if (!pdropfiles) {
249 OutputDebugString(L"PyShellExt::GetArguments - failed to lock CF_HDROP hGlobal");
250 ReleaseStgMedium(&medium);
251 return E_FAIL;
252 }
253
254 if (pdropfiles->fWide) {
255 LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles);
256 size_t len, count;
257 hr = FilenameListCchLengthW(files, 32767, &len, &count);
258 if (SUCCEEDED(hr)) {
259 LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
260 if (args) {
261 hr = FilenameListCchCopyW(args, 32767, files, L" ");
262 if (SUCCEEDED(hr)) {
263 *pArguments = args;
264 } else {
265 CoTaskMemFree(args);
266 }
267 } else {
268 hr = E_OUTOFMEMORY;
269 }
270 }
271 } else {
272 LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles);
273 size_t len, count;
274 hr = FilenameListCchLengthA(files, 32767, &len, &count);
275 if (SUCCEEDED(hr)) {
276 LPSTR temp = (LPSTR)CoTaskMemAlloc(sizeof(CHAR) * (len + 1));
277 if (temp) {
278 hr = FilenameListCchCopyA(temp, 32767, files, " ");
279 if (SUCCEEDED(hr)) {
280 int wlen = MultiByteToWideChar(CP_ACP, 0, temp, (int)len, NULL, 0);
281 if (wlen) {
282 LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1));
283 if (MultiByteToWideChar(CP_ACP, 0, temp, (int)len, args, wlen + 1)) {
284 *pArguments = args;
285 } else {
286 OutputDebugString(L"PyShellExt::GetArguments - failed to convert multi-byte to wide-char path");
287 CoTaskMemFree(args);
288 hr = E_FAIL;
289 }
290 } else {
291 OutputDebugString(L"PyShellExt::GetArguments - failed to get length of wide-char path");
292 hr = E_FAIL;
293 }
294 }
295 CoTaskMemFree(temp);
296 } else {
297 hr = E_OUTOFMEMORY;
298 }
299 }
300 }
301
302 GlobalUnlock(medium.hGlobal);
303 ReleaseStgMedium(&medium);
304
305 return hr;
306 }
307
308 HRESULT NotifyDragWindow(HWND hwnd) {
309 LRESULT res;
310
311 if (!hwnd) {
312 return S_FALSE;
313 }
314
315 res = SendMessage(hwnd, DDWM_UPDATEWINDOW, 0, NULL);
316
317 if (res) {
318 OutputDebugString(L"PyShellExt::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW");
319 return E_FAIL;
320 }
321
322 return S_OK;
323 }
324
325public:
326 // IDropTarget implementation
327
328 STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
329 HWND hwnd;
330
331 OutputDebugString(L"PyShellExt::DragEnter");
332
333 pDataObj->AddRef();
334 data_obj = pDataObj;
335
336 *pdwEffect = DROPEFFECT_MOVE;
337
338 if (FAILED(UpdateDropDescription(data_obj))) {
339 OutputDebugString(L"PyShellExt::DragEnter - failed to update drop description");
340 }
341 if (FAILED(GetDragWindow(data_obj, &hwnd))) {
342 OutputDebugString(L"PyShellExt::DragEnter - failed to get drag window");
343 }
344 if (FAILED(NotifyDragWindow(hwnd))) {
345 OutputDebugString(L"PyShellExt::DragEnter - failed to notify drag window");
346 }
347
348 return S_OK;
349 }
350
351 STDMETHODIMP DragLeave() {
352 return S_OK;
353 }
354
355 STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
356 return S_OK;
357 }
358
359 STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
360 LPCWSTR args;
361
362 OutputDebugString(L"PyShellExt::Drop");
363 *pdwEffect = DROPEFFECT_NONE;
364
365 if (pDataObj != data_obj) {
366 OutputDebugString(L"PyShellExt::Drop - unexpected data object");
367 return E_FAIL;
368 }
369
370 data_obj->Release();
371 data_obj = NULL;
372
373 if (SUCCEEDED(GetArguments(pDataObj, &args))) {
374 OutputDebugString(args);
375 ShellExecute(NULL, NULL, target, args, target_dir, SW_NORMAL);
376
377 CoTaskMemFree((LPVOID)args);
378 } else {
379 OutputDebugString(L"PyShellExt::Drop - failed to get launch arguments");
380 }
381
382 return S_OK;
383 }
384
385 // IPersistFile implementation
386
387 STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) {
388 HRESULT hr;
389 size_t len;
390
391 if (!ppszFileName) {
392 return E_POINTER;
393 }
394
395 hr = StringCchLength(target, STRSAFE_MAX_CCH - 1, &len);
396 if (FAILED(hr)) {
397 return E_FAIL;
398 }
399
400 *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
401 if (!*ppszFileName) {
402 return E_OUTOFMEMORY;
403 }
404
405 hr = StringCchCopy(*ppszFileName, len + 1, target);
406 if (FAILED(hr)) {
407 CoTaskMemFree(*ppszFileName);
408 *ppszFileName = NULL;
409 return E_FAIL;
410 }
411
412 return S_OK;
413 }
414
415 STDMETHODIMP IsDirty() {
416 return S_FALSE;
417 }
418
419 STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) {
420 HRESULT hr;
421 size_t len;
422
423 OutputDebugString(L"PyShellExt::Load");
424 OutputDebugString(pszFileName);
425
426 hr = StringCchLength(pszFileName, STRSAFE_MAX_CCH - 1, &len);
427 if (FAILED(hr)) {
428 OutputDebugString(L"PyShellExt::Load - failed to get string length");
429 return hr;
430 }
431
432 if (target) {
433 CoTaskMemFree(target);
434 }
435 if (target_dir) {
436 CoTaskMemFree(target_dir);
437 }
438
439 target = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
440 if (!target) {
441 OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
442 return E_OUTOFMEMORY;
443 }
444 target_dir = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
445 if (!target_dir) {
446 OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
447 return E_OUTOFMEMORY;
448 }
449
450 hr = StringCchCopy(target, len + 1, pszFileName);
451 if (FAILED(hr)) {
452 OutputDebugString(L"PyShellExt::Load - failed to copy string");
453 return hr;
454 }
455
456 hr = StringCchCopy(target_dir, len + 1, pszFileName);
457 if (FAILED(hr)) {
458 OutputDebugString(L"PyShellExt::Load - failed to copy string");
459 return hr;
460 }
461 if (!PathRemoveFileSpecW(target_dir)) {
462 OutputDebugStringW(L"PyShellExt::Load - failed to remove filespec from target");
463 return E_FAIL;
464 }
465
466 OutputDebugString(target);
467 target_mode = dwMode;
468 OutputDebugString(L"PyShellExt::Load - S_OK");
469 return S_OK;
470 }
471
472 STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember) {
473 return E_NOTIMPL;
474 }
475
476 STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) {
477 return E_NOTIMPL;
478 }
479
480 STDMETHODIMP GetClassID(CLSID *pClassID) {
481 *pClassID = CLSID_PyShellExt;
482 return S_OK;
483 }
484};
485
486CoCreatableClass(PyShellExt);
487
488STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) {
489 return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
490}
491
492STDAPI DllCanUnloadNow() {
493 return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
494}
495
496STDAPI DllRegisterServer() {
497 LONG res;
498 SECURITY_ATTRIBUTES secattr = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
499 LPSECURITY_ATTRIBUTES psecattr = NULL;
500 HKEY key, ipsKey;
501 WCHAR modname[MAX_PATH];
502 DWORD modname_len;
503
504 OutputDebugString(L"PyShellExt::DllRegisterServer");
505 if (!hModule) {
506 OutputDebugString(L"PyShellExt::DllRegisterServer - module handle was not set");
507 return SELFREG_E_CLASS;
508 }
509 modname_len = GetModuleFileName(hModule, modname, MAX_PATH);
510 if (modname_len == 0 ||
511 (modname_len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
512 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to get module file name");
513 return SELFREG_E_CLASS;
514 }
515
516 DWORD disp;
517 res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, CLASS_SUBKEY, 0, NULL, 0,
518 KEY_ALL_ACCESS, psecattr, &key, &disp);
519 if (res == ERROR_ACCESS_DENIED) {
520 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to write per-machine registration. Attempting per-user instead.");
521 res = RegCreateKeyEx(HKEY_CURRENT_USER, CLASS_SUBKEY, 0, NULL, 0,
522 KEY_ALL_ACCESS, psecattr, &key, &disp);
523 }
524 if (res != ERROR_SUCCESS) {
525 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create class key");
526 return SELFREG_E_CLASS;
527 }
528
529 res = RegCreateKeyEx(key, L"InProcServer32", 0, NULL, 0,
530 KEY_ALL_ACCESS, psecattr, &ipsKey, NULL);
531 if (res != ERROR_SUCCESS) {
532 RegCloseKey(key);
533 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create InProcServer32 key");
534 return SELFREG_E_CLASS;
535 }
536
537 res = RegSetValueEx(ipsKey, NULL, 0,
538 REG_SZ, (LPBYTE)modname, modname_len * sizeof(modname[0]));
539
540 if (res != ERROR_SUCCESS) {
541 RegCloseKey(ipsKey);
542 RegCloseKey(key);
543 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set server path");
544 return SELFREG_E_CLASS;
545 }
546
547 res = RegSetValueEx(ipsKey, L"ThreadingModel", 0,
548 REG_SZ, (LPBYTE)(L"Apartment"), sizeof(L"Apartment"));
549
550 RegCloseKey(ipsKey);
551 RegCloseKey(key);
552 if (res != ERROR_SUCCESS) {
553 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set threading model");
554 return SELFREG_E_CLASS;
555 }
556
557 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
558
559 OutputDebugString(L"PyShellExt::DllRegisterServer - S_OK");
560 return S_OK;
561}
562
563STDAPI DllUnregisterServer() {
564 LONG res_lm, res_cu;
565
566 res_lm = RegDeleteTree(HKEY_LOCAL_MACHINE, CLASS_SUBKEY);
567 if (res_lm != ERROR_SUCCESS && res_lm != ERROR_FILE_NOT_FOUND) {
568 OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-machine registration");
569 return SELFREG_E_CLASS;
570 }
571
572 res_cu = RegDeleteTree(HKEY_CURRENT_USER, CLASS_SUBKEY);
573 if (res_cu != ERROR_SUCCESS && res_cu != ERROR_FILE_NOT_FOUND) {
574 OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-user registration");
575 return SELFREG_E_CLASS;
576 }
577
578 if (res_lm == ERROR_FILE_NOT_FOUND && res_cu == ERROR_FILE_NOT_FOUND) {
579 OutputDebugString(L"PyShellExt::DllUnregisterServer - extension was not registered");
580 return SELFREG_E_CLASS;
581 }
582
583 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
584
585 OutputDebugString(L"PyShellExt::DllUnregisterServer - S_OK");
586 return S_OK;
587}
588
589STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) {
590 if (reason == DLL_PROCESS_ATTACH) {
591 hModule = hinst;
592
593 cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION);
594 if (!cfDropDescription) {
595 OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format");
596 }
597 cfDragWindow = RegisterClipboardFormat(L"DragWindow");
598 if (!cfDragWindow) {
599 OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format");
600 }
601
602 DisableThreadLibraryCalls(hinst);
603 }
604 return TRUE;
605}