Source code of Windows XP (NT5)
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.

2292 lines
62 KiB

  1. /*
  2. * Copyright (c) 1992-1997 Microsoft Corporation
  3. * hotplug routines
  4. *
  5. * 09-May-1997 Jonle , created
  6. *
  7. */
  8. #include "stdafx.h"
  9. #include <nt.h>
  10. #include <ntrtl.h>
  11. #include <nturtl.h>
  12. #include "systray.h"
  13. #include <setupapi.h>
  14. #include <cfgmgr32.h>
  15. #include <dbt.h>
  16. #include <initguid.h>
  17. #include <devguid.h>
  18. #include <ks.h>
  19. #include <ksmedia.h>
  20. #include <ntddstor.h>
  21. //
  22. // Support system sounds for device events.
  23. //
  24. #define SOUNDS 1
  25. #if SOUNDS
  26. BOOL
  27. HotplugPlaySoundThisSession(
  28. VOID
  29. );
  30. //
  31. // Hardware application sound event names.
  32. //
  33. #define DEVICE_ARRIVAL_SOUND TEXT("DeviceConnect")
  34. #define DEVICE_REMOVAL_SOUND TEXT("DeviceDisconnect")
  35. #define DEVICE_FAILURE_SOUND TEXT("DeviceFail")
  36. #endif // SOUNDS
  37. //
  38. // Simple checks for console / remote TS sessions.
  39. //
  40. #define MAIN_SESSION ((ULONG)0)
  41. #define THIS_SESSION ((ULONG)NtCurrentPeb()->SessionId)
  42. #define CONSOLE_SESSION ((ULONG)USER_SHARED_DATA->ActiveConsoleId)
  43. #define IsConsoleSession() (BOOL)(THIS_SESSION == CONSOLE_SESSION)
  44. #define IsRemoteSession() (BOOL)(THIS_SESSION != CONSOLE_SESSION)
  45. #define IsPseudoConsoleSession() (BOOL)(THIS_SESSION == MAIN_SESSION)
  46. //
  47. // 2001/02/01 - Disable support for *both* arrival bubbles and departure
  48. // bubbles (hotplug.dll must be kept in sync here.) This code is disabled for
  49. // beta2, and should be removed afterwards if feedback is positive.
  50. //
  51. #define BUBBLES 0
  52. #define HPLUG_EJECT_EVENT TEXT("HPlugEjectEvent")
  53. #if BUBBLES
  54. #define HPLUG_DOCK_TIMER TEXT("HPlugProfileChangeTimer")
  55. #define SURPRISE_UNDOCK_TIMER TEXT("Local\\HotPlug_SurpriseUndockTimer_{25126bc2-1ab0-4494-8b6d-e4034cb9c24a}")
  56. //
  57. // We suppress bubbles for some period of time after a dock event. Free build,
  58. // ~15 secs, Checked build ~60.
  59. //
  60. #if DBG
  61. #define BUBBLE_SUPPRESSION_TIME 60
  62. #else
  63. #define BUBBLE_SUPPRESSION_TIME 15
  64. #endif
  65. #endif // BUBBLES
  66. typedef struct _HotPlugDevices {
  67. struct _HotPlugDevices *Next;
  68. DEVINST DevInst;
  69. WORD EjectMenuIndex;
  70. BOOLEAN PendingEvent;
  71. PTCHAR DevName;
  72. TCHAR DevInstanceId[1];
  73. } HOTPLUGDEVICES, *PHOTPLUGDEVICES;
  74. BOOL HotPlugInitialized = FALSE;
  75. BOOL ShowShellIcon = FALSE;
  76. HICON HotPlugIcon = NULL;
  77. BOOL ServiceEnabled = FALSE;
  78. HANDLE hEjectEvent = NULL; // Event to if we are in the process of ejecting a device
  79. HDEVINFO g_hCurrentDeviceInfoSet = INVALID_HANDLE_VALUE;
  80. HDEVINFO g_hRemovableDeviceInfoSet = INVALID_HANDLE_VALUE;
  81. #if BUBBLES
  82. HANDLE hDockEventTimer = NULL;
  83. HANDLE hSurpriseUndockEventTimer = NULL;
  84. CRITICAL_SECTION g_BubbleCrit = { 0 };
  85. #endif // BUBBLES
  86. extern HINSTANCE g_hInstance; // Global instance handle 4 this application.
  87. BOOL
  88. pDoesUserHavePrivilege(
  89. PCTSTR PrivilegeName
  90. )
  91. /*++
  92. Routine Description:
  93. This routine returns TRUE if the caller's process has
  94. the specified privilege. The privilege does not have
  95. to be currently enabled. This routine is used to indicate
  96. whether the caller has the potential to enable the privilege.
  97. Caller is NOT expected to be impersonating anyone and IS
  98. expected to be able to open their own process and process
  99. token.
  100. Arguments:
  101. Privilege - the name form of privilege ID (such as
  102. SE_SECURITY_NAME).
  103. Return Value:
  104. TRUE - Caller has the specified privilege.
  105. FALSE - Caller does not have the specified privilege.
  106. --*/
  107. {
  108. HANDLE Token;
  109. ULONG BytesRequired;
  110. PTOKEN_PRIVILEGES Privileges;
  111. BOOL b;
  112. DWORD i;
  113. LUID Luid;
  114. //
  115. // Open the process token.
  116. //
  117. if(!OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&Token)) {
  118. return(FALSE);
  119. }
  120. b = FALSE;
  121. Privileges = NULL;
  122. //
  123. // Get privilege information.
  124. //
  125. if(!GetTokenInformation(Token,TokenPrivileges,NULL,0,&BytesRequired)
  126. && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
  127. && (Privileges = LocalAlloc(LPTR, BytesRequired))
  128. && GetTokenInformation(Token,TokenPrivileges,Privileges,BytesRequired,&BytesRequired)
  129. && LookupPrivilegeValue(NULL,PrivilegeName,&Luid)) {
  130. //
  131. // See if we have the requested privilege
  132. //
  133. for(i=0; i<Privileges->PrivilegeCount; i++) {
  134. if((Luid.LowPart == Privileges->Privileges[i].Luid.LowPart)
  135. && (Luid.HighPart == Privileges->Privileges[i].Luid.HighPart)) {
  136. b = TRUE;
  137. break;
  138. }
  139. }
  140. }
  141. //
  142. // Clean up and return.
  143. //
  144. if(Privileges) {
  145. LocalFree(Privileges);
  146. }
  147. CloseHandle(Token);
  148. return(b);
  149. }
  150. BOOL
  151. IsHotPlugDevice(
  152. DEVINST DevInst,
  153. PBOOL WarnUserOnInsertion OPTIONAL
  154. )
  155. /**+
  156. A device is considered a HotPlug device if the following are TRUE:
  157. - has Capability CM_DEVCAP_REMOVABLE
  158. - does NOT have Capability CM_DEVCAP_SURPRISEREMOVALOK
  159. - does NOT have Capability CM_DEVCAP_DOCKDEVICE
  160. - must be started (have the DN_STARTED devnode flag)
  161. - unless has capability CM_DEVCAP_EJECTSUPPORTED
  162. WarnUserOnInsertion - Receives TRUE if the user should be warned to use the
  163. hotplug applet when this device is inserted.
  164. Returns:
  165. TRUE if this is a HotPlug device
  166. FALSE if this is not a HotPlug device.
  167. -**/
  168. {
  169. DWORD Capabilities, ConfigFlags;
  170. DWORD Len;
  171. DWORD Status, Problem;
  172. CONFIGRET ConfigRet;
  173. Capabilities = Status = Problem = 0;
  174. if (ARGUMENT_PRESENT(WarnUserOnInsertion)) {
  175. *WarnUserOnInsertion = FALSE;
  176. }
  177. Len = sizeof(Capabilities);
  178. if (CM_Get_DevNode_Registry_Property(DevInst,
  179. CM_DRP_CAPABILITIES,
  180. NULL,
  181. (PVOID)&Capabilities,
  182. &Len,
  183. 0
  184. ) != CR_SUCCESS) {
  185. return FALSE;
  186. }
  187. if (CM_Get_DevNode_Status(&Status,
  188. &Problem,
  189. DevInst,
  190. 0
  191. ) != CR_SUCCESS) {
  192. return FALSE;
  193. }
  194. //
  195. // If this device is not removable, or it is surprise removal ok, or
  196. // it is a dock device, then it is not a hotplug device.
  197. //
  198. if ((!(Capabilities & CM_DEVCAP_REMOVABLE)) ||
  199. (Capabilities & CM_DEVCAP_SURPRISEREMOVALOK) ||
  200. (Capabilities & CM_DEVCAP_DOCKDEVICE)) {
  201. return FALSE;
  202. }
  203. //
  204. // We won't consider a device to be a hotplug device if it is not started,
  205. // unless it is an eject capable device.
  206. //
  207. // The reason for this test is that a bus driver might set the
  208. // CM_DEVCAP_REMOVABLE capability, but if the PDO doesn't get loaded then
  209. // it can't set the CM_DEVCAP_SURPRISEREMOVALOK. So we won't trust the
  210. // CM_DEVCAP_REMOVABLE capability if the PDO is not started.
  211. //
  212. if ((!(Capabilities & CM_DEVCAP_EJECTSUPPORTED)) &&
  213. (!(Status & DN_STARTED))) {
  214. return FALSE;
  215. }
  216. #if BUBBLES
  217. if (ARGUMENT_PRESENT(WarnUserOnInsertion)) {
  218. Len = sizeof(ConfigFlags);
  219. ConfigRet = CM_Get_DevNode_Registry_Property_Ex(DevInst,
  220. CM_DRP_CONFIGFLAGS,
  221. NULL,
  222. (PVOID)&ConfigFlags,
  223. &Len,
  224. 0,
  225. NULL);
  226. if ((ConfigRet == CR_SUCCESS) &&
  227. (ConfigFlags & CONFIGFLAG_SUPPRESS_SURPRISE)) {
  228. *WarnUserOnInsertion = FALSE;
  229. } else {
  230. *WarnUserOnInsertion = TRUE;
  231. }
  232. }
  233. #endif // BUBBLES
  234. return TRUE;
  235. }
  236. BOOL
  237. IsRemovableDevice(
  238. IN DEVINST dnDevInst
  239. )
  240. /*++
  241. Routine Description:
  242. This routine determines whether a device is removable.
  243. Arguments:
  244. dnDevInst - Device instance.
  245. Return Value:
  246. Returns TRUE if the device is removable.
  247. --*/
  248. {
  249. CONFIGRET Status = CR_SUCCESS;
  250. ULONG ulPropertyData, ulDataSize, ulRegDataType;
  251. //
  252. // Validate parameters.
  253. //
  254. if (dnDevInst == 0) {
  255. return FALSE;
  256. }
  257. //
  258. // Get the capabilities for this device.
  259. //
  260. ulDataSize = sizeof(ulPropertyData);
  261. if (CM_Get_DevNode_Registry_Property_Ex(dnDevInst,
  262. CM_DRP_CAPABILITIES,
  263. &ulRegDataType,
  264. &ulPropertyData,
  265. &ulDataSize,
  266. 0,
  267. NULL) != CR_SUCCESS) {
  268. return FALSE;
  269. }
  270. //
  271. // Check if the device has the removable capability.
  272. //
  273. if ((ulPropertyData & CM_DEVCAP_REMOVABLE) == 0) {
  274. return FALSE;
  275. }
  276. return TRUE;
  277. } // IsRemovableDevice
  278. LPTSTR
  279. DevNodeToDriveLetter(
  280. DEVINST DevInst
  281. )
  282. {
  283. BOOL Result = FALSE;
  284. ULONG ulSize;
  285. TCHAR DeviceID[MAX_DEVICE_ID_LEN];
  286. LPTSTR DriveName = NULL;
  287. LPTSTR DeviceInterface = NULL;
  288. if (CM_Get_Device_ID_Ex(DevInst,
  289. DeviceID,
  290. ARRAYSIZE(DeviceID),
  291. 0,
  292. NULL
  293. ) != CR_SUCCESS) {
  294. return FALSE;
  295. }
  296. ulSize = 0;
  297. if ((CM_Get_Device_Interface_List_Size(&ulSize,
  298. (LPGUID)&VolumeClassGuid,
  299. DeviceID,
  300. 0) == CR_SUCCESS) &&
  301. (ulSize > 1) &&
  302. ((DeviceInterface = LocalAlloc(LPTR, ulSize*sizeof(TCHAR))) != NULL) &&
  303. (CM_Get_Device_Interface_List((LPGUID)&VolumeClassGuid,
  304. DeviceID,
  305. DeviceInterface,
  306. ulSize,
  307. 0
  308. ) == CR_SUCCESS) &&
  309. *DeviceInterface)
  310. {
  311. LPTSTR devicePath, p;
  312. TCHAR thisVolumeName[MAX_PATH];
  313. TCHAR enumVolumeName[MAX_PATH];
  314. TCHAR driveName[4];
  315. ULONG length;
  316. BOOL bResult;
  317. length = lstrlen(DeviceInterface);
  318. devicePath = LocalAlloc(LPTR, (length + 1) * sizeof(TCHAR) + sizeof(UNICODE_NULL));
  319. if (devicePath) {
  320. lstrcpyn(devicePath, DeviceInterface, length + 1);
  321. p = wcschr(&(devicePath[4]), TEXT('\\'));
  322. if (!p) {
  323. //
  324. // No refstring is present in the symbolic link; add a trailing
  325. // '\' char (as required by GetVolumeNameForVolumeMountPoint).
  326. //
  327. p = devicePath + length;
  328. *p = TEXT('\\');
  329. }
  330. p++;
  331. *p = UNICODE_NULL;
  332. thisVolumeName[0] = UNICODE_NULL;
  333. bResult = GetVolumeNameForVolumeMountPoint(devicePath,
  334. thisVolumeName,
  335. MAX_PATH
  336. );
  337. LocalFree(devicePath);
  338. if (bResult && thisVolumeName[0]) {
  339. driveName[1] = TEXT(':');
  340. driveName[2] = TEXT('\\');
  341. driveName[3] = TEXT('\0');
  342. for (driveName[0] = TEXT('A'); driveName[0] <= TEXT('Z'); driveName[0]++) {
  343. enumVolumeName[0] = TEXT('\0');
  344. GetVolumeNameForVolumeMountPoint(driveName, enumVolumeName, MAX_PATH);
  345. if (!lstrcmpi(thisVolumeName, enumVolumeName)) {
  346. driveName[2] = TEXT('\0');
  347. DriveName = LocalAlloc(LPTR, (lstrlen(driveName) + 1) * sizeof(TCHAR));
  348. if (DriveName) {
  349. lstrcpy(DriveName, driveName);
  350. }
  351. break;
  352. }
  353. }
  354. }
  355. }
  356. }
  357. if (DeviceInterface) {
  358. LocalFree(DeviceInterface);
  359. }
  360. return DriveName;
  361. }
  362. int
  363. CollectRelationDriveLetters(
  364. DEVINST DevInst,
  365. LPTSTR ListOfDrives
  366. )
  367. /*++
  368. This function looks at the removal relations of the specified DevInst and adds any drive
  369. letters associated with these removal relations to the ListOfDrives.
  370. Return:
  371. Number of drive letters added to the list.
  372. --*/
  373. {
  374. int NumberOfDrives = 0;
  375. LPTSTR SingleDrive = NULL;
  376. TCHAR szSeparator[32];
  377. DEVINST RelationDevInst;
  378. TCHAR DeviceInstanceId[MAX_DEVICE_ID_LEN];
  379. ULONG Len;
  380. PTCHAR DeviceIdRelations, CurrDevId;
  381. if (CM_Get_Device_ID(DevInst,
  382. DeviceInstanceId,
  383. ARRAYSIZE(DeviceInstanceId),
  384. 0
  385. ) == CR_SUCCESS) {
  386. Len = 0;
  387. if ((CM_Get_Device_ID_List_Size(&Len,
  388. DeviceInstanceId,
  389. CM_GETIDLIST_FILTER_REMOVALRELATIONS
  390. ) == CR_SUCCESS) &&
  391. (Len)) {
  392. DeviceIdRelations = LocalAlloc(LPTR, Len*sizeof(TCHAR));
  393. if (DeviceIdRelations) {
  394. *DeviceIdRelations = TEXT('\0');
  395. if ((CM_Get_Device_ID_List(DeviceInstanceId,
  396. DeviceIdRelations,
  397. Len,
  398. CM_GETIDLIST_FILTER_REMOVALRELATIONS
  399. ) == CR_SUCCESS) &&
  400. (*DeviceIdRelations)) {
  401. for (CurrDevId = DeviceIdRelations; *CurrDevId; CurrDevId += lstrlen(CurrDevId) + 1) {
  402. if (CM_Locate_DevNode(&RelationDevInst, CurrDevId, 0) == CR_SUCCESS) {
  403. SingleDrive = DevNodeToDriveLetter(RelationDevInst);
  404. if (SingleDrive) {
  405. NumberOfDrives++;
  406. //
  407. // If this is not the first drive the add a comma space separator
  408. //
  409. if (ListOfDrives[0] != TEXT('\0')) {
  410. LoadString(g_hInstance, IDS_SEPARATOR, szSeparator, sizeof(szSeparator)/sizeof(TCHAR));
  411. lstrcat(ListOfDrives, szSeparator);
  412. }
  413. lstrcat(ListOfDrives, SingleDrive);
  414. LocalFree(SingleDrive);
  415. }
  416. }
  417. }
  418. }
  419. LocalFree(DeviceIdRelations);
  420. }
  421. }
  422. }
  423. return NumberOfDrives;
  424. }
  425. int
  426. CollectDriveLettersForDevNodeWorker(
  427. DEVINST DevInst,
  428. LPTSTR ListOfDrives
  429. )
  430. {
  431. DEVINST ChildDevInst;
  432. DEVINST SiblingDevInst;
  433. int NumberOfDrives = 0;
  434. LPTSTR SingleDrive = NULL;
  435. TCHAR szSeparator[32];
  436. //
  437. // Enumerate through all of the siblings and children of this devnode
  438. //
  439. do {
  440. ChildDevInst = 0;
  441. SiblingDevInst = 0;
  442. CM_Get_Child(&ChildDevInst, DevInst, 0);
  443. CM_Get_Sibling(&SiblingDevInst, DevInst, 0);
  444. //
  445. // Only get the drive letter for this device if it is NOT a hotplug
  446. // device. If it is a hotplug device then it will have it's own
  447. // subtree that contains it's drive letters.
  448. //
  449. if (!IsHotPlugDevice(DevInst, NULL)) {
  450. SingleDrive = DevNodeToDriveLetter(DevInst);
  451. if (SingleDrive) {
  452. NumberOfDrives++;
  453. //
  454. // If this is not the first drive the add a comma space separator
  455. //
  456. if (ListOfDrives[0] != TEXT('\0')) {
  457. LoadString(g_hInstance, IDS_SEPARATOR, szSeparator, sizeof(szSeparator)/sizeof(TCHAR));
  458. lstrcat(ListOfDrives, szSeparator);
  459. }
  460. lstrcat(ListOfDrives, SingleDrive);
  461. LocalFree(SingleDrive);
  462. }
  463. //
  464. // Get the drive letters for any children of this devnode
  465. //
  466. if (ChildDevInst) {
  467. NumberOfDrives += CollectDriveLettersForDevNodeWorker(ChildDevInst, ListOfDrives);
  468. }
  469. //
  470. // Add the drive letters for any removal relations of this devnode
  471. //
  472. NumberOfDrives += CollectRelationDriveLetters(DevInst, ListOfDrives);
  473. }
  474. } while ((DevInst = SiblingDevInst) != 0);
  475. return NumberOfDrives;
  476. }
  477. LPTSTR
  478. CollectDriveLettersForDevNode(
  479. DEVINST DevInst
  480. )
  481. {
  482. TCHAR Format[MAX_PATH];
  483. TCHAR ListOfDrives[MAX_PATH];
  484. DEVINST ChildDevInst;
  485. int NumberOfDrives = 0;
  486. LPTSTR SingleDrive = NULL;
  487. LPTSTR FinalDriveString = NULL;
  488. ListOfDrives[0] = TEXT('\0');
  489. //
  490. //First get any drive letter associated with this devnode
  491. //
  492. SingleDrive = DevNodeToDriveLetter(DevInst);
  493. if (SingleDrive) {
  494. NumberOfDrives++;
  495. lstrcat(ListOfDrives, SingleDrive);
  496. LocalFree(SingleDrive);
  497. }
  498. //
  499. // Next add on any drive letters associated with the children
  500. // of this devnode
  501. //
  502. ChildDevInst = 0;
  503. CM_Get_Child(&ChildDevInst, DevInst, 0);
  504. if (ChildDevInst) {
  505. NumberOfDrives += CollectDriveLettersForDevNodeWorker(ChildDevInst, ListOfDrives);
  506. }
  507. //
  508. // Finally add on any drive letters associated with the removal relations
  509. // of this devnode
  510. //
  511. NumberOfDrives += CollectRelationDriveLetters(DevInst, ListOfDrives);
  512. if (ListOfDrives[0] != TEXT('\0')) {
  513. LoadString(g_hInstance,
  514. (NumberOfDrives > 1) ? IDS_DISKDRIVES : IDS_DISKDRIVE,
  515. Format,
  516. sizeof(Format)/sizeof(TCHAR)
  517. );
  518. FinalDriveString = LocalAlloc(LPTR, (lstrlen(ListOfDrives) + lstrlen(Format) + 1) * sizeof(TCHAR));
  519. if (FinalDriveString) {
  520. wsprintf(FinalDriveString, Format, ListOfDrives);
  521. }
  522. }
  523. return FinalDriveString;
  524. }
  525. DWORD
  526. GetHotPlugFlags(
  527. void
  528. )
  529. {
  530. HKEY hKey;
  531. LONG Error;
  532. DWORD HotPlugFlags, cbHotPlugFlags;
  533. Error = RegCreateKey(HKEY_CURRENT_USER, REGSTR_PATH_SYSTRAY, &hKey);
  534. if (Error == ERROR_SUCCESS) {
  535. cbHotPlugFlags = sizeof(HotPlugFlags);
  536. Error = RegQueryValueEx(hKey,
  537. TEXT("HotPlugFlags"),
  538. NULL,
  539. NULL,
  540. (PVOID)&HotPlugFlags,
  541. &cbHotPlugFlags
  542. );
  543. RegCloseKey(hKey);
  544. if (Error == ERROR_SUCCESS) {
  545. return HotPlugFlags;
  546. }
  547. }
  548. return 0;
  549. }
  550. ULONG
  551. RegistryDeviceName(
  552. DEVINST DevInst,
  553. PTCHAR Buffer,
  554. DWORD cbBuffer
  555. )
  556. {
  557. ULONG ulSize = 0;
  558. CONFIGRET ConfigRet;
  559. LPTSTR ListOfDrives = NULL;
  560. //
  561. // Get the list of drives
  562. //
  563. ListOfDrives = CollectDriveLettersForDevNode(DevInst);
  564. //
  565. // Try the registry for FRIENDLYNAME
  566. //
  567. ulSize = cbBuffer;
  568. *Buffer = TEXT('\0');
  569. ConfigRet = CM_Get_DevNode_Registry_Property(DevInst,
  570. CM_DRP_FRIENDLYNAME,
  571. NULL,
  572. Buffer,
  573. &ulSize,
  574. 0
  575. );
  576. if (ConfigRet != CR_SUCCESS || !(*Buffer)) {
  577. //
  578. // Try the registry for DEVICEDESC
  579. //
  580. ulSize = cbBuffer;
  581. *Buffer = TEXT('\0');
  582. ConfigRet = CM_Get_DevNode_Registry_Property(DevInst,
  583. CM_DRP_DEVICEDESC,
  584. NULL,
  585. Buffer,
  586. &ulSize,
  587. 0);
  588. if (ConfigRet != CR_SUCCESS || !(*Buffer)) {
  589. //
  590. // try classname
  591. //
  592. ulSize = cbBuffer;
  593. *Buffer = TEXT('\0');
  594. ConfigRet = CM_Get_DevNode_Registry_Property(DevInst,
  595. CM_DRP_CLASS,
  596. NULL,
  597. Buffer,
  598. &ulSize,
  599. 0);
  600. if (ConfigRet != CR_SUCCESS || !(*Buffer)) {
  601. ulSize = 0;;
  602. }
  603. }
  604. }
  605. //
  606. // Concatonate on the list of drive letters if this device has drive
  607. // letters and there is enough space
  608. //
  609. if (ListOfDrives) {
  610. if ((ulSize + (lstrlen(ListOfDrives) * sizeof(TCHAR))) < cbBuffer) {
  611. lstrcat(Buffer, ListOfDrives);
  612. ulSize += (lstrlen(ListOfDrives) * sizeof(TCHAR));
  613. }
  614. LocalFree(ListOfDrives);
  615. }
  616. return ulSize;
  617. }
  618. BOOL
  619. IsDevInstInDeviceInfoSet(
  620. IN DEVINST DevInst,
  621. IN HDEVINFO hDeviceInfoSet,
  622. OUT PSP_DEVINFO_DATA DeviceInfoDataInSet OPTIONAL
  623. )
  624. {
  625. DWORD MemberIndex;
  626. SP_DEVINFO_DATA DeviceInfoData;
  627. BOOL bIsMember = FALSE;
  628. ASSERT(hDeviceInfoSet != INVALID_HANDLE_VALUE);
  629. if (hDeviceInfoSet == INVALID_HANDLE_VALUE) {
  630. return FALSE;
  631. }
  632. DeviceInfoData.cbSize = sizeof(DeviceInfoData);
  633. MemberIndex = 0;
  634. while (SetupDiEnumDeviceInfo(hDeviceInfoSet,
  635. MemberIndex,
  636. &DeviceInfoData
  637. )) {
  638. if (DevInst == DeviceInfoData.DevInst) {
  639. bIsMember = TRUE;
  640. if (ARGUMENT_PRESENT(DeviceInfoDataInSet)) {
  641. ASSERT(DeviceInfoDataInSet->cbSize >= DeviceInfoData.cbSize);
  642. memcpy(DeviceInfoDataInSet, &DeviceInfoData, DeviceInfoDataInSet->cbSize);
  643. }
  644. break;
  645. }
  646. MemberIndex++;
  647. }
  648. return bIsMember;
  649. }
  650. BOOL
  651. AnyHotPlugDevices(
  652. IN HDEVINFO hRemovableDeviceInfoSet,
  653. IN HDEVINFO hOldDeviceInfoSet,
  654. OUT PBOOL bNewHotPlugDevice OPTIONAL,
  655. OUT PBOOL bWarnUserAboutNewDevices OPTIONAL
  656. )
  657. {
  658. SP_DEVINFO_DATA DeviceInfoData;
  659. DWORD dwMemberIndex;
  660. BOOL bAnyHotPlugDevices = FALSE;
  661. BOOL bWarnOnInsertion;
  662. //
  663. // Initialize output parameters.
  664. //
  665. if (ARGUMENT_PRESENT(bNewHotPlugDevice)) {
  666. *bNewHotPlugDevice = FALSE;
  667. }
  668. if (ARGUMENT_PRESENT(bWarnUserAboutNewDevices)) {
  669. *bWarnUserAboutNewDevices = FALSE;
  670. }
  671. //
  672. // We already have an updated list of just removable devices, so we can just
  673. // enumerate those devices and see if any also meet the criteria for hotplug
  674. // devices.
  675. //
  676. DeviceInfoData.cbSize = sizeof(DeviceInfoData);
  677. dwMemberIndex = 0;
  678. while (SetupDiEnumDeviceInfo(hRemovableDeviceInfoSet,
  679. dwMemberIndex,
  680. &DeviceInfoData)) {
  681. if (IsHotPlugDevice(DeviceInfoData.DevInst, &bWarnOnInsertion)) {
  682. bAnyHotPlugDevices = TRUE;
  683. //
  684. // If the caller doesn't want to know if any new hotplug devices
  685. // have arrived then just break at this point.
  686. //
  687. if (!ARGUMENT_PRESENT(bNewHotPlugDevice)) {
  688. break;
  689. }
  690. //
  691. // If the caller wants to know if the hotplug device is new, we must
  692. // have a list of devices to check against.
  693. //
  694. ASSERT(hOldDeviceInfoSet != INVALID_HANDLE_VALUE);
  695. //
  696. // The caller wants to know if we have any new hotplug devices. So,
  697. // we will compare this hotplug device to see if it is also in the
  698. // old current list of devices. If it is not then we have found a
  699. // new hotplug device.
  700. //
  701. if (!IsDevInstInDeviceInfoSet(DeviceInfoData.DevInst,
  702. hOldDeviceInfoSet,
  703. NULL)) {
  704. *bNewHotPlugDevice = TRUE;
  705. if (ARGUMENT_PRESENT(bWarnUserAboutNewDevices) && bWarnOnInsertion) {
  706. *bWarnUserAboutNewDevices = TRUE;
  707. //
  708. // There is no longer any reason to continue the
  709. // enumeration.
  710. //
  711. break;
  712. }
  713. }
  714. }
  715. dwMemberIndex++;
  716. }
  717. return bAnyHotPlugDevices;
  718. }
  719. BOOL
  720. UpdateRemovableDeviceList(
  721. IN HDEVINFO hDeviceInfoSet,
  722. OUT PBOOL bRemovableDeviceAdded OPTIONAL,
  723. OUT PBOOL bRemovableDeviceRemoved OPTIONAL,
  724. OUT PBOOL bRemovableDeviceFailure OPTIONAL
  725. )
  726. {
  727. SP_DEVINFO_DATA DeviceInfoData;
  728. TCHAR DeviceInstanceId[MAX_DEVICE_ID_LEN];
  729. DWORD dwMemberIndex;
  730. ULONG ulDevStatus, ulDevProblem;
  731. //
  732. // Initialize output parameters.
  733. //
  734. if (ARGUMENT_PRESENT(bRemovableDeviceAdded)) {
  735. *bRemovableDeviceAdded = FALSE;
  736. }
  737. if (ARGUMENT_PRESENT(bRemovableDeviceRemoved)) {
  738. *bRemovableDeviceRemoved = FALSE;
  739. }
  740. if (ARGUMENT_PRESENT(bRemovableDeviceFailure)) {
  741. *bRemovableDeviceFailure = FALSE;
  742. }
  743. //
  744. // We at least need a current list of devices in the system.
  745. //
  746. if (hDeviceInfoSet == INVALID_HANDLE_VALUE) {
  747. return FALSE;
  748. }
  749. if (g_hRemovableDeviceInfoSet == INVALID_HANDLE_VALUE) {
  750. //
  751. // If we don't already have a global device info set for removable
  752. // devices in the system, create one now. No removable devices have
  753. // been removed in this case, because we didn't know about any prior to
  754. // this.
  755. //
  756. g_hRemovableDeviceInfoSet = SetupDiCreateDeviceInfoListEx(NULL,
  757. NULL,
  758. NULL,
  759. NULL);
  760. //
  761. // If we couldn't create a list to store removable devices, there's no
  762. // point in checking anything else here.
  763. //
  764. if (g_hRemovableDeviceInfoSet == INVALID_HANDLE_VALUE) {
  765. return FALSE;
  766. }
  767. } else {
  768. //
  769. // If we already had a list of removable devices, enumerate the devices
  770. // to see if any have been removed from the system since we last
  771. // checked.
  772. //
  773. DeviceInfoData.cbSize = sizeof(DeviceInfoData);
  774. dwMemberIndex = 0;
  775. while (SetupDiEnumDeviceInfo(g_hRemovableDeviceInfoSet,
  776. dwMemberIndex,
  777. &DeviceInfoData)) {
  778. if (!IsDevInstInDeviceInfoSet(DeviceInfoData.DevInst,
  779. hDeviceInfoSet,
  780. NULL)) {
  781. //
  782. // A removable device is missing from the system.
  783. //
  784. if (ARGUMENT_PRESENT(bRemovableDeviceRemoved)) {
  785. *bRemovableDeviceRemoved = TRUE;
  786. }
  787. #if DBG // DBG
  788. if (SetupDiGetDeviceInstanceId(g_hRemovableDeviceInfoSet,
  789. &DeviceInfoData,
  790. DeviceInstanceId,
  791. MAX_DEVICE_ID_LEN,
  792. NULL)) {
  793. KdPrintEx((DPFLTR_PNPMGR_ID,
  794. (0x00000010 | DPFLTR_MASK),
  795. "HPLUG: Removing device %ws from g_hRemovableDeviceInfoSet.\n",
  796. DeviceInstanceId));
  797. }
  798. #endif // DBG
  799. //
  800. // Remove the device from the global list of removable devices.
  801. //
  802. SetupDiDeleteDeviceInfo(g_hRemovableDeviceInfoSet,
  803. &DeviceInfoData);
  804. }
  805. //
  806. // Increment the enumeration index.
  807. //
  808. dwMemberIndex++;
  809. }
  810. }
  811. //
  812. // Enumerate the current list of devices and see if any removable devices
  813. // have been added to the system.
  814. //
  815. DeviceInfoData.cbSize = sizeof(DeviceInfoData);
  816. dwMemberIndex = 0;
  817. while (SetupDiEnumDeviceInfo(hDeviceInfoSet,
  818. dwMemberIndex,
  819. &DeviceInfoData)) {
  820. //
  821. // If this device is not already in the removable device list, and it's
  822. // removable, add it to the list.
  823. //
  824. if ((!IsDevInstInDeviceInfoSet(DeviceInfoData.DevInst,
  825. g_hRemovableDeviceInfoSet,
  826. NULL)) &&
  827. (IsRemovableDevice(DeviceInfoData.DevInst))) {
  828. //
  829. // A removable device was added to the system.
  830. //
  831. if (ARGUMENT_PRESENT(bRemovableDeviceAdded)) {
  832. *bRemovableDeviceAdded = TRUE;
  833. }
  834. //
  835. // Add the device to the global list of removable devices.
  836. //
  837. if (SetupDiGetDeviceInstanceId(hDeviceInfoSet,
  838. &DeviceInfoData,
  839. DeviceInstanceId,
  840. MAX_DEVICE_ID_LEN,
  841. NULL)) {
  842. KdPrintEx((DPFLTR_PNPMGR_ID,
  843. (0x00000010 | DPFLTR_MASK),
  844. "HPLUG: Adding device %ws to g_hRemovableDeviceInfoSet\n",
  845. DeviceInstanceId));
  846. SetupDiOpenDeviceInfo(g_hRemovableDeviceInfoSet,
  847. DeviceInstanceId,
  848. NULL,
  849. 0,
  850. NULL);
  851. }
  852. //
  853. // If the caller is also interested in device failures, check the
  854. // status of the new device.
  855. //
  856. if (ARGUMENT_PRESENT(bRemovableDeviceFailure)) {
  857. if (CM_Get_DevNode_Status_Ex(&ulDevStatus,
  858. &ulDevProblem,
  859. DeviceInfoData.DevInst,
  860. 0,
  861. NULL) == CR_SUCCESS) {
  862. if (((ulDevStatus & DN_HAS_PROBLEM) != 0) &&
  863. (ulDevProblem != CM_PROB_NOT_CONFIGURED) &&
  864. (ulDevProblem != CM_PROB_REINSTALL)) {
  865. *bRemovableDeviceFailure = TRUE;
  866. KdPrintEx((DPFLTR_PNPMGR_ID,
  867. (0x00000010 | DPFLTR_MASK),
  868. "HPLUG: Device %ws considered a failed insertion (Status = 0x%08lx, Problem = 0x%08lx)\n",
  869. DeviceInstanceId, ulDevStatus, ulDevProblem));
  870. }
  871. }
  872. }
  873. }
  874. //
  875. // Increment the enumeration index.
  876. //
  877. dwMemberIndex++;
  878. }
  879. return TRUE;
  880. }
  881. BOOL
  882. AddHotPlugDevice(
  883. DEVINST DeviceInstance,
  884. PHOTPLUGDEVICES *HotPlugDevicesList
  885. )
  886. {
  887. PHOTPLUGDEVICES HotPlugDevice;
  888. DWORD Len, LenDevName, LenDevInstanceId;
  889. CONFIGRET ConfigRet;
  890. TCHAR DevInstanceId[MAX_DEVICE_ID_LEN];
  891. TCHAR DevName[MAX_PATH];
  892. //
  893. // Retrieve the device instance id
  894. //
  895. *DevInstanceId = TEXT('\0');
  896. LenDevInstanceId = ARRAYSIZE(DevInstanceId);
  897. ConfigRet = CM_Get_Device_ID(DeviceInstance,
  898. (PVOID)DevInstanceId,
  899. LenDevInstanceId,
  900. 0);
  901. if (ConfigRet != CR_SUCCESS || !*DevInstanceId) {
  902. *DevInstanceId = TEXT('\0');
  903. LenDevInstanceId = 0;
  904. }
  905. Len = sizeof(HOTPLUGDEVICES) + LenDevInstanceId;
  906. HotPlugDevice = LocalAlloc(LPTR, Len);
  907. if (!HotPlugDevice) {
  908. return FALSE;
  909. }
  910. //
  911. // link it in
  912. //
  913. HotPlugDevice->Next = *HotPlugDevicesList;
  914. *HotPlugDevicesList = HotPlugDevice;
  915. HotPlugDevice->DevInst = DeviceInstance;
  916. //
  917. // copy in the names
  918. //
  919. memcpy(HotPlugDevice->DevInstanceId, DevInstanceId, LenDevInstanceId);
  920. LenDevName = RegistryDeviceName(DeviceInstance, DevName, sizeof(DevName));
  921. HotPlugDevice->DevName = LocalAlloc(LPTR, LenDevName + sizeof(TCHAR));
  922. if (HotPlugDevice->DevName) {
  923. memcpy(HotPlugDevice->DevName, DevName, LenDevName);
  924. }
  925. return TRUE;
  926. }
  927. BOOL
  928. AddHotPlugDevices(
  929. PHOTPLUGDEVICES *HotPlugDevicesList
  930. )
  931. {
  932. CONFIGRET ConfigRet;
  933. SP_DEVINFO_DATA DeviceInfoData;
  934. DWORD dwMemberIndex;
  935. //
  936. // Initialize output list of hotplug devices.
  937. //
  938. *HotPlugDevicesList = NULL;
  939. //
  940. // Enumerate the list of removable devices.
  941. //
  942. DeviceInfoData.cbSize = sizeof(DeviceInfoData);
  943. dwMemberIndex = 0;
  944. while (SetupDiEnumDeviceInfo(g_hRemovableDeviceInfoSet,
  945. dwMemberIndex,
  946. &DeviceInfoData)) {
  947. //
  948. // If any removable device also meets the criteria of a hotplug device,
  949. // add it to the linked list.
  950. //
  951. if (IsHotPlugDevice(DeviceInfoData.DevInst, NULL)) {
  952. AddHotPlugDevice(DeviceInfoData.DevInst, HotPlugDevicesList);
  953. }
  954. dwMemberIndex++;
  955. }
  956. return TRUE;
  957. }
  958. void
  959. FreeHotPlugDevicesList(
  960. PHOTPLUGDEVICES *HotPlugDevicesList
  961. )
  962. {
  963. PHOTPLUGDEVICES HotPlugDevices, HotPlugDevicesFree;
  964. HotPlugDevices = *HotPlugDevicesList;
  965. *HotPlugDevicesList = NULL;
  966. while (HotPlugDevices) {
  967. HotPlugDevicesFree = HotPlugDevices;
  968. HotPlugDevices = HotPlugDevicesFree->Next;
  969. if (HotPlugDevicesFree->DevName) {
  970. LocalFree(HotPlugDevicesFree->DevName);
  971. HotPlugDevicesFree->DevName = NULL;
  972. }
  973. LocalFree(HotPlugDevicesFree);
  974. }
  975. }
  976. /*
  977. * Shows or deletes the shell notify icon and tip
  978. */
  979. void
  980. HotPlugShowNotifyIcon(
  981. HWND hWnd,
  982. BOOL bShowIcon
  983. )
  984. {
  985. TCHAR HotPlugTip[64];
  986. ShowShellIcon = bShowIcon;
  987. if (bShowIcon) {
  988. LoadString(g_hInstance,
  989. IDS_HOTPLUGTIP,
  990. HotPlugTip,
  991. sizeof(HotPlugTip)/sizeof(TCHAR)
  992. );
  993. HotPlugIcon = LoadImage(g_hInstance,
  994. MAKEINTRESOURCE(IDI_HOTPLUG),
  995. IMAGE_ICON,
  996. 16,
  997. 16,
  998. 0
  999. );
  1000. SysTray_NotifyIcon(hWnd, STWM_NOTIFYHOTPLUG, NIM_ADD, HotPlugIcon, HotPlugTip);
  1001. }
  1002. else {
  1003. SysTray_NotifyIcon(hWnd, STWM_NOTIFYHOTPLUG, NIM_DELETE, NULL, NULL);
  1004. if (HotPlugIcon) {
  1005. DestroyIcon(HotPlugIcon);
  1006. }
  1007. }
  1008. }
  1009. //
  1010. // first time intialization of Hotplug module.
  1011. //
  1012. BOOL
  1013. HotPlugInit(
  1014. HWND hWnd
  1015. )
  1016. {
  1017. HDEVINFO hNewDeviceInfoSet;
  1018. BOOL bAnyHotPlugDevices;
  1019. LARGE_INTEGER liDelayTime;
  1020. //
  1021. // Get a new "current" list of all devices present in the system.
  1022. //
  1023. hNewDeviceInfoSet = SetupDiGetClassDevs(NULL,
  1024. NULL,
  1025. NULL,
  1026. DIGCF_ALLCLASSES | DIGCF_PRESENT);
  1027. //
  1028. // Update the list of removable devices, don't play any sounds.
  1029. //
  1030. UpdateRemovableDeviceList(hNewDeviceInfoSet,
  1031. NULL,
  1032. NULL,
  1033. NULL);
  1034. //
  1035. // Find out whether there are any HotPlug devices in the list of removable
  1036. // devices. We're just deciding whether the icon needs to be enabled or
  1037. // not, so we don't care if there are any new hotplug devices or not (we
  1038. // won't even look at g_hCurrentDeviceInfoSet).
  1039. //
  1040. bAnyHotPlugDevices = AnyHotPlugDevices(g_hRemovableDeviceInfoSet,
  1041. g_hCurrentDeviceInfoSet,
  1042. NULL,
  1043. NULL);
  1044. //
  1045. // Delete the old current list of devices and set it
  1046. // (g_hCurrentDeviceInfoSet) to the new current list.
  1047. //
  1048. if (g_hCurrentDeviceInfoSet != INVALID_HANDLE_VALUE) {
  1049. SetupDiDestroyDeviceInfoList(g_hCurrentDeviceInfoSet);
  1050. }
  1051. //
  1052. // Update the global list of devices currently in the system.
  1053. //
  1054. g_hCurrentDeviceInfoSet = hNewDeviceInfoSet;
  1055. //
  1056. // If hotplug was previously initialized, we don't need to create the events
  1057. // and timers below.
  1058. //
  1059. if (HotPlugInitialized) {
  1060. return bAnyHotPlugDevices;
  1061. }
  1062. hEjectEvent = CreateEvent(NULL, TRUE, TRUE, HPLUG_EJECT_EVENT);
  1063. #if BUBBLES
  1064. //
  1065. // Create a timer that will be used to suppress "insertion warnings" that
  1066. // occur when a dock is attached.
  1067. //
  1068. hDockEventTimer = CreateWaitableTimer(NULL, TRUE, HPLUG_DOCK_TIMER);
  1069. if (hDockEventTimer) {
  1070. //
  1071. // Signal it immediately
  1072. //
  1073. liDelayTime.QuadPart = 0;
  1074. SetWaitableTimer(hDockEventTimer, &liDelayTime, 0, NULL, NULL, FALSE);
  1075. }
  1076. //
  1077. // Create a session-wide timer hotplug.dll will use to suppress surprise
  1078. // remove messages during surprise undocks. We create the timer here as
  1079. // hotplug comes and goes while we stick around.
  1080. //
  1081. hSurpriseUndockEventTimer = CreateWaitableTimer(
  1082. NULL,
  1083. TRUE,
  1084. SURPRISE_UNDOCK_TIMER
  1085. );
  1086. if ((hSurpriseUndockEventTimer != NULL) &&
  1087. (GetLastError() == ERROR_SUCCESS)) {
  1088. //
  1089. // We created it (if not the status would be
  1090. // ERROR_ALREADY_EXISTS). Signal it immediately.
  1091. //
  1092. liDelayTime.QuadPart = 0;
  1093. SetWaitableTimer(hSurpriseUndockEventTimer, &liDelayTime, 0, NULL, NULL, FALSE);
  1094. }
  1095. try {
  1096. InitializeCriticalSection(&g_BubbleCrit);
  1097. } except(EXCEPTION_EXECUTE_HANDLER) {
  1098. NOTHING;
  1099. }
  1100. #endif // BUBBLES
  1101. HotPlugInitialized = TRUE;
  1102. return bAnyHotPlugDevices;
  1103. }
  1104. BOOL
  1105. HotPlug_CheckEnable(
  1106. HWND hWnd,
  1107. BOOL bSvcEnabled
  1108. )
  1109. /*++
  1110. Routine Description:
  1111. Called at init time and whenever services are enabled/disabled.
  1112. Hotplug is always alive to receive device change notifications.
  1113. The shell notify icon is enabled\disabled depending on:
  1114. - systray registry setting for services,
  1115. AND
  1116. - availability of removable devices.
  1117. Arguments:
  1118. hwnd - Our Window handle
  1119. bSvcEnabled - TRUE Service is being enabled.
  1120. Return Value:
  1121. BOOL Returns TRUE if active.
  1122. --*/
  1123. {
  1124. BOOL EnableShellIcon;
  1125. HANDLE hHotplugBalloonEvent = NULL;
  1126. //
  1127. // If we are being enabled and we are already enabled, or we
  1128. // are being disabled and we are already disabled then just
  1129. // return since we have nothing to do.
  1130. //
  1131. if (ServiceEnabled == bSvcEnabled) {
  1132. return ServiceEnabled;
  1133. }
  1134. ServiceEnabled = bSvcEnabled;
  1135. //
  1136. // There are some special checks we need to make if we are enabling the
  1137. // hotplug service.
  1138. //
  1139. if (bSvcEnabled) {
  1140. //
  1141. // If this is a remote session and the user does not have the
  1142. // SE_LOAD_DRIVER_NAME privileges then we won't enable the service
  1143. // since they do not have the privileges to stop any hotplug devices.
  1144. //
  1145. if (GetSystemMetrics(SM_REMOTESESSION) &&
  1146. !pDoesUserHavePrivilege((PCTSTR)SE_LOAD_DRIVER_NAME)) {
  1147. ServiceEnabled = FALSE;
  1148. } else {
  1149. //
  1150. // hotplug.dll will disable the hotplug service when it is
  1151. // displaying a balloon for a surprise removal or safe removal
  1152. // event. When it is displaying it's balloon we don't want to
  1153. // enable our service because then there will be two hotplug icons
  1154. // in the tray. So if it's named event is set then we will ignore
  1155. // any attempts to enable our service. Once hotplug.dll's balloon
  1156. // has gone away then it will automatically enable the hotplug
  1157. // service.
  1158. //
  1159. hHotplugBalloonEvent = CreateEvent(NULL,
  1160. FALSE,
  1161. TRUE,
  1162. TEXT("Local\\HotPlug_TaskBarIcon_Event")
  1163. );
  1164. if (hHotplugBalloonEvent) {
  1165. if (WaitForSingleObject(hHotplugBalloonEvent, 0) != WAIT_OBJECT_0) {
  1166. ServiceEnabled = FALSE;
  1167. }
  1168. CloseHandle(hHotplugBalloonEvent);
  1169. }
  1170. }
  1171. }
  1172. EnableShellIcon = ServiceEnabled && HotPlugInit(hWnd);
  1173. HotPlugShowNotifyIcon(hWnd, EnableShellIcon);
  1174. return EnableShellIcon;
  1175. }
  1176. DWORD
  1177. HotPlugEjectDevice_Thread(
  1178. LPVOID pThreadParam
  1179. )
  1180. {
  1181. DEVNODE DevNode = (DEVNODE)(ULONG_PTR)pThreadParam;
  1182. CONFIGRET ConfigRet;
  1183. ConfigRet = CM_Request_Device_Eject_Ex(DevNode,
  1184. NULL,
  1185. NULL,
  1186. 0,
  1187. 0,
  1188. NULL);
  1189. //
  1190. // Set the hEjectEvent so that the right-click popup menu will work again now that we are finished
  1191. // ejecting/stopping the device.
  1192. //
  1193. SetEvent(hEjectEvent);
  1194. SetLastError(ConfigRet);
  1195. return (ConfigRet == CR_SUCCESS);
  1196. }
  1197. void
  1198. HotPlugEjectDevice(
  1199. HWND hwnd,
  1200. DEVNODE DevNode
  1201. )
  1202. {
  1203. DWORD ThreadId;
  1204. //
  1205. // Reset the hEjectEvent so that the user can't bring up the right-click popup menu when
  1206. // we are in the process of ejecting/stopping a device.
  1207. //
  1208. ResetEvent(hEjectEvent);
  1209. //
  1210. // We need to have stobject.dll eject/stop the device on a separate thread because if
  1211. // we remove a device that stobject.dll listens for (battery, sound, ect.) we will cause
  1212. // a large delay and the eject/stop could end up getting vetoed because the stobject.dll
  1213. // code could not be processed and release it's handles because we were locking up the main
  1214. // thread.
  1215. //
  1216. CreateThread(NULL,
  1217. 0,
  1218. (LPTHREAD_START_ROUTINE)HotPlugEjectDevice_Thread,
  1219. (LPVOID)(ULONG_PTR)DevNode,
  1220. 0,
  1221. &ThreadId
  1222. );
  1223. }
  1224. void
  1225. HotPlug_Timer(
  1226. HWND hwnd
  1227. )
  1228. /*++
  1229. Routine Description:
  1230. Hotplug Timer msg handler, used to invoke hmenuEject for single Left click
  1231. Arguments:
  1232. hDlg - Our Window handle
  1233. Return Value:
  1234. BOOL Returns TRUE if active.
  1235. --*/
  1236. {
  1237. POINT pt;
  1238. UINT MenuIndex;
  1239. PHOTPLUGDEVICES HotPlugDevicesList;
  1240. PHOTPLUGDEVICES SingleHotPlugDevice;
  1241. TCHAR MenuDeviceName[MAX_PATH+64];
  1242. TCHAR Format[64];
  1243. KillTimer(hwnd, HOTPLUG_TIMER_ID);
  1244. if (!HotPlugInitialized) {
  1245. PostMessage(hwnd, STWM_ENABLESERVICE, 0, TRUE);
  1246. return;
  1247. }
  1248. //
  1249. // We only want to create the popup menu if the hEjectEvent is signaled. If it is not
  1250. // signaled then we are in the middle of ejecting/stopping a device on a separate thread
  1251. // and don't want to allow the user to bring up the menu until we are finished with that
  1252. // device.
  1253. //
  1254. if (!hEjectEvent ||
  1255. WaitForSingleObject(hEjectEvent, 0) == WAIT_OBJECT_0) {
  1256. //
  1257. // We are not in the middle of ejecting/stopping a device so we should display the popup
  1258. // menu.
  1259. //
  1260. HMENU hmenuEject = CreatePopupMenu();
  1261. if (hmenuEject)
  1262. {
  1263. SetForegroundWindow(hwnd);
  1264. GetCursorPos(&pt);
  1265. //
  1266. // Add each of the removable devices in the list to the menu.
  1267. //
  1268. if (!AddHotPlugDevices(&HotPlugDevicesList)) {
  1269. DestroyMenu(hmenuEject);
  1270. return;
  1271. }
  1272. SingleHotPlugDevice = HotPlugDevicesList;
  1273. //
  1274. // Add a title and separator at the top of the menu.
  1275. //
  1276. LoadString(g_hInstance,
  1277. IDS_HPLUGMENU_REMOVE,
  1278. Format,
  1279. sizeof(Format)/sizeof(TCHAR)
  1280. );
  1281. MenuIndex = 1;
  1282. while (SingleHotPlugDevice) {
  1283. wsprintf(MenuDeviceName, Format, SingleHotPlugDevice->DevName);
  1284. AppendMenu(hmenuEject, MF_STRING, MenuIndex, MenuDeviceName);
  1285. SingleHotPlugDevice->EjectMenuIndex = MenuIndex++;
  1286. SingleHotPlugDevice = SingleHotPlugDevice->Next;
  1287. }
  1288. MenuIndex = TrackPopupMenu(hmenuEject,
  1289. TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
  1290. pt.x,
  1291. pt.y,
  1292. 0,
  1293. hwnd,
  1294. NULL
  1295. );
  1296. // now do an eject!
  1297. SingleHotPlugDevice = HotPlugDevicesList;
  1298. while (SingleHotPlugDevice) {
  1299. if (MenuIndex == SingleHotPlugDevice->EjectMenuIndex) {
  1300. DEVNODE DevNode;
  1301. if (CM_Locate_DevNode(&DevNode,
  1302. SingleHotPlugDevice->DevInstanceId,
  1303. 0) == CR_SUCCESS) {
  1304. HotPlugEjectDevice(hwnd, DevNode);
  1305. }
  1306. break;
  1307. }
  1308. SingleHotPlugDevice = SingleHotPlugDevice->Next;
  1309. }
  1310. if (!SingleHotPlugDevice) {
  1311. SetIconFocus(hwnd, STWM_NOTIFYHOTPLUG);
  1312. }
  1313. FreeHotPlugDevicesList(&HotPlugDevicesList);
  1314. }
  1315. DestroyMenu(hmenuEject);
  1316. }
  1317. return;
  1318. }
  1319. void
  1320. HotPlugContextMenu(
  1321. HWND hwnd
  1322. )
  1323. {
  1324. POINT pt;
  1325. HMENU ContextMenu;
  1326. UINT MenuIndex;
  1327. TCHAR Buffer[MAX_PATH];
  1328. ContextMenu = CreatePopupMenu();
  1329. if (!ContextMenu) {
  1330. return;
  1331. }
  1332. SetForegroundWindow(hwnd);
  1333. GetCursorPos(&pt);
  1334. LoadString(g_hInstance, IDS_HPLUGMENU_PROPERTIES, Buffer, sizeof(Buffer)/sizeof(TCHAR));
  1335. AppendMenu(ContextMenu, MF_STRING,IDS_HPLUGMENU_PROPERTIES, Buffer);
  1336. SetMenuDefaultItem(ContextMenu, IDS_HPLUGMENU_PROPERTIES, FALSE);
  1337. MenuIndex = TrackPopupMenu(ContextMenu,
  1338. TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
  1339. pt.x,
  1340. pt.y,
  1341. 0,
  1342. hwnd,
  1343. NULL
  1344. );
  1345. switch (MenuIndex) {
  1346. case IDS_HPLUGMENU_PROPERTIES:
  1347. SysTray_RunProperties(IDS_RUNHPLUGPROPERTIES);
  1348. break;
  1349. }
  1350. DestroyMenu(ContextMenu);
  1351. SetIconFocus(hwnd, STWM_NOTIFYHOTPLUG);
  1352. return;
  1353. }
  1354. void
  1355. HotPlug_Notify(
  1356. HWND hwnd,
  1357. WPARAM wParam,
  1358. LPARAM lParam
  1359. )
  1360. {
  1361. switch (lParam) {
  1362. case WM_RBUTTONUP:
  1363. HotPlugContextMenu(hwnd);
  1364. break;
  1365. case WM_LBUTTONDOWN:
  1366. SetTimer(hwnd, HOTPLUG_TIMER_ID, GetDoubleClickTime()+100, NULL);
  1367. break;
  1368. case WM_LBUTTONDBLCLK:
  1369. KillTimer(hwnd, HOTPLUG_TIMER_ID);
  1370. SysTray_RunProperties(IDS_RUNHPLUGPROPERTIES);
  1371. break;
  1372. }
  1373. return;
  1374. }
  1375. int
  1376. HotPlug_DeviceChangeTimer(
  1377. HWND hDlg
  1378. )
  1379. {
  1380. BOOL bAnyHotPlugDevices, bNewHotPlugDevice, bWarnUser;
  1381. BOOL bRemovableDeviceAdded, bRemovableDeviceRemoved, bRemovableDeviceFailure;
  1382. HDEVINFO hNewDeviceInfoSet;
  1383. KillTimer(hDlg, HOTPLUG_DEVICECHANGE_TIMERID);
  1384. //
  1385. // If the service is not enabled then don't bother because the icon will NOT
  1386. // be shown, sounds will not be played, etc. (see notes for
  1387. // HotplugPlaySoundThisSession).
  1388. //
  1389. if (!ServiceEnabled) {
  1390. goto Clean0;
  1391. }
  1392. //
  1393. // Get a new "current" list of all devices present in the system.
  1394. //
  1395. hNewDeviceInfoSet = SetupDiGetClassDevs(NULL,
  1396. NULL,
  1397. NULL,
  1398. DIGCF_ALLCLASSES | DIGCF_PRESENT);
  1399. //
  1400. // Update the list of removable devices, based on the new current list.
  1401. //
  1402. UpdateRemovableDeviceList(hNewDeviceInfoSet,
  1403. &bRemovableDeviceAdded,
  1404. &bRemovableDeviceRemoved,
  1405. &bRemovableDeviceFailure);
  1406. #if SOUNDS
  1407. //
  1408. // If we should play sounds in this session, check if any removable devices
  1409. // were either added or removed.
  1410. //
  1411. if (HotplugPlaySoundThisSession()) {
  1412. //
  1413. // We'll only play one sound at a time, so if we discover that multiple
  1414. // events have happened simultaneously, let failure override arrival,
  1415. // which overrides removal. This way the user receives notification of
  1416. // the most important event.
  1417. //
  1418. if (bRemovableDeviceFailure) {
  1419. PlaySound(DEVICE_FAILURE_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
  1420. } else if (bRemovableDeviceAdded) {
  1421. PlaySound(DEVICE_ARRIVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
  1422. } else if (bRemovableDeviceRemoved) {
  1423. PlaySound(DEVICE_REMOVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
  1424. }
  1425. }
  1426. #endif // SOUNDS
  1427. //
  1428. // Let's see if we have any hot plug devices, which means we need to
  1429. // show the systray icon. We also want to know about new hotplug
  1430. // devices that just arrived, so we compare the set of removable devices
  1431. // (which we just updated) against the old current set of devices in the
  1432. // system.
  1433. //
  1434. bAnyHotPlugDevices = AnyHotPlugDevices(g_hRemovableDeviceInfoSet,
  1435. g_hCurrentDeviceInfoSet,
  1436. &bNewHotPlugDevice,
  1437. &bWarnUser);
  1438. if (bAnyHotPlugDevices) {
  1439. //
  1440. // We have some hotplug devices so make sure the icon is shown and show
  1441. // the balloon info if necessary.
  1442. //
  1443. //
  1444. // If the hotplug icon is not being shown, then show it.
  1445. //
  1446. if (!ShowShellIcon) {
  1447. HotPlugShowNotifyIcon(hDlg, TRUE);
  1448. }
  1449. #if BUBBLES
  1450. //
  1451. // If we have any new hotplug devices then show the balloon info.
  1452. //
  1453. if (bNewHotPlugDevice && bWarnUser) {
  1454. NOTIFYICONDATA nid = { sizeof(nid), hDlg, 0 };
  1455. try {
  1456. EnterCriticalSection(&g_BubbleCrit);
  1457. if ((hDockEventTimer == NULL) ||
  1458. (WaitForSingleObject(hDockEventTimer, 0) == WAIT_OBJECT_0)) {
  1459. nid.uID = STWM_NOTIFYHOTPLUG;
  1460. nid.uFlags = NIF_INFO;
  1461. nid.uTimeout = 10000;
  1462. nid.dwInfoFlags = NIIF_INFO;
  1463. LoadString(g_hInstance,
  1464. IDS_HOTPLUG_TITLE,
  1465. nid.szInfoTitle,
  1466. sizeof(nid.szInfoTitle) / sizeof(TCHAR)
  1467. );
  1468. LoadString(g_hInstance,
  1469. IDS_HOTPLUG_INSERT_INFO,
  1470. nid.szInfo,
  1471. sizeof(nid.szInfo) / sizeof(TCHAR)
  1472. );
  1473. Shell_NotifyIcon(NIM_MODIFY, &nid);
  1474. }
  1475. }
  1476. finally
  1477. {
  1478. LeaveCriticalSection(&g_BubbleCrit);
  1479. }
  1480. }
  1481. #endif // BUBBLES
  1482. } else {
  1483. //
  1484. // There are NOT any hot plug devices so if the icon is still being
  1485. // shown, then hide it.
  1486. //
  1487. if (ShowShellIcon) {
  1488. HotPlugShowNotifyIcon(hDlg, FALSE);
  1489. }
  1490. }
  1491. //
  1492. // Delete the old current list of devices and set it
  1493. // (g_hCurrentDeviceInfoSet) to the new current list.
  1494. //
  1495. if (g_hCurrentDeviceInfoSet != INVALID_HANDLE_VALUE) {
  1496. SetupDiDestroyDeviceInfoList(g_hCurrentDeviceInfoSet);
  1497. }
  1498. g_hCurrentDeviceInfoSet = hNewDeviceInfoSet;
  1499. Clean0:
  1500. return 0;
  1501. }
  1502. void
  1503. HotPlug_DeviceChange(
  1504. HWND hwnd,
  1505. WPARAM wParam,
  1506. LPARAM lParam
  1507. )
  1508. /*++
  1509. Routine Description:
  1510. Handle WM_DEVICECHANGE messages.
  1511. Arguments:
  1512. hDlg - Window handle of Dialog
  1513. wParam - DBT Event
  1514. lParam - DBT event notification type.
  1515. Return Value:
  1516. --*/
  1517. {
  1518. LARGE_INTEGER liDelayTime;
  1519. NOTIFYICONDATA nid;
  1520. BOOL bPresent;
  1521. switch(wParam) {
  1522. case DBT_DEVNODES_CHANGED:
  1523. //
  1524. // To avoid deadlock with CM, a timer is started and the timer
  1525. // message handler does the real work.
  1526. //
  1527. SetTimer(hwnd, HOTPLUG_DEVICECHANGE_TIMERID, 100, NULL);
  1528. break;
  1529. case DBT_CONFIGCHANGED:
  1530. //
  1531. // A docking event (dock, undock, surprise undock, etc) has
  1532. // occured.
  1533. //
  1534. #if SOUNDS
  1535. //
  1536. // Play a sound for hardware profile changes if we're supposed to.
  1537. //
  1538. if (HotplugPlaySoundThisSession()) {
  1539. if ((CM_Is_Dock_Station_Present(&bPresent) == CR_SUCCESS) &&
  1540. (bPresent)) {
  1541. //
  1542. // If there is a dock present, we most-likely just docked
  1543. // (though we may have just ejected one of many docks), so
  1544. // play an arrival.
  1545. //
  1546. PlaySound(DEVICE_ARRIVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
  1547. } else {
  1548. //
  1549. // If no dock is present we just undocked, so play a
  1550. // removal.
  1551. //
  1552. PlaySound(DEVICE_REMOVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
  1553. }
  1554. }
  1555. #endif // SOUNDS
  1556. #if BUBBLES
  1557. //
  1558. // Make sure no bubbles appear for devices in a newly
  1559. // attached dock.
  1560. //
  1561. if ((CM_Is_Dock_Station_Present(&bPresent) != CR_SUCCESS) ||
  1562. (!bPresent)) {
  1563. //
  1564. // We don't suppress if something appears after an undock.
  1565. //
  1566. break;
  1567. }
  1568. try {
  1569. EnterCriticalSection(&g_BubbleCrit);
  1570. if (hDockEventTimer) {
  1571. liDelayTime.QuadPart = BUBBLE_SUPPRESSION_TIME * -10000000;
  1572. SetWaitableTimer(
  1573. hDockEventTimer,
  1574. &liDelayTime,
  1575. 0,
  1576. NULL,
  1577. NULL,
  1578. FALSE
  1579. );
  1580. }
  1581. //
  1582. // Slap down any bubble that might currently be displayed.
  1583. //
  1584. nid.cbSize = sizeof(nid);
  1585. nid.hWnd = hwnd;
  1586. nid.uID = STWM_NOTIFYHOTPLUG;
  1587. nid.uFlags = 0;
  1588. nid.uTimeout = 0;
  1589. nid.dwInfoFlags = 0;
  1590. Shell_NotifyIcon(NIM_MODIFY, &nid);
  1591. }
  1592. finally
  1593. {
  1594. LeaveCriticalSection(&g_BubbleCrit);
  1595. }
  1596. #endif // BUBBLES
  1597. break;
  1598. default:
  1599. break;
  1600. }
  1601. return;
  1602. }
  1603. void
  1604. HotPlug_WmDestroy(
  1605. HWND hWnd
  1606. )
  1607. {
  1608. if (hEjectEvent) {
  1609. CloseHandle(hEjectEvent);
  1610. }
  1611. #if BUBBLES
  1612. if (hDockEventTimer) {
  1613. CloseHandle(hDockEventTimer);
  1614. }
  1615. if (hSurpriseUndockEventTimer) {
  1616. CloseHandle(hSurpriseUndockEventTimer);
  1617. }
  1618. try {
  1619. DeleteCriticalSection(&g_BubbleCrit);
  1620. } except(EXCEPTION_EXECUTE_HANDLER) {
  1621. NOTHING;
  1622. }
  1623. #endif // BUBBLES
  1624. if (g_hCurrentDeviceInfoSet != INVALID_HANDLE_VALUE) {
  1625. SetupDiDestroyDeviceInfoList(g_hCurrentDeviceInfoSet);
  1626. g_hCurrentDeviceInfoSet = INVALID_HANDLE_VALUE;
  1627. }
  1628. if (g_hRemovableDeviceInfoSet != INVALID_HANDLE_VALUE) {
  1629. SetupDiDestroyDeviceInfoList(g_hRemovableDeviceInfoSet);
  1630. g_hRemovableDeviceInfoSet = INVALID_HANDLE_VALUE;
  1631. }
  1632. }
  1633. void
  1634. HotPlug_SessionChange(
  1635. HWND hWnd,
  1636. WPARAM wParam,
  1637. LPARAM lParam
  1638. )
  1639. {
  1640. //
  1641. // If our console session is getting disconnected then disable our service
  1642. // since we don't need to do any work if no UI is being displayed.
  1643. //
  1644. // If our console session is getting connected then re-enable our service.
  1645. //
  1646. if ((wParam == WTS_CONSOLE_CONNECT) ||
  1647. (wParam == WTS_REMOTE_CONNECT)) {
  1648. HotPlug_CheckEnable(hWnd, TRUE);
  1649. } else if ((wParam == WTS_CONSOLE_DISCONNECT) ||
  1650. (wParam == WTS_REMOTE_DISCONNECT)) {
  1651. HotPlug_CheckEnable(hWnd, FALSE);
  1652. }
  1653. }
  1654. #if SOUNDS
  1655. BOOL
  1656. IsFastUserSwitchingEnabled(
  1657. VOID
  1658. )
  1659. /*++
  1660. Routine Description:
  1661. Checks to see if Terminal Services Fast User Switching is enabled. This is
  1662. to check if we should use the physical console session for UI dialogs, or
  1663. always use session 0.
  1664. Fast User Switching exists only on workstation product version, where terminal
  1665. services are available, when AllowMultipleTSSessions is set.
  1666. On server and above, or when multiple TS users are not allowed, session 0
  1667. can only be attached remotely be special request, in which case it should be
  1668. considered the "Console" session.
  1669. Arguments:
  1670. None.
  1671. Return Value:
  1672. Returns TRUE if Fast User Switching is currently enabled, FALSE otherwise.
  1673. --*/
  1674. {
  1675. static BOOL bVerified = FALSE;
  1676. static BOOL bIsTSWorkstation = FALSE;
  1677. HKEY hKey;
  1678. ULONG ulSize, ulValue;
  1679. BOOL bFusEnabled;
  1680. //
  1681. // Verify the product version if we haven't already.
  1682. //
  1683. if (!bVerified) {
  1684. OSVERSIONINFOEX osvix;
  1685. DWORDLONG dwlConditionMask = 0;
  1686. ZeroMemory(&osvix, sizeof(OSVERSIONINFOEX));
  1687. osvix.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
  1688. osvix.wProductType = VER_NT_WORKSTATION;
  1689. VER_SET_CONDITION(dwlConditionMask, VER_PRODUCT_TYPE, VER_LESS_EQUAL);
  1690. osvix.wSuiteMask = VER_SUITE_TERMINAL | VER_SUITE_SINGLEUSERTS;
  1691. VER_SET_CONDITION(dwlConditionMask, VER_SUITENAME, VER_OR);
  1692. if (VerifyVersionInfo(&osvix,
  1693. VER_PRODUCT_TYPE | VER_SUITENAME,
  1694. dwlConditionMask)) {
  1695. bIsTSWorkstation = TRUE;
  1696. }
  1697. bVerified = TRUE;
  1698. }
  1699. //
  1700. // Fast user switching (FUS) only applies to the Workstation product where
  1701. // Terminal Services are enabled (i.e. Personal, Professional).
  1702. //
  1703. if (!bIsTSWorkstation) {
  1704. return FALSE;
  1705. }
  1706. //
  1707. // Check if multiple TS sessions are currently allowed. We can't make this
  1708. // info static because it can change dynamically.
  1709. //
  1710. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  1711. TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"),
  1712. 0,
  1713. KEY_READ,
  1714. &hKey) != ERROR_SUCCESS) {
  1715. return FALSE;
  1716. }
  1717. ulValue = 0;
  1718. ulSize = sizeof(ulValue);
  1719. bFusEnabled = FALSE;
  1720. if (RegQueryValueEx(hKey,
  1721. TEXT("AllowMultipleTSSessions"),
  1722. NULL,
  1723. NULL,
  1724. (LPBYTE)&ulValue,
  1725. &ulSize) == ERROR_SUCCESS) {
  1726. bFusEnabled = (ulValue != 0);
  1727. }
  1728. RegCloseKey(hKey);
  1729. return bFusEnabled;
  1730. } // IsFastUserSwitchingEnabled
  1731. BOOL
  1732. HotplugPlaySoundThisSession(
  1733. VOID
  1734. )
  1735. /*++
  1736. Routine Description:
  1737. This routine determines whether a sound should be played in the current
  1738. session.
  1739. Arguments:
  1740. None.
  1741. Return Value:
  1742. Returns TRUE if sounds should be played in this session.
  1743. Notes:
  1744. The user-mode plug and play manager (umpnpmgr.dll) implements the following
  1745. behavior for UI dialogs:
  1746. * When Fast User Switching is enabled, only the physical Console session
  1747. is used for UI dialogs.
  1748. * When Fast User Switching is not enabled, only Session 0 is used for UI
  1749. dialogs.
  1750. Since sound events require no user interaction there is no problem with
  1751. multiple sessions responding to these events simultaneously.
  1752. We should *always* play a sound on the physical console when possible, and
  1753. adopt a behavior similar to umpnpmgr for for the non-Fast User Switching
  1754. case, such that session 0 will also play sound events when possible because
  1755. it should be treated somewhat special in the non-FUS case...
  1756. ... BUT, since we disable the service altogether if the session is remote
  1757. and the user doesn't have permission to eject hotplug devices (so we don't
  1758. show the icon), we won't even respond to DBT_DEVNODES_CHANGED events, and
  1759. consequently won't play sound. We could actually turn this on just by
  1760. allowing those events to be processed when the services is disabled, but
  1761. this function is successful. Since the idea of allowing hardware events on
  1762. remote session 0 without FUS is really just for remote management, then it's
  1763. probably ok that we don't play sounds for a user that can't manage hardware.
  1764. --*/
  1765. {
  1766. //
  1767. // Always play sound events on the physical console.
  1768. //
  1769. if (IsConsoleSession()) {
  1770. return TRUE;
  1771. }
  1772. //
  1773. // If fast user switching is not enabled, play sound events on the
  1774. // pseudo-console (Session 0) also.
  1775. //
  1776. if ((IsPseudoConsoleSession()) &&
  1777. (!IsFastUserSwitchingEnabled())) {
  1778. return TRUE;
  1779. }
  1780. //
  1781. // Otherwise, no sound.
  1782. //
  1783. return FALSE;
  1784. } // HotplugPlaySoundThisSession
  1785. #endif // SOUNDS
  1786.