startmenu.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. /*
  2. * Copyright (C) 2008 Vincent Povirk
  3. *
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Lesser General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2.1 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public
  15. * License along with this library; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  17. */
  18. #define COBJMACROS
  19. #include <windows.h>
  20. #include <shellapi.h>
  21. #include <shlguid.h>
  22. #include <shlobj.h>
  23. #include <shlwapi.h>
  24. #include <shobjidl.h>
  25. #include "wine/debug.h"
  26. #include "wine/list.h"
  27. #include "explorer_private.h"
  28. #include "resource.h"
  29. WINE_DEFAULT_DEBUG_CHANNEL(explorer);
  30. struct menu_item
  31. {
  32. struct list entry;
  33. LPWSTR displayname;
  34. /* parent information */
  35. struct menu_item* parent;
  36. LPITEMIDLIST pidl; /* relative to parent; absolute if parent->pidl is NULL */
  37. /* folder information */
  38. IShellFolder* folder;
  39. struct menu_item* base;
  40. HMENU menuhandle;
  41. BOOL menu_filled;
  42. };
  43. static struct list items = LIST_INIT(items);
  44. static struct menu_item root_menu;
  45. static struct menu_item public_startmenu;
  46. static struct menu_item user_startmenu;
  47. #define MENU_ID_RUN 1
  48. static ULONG copy_pidls(struct menu_item* item, LPITEMIDLIST dest)
  49. {
  50. ULONG item_size;
  51. ULONG bytes_copied = 2;
  52. if (item->parent->pidl)
  53. {
  54. bytes_copied = copy_pidls(item->parent, dest);
  55. }
  56. item_size = ILGetSize(item->pidl);
  57. if (dest)
  58. memcpy(((char*)dest) + bytes_copied - 2, item->pidl, item_size);
  59. return bytes_copied + item_size - 2;
  60. }
  61. static LPITEMIDLIST build_pidl(struct menu_item* item)
  62. {
  63. ULONG length;
  64. LPITEMIDLIST result;
  65. length = copy_pidls(item, NULL);
  66. result = CoTaskMemAlloc(length);
  67. copy_pidls(item, result);
  68. return result;
  69. }
  70. static void exec_item(struct menu_item* item)
  71. {
  72. LPITEMIDLIST abs_pidl;
  73. SHELLEXECUTEINFOW sei;
  74. abs_pidl = build_pidl(item);
  75. ZeroMemory(&sei, sizeof(sei));
  76. sei.cbSize = sizeof(sei);
  77. sei.fMask = SEE_MASK_IDLIST;
  78. sei.nShow = SW_SHOWNORMAL;
  79. sei.lpIDList = abs_pidl;
  80. ShellExecuteExW(&sei);
  81. CoTaskMemFree(abs_pidl);
  82. }
  83. static HRESULT pidl_to_shellfolder(LPITEMIDLIST pidl, LPWSTR *displayname, IShellFolder **out_folder)
  84. {
  85. IShellFolder* parent_folder=NULL;
  86. LPCITEMIDLIST relative_pidl=NULL;
  87. STRRET strret;
  88. HRESULT hr;
  89. hr = SHBindToParent(pidl, &IID_IShellFolder, (void**)&parent_folder, &relative_pidl);
  90. if (displayname)
  91. {
  92. if (SUCCEEDED(hr))
  93. hr = IShellFolder_GetDisplayNameOf(parent_folder, relative_pidl, SHGDN_INFOLDER, &strret);
  94. if (SUCCEEDED(hr))
  95. hr = StrRetToStrW(&strret, NULL, displayname);
  96. }
  97. if (SUCCEEDED(hr))
  98. hr = IShellFolder_BindToObject(parent_folder, relative_pidl, NULL, &IID_IShellFolder, (void**)out_folder);
  99. if (parent_folder)
  100. IShellFolder_Release(parent_folder);
  101. return hr;
  102. }
  103. static BOOL shell_folder_is_empty(IShellFolder* folder)
  104. {
  105. IEnumIDList* enumidl;
  106. LPITEMIDLIST pidl=NULL;
  107. if (IShellFolder_EnumObjects(folder, NULL, SHCONTF_NONFOLDERS, &enumidl) == S_OK)
  108. {
  109. if (IEnumIDList_Next(enumidl, 1, &pidl, NULL) == S_OK)
  110. {
  111. CoTaskMemFree(pidl);
  112. IEnumIDList_Release(enumidl);
  113. return FALSE;
  114. }
  115. IEnumIDList_Release(enumidl);
  116. }
  117. if (IShellFolder_EnumObjects(folder, NULL, SHCONTF_FOLDERS, &enumidl) == S_OK)
  118. {
  119. BOOL found = FALSE;
  120. IShellFolder *child_folder;
  121. while (!found && IEnumIDList_Next(enumidl, 1, &pidl, NULL) == S_OK)
  122. {
  123. if (IShellFolder_BindToObject(folder, pidl, NULL, &IID_IShellFolder, (void *)&child_folder) == S_OK)
  124. {
  125. if (!shell_folder_is_empty(child_folder))
  126. found = TRUE;
  127. IShellFolder_Release(child_folder);
  128. }
  129. CoTaskMemFree(pidl);
  130. }
  131. IEnumIDList_Release(enumidl);
  132. if (found)
  133. return FALSE;
  134. }
  135. return TRUE;
  136. }
  137. /* add an individual file or folder to the menu, takes ownership of pidl */
  138. static struct menu_item* add_shell_item(struct menu_item* parent, LPITEMIDLIST pidl)
  139. {
  140. struct menu_item* item;
  141. MENUITEMINFOW mii;
  142. HMENU parent_menu;
  143. int existing_item_count, i;
  144. BOOL match = FALSE;
  145. SFGAOF flags;
  146. item = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct menu_item));
  147. if (parent->pidl == NULL)
  148. {
  149. pidl_to_shellfolder(pidl, &item->displayname, &item->folder);
  150. }
  151. else
  152. {
  153. STRRET strret;
  154. if (SUCCEEDED(IShellFolder_GetDisplayNameOf(parent->folder, pidl, SHGDN_INFOLDER, &strret)))
  155. StrRetToStrW(&strret, NULL, &item->displayname);
  156. flags = SFGAO_FOLDER;
  157. IShellFolder_GetAttributesOf(parent->folder, 1, (LPCITEMIDLIST*)&pidl, &flags);
  158. if (flags & SFGAO_FOLDER)
  159. IShellFolder_BindToObject(parent->folder, pidl, NULL, &IID_IShellFolder, (void *)&item->folder);
  160. }
  161. if (item->folder && shell_folder_is_empty(item->folder))
  162. {
  163. IShellFolder_Release(item->folder);
  164. HeapFree(GetProcessHeap(), 0, item->displayname);
  165. HeapFree(GetProcessHeap(), 0, item);
  166. CoTaskMemFree(pidl);
  167. return NULL;
  168. }
  169. parent_menu = parent->menuhandle;
  170. item->parent = parent;
  171. item->pidl = pidl;
  172. existing_item_count = GetMenuItemCount(parent_menu);
  173. mii.cbSize = sizeof(mii);
  174. mii.fMask = MIIM_SUBMENU|MIIM_DATA;
  175. /* search for an existing menu item with this name or the spot to insert this item */
  176. if (parent->pidl != NULL)
  177. {
  178. for (i=0; i<existing_item_count; i++)
  179. {
  180. struct menu_item* existing_item;
  181. int cmp;
  182. GetMenuItemInfoW(parent_menu, i, TRUE, &mii);
  183. existing_item = ((struct menu_item*)mii.dwItemData);
  184. if (!existing_item)
  185. continue;
  186. /* folders before files */
  187. if (existing_item->folder && !item->folder)
  188. continue;
  189. if (!existing_item->folder && item->folder)
  190. break;
  191. cmp = CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, item->displayname, -1, existing_item->displayname, -1);
  192. if (cmp == CSTR_LESS_THAN)
  193. break;
  194. if (cmp == CSTR_EQUAL)
  195. {
  196. match = TRUE;
  197. break;
  198. }
  199. }
  200. }
  201. else
  202. /* This item manually added to the root menu, so put it at the end */
  203. i = existing_item_count;
  204. if (!match)
  205. {
  206. /* no existing item with the same name; just add it */
  207. mii.fMask = MIIM_STRING|MIIM_DATA;
  208. mii.dwTypeData = item->displayname;
  209. mii.dwItemData = (ULONG_PTR)item;
  210. if (item->folder)
  211. {
  212. MENUINFO mi;
  213. item->menuhandle = CreatePopupMenu();
  214. mii.fMask |= MIIM_SUBMENU;
  215. mii.hSubMenu = item->menuhandle;
  216. mi.cbSize = sizeof(mi);
  217. mi.fMask = MIM_MENUDATA;
  218. mi.dwMenuData = (ULONG_PTR)item;
  219. SetMenuInfo(item->menuhandle, &mi);
  220. }
  221. InsertMenuItemW(parent->menuhandle, i, TRUE, &mii);
  222. list_add_tail(&items, &item->entry);
  223. }
  224. else if (item->folder)
  225. {
  226. /* there is an existing folder with the same name, combine them */
  227. MENUINFO mi;
  228. item->base = (struct menu_item*)mii.dwItemData;
  229. item->menuhandle = item->base->menuhandle;
  230. mii.dwItemData = (ULONG_PTR)item;
  231. SetMenuItemInfoW(parent_menu, i, TRUE, &mii);
  232. mi.cbSize = sizeof(mi);
  233. mi.fMask = MIM_MENUDATA;
  234. mi.dwMenuData = (ULONG_PTR)item;
  235. SetMenuInfo(item->menuhandle, &mi);
  236. list_add_tail(&items, &item->entry);
  237. }
  238. else {
  239. /* duplicate shortcut, do nothing */
  240. HeapFree(GetProcessHeap(), 0, item->displayname);
  241. HeapFree(GetProcessHeap(), 0, item);
  242. CoTaskMemFree(pidl);
  243. item = NULL;
  244. }
  245. return item;
  246. }
  247. static void add_folder_contents(struct menu_item* parent)
  248. {
  249. IEnumIDList* enumidl;
  250. if (IShellFolder_EnumObjects(parent->folder, NULL,
  251. SHCONTF_FOLDERS|SHCONTF_NONFOLDERS, &enumidl) == S_OK)
  252. {
  253. LPITEMIDLIST rel_pidl=NULL;
  254. while (S_OK == IEnumIDList_Next(enumidl, 1, &rel_pidl, NULL))
  255. {
  256. add_shell_item(parent, rel_pidl);
  257. }
  258. IEnumIDList_Release(enumidl);
  259. }
  260. }
  261. static void destroy_menus(void)
  262. {
  263. if (!root_menu.menuhandle)
  264. return;
  265. DestroyMenu(root_menu.menuhandle);
  266. root_menu.menuhandle = NULL;
  267. while (!list_empty(&items))
  268. {
  269. struct menu_item* item;
  270. item = LIST_ENTRY(list_head(&items), struct menu_item, entry);
  271. if (item->folder)
  272. IShellFolder_Release(item->folder);
  273. CoTaskMemFree(item->pidl);
  274. CoTaskMemFree(item->displayname);
  275. list_remove(&item->entry);
  276. HeapFree(GetProcessHeap(), 0, item);
  277. }
  278. }
  279. static void fill_menu(struct menu_item* item)
  280. {
  281. if (!item->menu_filled)
  282. {
  283. add_folder_contents(item);
  284. if (item->base)
  285. {
  286. fill_menu(item->base);
  287. }
  288. item->menu_filled = TRUE;
  289. }
  290. }
  291. static void run_dialog(void)
  292. {
  293. void WINAPI (*pRunFileDlg)(HWND hWndOwner, HICON hIcon, LPCSTR lpszDir,
  294. LPCSTR lpszTitle, LPCSTR lpszDesc, DWORD dwFlags);
  295. HMODULE hShell32;
  296. hShell32 = LoadLibraryW(L"shell32");
  297. pRunFileDlg = (void*)GetProcAddress(hShell32, (LPCSTR)61);
  298. pRunFileDlg(NULL, NULL, NULL, NULL, NULL, 0);
  299. FreeLibrary(hShell32);
  300. }
  301. LRESULT menu_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
  302. {
  303. switch (msg)
  304. {
  305. case WM_INITMENUPOPUP:
  306. {
  307. HMENU hmenu = (HMENU)wparam;
  308. struct menu_item* item;
  309. MENUINFO mi;
  310. mi.cbSize = sizeof(mi);
  311. mi.fMask = MIM_MENUDATA;
  312. GetMenuInfo(hmenu, &mi);
  313. item = (struct menu_item*)mi.dwMenuData;
  314. if (item)
  315. fill_menu(item);
  316. return 0;
  317. }
  318. break;
  319. case WM_MENUCOMMAND:
  320. {
  321. HMENU hmenu = (HMENU)lparam;
  322. struct menu_item* item;
  323. MENUITEMINFOW mii;
  324. mii.cbSize = sizeof(mii);
  325. mii.fMask = MIIM_DATA|MIIM_ID;
  326. GetMenuItemInfoW(hmenu, wparam, TRUE, &mii);
  327. item = (struct menu_item*)mii.dwItemData;
  328. if (item)
  329. exec_item(item);
  330. else if (mii.wID == MENU_ID_RUN)
  331. run_dialog();
  332. destroy_menus();
  333. return 0;
  334. }
  335. }
  336. return DefWindowProcW(hwnd, msg, wparam, lparam);
  337. }
  338. void do_startmenu(HWND hwnd)
  339. {
  340. LPITEMIDLIST pidl;
  341. MENUINFO mi;
  342. MENUITEMINFOW mii;
  343. RECT rc={0,0,0,0};
  344. TPMPARAMS tpm;
  345. WCHAR run_label[50];
  346. destroy_menus();
  347. WINE_TRACE("creating start menu\n");
  348. root_menu.menuhandle = public_startmenu.menuhandle = user_startmenu.menuhandle = CreatePopupMenu();
  349. if (!root_menu.menuhandle)
  350. {
  351. return;
  352. }
  353. user_startmenu.parent = public_startmenu.parent = &root_menu;
  354. user_startmenu.base = &public_startmenu;
  355. user_startmenu.menu_filled = public_startmenu.menu_filled = FALSE;
  356. if (!user_startmenu.pidl)
  357. SHGetSpecialFolderLocation(NULL, CSIDL_STARTMENU, &user_startmenu.pidl);
  358. if (!user_startmenu.folder)
  359. pidl_to_shellfolder(user_startmenu.pidl, NULL, &user_startmenu.folder);
  360. if (!public_startmenu.pidl)
  361. SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_STARTMENU, &public_startmenu.pidl);
  362. if (!public_startmenu.folder)
  363. pidl_to_shellfolder(public_startmenu.pidl, NULL, &public_startmenu.folder);
  364. if ((user_startmenu.folder && !shell_folder_is_empty(user_startmenu.folder)) ||
  365. (public_startmenu.folder && !shell_folder_is_empty(public_startmenu.folder)))
  366. {
  367. fill_menu(&user_startmenu);
  368. AppendMenuW(root_menu.menuhandle, MF_SEPARATOR, 0, NULL);
  369. }
  370. if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_CONTROLS, &pidl)))
  371. add_shell_item(&root_menu, pidl);
  372. LoadStringW(NULL, IDS_RUN, run_label, ARRAY_SIZE(run_label));
  373. mii.cbSize = sizeof(mii);
  374. mii.fMask = MIIM_STRING|MIIM_ID;
  375. mii.dwTypeData = run_label;
  376. mii.wID = MENU_ID_RUN;
  377. InsertMenuItemW(root_menu.menuhandle, -1, TRUE, &mii);
  378. mi.cbSize = sizeof(mi);
  379. mi.fMask = MIM_STYLE;
  380. mi.dwStyle = MNS_NOTIFYBYPOS;
  381. SetMenuInfo(root_menu.menuhandle, &mi);
  382. GetWindowRect(hwnd, &rc);
  383. tpm.cbSize = sizeof(tpm);
  384. tpm.rcExclude = rc;
  385. if (!TrackPopupMenuEx(root_menu.menuhandle,
  386. TPM_LEFTALIGN|TPM_BOTTOMALIGN|TPM_VERTICAL,
  387. rc.left, rc.top, hwnd, &tpm))
  388. {
  389. WINE_ERR("couldn't display menu\n");
  390. }
  391. }