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