blob: 019880264bee92946deea391d0a765a29c061f9f [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);
Zackery Spytzf6c80072018-09-24 22:25:23 -0600175 if (!dd) {
176 OutputDebugString(L"PyShellExt::UpdateDropDescription - failed to lock DROPDESCRIPTION hGlobal");
177 ReleaseStgMedium(&medium);
178 return E_FAIL;
179 }
Steve Dowerdf450d12016-07-16 16:17:33 -0700180 StringCchCopy(dd->szMessage, sizeof(dd->szMessage) / sizeof(dd->szMessage[0]), DRAG_MESSAGE);
181 StringCchCopy(dd->szInsert, sizeof(dd->szInsert) / sizeof(dd->szInsert[0]), PathFindFileNameW(target));
182 dd->type = DROPIMAGE_MOVE;
183
184 GlobalUnlock(medium.hGlobal);
185 ReleaseStgMedium(&medium);
186
187 return S_OK;
188 }
189
190 HRESULT GetDragWindow(IDataObject *pDataObj, HWND *phWnd) {
191 HRESULT hr;
192 HWND *pMem;
193 STGMEDIUM medium;
194 FORMATETC fmt = {
195 cfDragWindow,
196 NULL,
197 DVASPECT_CONTENT,
198 -1,
199 TYMED_HGLOBAL
200 };
201
202 hr = pDataObj->GetData(&fmt, &medium);
203 if (FAILED(hr)) {
204 OutputDebugString(L"PyShellExt::GetDragWindow - failed to get DragWindow format");
205 return hr;
206 }
207 if (!medium.hGlobal) {
208 OutputDebugString(L"PyShellExt::GetDragWindow - DragWindow format had NULL hGlobal");
209 ReleaseStgMedium(&medium);
210 return E_FAIL;
211 }
212
213 pMem = (HWND*)GlobalLock(medium.hGlobal);
214 if (!pMem) {
215 OutputDebugString(L"PyShellExt::GetDragWindow - failed to lock DragWindow hGlobal");
216 ReleaseStgMedium(&medium);
217 return E_FAIL;
218 }
219
220 *phWnd = *pMem;
221
222 GlobalUnlock(medium.hGlobal);
223 ReleaseStgMedium(&medium);
224
225 return S_OK;
226 }
227
228 HRESULT GetArguments(IDataObject *pDataObj, LPCWSTR *pArguments) {
229 HRESULT hr;
230 DROPFILES *pdropfiles;
231
232 STGMEDIUM medium;
233 FORMATETC fmt = {
234 CF_HDROP,
235 NULL,
236 DVASPECT_CONTENT,
237 -1,
238 TYMED_HGLOBAL
239 };
240
241 hr = pDataObj->GetData(&fmt, &medium);
242 if (FAILED(hr)) {
243 OutputDebugString(L"PyShellExt::GetArguments - failed to get CF_HDROP format");
244 return hr;
245 }
246 if (!medium.hGlobal) {
247 OutputDebugString(L"PyShellExt::GetArguments - CF_HDROP format had NULL hGlobal");
248 ReleaseStgMedium(&medium);
249 return E_FAIL;
250 }
251
252 pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal);
253 if (!pdropfiles) {
254 OutputDebugString(L"PyShellExt::GetArguments - failed to lock CF_HDROP hGlobal");
255 ReleaseStgMedium(&medium);
256 return E_FAIL;
257 }
258
259 if (pdropfiles->fWide) {
260 LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles);
261 size_t len, count;
262 hr = FilenameListCchLengthW(files, 32767, &len, &count);
263 if (SUCCEEDED(hr)) {
264 LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
265 if (args) {
266 hr = FilenameListCchCopyW(args, 32767, files, L" ");
267 if (SUCCEEDED(hr)) {
268 *pArguments = args;
269 } else {
270 CoTaskMemFree(args);
271 }
272 } else {
273 hr = E_OUTOFMEMORY;
274 }
275 }
276 } else {
277 LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles);
278 size_t len, count;
279 hr = FilenameListCchLengthA(files, 32767, &len, &count);
280 if (SUCCEEDED(hr)) {
281 LPSTR temp = (LPSTR)CoTaskMemAlloc(sizeof(CHAR) * (len + 1));
282 if (temp) {
283 hr = FilenameListCchCopyA(temp, 32767, files, " ");
284 if (SUCCEEDED(hr)) {
285 int wlen = MultiByteToWideChar(CP_ACP, 0, temp, (int)len, NULL, 0);
286 if (wlen) {
287 LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1));
288 if (MultiByteToWideChar(CP_ACP, 0, temp, (int)len, args, wlen + 1)) {
289 *pArguments = args;
290 } else {
291 OutputDebugString(L"PyShellExt::GetArguments - failed to convert multi-byte to wide-char path");
292 CoTaskMemFree(args);
293 hr = E_FAIL;
294 }
295 } else {
296 OutputDebugString(L"PyShellExt::GetArguments - failed to get length of wide-char path");
297 hr = E_FAIL;
298 }
299 }
300 CoTaskMemFree(temp);
301 } else {
302 hr = E_OUTOFMEMORY;
303 }
304 }
305 }
306
307 GlobalUnlock(medium.hGlobal);
308 ReleaseStgMedium(&medium);
309
310 return hr;
311 }
312
313 HRESULT NotifyDragWindow(HWND hwnd) {
314 LRESULT res;
315
316 if (!hwnd) {
317 return S_FALSE;
318 }
319
320 res = SendMessage(hwnd, DDWM_UPDATEWINDOW, 0, NULL);
321
322 if (res) {
323 OutputDebugString(L"PyShellExt::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW");
324 return E_FAIL;
325 }
326
327 return S_OK;
328 }
329
330public:
331 // IDropTarget implementation
332
333 STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
334 HWND hwnd;
335
336 OutputDebugString(L"PyShellExt::DragEnter");
337
338 pDataObj->AddRef();
339 data_obj = pDataObj;
340
341 *pdwEffect = DROPEFFECT_MOVE;
342
343 if (FAILED(UpdateDropDescription(data_obj))) {
344 OutputDebugString(L"PyShellExt::DragEnter - failed to update drop description");
345 }
346 if (FAILED(GetDragWindow(data_obj, &hwnd))) {
347 OutputDebugString(L"PyShellExt::DragEnter - failed to get drag window");
348 }
349 if (FAILED(NotifyDragWindow(hwnd))) {
350 OutputDebugString(L"PyShellExt::DragEnter - failed to notify drag window");
351 }
352
353 return S_OK;
354 }
355
356 STDMETHODIMP DragLeave() {
357 return S_OK;
358 }
359
360 STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
361 return S_OK;
362 }
363
364 STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
365 LPCWSTR args;
366
367 OutputDebugString(L"PyShellExt::Drop");
368 *pdwEffect = DROPEFFECT_NONE;
369
370 if (pDataObj != data_obj) {
371 OutputDebugString(L"PyShellExt::Drop - unexpected data object");
372 return E_FAIL;
373 }
374
375 data_obj->Release();
376 data_obj = NULL;
377
378 if (SUCCEEDED(GetArguments(pDataObj, &args))) {
379 OutputDebugString(args);
380 ShellExecute(NULL, NULL, target, args, target_dir, SW_NORMAL);
381
382 CoTaskMemFree((LPVOID)args);
383 } else {
384 OutputDebugString(L"PyShellExt::Drop - failed to get launch arguments");
385 }
386
387 return S_OK;
388 }
389
390 // IPersistFile implementation
391
392 STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) {
393 HRESULT hr;
394 size_t len;
395
396 if (!ppszFileName) {
397 return E_POINTER;
398 }
399
400 hr = StringCchLength(target, STRSAFE_MAX_CCH - 1, &len);
401 if (FAILED(hr)) {
402 return E_FAIL;
403 }
404
405 *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
406 if (!*ppszFileName) {
407 return E_OUTOFMEMORY;
408 }
409
410 hr = StringCchCopy(*ppszFileName, len + 1, target);
411 if (FAILED(hr)) {
412 CoTaskMemFree(*ppszFileName);
413 *ppszFileName = NULL;
414 return E_FAIL;
415 }
416
417 return S_OK;
418 }
419
420 STDMETHODIMP IsDirty() {
421 return S_FALSE;
422 }
423
424 STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) {
425 HRESULT hr;
426 size_t len;
427
428 OutputDebugString(L"PyShellExt::Load");
429 OutputDebugString(pszFileName);
430
431 hr = StringCchLength(pszFileName, STRSAFE_MAX_CCH - 1, &len);
432 if (FAILED(hr)) {
433 OutputDebugString(L"PyShellExt::Load - failed to get string length");
434 return hr;
435 }
436
437 if (target) {
438 CoTaskMemFree(target);
439 }
440 if (target_dir) {
441 CoTaskMemFree(target_dir);
442 }
443
444 target = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
445 if (!target) {
446 OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
447 return E_OUTOFMEMORY;
448 }
449 target_dir = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
450 if (!target_dir) {
451 OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
452 return E_OUTOFMEMORY;
453 }
454
455 hr = StringCchCopy(target, len + 1, pszFileName);
456 if (FAILED(hr)) {
457 OutputDebugString(L"PyShellExt::Load - failed to copy string");
458 return hr;
459 }
460
461 hr = StringCchCopy(target_dir, len + 1, pszFileName);
462 if (FAILED(hr)) {
463 OutputDebugString(L"PyShellExt::Load - failed to copy string");
464 return hr;
465 }
466 if (!PathRemoveFileSpecW(target_dir)) {
467 OutputDebugStringW(L"PyShellExt::Load - failed to remove filespec from target");
468 return E_FAIL;
469 }
470
471 OutputDebugString(target);
472 target_mode = dwMode;
473 OutputDebugString(L"PyShellExt::Load - S_OK");
474 return S_OK;
475 }
476
477 STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember) {
478 return E_NOTIMPL;
479 }
480
481 STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) {
482 return E_NOTIMPL;
483 }
484
485 STDMETHODIMP GetClassID(CLSID *pClassID) {
486 *pClassID = CLSID_PyShellExt;
487 return S_OK;
488 }
489};
490
491CoCreatableClass(PyShellExt);
492
493STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) {
494 return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
495}
496
497STDAPI DllCanUnloadNow() {
498 return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
499}
500
501STDAPI DllRegisterServer() {
502 LONG res;
503 SECURITY_ATTRIBUTES secattr = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
504 LPSECURITY_ATTRIBUTES psecattr = NULL;
505 HKEY key, ipsKey;
506 WCHAR modname[MAX_PATH];
507 DWORD modname_len;
508
509 OutputDebugString(L"PyShellExt::DllRegisterServer");
510 if (!hModule) {
511 OutputDebugString(L"PyShellExt::DllRegisterServer - module handle was not set");
512 return SELFREG_E_CLASS;
513 }
514 modname_len = GetModuleFileName(hModule, modname, MAX_PATH);
515 if (modname_len == 0 ||
516 (modname_len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
517 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to get module file name");
518 return SELFREG_E_CLASS;
519 }
520
521 DWORD disp;
522 res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, CLASS_SUBKEY, 0, NULL, 0,
523 KEY_ALL_ACCESS, psecattr, &key, &disp);
524 if (res == ERROR_ACCESS_DENIED) {
525 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to write per-machine registration. Attempting per-user instead.");
526 res = RegCreateKeyEx(HKEY_CURRENT_USER, CLASS_SUBKEY, 0, NULL, 0,
527 KEY_ALL_ACCESS, psecattr, &key, &disp);
528 }
529 if (res != ERROR_SUCCESS) {
530 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create class key");
531 return SELFREG_E_CLASS;
532 }
533
534 res = RegCreateKeyEx(key, L"InProcServer32", 0, NULL, 0,
535 KEY_ALL_ACCESS, psecattr, &ipsKey, NULL);
536 if (res != ERROR_SUCCESS) {
537 RegCloseKey(key);
538 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create InProcServer32 key");
539 return SELFREG_E_CLASS;
540 }
541
542 res = RegSetValueEx(ipsKey, NULL, 0,
543 REG_SZ, (LPBYTE)modname, modname_len * sizeof(modname[0]));
544
545 if (res != ERROR_SUCCESS) {
546 RegCloseKey(ipsKey);
547 RegCloseKey(key);
548 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set server path");
549 return SELFREG_E_CLASS;
550 }
551
552 res = RegSetValueEx(ipsKey, L"ThreadingModel", 0,
553 REG_SZ, (LPBYTE)(L"Apartment"), sizeof(L"Apartment"));
554
555 RegCloseKey(ipsKey);
556 RegCloseKey(key);
557 if (res != ERROR_SUCCESS) {
558 OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set threading model");
559 return SELFREG_E_CLASS;
560 }
561
562 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
563
564 OutputDebugString(L"PyShellExt::DllRegisterServer - S_OK");
565 return S_OK;
566}
567
568STDAPI DllUnregisterServer() {
569 LONG res_lm, res_cu;
570
571 res_lm = RegDeleteTree(HKEY_LOCAL_MACHINE, CLASS_SUBKEY);
572 if (res_lm != ERROR_SUCCESS && res_lm != ERROR_FILE_NOT_FOUND) {
573 OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-machine registration");
574 return SELFREG_E_CLASS;
575 }
576
577 res_cu = RegDeleteTree(HKEY_CURRENT_USER, CLASS_SUBKEY);
578 if (res_cu != ERROR_SUCCESS && res_cu != ERROR_FILE_NOT_FOUND) {
579 OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-user registration");
580 return SELFREG_E_CLASS;
581 }
582
583 if (res_lm == ERROR_FILE_NOT_FOUND && res_cu == ERROR_FILE_NOT_FOUND) {
584 OutputDebugString(L"PyShellExt::DllUnregisterServer - extension was not registered");
585 return SELFREG_E_CLASS;
586 }
587
588 SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
589
590 OutputDebugString(L"PyShellExt::DllUnregisterServer - S_OK");
591 return S_OK;
592}
593
594STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) {
595 if (reason == DLL_PROCESS_ATTACH) {
596 hModule = hinst;
597
598 cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION);
599 if (!cfDropDescription) {
600 OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format");
601 }
602 cfDragWindow = RegisterClipboardFormat(L"DragWindow");
603 if (!cfDragWindow) {
604 OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format");
605 }
606
607 DisableThreadLibraryCalls(hinst);
608 }
609 return TRUE;
610}