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.

604 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. StringCchCopy(dd->szMessage, sizeof(dd->szMessage) / sizeof(dd->szMessage[0]), DRAG_MESSAGE);
  146. StringCchCopy(dd->szInsert, sizeof(dd->szInsert) / sizeof(dd->szInsert[0]), PathFindFileNameW(target));
  147. dd->type = DROPIMAGE_MOVE;
  148. GlobalUnlock(medium.hGlobal);
  149. ReleaseStgMedium(&medium);
  150. return S_OK;
  151. }
  152. HRESULT GetDragWindow(IDataObject *pDataObj, HWND *phWnd) {
  153. HRESULT hr;
  154. HWND *pMem;
  155. STGMEDIUM medium;
  156. FORMATETC fmt = {
  157. cfDragWindow,
  158. NULL,
  159. DVASPECT_CONTENT,
  160. -1,
  161. TYMED_HGLOBAL
  162. };
  163. hr = pDataObj->GetData(&fmt, &medium);
  164. if (FAILED(hr)) {
  165. OutputDebugString(L"PyShellExt::GetDragWindow - failed to get DragWindow format");
  166. return hr;
  167. }
  168. if (!medium.hGlobal) {
  169. OutputDebugString(L"PyShellExt::GetDragWindow - DragWindow format had NULL hGlobal");
  170. ReleaseStgMedium(&medium);
  171. return E_FAIL;
  172. }
  173. pMem = (HWND*)GlobalLock(medium.hGlobal);
  174. if (!pMem) {
  175. OutputDebugString(L"PyShellExt::GetDragWindow - failed to lock DragWindow hGlobal");
  176. ReleaseStgMedium(&medium);
  177. return E_FAIL;
  178. }
  179. *phWnd = *pMem;
  180. GlobalUnlock(medium.hGlobal);
  181. ReleaseStgMedium(&medium);
  182. return S_OK;
  183. }
  184. HRESULT GetArguments(IDataObject *pDataObj, LPCWSTR *pArguments) {
  185. HRESULT hr;
  186. DROPFILES *pdropfiles;
  187. STGMEDIUM medium;
  188. FORMATETC fmt = {
  189. CF_HDROP,
  190. NULL,
  191. DVASPECT_CONTENT,
  192. -1,
  193. TYMED_HGLOBAL
  194. };
  195. hr = pDataObj->GetData(&fmt, &medium);
  196. if (FAILED(hr)) {
  197. OutputDebugString(L"PyShellExt::GetArguments - failed to get CF_HDROP format");
  198. return hr;
  199. }
  200. if (!medium.hGlobal) {
  201. OutputDebugString(L"PyShellExt::GetArguments - CF_HDROP format had NULL hGlobal");
  202. ReleaseStgMedium(&medium);
  203. return E_FAIL;
  204. }
  205. pdropfiles = (DROPFILES*)GlobalLock(medium.hGlobal);
  206. if (!pdropfiles) {
  207. OutputDebugString(L"PyShellExt::GetArguments - failed to lock CF_HDROP hGlobal");
  208. ReleaseStgMedium(&medium);
  209. return E_FAIL;
  210. }
  211. if (pdropfiles->fWide) {
  212. LPCWSTR files = (LPCWSTR)((char*)pdropfiles + pdropfiles->pFiles);
  213. size_t len, count;
  214. hr = FilenameListCchLengthW(files, 32767, &len, &count);
  215. if (SUCCEEDED(hr)) {
  216. LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
  217. if (args) {
  218. hr = FilenameListCchCopyW(args, 32767, files, L" ");
  219. if (SUCCEEDED(hr)) {
  220. *pArguments = args;
  221. } else {
  222. CoTaskMemFree(args);
  223. }
  224. } else {
  225. hr = E_OUTOFMEMORY;
  226. }
  227. }
  228. } else {
  229. LPCSTR files = (LPCSTR)((char*)pdropfiles + pdropfiles->pFiles);
  230. size_t len, count;
  231. hr = FilenameListCchLengthA(files, 32767, &len, &count);
  232. if (SUCCEEDED(hr)) {
  233. LPSTR temp = (LPSTR)CoTaskMemAlloc(sizeof(CHAR) * (len + 1));
  234. if (temp) {
  235. hr = FilenameListCchCopyA(temp, 32767, files, " ");
  236. if (SUCCEEDED(hr)) {
  237. int wlen = MultiByteToWideChar(CP_ACP, 0, temp, (int)len, NULL, 0);
  238. if (wlen) {
  239. LPWSTR args = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (wlen + 1));
  240. if (MultiByteToWideChar(CP_ACP, 0, temp, (int)len, args, wlen + 1)) {
  241. *pArguments = args;
  242. } else {
  243. OutputDebugString(L"PyShellExt::GetArguments - failed to convert multi-byte to wide-char path");
  244. CoTaskMemFree(args);
  245. hr = E_FAIL;
  246. }
  247. } else {
  248. OutputDebugString(L"PyShellExt::GetArguments - failed to get length of wide-char path");
  249. hr = E_FAIL;
  250. }
  251. }
  252. CoTaskMemFree(temp);
  253. } else {
  254. hr = E_OUTOFMEMORY;
  255. }
  256. }
  257. }
  258. GlobalUnlock(medium.hGlobal);
  259. ReleaseStgMedium(&medium);
  260. return hr;
  261. }
  262. HRESULT NotifyDragWindow(HWND hwnd) {
  263. LRESULT res;
  264. if (!hwnd) {
  265. return S_FALSE;
  266. }
  267. res = SendMessage(hwnd, DDWM_UPDATEWINDOW, 0, NULL);
  268. if (res) {
  269. OutputDebugString(L"PyShellExt::NotifyDragWindow - failed to post DDWM_UPDATEWINDOW");
  270. return E_FAIL;
  271. }
  272. return S_OK;
  273. }
  274. public:
  275. // IDropTarget implementation
  276. STDMETHODIMP DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
  277. HWND hwnd;
  278. OutputDebugString(L"PyShellExt::DragEnter");
  279. pDataObj->AddRef();
  280. data_obj = pDataObj;
  281. *pdwEffect = DROPEFFECT_MOVE;
  282. if (FAILED(UpdateDropDescription(data_obj))) {
  283. OutputDebugString(L"PyShellExt::DragEnter - failed to update drop description");
  284. }
  285. if (FAILED(GetDragWindow(data_obj, &hwnd))) {
  286. OutputDebugString(L"PyShellExt::DragEnter - failed to get drag window");
  287. }
  288. if (FAILED(NotifyDragWindow(hwnd))) {
  289. OutputDebugString(L"PyShellExt::DragEnter - failed to notify drag window");
  290. }
  291. return S_OK;
  292. }
  293. STDMETHODIMP DragLeave() {
  294. return S_OK;
  295. }
  296. STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
  297. return S_OK;
  298. }
  299. STDMETHODIMP Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
  300. LPCWSTR args;
  301. OutputDebugString(L"PyShellExt::Drop");
  302. *pdwEffect = DROPEFFECT_NONE;
  303. if (pDataObj != data_obj) {
  304. OutputDebugString(L"PyShellExt::Drop - unexpected data object");
  305. return E_FAIL;
  306. }
  307. data_obj->Release();
  308. data_obj = NULL;
  309. if (SUCCEEDED(GetArguments(pDataObj, &args))) {
  310. OutputDebugString(args);
  311. ShellExecute(NULL, NULL, target, args, target_dir, SW_NORMAL);
  312. CoTaskMemFree((LPVOID)args);
  313. } else {
  314. OutputDebugString(L"PyShellExt::Drop - failed to get launch arguments");
  315. }
  316. return S_OK;
  317. }
  318. // IPersistFile implementation
  319. STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName) {
  320. HRESULT hr;
  321. size_t len;
  322. if (!ppszFileName) {
  323. return E_POINTER;
  324. }
  325. hr = StringCchLength(target, STRSAFE_MAX_CCH - 1, &len);
  326. if (FAILED(hr)) {
  327. return E_FAIL;
  328. }
  329. *ppszFileName = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
  330. if (!*ppszFileName) {
  331. return E_OUTOFMEMORY;
  332. }
  333. hr = StringCchCopy(*ppszFileName, len + 1, target);
  334. if (FAILED(hr)) {
  335. CoTaskMemFree(*ppszFileName);
  336. *ppszFileName = NULL;
  337. return E_FAIL;
  338. }
  339. return S_OK;
  340. }
  341. STDMETHODIMP IsDirty() {
  342. return S_FALSE;
  343. }
  344. STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) {
  345. HRESULT hr;
  346. size_t len;
  347. OutputDebugString(L"PyShellExt::Load");
  348. OutputDebugString(pszFileName);
  349. hr = StringCchLength(pszFileName, STRSAFE_MAX_CCH - 1, &len);
  350. if (FAILED(hr)) {
  351. OutputDebugString(L"PyShellExt::Load - failed to get string length");
  352. return hr;
  353. }
  354. if (target) {
  355. CoTaskMemFree(target);
  356. }
  357. if (target_dir) {
  358. CoTaskMemFree(target_dir);
  359. }
  360. target = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
  361. if (!target) {
  362. OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
  363. return E_OUTOFMEMORY;
  364. }
  365. target_dir = (LPOLESTR)CoTaskMemAlloc(sizeof(WCHAR) * (len + 1));
  366. if (!target_dir) {
  367. OutputDebugString(L"PyShellExt::Load - E_OUTOFMEMORY");
  368. return E_OUTOFMEMORY;
  369. }
  370. hr = StringCchCopy(target, len + 1, pszFileName);
  371. if (FAILED(hr)) {
  372. OutputDebugString(L"PyShellExt::Load - failed to copy string");
  373. return hr;
  374. }
  375. hr = StringCchCopy(target_dir, len + 1, pszFileName);
  376. if (FAILED(hr)) {
  377. OutputDebugString(L"PyShellExt::Load - failed to copy string");
  378. return hr;
  379. }
  380. if (!PathRemoveFileSpecW(target_dir)) {
  381. OutputDebugStringW(L"PyShellExt::Load - failed to remove filespec from target");
  382. return E_FAIL;
  383. }
  384. OutputDebugString(target);
  385. target_mode = dwMode;
  386. OutputDebugString(L"PyShellExt::Load - S_OK");
  387. return S_OK;
  388. }
  389. STDMETHODIMP Save(LPCOLESTR pszFileName, BOOL fRemember) {
  390. return E_NOTIMPL;
  391. }
  392. STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName) {
  393. return E_NOTIMPL;
  394. }
  395. STDMETHODIMP GetClassID(CLSID *pClassID) {
  396. *pClassID = CLSID_PyShellExt;
  397. return S_OK;
  398. }
  399. };
  400. CoCreatableClass(PyShellExt);
  401. STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) {
  402. return Module<InProc>::GetModule().GetClassObject(rclsid, riid, ppv);
  403. }
  404. STDAPI DllCanUnloadNow() {
  405. return Module<InProc>::GetModule().Terminate() ? S_OK : S_FALSE;
  406. }
  407. STDAPI DllRegisterServer() {
  408. LONG res;
  409. SECURITY_ATTRIBUTES secattr = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
  410. LPSECURITY_ATTRIBUTES psecattr = NULL;
  411. HKEY key, ipsKey;
  412. WCHAR modname[MAX_PATH];
  413. DWORD modname_len;
  414. OutputDebugString(L"PyShellExt::DllRegisterServer");
  415. if (!hModule) {
  416. OutputDebugString(L"PyShellExt::DllRegisterServer - module handle was not set");
  417. return SELFREG_E_CLASS;
  418. }
  419. modname_len = GetModuleFileName(hModule, modname, MAX_PATH);
  420. if (modname_len == 0 ||
  421. (modname_len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
  422. OutputDebugString(L"PyShellExt::DllRegisterServer - failed to get module file name");
  423. return SELFREG_E_CLASS;
  424. }
  425. DWORD disp;
  426. res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, CLASS_SUBKEY, 0, NULL, 0,
  427. KEY_ALL_ACCESS, psecattr, &key, &disp);
  428. if (res == ERROR_ACCESS_DENIED) {
  429. OutputDebugString(L"PyShellExt::DllRegisterServer - failed to write per-machine registration. Attempting per-user instead.");
  430. res = RegCreateKeyEx(HKEY_CURRENT_USER, CLASS_SUBKEY, 0, NULL, 0,
  431. KEY_ALL_ACCESS, psecattr, &key, &disp);
  432. }
  433. if (res != ERROR_SUCCESS) {
  434. OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create class key");
  435. return SELFREG_E_CLASS;
  436. }
  437. res = RegCreateKeyEx(key, L"InProcServer32", 0, NULL, 0,
  438. KEY_ALL_ACCESS, psecattr, &ipsKey, NULL);
  439. if (res != ERROR_SUCCESS) {
  440. RegCloseKey(key);
  441. OutputDebugString(L"PyShellExt::DllRegisterServer - failed to create InProcServer32 key");
  442. return SELFREG_E_CLASS;
  443. }
  444. res = RegSetValueEx(ipsKey, NULL, 0,
  445. REG_SZ, (LPBYTE)modname, modname_len * sizeof(modname[0]));
  446. if (res != ERROR_SUCCESS) {
  447. RegCloseKey(ipsKey);
  448. RegCloseKey(key);
  449. OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set server path");
  450. return SELFREG_E_CLASS;
  451. }
  452. res = RegSetValueEx(ipsKey, L"ThreadingModel", 0,
  453. REG_SZ, (LPBYTE)(L"Apartment"), sizeof(L"Apartment"));
  454. RegCloseKey(ipsKey);
  455. RegCloseKey(key);
  456. if (res != ERROR_SUCCESS) {
  457. OutputDebugString(L"PyShellExt::DllRegisterServer - failed to set threading model");
  458. return SELFREG_E_CLASS;
  459. }
  460. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
  461. OutputDebugString(L"PyShellExt::DllRegisterServer - S_OK");
  462. return S_OK;
  463. }
  464. STDAPI DllUnregisterServer() {
  465. LONG res_lm, res_cu;
  466. res_lm = RegDeleteTree(HKEY_LOCAL_MACHINE, CLASS_SUBKEY);
  467. if (res_lm != ERROR_SUCCESS && res_lm != ERROR_FILE_NOT_FOUND) {
  468. OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-machine registration");
  469. return SELFREG_E_CLASS;
  470. }
  471. res_cu = RegDeleteTree(HKEY_CURRENT_USER, CLASS_SUBKEY);
  472. if (res_cu != ERROR_SUCCESS && res_cu != ERROR_FILE_NOT_FOUND) {
  473. OutputDebugString(L"PyShellExt::DllUnregisterServer - failed to delete per-user registration");
  474. return SELFREG_E_CLASS;
  475. }
  476. if (res_lm == ERROR_FILE_NOT_FOUND && res_cu == ERROR_FILE_NOT_FOUND) {
  477. OutputDebugString(L"PyShellExt::DllUnregisterServer - extension was not registered");
  478. return SELFREG_E_CLASS;
  479. }
  480. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
  481. OutputDebugString(L"PyShellExt::DllUnregisterServer - S_OK");
  482. return S_OK;
  483. }
  484. STDAPI_(BOOL) DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) {
  485. if (reason == DLL_PROCESS_ATTACH) {
  486. hModule = hinst;
  487. cfDropDescription = RegisterClipboardFormat(CFSTR_DROPDESCRIPTION);
  488. if (!cfDropDescription) {
  489. OutputDebugString(L"PyShellExt::DllMain - failed to get CFSTR_DROPDESCRIPTION format");
  490. }
  491. cfDragWindow = RegisterClipboardFormat(L"DragWindow");
  492. if (!cfDragWindow) {
  493. OutputDebugString(L"PyShellExt::DllMain - failed to get DragWindow format");
  494. }
  495. DisableThreadLibraryCalls(hinst);
  496. }
  497. return TRUE;
  498. }