You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

609 lines
18 KiB

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