Leaked source code of windows server 2003
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.

768 lines
22 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. //
  5. // Copyright (C) Microsoft Corporation
  6. //
  7. // File: init.cpp
  8. //
  9. //--------------------------------------------------------------------------
  10. #include "hotplug.h"
  11. #define HOTPLUG_CLASS_NAME TEXT("HotPlugClass")
  12. VOID
  13. HotPlugDeviceTree(
  14. HWND hwndParent,
  15. BOOLEAN HotPlugTree
  16. )
  17. {
  18. CONFIGRET ConfigRet;
  19. DEVICETREE DeviceTree;
  20. ZeroMemory(&DeviceTree, sizeof(DeviceTree));
  21. DeviceTree.HotPlugTree = HotPlugTree;
  22. InitializeListHead(&DeviceTree.ChildSiblingList);
  23. DialogBoxParam(hHotPlug,
  24. MAKEINTRESOURCE(DLG_DEVTREE),
  25. hwndParent,
  26. DevTreeDlgProc,
  27. (LPARAM)&DeviceTree
  28. );
  29. return;
  30. }
  31. DWORD
  32. WINAPI
  33. HotPlugRemovalVetoedW(
  34. HWND hwnd,
  35. HINSTANCE hInst,
  36. LPWSTR szCmd,
  37. int nShow
  38. )
  39. {
  40. UNREFERENCED_PARAMETER(hwnd);
  41. UNREFERENCED_PARAMETER(hInst);
  42. UNREFERENCED_PARAMETER(nShow);
  43. return HandleVetoedOperation(szCmd, VETOED_REMOVAL);
  44. }
  45. DWORD
  46. WINAPI
  47. HotPlugEjectVetoedW(
  48. HWND hwnd,
  49. HINSTANCE hInst,
  50. LPWSTR szCmd,
  51. int nShow
  52. )
  53. {
  54. UNREFERENCED_PARAMETER(hwnd);
  55. UNREFERENCED_PARAMETER(hInst);
  56. UNREFERENCED_PARAMETER(nShow);
  57. return HandleVetoedOperation(szCmd, VETOED_EJECT);
  58. }
  59. DWORD
  60. WINAPI
  61. HotPlugStandbyVetoedW(
  62. HWND hwnd,
  63. HINSTANCE hInst,
  64. LPWSTR szCmd,
  65. int nShow
  66. )
  67. {
  68. UNREFERENCED_PARAMETER(hwnd);
  69. UNREFERENCED_PARAMETER(hInst);
  70. UNREFERENCED_PARAMETER(nShow);
  71. return HandleVetoedOperation(szCmd, VETOED_STANDBY);
  72. }
  73. DWORD
  74. WINAPI
  75. HotPlugHibernateVetoedW(
  76. HWND hwnd,
  77. HINSTANCE hInst,
  78. LPWSTR szCmd,
  79. int nShow
  80. )
  81. {
  82. UNREFERENCED_PARAMETER(hwnd);
  83. UNREFERENCED_PARAMETER(hInst);
  84. UNREFERENCED_PARAMETER(nShow);
  85. return HandleVetoedOperation(szCmd, VETOED_HIBERNATE);
  86. }
  87. DWORD
  88. WINAPI
  89. HotPlugWarmEjectVetoedW(
  90. HWND hwnd,
  91. HINSTANCE hInst,
  92. LPWSTR szCmd,
  93. int nShow
  94. )
  95. {
  96. UNREFERENCED_PARAMETER(hwnd);
  97. UNREFERENCED_PARAMETER(hInst);
  98. UNREFERENCED_PARAMETER(nShow);
  99. return HandleVetoedOperation(szCmd, VETOED_WARM_EJECT);
  100. }
  101. DWORD
  102. WINAPI
  103. HandleVetoedOperation(
  104. LPWSTR szCmd,
  105. VETOED_OPERATION VetoedOperation
  106. )
  107. {
  108. HANDLE hPipeRead;
  109. HANDLE hEvent;
  110. PNP_VETO_TYPE vetoType;
  111. DWORD bytesRead;
  112. VETO_DEVICE_COLLECTION removalVetoCollection;
  113. //
  114. // Open the specified name pipe and event.
  115. //
  116. if (!OpenPipeAndEventHandles(szCmd,
  117. &hPipeRead,
  118. &hEvent)) {
  119. return 1;
  120. }
  121. ASSERT((hEvent != NULL) && (hEvent != INVALID_HANDLE_VALUE));
  122. ASSERT((hPipeRead != NULL) && (hPipeRead != INVALID_HANDLE_VALUE));
  123. //
  124. // The first DWORD is the VetoType
  125. //
  126. if (!ReadFile(hPipeRead,
  127. (LPVOID)&vetoType,
  128. sizeof(PNP_VETO_TYPE),
  129. &bytesRead,
  130. NULL)) {
  131. CloseHandle(hPipeRead);
  132. SetEvent(hEvent);
  133. CloseHandle(hEvent);
  134. return 1;
  135. }
  136. //
  137. // Now drain all the removal strings. Note that some of them will be
  138. // device instance paths (definitely the first)
  139. //
  140. DeviceCollectionBuildFromPipe(
  141. hPipeRead,
  142. CT_VETOED_REMOVAL_NOTIFICATION,
  143. (PDEVICE_COLLECTION) &removalVetoCollection
  144. );
  145. //
  146. // We are finished reading from the pipe, so close the handle and tell
  147. // umpnpmgr that it can continue.
  148. //
  149. CloseHandle(hPipeRead);
  150. SetEvent(hEvent);
  151. CloseHandle(hEvent);
  152. //
  153. // There should always be one device as that is the device who's removal
  154. // was vetoed.
  155. //
  156. ASSERT(removalVetoCollection.dc.NumDevices);
  157. //
  158. // Invent the VetoedOperation "VETOED_UNDOCK" from an eject containing
  159. // another dock.
  160. //
  161. if (removalVetoCollection.dc.DockInList) {
  162. if (VetoedOperation == VETOED_EJECT) {
  163. VetoedOperation = VETOED_UNDOCK;
  164. } else if (VetoedOperation == VETOED_WARM_EJECT) {
  165. VetoedOperation = VETOED_WARM_UNDOCK;
  166. }
  167. }
  168. removalVetoCollection.VetoType = vetoType;
  169. removalVetoCollection.VetoedOperation = VetoedOperation;
  170. VetoedRemovalUI(&removalVetoCollection);
  171. DeviceCollectionDestroy(
  172. (PDEVICE_COLLECTION) &removalVetoCollection
  173. );
  174. return 1;
  175. }
  176. DWORD
  177. WINAPI
  178. HotPlugSafeRemovalNotificationW(
  179. HWND hwnd,
  180. HINSTANCE hInst,
  181. LPWSTR szCmd,
  182. int nShow
  183. )
  184. {
  185. HANDLE hPipeRead, hEvent;
  186. DEVICE_COLLECTION safeRemovalCollection;
  187. MSG Msg;
  188. WNDCLASS wndClass;
  189. HWND hSafeRemovalWnd;
  190. HANDLE hHotplugIconEvent;
  191. UNREFERENCED_PARAMETER(hwnd);
  192. UNREFERENCED_PARAMETER(hInst);
  193. UNREFERENCED_PARAMETER(nShow);
  194. //
  195. // Open the specified name pipe and event.
  196. //
  197. if (!OpenPipeAndEventHandles(szCmd,
  198. &hPipeRead,
  199. &hEvent)) {
  200. return 1;
  201. }
  202. ASSERT((hEvent != NULL) && (hEvent != INVALID_HANDLE_VALUE));
  203. ASSERT((hPipeRead != NULL) && (hPipeRead != INVALID_HANDLE_VALUE));
  204. //
  205. // Read out the device ID list from the Pipe
  206. //
  207. DeviceCollectionBuildFromPipe(
  208. hPipeRead,
  209. CT_SAFE_REMOVAL_NOTIFICATION,
  210. &safeRemovalCollection
  211. );
  212. //
  213. // On success or error, we are finished reading from the pipe, so close the
  214. // handle and tell umpnpmgr it can continue.
  215. //
  216. CloseHandle(hPipeRead);
  217. SetEvent(hEvent);
  218. CloseHandle(hEvent);
  219. //
  220. // If we have any devices then bring up the safe removal dialog
  221. //
  222. if (safeRemovalCollection.NumDevices) {
  223. if (!GetClassInfo(hHotPlug, HOTPLUG_CLASS_NAME, &wndClass)) {
  224. ZeroMemory(&wndClass, sizeof(wndClass));
  225. wndClass.lpfnWndProc = (safeRemovalCollection.DockInList)
  226. ? DockSafeRemovalBalloonProc
  227. : SafeRemovalBalloonProc;
  228. wndClass.hInstance = hHotPlug;
  229. wndClass.lpszClassName = HOTPLUG_CLASS_NAME;
  230. if (!RegisterClass(&wndClass)) {
  231. goto clean0;
  232. }
  233. }
  234. //
  235. // In order to prevent multiple similar icons on the tray, we will
  236. // create a named event that will be used to serialize the UI.
  237. //
  238. // Note that if we can't create the event for some reason then we will just
  239. // display the UI. This might cause multiple icons, but it is better
  240. // than not displaying any UI at all.
  241. //
  242. hHotplugIconEvent = CreateEvent(NULL,
  243. FALSE,
  244. TRUE,
  245. safeRemovalCollection.DockInList
  246. ? TEXT("Local\\Dock_TaskBarIcon_Event")
  247. : TEXT("Local\\HotPlug_TaskBarIcon_Event")
  248. );
  249. if (hHotplugIconEvent) {
  250. WaitForSingleObject(hHotplugIconEvent, INFINITE);
  251. }
  252. if (!safeRemovalCollection.DockInList) {
  253. //
  254. // First disable the hotplug service so that the icon will go away from
  255. // the taskbar. We do this just in case there are any other hotplug devices
  256. // in the machine since we don't want multiple hotplug icons
  257. // showing up in the taskbar.
  258. //
  259. // NOTE: We don't need to do this for the safe to undock case since
  260. // the docking icon is different.
  261. //
  262. SysTray_EnableService(STSERVICE_HOTPLUG, FALSE);
  263. }
  264. hSafeRemovalWnd = CreateWindowEx(WS_EX_TOOLWINDOW,
  265. HOTPLUG_CLASS_NAME,
  266. TEXT(""),
  267. WS_DLGFRAME | WS_BORDER | WS_DISABLED,
  268. CW_USEDEFAULT,
  269. CW_USEDEFAULT,
  270. 0,
  271. 0,
  272. NULL,
  273. NULL,
  274. hHotPlug,
  275. (LPVOID)&safeRemovalCollection
  276. );
  277. if (hSafeRemovalWnd != NULL) {
  278. while (IsWindow(hSafeRemovalWnd)) {
  279. if (GetMessage(&Msg, NULL, 0, 0)) {
  280. TranslateMessage(&Msg);
  281. DispatchMessage(&Msg);
  282. }
  283. }
  284. }
  285. //
  286. // Set the Event so the next surprise removal process can go to work
  287. // and then close the event handle.
  288. //
  289. if (hHotplugIconEvent) {
  290. SetEvent(hHotplugIconEvent);
  291. CloseHandle(hHotplugIconEvent);
  292. }
  293. if (!safeRemovalCollection.DockInList) {
  294. //
  295. // Re-enable the hotplug service so that the icon can show back up in
  296. // the taskbar if we have any hotplug devices.
  297. //
  298. SysTray_EnableService(STSERVICE_HOTPLUG, TRUE);
  299. }
  300. }
  301. clean0:
  302. DeviceCollectionDestroy(&safeRemovalCollection);
  303. return 1;
  304. }
  305. DWORD
  306. WINAPI
  307. HotPlugDriverBlockedW(
  308. HWND hwnd,
  309. HINSTANCE hInst,
  310. LPWSTR szCmd,
  311. int nShow
  312. )
  313. {
  314. HANDLE hPipeRead, hEvent;
  315. DEVICE_COLLECTION blockedDriverCollection;
  316. HANDLE hDriverBlockIconEvent = NULL;
  317. HANDLE hDriverBlockEvent = NULL;
  318. TCHAR szEventName[MAX_PATH];
  319. UNREFERENCED_PARAMETER(hwnd);
  320. UNREFERENCED_PARAMETER(hInst);
  321. UNREFERENCED_PARAMETER(nShow);
  322. //
  323. // Open the specified name pipe and event.
  324. //
  325. if (OpenPipeAndEventHandles(szCmd,
  326. &hPipeRead,
  327. &hEvent) == FALSE) {
  328. return 1;
  329. }
  330. ASSERT((hEvent != NULL) && (hEvent != INVALID_HANDLE_VALUE));
  331. ASSERT((hPipeRead != NULL) && (hPipeRead != INVALID_HANDLE_VALUE));
  332. //
  333. // Read out the list of blocked driver GUIDs from the Pipe. Note that for
  334. // the CT_BLOCKED_DRIVER_NOTIFICATION collection type, we use only the
  335. // DeviceInstanceId field of each collection entry (which is OK because
  336. // MAX_GUID_STRING_LEN << MAX_DEVICE_ID_LEN). All other fields are skipped.
  337. //
  338. DeviceCollectionBuildFromPipe(
  339. hPipeRead,
  340. CT_BLOCKED_DRIVER_NOTIFICATION,
  341. &blockedDriverCollection
  342. );
  343. //
  344. // On success or error, we are finished reading from the pipe, so close the
  345. // handle and tell umpnpmgr it can continue.
  346. //
  347. CloseHandle(hPipeRead);
  348. SetEvent(hEvent);
  349. CloseHandle(hEvent);
  350. //
  351. // Since the balloons can hang around for numerous seconds, or longer if
  352. // there is no user input to the system, we need to make sure that we only
  353. // have one driver block event queued for any given type of driver block.
  354. // This way if there is an attempt to load a driver many times in a row, we
  355. // won't queue up numerous driver blocked ballons for the same driver.
  356. // We will do this by creating a local event that includes the GUID of the
  357. // blocked driver or no GUID if we are showing the generic balloon.
  358. //
  359. if (SUCCEEDED(StringCchPrintf(szEventName,
  360. SIZECHARS(szEventName),
  361. TEXT("Local\\DRIVERBLOCK-%s"),
  362. (blockedDriverCollection.NumDevices == 1)
  363. ? DeviceCollectionGetDeviceInstancePath(&blockedDriverCollection, 0)
  364. : TEXT("ALL")
  365. ))) {
  366. hDriverBlockEvent = CreateEvent(NULL,
  367. FALSE,
  368. TRUE,
  369. szEventName
  370. );
  371. if (hDriverBlockEvent) {
  372. if (WaitForSingleObject(hDriverBlockEvent, 0) != WAIT_OBJECT_0) {
  373. //
  374. // This means that this driver block balloon is either being
  375. // displayed, or in the queue to be displayed, so we can just
  376. // exit this process.
  377. //
  378. goto clean0;
  379. }
  380. }
  381. }
  382. //
  383. // In order to prevent multipe driver blocked icons and ballons showing up
  384. // on the taskbar together and stepping on each other, we will create a
  385. // named event that will be used to serialize the driver blocked icons and
  386. // balloon UI.
  387. //
  388. // Note that if we can't create the event for some reason then we will just
  389. // display the UI. This might cause multiple driver blocked icons, but it
  390. // is better than not displaying any UI at all.
  391. //
  392. // Also note that we can coexist with normal hotplug icon. As such we have
  393. // a different event name and a different icon.
  394. //
  395. hDriverBlockIconEvent = CreateEvent(NULL,
  396. FALSE,
  397. TRUE,
  398. TEXT("Local\\HotPlug_DriverBlockedIcon_Event")
  399. );
  400. if (hDriverBlockIconEvent) {
  401. for (;;) {
  402. DWORD waitStatus;
  403. waitStatus = MsgWaitForMultipleObjects(1,
  404. &hDriverBlockIconEvent,
  405. FALSE,
  406. INFINITE,
  407. QS_ALLINPUT);
  408. if (waitStatus == WAIT_OBJECT_0) {
  409. //
  410. // The current driver block icon went away so it is our turn.
  411. //
  412. break;
  413. } else if (waitStatus == (WAIT_OBJECT_0 + 1)) {
  414. //
  415. // Message in the queue.
  416. //
  417. MSG msg;
  418. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  419. if (msg.message == WM_CLOSE) {
  420. goto clean0;
  421. }
  422. TranslateMessage(&msg);
  423. DispatchMessage(&msg);
  424. }
  425. } else {
  426. //
  427. // This shouldn't happen.
  428. //
  429. goto clean0;
  430. }
  431. }
  432. }
  433. //
  434. // Show the balloon.
  435. //
  436. DisplayDriverBlockBalloon(&blockedDriverCollection);
  437. //
  438. // Since the balloon has now gone away, set the event so if we get another
  439. // block on the same driver it will display another balloon.
  440. //
  441. if (hDriverBlockEvent) {
  442. SetEvent(hDriverBlockEvent);
  443. }
  444. clean0:
  445. //
  446. // Set the Event so the next blocked driver process can go to work and then
  447. // close the event handle.
  448. //
  449. if (hDriverBlockIconEvent) {
  450. SetEvent(hDriverBlockIconEvent);
  451. CloseHandle(hDriverBlockIconEvent);
  452. }
  453. if (hDriverBlockEvent) {
  454. CloseHandle(hDriverBlockEvent);
  455. }
  456. //
  457. // Destroy the collection.
  458. //
  459. DeviceCollectionDestroy(&blockedDriverCollection);
  460. return 1;
  461. }
  462. DWORD
  463. WINAPI
  464. HotPlugChildWithInvalidIdW(
  465. HWND hwnd,
  466. HINSTANCE hInst,
  467. LPWSTR szCmd,
  468. int nShow
  469. )
  470. {
  471. HANDLE hPipeRead, hEvent;
  472. DEVICE_COLLECTION childWithInvalidIdCollection;
  473. HANDLE hChildWithInvalidIdIconEvent = NULL;
  474. HANDLE hChildWithInvalidIdEvent = NULL;
  475. TCHAR szEventName[MAX_PATH];
  476. UNREFERENCED_PARAMETER(hwnd);
  477. UNREFERENCED_PARAMETER(hInst);
  478. UNREFERENCED_PARAMETER(nShow);
  479. //
  480. // Open the specified name pipe and event.
  481. //
  482. if (!OpenPipeAndEventHandles(szCmd,
  483. &hPipeRead,
  484. &hEvent)) {
  485. return 1;
  486. }
  487. ASSERT((hEvent != NULL) && (hEvent != INVALID_HANDLE_VALUE));
  488. ASSERT((hPipeRead != NULL) && (hPipeRead != INVALID_HANDLE_VALUE));
  489. //
  490. // Read out the device instance Id of the parent that has a child device
  491. // with an invalid Id. Note, if we are passed multiple device instance
  492. // Ids, we will only display UI for the first one in the list.
  493. //
  494. DeviceCollectionBuildFromPipe(
  495. hPipeRead,
  496. CT_CHILD_WITH_INVALID_ID_NOTIFICATION,
  497. &childWithInvalidIdCollection
  498. );
  499. //
  500. // On success or error, we are finished reading from the pipe, so close the
  501. // handle and tell umpnpmgr it can continue.
  502. //
  503. CloseHandle(hPipeRead);
  504. SetEvent(hEvent);
  505. CloseHandle(hEvent);
  506. //
  507. // Since the balloons can hang around for numerous seconds, or longer if
  508. // there is no user input to the system, we need to make sure that we only
  509. // have one invalid child event queued for any given parent with an invalid
  510. // child.
  511. //
  512. StringCchPrintf(szEventName,
  513. SIZECHARS(szEventName),
  514. TEXT("Local\\CHILDWITHINVALIDID-%s"),
  515. DeviceCollectionGetDeviceInstancePath(&childWithInvalidIdCollection, 0)
  516. );
  517. hChildWithInvalidIdEvent = CreateEvent(NULL,
  518. FALSE,
  519. TRUE,
  520. szEventName
  521. );
  522. if (hChildWithInvalidIdEvent) {
  523. if (WaitForSingleObject(hChildWithInvalidIdEvent, 0) != WAIT_OBJECT_0) {
  524. //
  525. // This means that this invalid child balloon is either being
  526. // displayed, or in the queue to be displayed, so we can just
  527. // exit this process.
  528. //
  529. goto clean0;
  530. }
  531. }
  532. //
  533. // In order to prevent multipe invalid child icons and ballons showing up
  534. // on the taskbar together and stepping on each other, we will create a
  535. // named event that will be used to serialize the invalid child icons and
  536. // balloon UI.
  537. //
  538. // Note that if we can't create the event for some reason then we will just
  539. // display the UI. This might cause multiple invalid child icons, but it
  540. // is better than not displaying any UI at all.
  541. //
  542. // Also note that we can coexist with normal hotplug icon. As such we have
  543. // a different event name and a different icon.
  544. //
  545. hChildWithInvalidIdIconEvent = CreateEvent(NULL,
  546. FALSE,
  547. TRUE,
  548. TEXT("Local\\HotPlug_ChildWithInvalidId_Event")
  549. );
  550. if (hChildWithInvalidIdIconEvent) {
  551. for (;;) {
  552. DWORD waitStatus;
  553. waitStatus = MsgWaitForMultipleObjects(1,
  554. &hChildWithInvalidIdIconEvent,
  555. FALSE,
  556. INFINITE,
  557. QS_ALLINPUT);
  558. if (waitStatus == WAIT_OBJECT_0) {
  559. //
  560. // The current invalid child icon went away so it is our turn.
  561. //
  562. break;
  563. } else if (waitStatus == (WAIT_OBJECT_0 + 1)) {
  564. //
  565. // Message in the queue.
  566. //
  567. MSG msg;
  568. while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  569. if (msg.message == WM_CLOSE) {
  570. goto clean0;
  571. }
  572. TranslateMessage(&msg);
  573. DispatchMessage(&msg);
  574. }
  575. } else {
  576. //
  577. // This shouldn't happen.
  578. //
  579. goto clean0;
  580. }
  581. }
  582. }
  583. //
  584. // Show the balloon.
  585. //
  586. DisplayChildWithInvalidIdBalloon(&childWithInvalidIdCollection);
  587. //
  588. // Since the balloon has now gone away, set the event so if we get another
  589. // invalid child device it will display another balloon.
  590. //
  591. if (hChildWithInvalidIdEvent) {
  592. SetEvent(hChildWithInvalidIdEvent);
  593. }
  594. clean0:
  595. //
  596. // Set the Event so the next invalid child process can go to work and then
  597. // close the event handle.
  598. //
  599. if (hChildWithInvalidIdIconEvent) {
  600. SetEvent(hChildWithInvalidIdIconEvent);
  601. CloseHandle(hChildWithInvalidIdIconEvent);
  602. }
  603. if (hChildWithInvalidIdEvent) {
  604. CloseHandle(hChildWithInvalidIdEvent);
  605. }
  606. //
  607. // Destroy the collection.
  608. //
  609. DeviceCollectionDestroy(&childWithInvalidIdCollection);
  610. return 1;
  611. }
  612. LONG
  613. CPlApplet(
  614. HWND hWnd,
  615. WORD uMsg,
  616. DWORD_PTR lParam1,
  617. LRESULT lParam2
  618. )
  619. {
  620. LPNEWCPLINFO lpCPlInfo;
  621. LPCPLINFO lpOldCPlInfo;
  622. UNREFERENCED_PARAMETER(lParam1);
  623. switch (uMsg) {
  624. case CPL_INIT:
  625. return TRUE;
  626. case CPL_GETCOUNT:
  627. return 1;
  628. case CPL_INQUIRE:
  629. lpOldCPlInfo = (LPCPLINFO)(LPARAM)lParam2;
  630. lpOldCPlInfo->lData = 0L;
  631. lpOldCPlInfo->idIcon = IDI_HOTPLUGICON;
  632. lpOldCPlInfo->idName = IDS_HOTPLUGNAME;
  633. lpOldCPlInfo->idInfo = IDS_HOTPLUGINFO;
  634. return TRUE;
  635. case CPL_NEWINQUIRE:
  636. lpCPlInfo = (LPNEWCPLINFO)(LPARAM)lParam2;
  637. lpCPlInfo->hIcon = LoadIcon(hHotPlug, MAKEINTRESOURCE(IDI_HOTPLUGICON));
  638. LoadString(hHotPlug, IDS_HOTPLUGNAME, lpCPlInfo->szName, SIZECHARS(lpCPlInfo->szName));
  639. LoadString(hHotPlug, IDS_HOTPLUGINFO, lpCPlInfo->szInfo, SIZECHARS(lpCPlInfo->szInfo));
  640. lpCPlInfo->dwHelpContext = IDH_HOTPLUGAPPLET;
  641. lpCPlInfo->dwSize = sizeof(NEWCPLINFO);
  642. lpCPlInfo->lData = 0;
  643. lpCPlInfo->szHelpFile[0] = '\0';
  644. return TRUE;
  645. case CPL_DBLCLK:
  646. HotPlugDeviceTree(hWnd, TRUE);
  647. break;
  648. default:
  649. break;
  650. }
  651. return 0L;
  652. }