windows-nt/Source/XPSP1/NT/shell/ext/systray/dll/hplug.c
2020-09-26 16:20:57 +08:00

2293 lines
62 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 1992-1997 Microsoft Corporation
* hotplug routines
*
* 09-May-1997 Jonle , created
*
*/
#include "stdafx.h"
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "systray.h"
#include <setupapi.h>
#include <cfgmgr32.h>
#include <dbt.h>
#include <initguid.h>
#include <devguid.h>
#include <ks.h>
#include <ksmedia.h>
#include <ntddstor.h>
//
// Support system sounds for device events.
//
#define SOUNDS 1
#if SOUNDS
BOOL
HotplugPlaySoundThisSession(
VOID
);
//
// Hardware application sound event names.
//
#define DEVICE_ARRIVAL_SOUND TEXT("DeviceConnect")
#define DEVICE_REMOVAL_SOUND TEXT("DeviceDisconnect")
#define DEVICE_FAILURE_SOUND TEXT("DeviceFail")
#endif // SOUNDS
//
// Simple checks for console / remote TS sessions.
//
#define MAIN_SESSION ((ULONG)0)
#define THIS_SESSION ((ULONG)NtCurrentPeb()->SessionId)
#define CONSOLE_SESSION ((ULONG)USER_SHARED_DATA->ActiveConsoleId)
#define IsConsoleSession() (BOOL)(THIS_SESSION == CONSOLE_SESSION)
#define IsRemoteSession() (BOOL)(THIS_SESSION != CONSOLE_SESSION)
#define IsPseudoConsoleSession() (BOOL)(THIS_SESSION == MAIN_SESSION)
//
// 2001/02/01 - Disable support for *both* arrival bubbles and departure
// bubbles (hotplug.dll must be kept in sync here.) This code is disabled for
// beta2, and should be removed afterwards if feedback is positive.
//
#define BUBBLES 0
#define HPLUG_EJECT_EVENT TEXT("HPlugEjectEvent")
#if BUBBLES
#define HPLUG_DOCK_TIMER TEXT("HPlugProfileChangeTimer")
#define SURPRISE_UNDOCK_TIMER TEXT("Local\\HotPlug_SurpriseUndockTimer_{25126bc2-1ab0-4494-8b6d-e4034cb9c24a}")
//
// We suppress bubbles for some period of time after a dock event. Free build,
// ~15 secs, Checked build ~60.
//
#if DBG
#define BUBBLE_SUPPRESSION_TIME 60
#else
#define BUBBLE_SUPPRESSION_TIME 15
#endif
#endif // BUBBLES
typedef struct _HotPlugDevices {
struct _HotPlugDevices *Next;
DEVINST DevInst;
WORD EjectMenuIndex;
BOOLEAN PendingEvent;
PTCHAR DevName;
TCHAR DevInstanceId[1];
} HOTPLUGDEVICES, *PHOTPLUGDEVICES;
BOOL HotPlugInitialized = FALSE;
BOOL ShowShellIcon = FALSE;
HICON HotPlugIcon = NULL;
BOOL ServiceEnabled = FALSE;
HANDLE hEjectEvent = NULL; // Event to if we are in the process of ejecting a device
HDEVINFO g_hCurrentDeviceInfoSet = INVALID_HANDLE_VALUE;
HDEVINFO g_hRemovableDeviceInfoSet = INVALID_HANDLE_VALUE;
#if BUBBLES
HANDLE hDockEventTimer = NULL;
HANDLE hSurpriseUndockEventTimer = NULL;
CRITICAL_SECTION g_BubbleCrit = { 0 };
#endif // BUBBLES
extern HINSTANCE g_hInstance; // Global instance handle 4 this application.
BOOL
pDoesUserHavePrivilege(
PCTSTR PrivilegeName
)
/*++
Routine Description:
This routine returns TRUE if the caller's process has
the specified privilege. The privilege does not have
to be currently enabled. This routine is used to indicate
whether the caller has the potential to enable the privilege.
Caller is NOT expected to be impersonating anyone and IS
expected to be able to open their own process and process
token.
Arguments:
Privilege - the name form of privilege ID (such as
SE_SECURITY_NAME).
Return Value:
TRUE - Caller has the specified privilege.
FALSE - Caller does not have the specified privilege.
--*/
{
HANDLE Token;
ULONG BytesRequired;
PTOKEN_PRIVILEGES Privileges;
BOOL b;
DWORD i;
LUID Luid;
//
// Open the process token.
//
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,&Token)) {
return(FALSE);
}
b = FALSE;
Privileges = NULL;
//
// Get privilege information.
//
if(!GetTokenInformation(Token,TokenPrivileges,NULL,0,&BytesRequired)
&& (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
&& (Privileges = LocalAlloc(LPTR, BytesRequired))
&& GetTokenInformation(Token,TokenPrivileges,Privileges,BytesRequired,&BytesRequired)
&& LookupPrivilegeValue(NULL,PrivilegeName,&Luid)) {
//
// See if we have the requested privilege
//
for(i=0; i<Privileges->PrivilegeCount; i++) {
if((Luid.LowPart == Privileges->Privileges[i].Luid.LowPart)
&& (Luid.HighPart == Privileges->Privileges[i].Luid.HighPart)) {
b = TRUE;
break;
}
}
}
//
// Clean up and return.
//
if(Privileges) {
LocalFree(Privileges);
}
CloseHandle(Token);
return(b);
}
BOOL
IsHotPlugDevice(
DEVINST DevInst,
PBOOL WarnUserOnInsertion OPTIONAL
)
/**+
A device is considered a HotPlug device if the following are TRUE:
- has Capability CM_DEVCAP_REMOVABLE
- does NOT have Capability CM_DEVCAP_SURPRISEREMOVALOK
- does NOT have Capability CM_DEVCAP_DOCKDEVICE
- must be started (have the DN_STARTED devnode flag)
- unless has capability CM_DEVCAP_EJECTSUPPORTED
WarnUserOnInsertion - Receives TRUE if the user should be warned to use the
hotplug applet when this device is inserted.
Returns:
TRUE if this is a HotPlug device
FALSE if this is not a HotPlug device.
-**/
{
DWORD Capabilities, ConfigFlags;
DWORD Len;
DWORD Status, Problem;
CONFIGRET ConfigRet;
Capabilities = Status = Problem = 0;
if (ARGUMENT_PRESENT(WarnUserOnInsertion)) {
*WarnUserOnInsertion = FALSE;
}
Len = sizeof(Capabilities);
if (CM_Get_DevNode_Registry_Property(DevInst,
CM_DRP_CAPABILITIES,
NULL,
(PVOID)&Capabilities,
&Len,
0
) != CR_SUCCESS) {
return FALSE;
}
if (CM_Get_DevNode_Status(&Status,
&Problem,
DevInst,
0
) != CR_SUCCESS) {
return FALSE;
}
//
// If this device is not removable, or it is surprise removal ok, or
// it is a dock device, then it is not a hotplug device.
//
if ((!(Capabilities & CM_DEVCAP_REMOVABLE)) ||
(Capabilities & CM_DEVCAP_SURPRISEREMOVALOK) ||
(Capabilities & CM_DEVCAP_DOCKDEVICE)) {
return FALSE;
}
//
// We won't consider a device to be a hotplug device if it is not started,
// unless it is an eject capable device.
//
// The reason for this test is that a bus driver might set the
// CM_DEVCAP_REMOVABLE capability, but if the PDO doesn't get loaded then
// it can't set the CM_DEVCAP_SURPRISEREMOVALOK. So we won't trust the
// CM_DEVCAP_REMOVABLE capability if the PDO is not started.
//
if ((!(Capabilities & CM_DEVCAP_EJECTSUPPORTED)) &&
(!(Status & DN_STARTED))) {
return FALSE;
}
#if BUBBLES
if (ARGUMENT_PRESENT(WarnUserOnInsertion)) {
Len = sizeof(ConfigFlags);
ConfigRet = CM_Get_DevNode_Registry_Property_Ex(DevInst,
CM_DRP_CONFIGFLAGS,
NULL,
(PVOID)&ConfigFlags,
&Len,
0,
NULL);
if ((ConfigRet == CR_SUCCESS) &&
(ConfigFlags & CONFIGFLAG_SUPPRESS_SURPRISE)) {
*WarnUserOnInsertion = FALSE;
} else {
*WarnUserOnInsertion = TRUE;
}
}
#endif // BUBBLES
return TRUE;
}
BOOL
IsRemovableDevice(
IN DEVINST dnDevInst
)
/*++
Routine Description:
This routine determines whether a device is removable.
Arguments:
dnDevInst - Device instance.
Return Value:
Returns TRUE if the device is removable.
--*/
{
CONFIGRET Status = CR_SUCCESS;
ULONG ulPropertyData, ulDataSize, ulRegDataType;
//
// Validate parameters.
//
if (dnDevInst == 0) {
return FALSE;
}
//
// Get the capabilities for this device.
//
ulDataSize = sizeof(ulPropertyData);
if (CM_Get_DevNode_Registry_Property_Ex(dnDevInst,
CM_DRP_CAPABILITIES,
&ulRegDataType,
&ulPropertyData,
&ulDataSize,
0,
NULL) != CR_SUCCESS) {
return FALSE;
}
//
// Check if the device has the removable capability.
//
if ((ulPropertyData & CM_DEVCAP_REMOVABLE) == 0) {
return FALSE;
}
return TRUE;
} // IsRemovableDevice
LPTSTR
DevNodeToDriveLetter(
DEVINST DevInst
)
{
BOOL Result = FALSE;
ULONG ulSize;
TCHAR DeviceID[MAX_DEVICE_ID_LEN];
LPTSTR DriveName = NULL;
LPTSTR DeviceInterface = NULL;
if (CM_Get_Device_ID_Ex(DevInst,
DeviceID,
ARRAYSIZE(DeviceID),
0,
NULL
) != CR_SUCCESS) {
return FALSE;
}
ulSize = 0;
if ((CM_Get_Device_Interface_List_Size(&ulSize,
(LPGUID)&VolumeClassGuid,
DeviceID,
0) == CR_SUCCESS) &&
(ulSize > 1) &&
((DeviceInterface = LocalAlloc(LPTR, ulSize*sizeof(TCHAR))) != NULL) &&
(CM_Get_Device_Interface_List((LPGUID)&VolumeClassGuid,
DeviceID,
DeviceInterface,
ulSize,
0
) == CR_SUCCESS) &&
*DeviceInterface)
{
LPTSTR devicePath, p;
TCHAR thisVolumeName[MAX_PATH];
TCHAR enumVolumeName[MAX_PATH];
TCHAR driveName[4];
ULONG length;
BOOL bResult;
length = lstrlen(DeviceInterface);
devicePath = LocalAlloc(LPTR, (length + 1) * sizeof(TCHAR) + sizeof(UNICODE_NULL));
if (devicePath) {
lstrcpyn(devicePath, DeviceInterface, length + 1);
p = wcschr(&(devicePath[4]), TEXT('\\'));
if (!p) {
//
// No refstring is present in the symbolic link; add a trailing
// '\' char (as required by GetVolumeNameForVolumeMountPoint).
//
p = devicePath + length;
*p = TEXT('\\');
}
p++;
*p = UNICODE_NULL;
thisVolumeName[0] = UNICODE_NULL;
bResult = GetVolumeNameForVolumeMountPoint(devicePath,
thisVolumeName,
MAX_PATH
);
LocalFree(devicePath);
if (bResult && thisVolumeName[0]) {
driveName[1] = TEXT(':');
driveName[2] = TEXT('\\');
driveName[3] = TEXT('\0');
for (driveName[0] = TEXT('A'); driveName[0] <= TEXT('Z'); driveName[0]++) {
enumVolumeName[0] = TEXT('\0');
GetVolumeNameForVolumeMountPoint(driveName, enumVolumeName, MAX_PATH);
if (!lstrcmpi(thisVolumeName, enumVolumeName)) {
driveName[2] = TEXT('\0');
DriveName = LocalAlloc(LPTR, (lstrlen(driveName) + 1) * sizeof(TCHAR));
if (DriveName) {
lstrcpy(DriveName, driveName);
}
break;
}
}
}
}
}
if (DeviceInterface) {
LocalFree(DeviceInterface);
}
return DriveName;
}
int
CollectRelationDriveLetters(
DEVINST DevInst,
LPTSTR ListOfDrives
)
/*++
This function looks at the removal relations of the specified DevInst and adds any drive
letters associated with these removal relations to the ListOfDrives.
Return:
Number of drive letters added to the list.
--*/
{
int NumberOfDrives = 0;
LPTSTR SingleDrive = NULL;
TCHAR szSeparator[32];
DEVINST RelationDevInst;
TCHAR DeviceInstanceId[MAX_DEVICE_ID_LEN];
ULONG Len;
PTCHAR DeviceIdRelations, CurrDevId;
if (CM_Get_Device_ID(DevInst,
DeviceInstanceId,
ARRAYSIZE(DeviceInstanceId),
0
) == CR_SUCCESS) {
Len = 0;
if ((CM_Get_Device_ID_List_Size(&Len,
DeviceInstanceId,
CM_GETIDLIST_FILTER_REMOVALRELATIONS
) == CR_SUCCESS) &&
(Len)) {
DeviceIdRelations = LocalAlloc(LPTR, Len*sizeof(TCHAR));
if (DeviceIdRelations) {
*DeviceIdRelations = TEXT('\0');
if ((CM_Get_Device_ID_List(DeviceInstanceId,
DeviceIdRelations,
Len,
CM_GETIDLIST_FILTER_REMOVALRELATIONS
) == CR_SUCCESS) &&
(*DeviceIdRelations)) {
for (CurrDevId = DeviceIdRelations; *CurrDevId; CurrDevId += lstrlen(CurrDevId) + 1) {
if (CM_Locate_DevNode(&RelationDevInst, CurrDevId, 0) == CR_SUCCESS) {
SingleDrive = DevNodeToDriveLetter(RelationDevInst);
if (SingleDrive) {
NumberOfDrives++;
//
// If this is not the first drive the add a comma space separator
//
if (ListOfDrives[0] != TEXT('\0')) {
LoadString(g_hInstance, IDS_SEPARATOR, szSeparator, sizeof(szSeparator)/sizeof(TCHAR));
lstrcat(ListOfDrives, szSeparator);
}
lstrcat(ListOfDrives, SingleDrive);
LocalFree(SingleDrive);
}
}
}
}
LocalFree(DeviceIdRelations);
}
}
}
return NumberOfDrives;
}
int
CollectDriveLettersForDevNodeWorker(
DEVINST DevInst,
LPTSTR ListOfDrives
)
{
DEVINST ChildDevInst;
DEVINST SiblingDevInst;
int NumberOfDrives = 0;
LPTSTR SingleDrive = NULL;
TCHAR szSeparator[32];
//
// Enumerate through all of the siblings and children of this devnode
//
do {
ChildDevInst = 0;
SiblingDevInst = 0;
CM_Get_Child(&ChildDevInst, DevInst, 0);
CM_Get_Sibling(&SiblingDevInst, DevInst, 0);
//
// Only get the drive letter for this device if it is NOT a hotplug
// device. If it is a hotplug device then it will have it's own
// subtree that contains it's drive letters.
//
if (!IsHotPlugDevice(DevInst, NULL)) {
SingleDrive = DevNodeToDriveLetter(DevInst);
if (SingleDrive) {
NumberOfDrives++;
//
// If this is not the first drive the add a comma space separator
//
if (ListOfDrives[0] != TEXT('\0')) {
LoadString(g_hInstance, IDS_SEPARATOR, szSeparator, sizeof(szSeparator)/sizeof(TCHAR));
lstrcat(ListOfDrives, szSeparator);
}
lstrcat(ListOfDrives, SingleDrive);
LocalFree(SingleDrive);
}
//
// Get the drive letters for any children of this devnode
//
if (ChildDevInst) {
NumberOfDrives += CollectDriveLettersForDevNodeWorker(ChildDevInst, ListOfDrives);
}
//
// Add the drive letters for any removal relations of this devnode
//
NumberOfDrives += CollectRelationDriveLetters(DevInst, ListOfDrives);
}
} while ((DevInst = SiblingDevInst) != 0);
return NumberOfDrives;
}
LPTSTR
CollectDriveLettersForDevNode(
DEVINST DevInst
)
{
TCHAR Format[MAX_PATH];
TCHAR ListOfDrives[MAX_PATH];
DEVINST ChildDevInst;
int NumberOfDrives = 0;
LPTSTR SingleDrive = NULL;
LPTSTR FinalDriveString = NULL;
ListOfDrives[0] = TEXT('\0');
//
//First get any drive letter associated with this devnode
//
SingleDrive = DevNodeToDriveLetter(DevInst);
if (SingleDrive) {
NumberOfDrives++;
lstrcat(ListOfDrives, SingleDrive);
LocalFree(SingleDrive);
}
//
// Next add on any drive letters associated with the children
// of this devnode
//
ChildDevInst = 0;
CM_Get_Child(&ChildDevInst, DevInst, 0);
if (ChildDevInst) {
NumberOfDrives += CollectDriveLettersForDevNodeWorker(ChildDevInst, ListOfDrives);
}
//
// Finally add on any drive letters associated with the removal relations
// of this devnode
//
NumberOfDrives += CollectRelationDriveLetters(DevInst, ListOfDrives);
if (ListOfDrives[0] != TEXT('\0')) {
LoadString(g_hInstance,
(NumberOfDrives > 1) ? IDS_DISKDRIVES : IDS_DISKDRIVE,
Format,
sizeof(Format)/sizeof(TCHAR)
);
FinalDriveString = LocalAlloc(LPTR, (lstrlen(ListOfDrives) + lstrlen(Format) + 1) * sizeof(TCHAR));
if (FinalDriveString) {
wsprintf(FinalDriveString, Format, ListOfDrives);
}
}
return FinalDriveString;
}
DWORD
GetHotPlugFlags(
void
)
{
HKEY hKey;
LONG Error;
DWORD HotPlugFlags, cbHotPlugFlags;
Error = RegCreateKey(HKEY_CURRENT_USER, REGSTR_PATH_SYSTRAY, &hKey);
if (Error == ERROR_SUCCESS) {
cbHotPlugFlags = sizeof(HotPlugFlags);
Error = RegQueryValueEx(hKey,
TEXT("HotPlugFlags"),
NULL,
NULL,
(PVOID)&HotPlugFlags,
&cbHotPlugFlags
);
RegCloseKey(hKey);
if (Error == ERROR_SUCCESS) {
return HotPlugFlags;
}
}
return 0;
}
ULONG
RegistryDeviceName(
DEVINST DevInst,
PTCHAR Buffer,
DWORD cbBuffer
)
{
ULONG ulSize = 0;
CONFIGRET ConfigRet;
LPTSTR ListOfDrives = NULL;
//
// Get the list of drives
//
ListOfDrives = CollectDriveLettersForDevNode(DevInst);
//
// Try the registry for FRIENDLYNAME
//
ulSize = cbBuffer;
*Buffer = TEXT('\0');
ConfigRet = CM_Get_DevNode_Registry_Property(DevInst,
CM_DRP_FRIENDLYNAME,
NULL,
Buffer,
&ulSize,
0
);
if (ConfigRet != CR_SUCCESS || !(*Buffer)) {
//
// Try the registry for DEVICEDESC
//
ulSize = cbBuffer;
*Buffer = TEXT('\0');
ConfigRet = CM_Get_DevNode_Registry_Property(DevInst,
CM_DRP_DEVICEDESC,
NULL,
Buffer,
&ulSize,
0);
if (ConfigRet != CR_SUCCESS || !(*Buffer)) {
//
// try classname
//
ulSize = cbBuffer;
*Buffer = TEXT('\0');
ConfigRet = CM_Get_DevNode_Registry_Property(DevInst,
CM_DRP_CLASS,
NULL,
Buffer,
&ulSize,
0);
if (ConfigRet != CR_SUCCESS || !(*Buffer)) {
ulSize = 0;;
}
}
}
//
// Concatonate on the list of drive letters if this device has drive
// letters and there is enough space
//
if (ListOfDrives) {
if ((ulSize + (lstrlen(ListOfDrives) * sizeof(TCHAR))) < cbBuffer) {
lstrcat(Buffer, ListOfDrives);
ulSize += (lstrlen(ListOfDrives) * sizeof(TCHAR));
}
LocalFree(ListOfDrives);
}
return ulSize;
}
BOOL
IsDevInstInDeviceInfoSet(
IN DEVINST DevInst,
IN HDEVINFO hDeviceInfoSet,
OUT PSP_DEVINFO_DATA DeviceInfoDataInSet OPTIONAL
)
{
DWORD MemberIndex;
SP_DEVINFO_DATA DeviceInfoData;
BOOL bIsMember = FALSE;
ASSERT(hDeviceInfoSet != INVALID_HANDLE_VALUE);
if (hDeviceInfoSet == INVALID_HANDLE_VALUE) {
return FALSE;
}
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
MemberIndex = 0;
while (SetupDiEnumDeviceInfo(hDeviceInfoSet,
MemberIndex,
&DeviceInfoData
)) {
if (DevInst == DeviceInfoData.DevInst) {
bIsMember = TRUE;
if (ARGUMENT_PRESENT(DeviceInfoDataInSet)) {
ASSERT(DeviceInfoDataInSet->cbSize >= DeviceInfoData.cbSize);
memcpy(DeviceInfoDataInSet, &DeviceInfoData, DeviceInfoDataInSet->cbSize);
}
break;
}
MemberIndex++;
}
return bIsMember;
}
BOOL
AnyHotPlugDevices(
IN HDEVINFO hRemovableDeviceInfoSet,
IN HDEVINFO hOldDeviceInfoSet,
OUT PBOOL bNewHotPlugDevice OPTIONAL,
OUT PBOOL bWarnUserAboutNewDevices OPTIONAL
)
{
SP_DEVINFO_DATA DeviceInfoData;
DWORD dwMemberIndex;
BOOL bAnyHotPlugDevices = FALSE;
BOOL bWarnOnInsertion;
//
// Initialize output parameters.
//
if (ARGUMENT_PRESENT(bNewHotPlugDevice)) {
*bNewHotPlugDevice = FALSE;
}
if (ARGUMENT_PRESENT(bWarnUserAboutNewDevices)) {
*bWarnUserAboutNewDevices = FALSE;
}
//
// We already have an updated list of just removable devices, so we can just
// enumerate those devices and see if any also meet the criteria for hotplug
// devices.
//
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
dwMemberIndex = 0;
while (SetupDiEnumDeviceInfo(hRemovableDeviceInfoSet,
dwMemberIndex,
&DeviceInfoData)) {
if (IsHotPlugDevice(DeviceInfoData.DevInst, &bWarnOnInsertion)) {
bAnyHotPlugDevices = TRUE;
//
// If the caller doesn't want to know if any new hotplug devices
// have arrived then just break at this point.
//
if (!ARGUMENT_PRESENT(bNewHotPlugDevice)) {
break;
}
//
// If the caller wants to know if the hotplug device is new, we must
// have a list of devices to check against.
//
ASSERT(hOldDeviceInfoSet != INVALID_HANDLE_VALUE);
//
// The caller wants to know if we have any new hotplug devices. So,
// we will compare this hotplug device to see if it is also in the
// old current list of devices. If it is not then we have found a
// new hotplug device.
//
if (!IsDevInstInDeviceInfoSet(DeviceInfoData.DevInst,
hOldDeviceInfoSet,
NULL)) {
*bNewHotPlugDevice = TRUE;
if (ARGUMENT_PRESENT(bWarnUserAboutNewDevices) && bWarnOnInsertion) {
*bWarnUserAboutNewDevices = TRUE;
//
// There is no longer any reason to continue the
// enumeration.
//
break;
}
}
}
dwMemberIndex++;
}
return bAnyHotPlugDevices;
}
BOOL
UpdateRemovableDeviceList(
IN HDEVINFO hDeviceInfoSet,
OUT PBOOL bRemovableDeviceAdded OPTIONAL,
OUT PBOOL bRemovableDeviceRemoved OPTIONAL,
OUT PBOOL bRemovableDeviceFailure OPTIONAL
)
{
SP_DEVINFO_DATA DeviceInfoData;
TCHAR DeviceInstanceId[MAX_DEVICE_ID_LEN];
DWORD dwMemberIndex;
ULONG ulDevStatus, ulDevProblem;
//
// Initialize output parameters.
//
if (ARGUMENT_PRESENT(bRemovableDeviceAdded)) {
*bRemovableDeviceAdded = FALSE;
}
if (ARGUMENT_PRESENT(bRemovableDeviceRemoved)) {
*bRemovableDeviceRemoved = FALSE;
}
if (ARGUMENT_PRESENT(bRemovableDeviceFailure)) {
*bRemovableDeviceFailure = FALSE;
}
//
// We at least need a current list of devices in the system.
//
if (hDeviceInfoSet == INVALID_HANDLE_VALUE) {
return FALSE;
}
if (g_hRemovableDeviceInfoSet == INVALID_HANDLE_VALUE) {
//
// If we don't already have a global device info set for removable
// devices in the system, create one now. No removable devices have
// been removed in this case, because we didn't know about any prior to
// this.
//
g_hRemovableDeviceInfoSet = SetupDiCreateDeviceInfoListEx(NULL,
NULL,
NULL,
NULL);
//
// If we couldn't create a list to store removable devices, there's no
// point in checking anything else here.
//
if (g_hRemovableDeviceInfoSet == INVALID_HANDLE_VALUE) {
return FALSE;
}
} else {
//
// If we already had a list of removable devices, enumerate the devices
// to see if any have been removed from the system since we last
// checked.
//
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
dwMemberIndex = 0;
while (SetupDiEnumDeviceInfo(g_hRemovableDeviceInfoSet,
dwMemberIndex,
&DeviceInfoData)) {
if (!IsDevInstInDeviceInfoSet(DeviceInfoData.DevInst,
hDeviceInfoSet,
NULL)) {
//
// A removable device is missing from the system.
//
if (ARGUMENT_PRESENT(bRemovableDeviceRemoved)) {
*bRemovableDeviceRemoved = TRUE;
}
#if DBG // DBG
if (SetupDiGetDeviceInstanceId(g_hRemovableDeviceInfoSet,
&DeviceInfoData,
DeviceInstanceId,
MAX_DEVICE_ID_LEN,
NULL)) {
KdPrintEx((DPFLTR_PNPMGR_ID,
(0x00000010 | DPFLTR_MASK),
"HPLUG: Removing device %ws from g_hRemovableDeviceInfoSet.\n",
DeviceInstanceId));
}
#endif // DBG
//
// Remove the device from the global list of removable devices.
//
SetupDiDeleteDeviceInfo(g_hRemovableDeviceInfoSet,
&DeviceInfoData);
}
//
// Increment the enumeration index.
//
dwMemberIndex++;
}
}
//
// Enumerate the current list of devices and see if any removable devices
// have been added to the system.
//
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
dwMemberIndex = 0;
while (SetupDiEnumDeviceInfo(hDeviceInfoSet,
dwMemberIndex,
&DeviceInfoData)) {
//
// If this device is not already in the removable device list, and it's
// removable, add it to the list.
//
if ((!IsDevInstInDeviceInfoSet(DeviceInfoData.DevInst,
g_hRemovableDeviceInfoSet,
NULL)) &&
(IsRemovableDevice(DeviceInfoData.DevInst))) {
//
// A removable device was added to the system.
//
if (ARGUMENT_PRESENT(bRemovableDeviceAdded)) {
*bRemovableDeviceAdded = TRUE;
}
//
// Add the device to the global list of removable devices.
//
if (SetupDiGetDeviceInstanceId(hDeviceInfoSet,
&DeviceInfoData,
DeviceInstanceId,
MAX_DEVICE_ID_LEN,
NULL)) {
KdPrintEx((DPFLTR_PNPMGR_ID,
(0x00000010 | DPFLTR_MASK),
"HPLUG: Adding device %ws to g_hRemovableDeviceInfoSet\n",
DeviceInstanceId));
SetupDiOpenDeviceInfo(g_hRemovableDeviceInfoSet,
DeviceInstanceId,
NULL,
0,
NULL);
}
//
// If the caller is also interested in device failures, check the
// status of the new device.
//
if (ARGUMENT_PRESENT(bRemovableDeviceFailure)) {
if (CM_Get_DevNode_Status_Ex(&ulDevStatus,
&ulDevProblem,
DeviceInfoData.DevInst,
0,
NULL) == CR_SUCCESS) {
if (((ulDevStatus & DN_HAS_PROBLEM) != 0) &&
(ulDevProblem != CM_PROB_NOT_CONFIGURED) &&
(ulDevProblem != CM_PROB_REINSTALL)) {
*bRemovableDeviceFailure = TRUE;
KdPrintEx((DPFLTR_PNPMGR_ID,
(0x00000010 | DPFLTR_MASK),
"HPLUG: Device %ws considered a failed insertion (Status = 0x%08lx, Problem = 0x%08lx)\n",
DeviceInstanceId, ulDevStatus, ulDevProblem));
}
}
}
}
//
// Increment the enumeration index.
//
dwMemberIndex++;
}
return TRUE;
}
BOOL
AddHotPlugDevice(
DEVINST DeviceInstance,
PHOTPLUGDEVICES *HotPlugDevicesList
)
{
PHOTPLUGDEVICES HotPlugDevice;
DWORD Len, LenDevName, LenDevInstanceId;
CONFIGRET ConfigRet;
TCHAR DevInstanceId[MAX_DEVICE_ID_LEN];
TCHAR DevName[MAX_PATH];
//
// Retrieve the device instance id
//
*DevInstanceId = TEXT('\0');
LenDevInstanceId = ARRAYSIZE(DevInstanceId);
ConfigRet = CM_Get_Device_ID(DeviceInstance,
(PVOID)DevInstanceId,
LenDevInstanceId,
0);
if (ConfigRet != CR_SUCCESS || !*DevInstanceId) {
*DevInstanceId = TEXT('\0');
LenDevInstanceId = 0;
}
Len = sizeof(HOTPLUGDEVICES) + LenDevInstanceId;
HotPlugDevice = LocalAlloc(LPTR, Len);
if (!HotPlugDevice) {
return FALSE;
}
//
// link it in
//
HotPlugDevice->Next = *HotPlugDevicesList;
*HotPlugDevicesList = HotPlugDevice;
HotPlugDevice->DevInst = DeviceInstance;
//
// copy in the names
//
memcpy(HotPlugDevice->DevInstanceId, DevInstanceId, LenDevInstanceId);
LenDevName = RegistryDeviceName(DeviceInstance, DevName, sizeof(DevName));
HotPlugDevice->DevName = LocalAlloc(LPTR, LenDevName + sizeof(TCHAR));
if (HotPlugDevice->DevName) {
memcpy(HotPlugDevice->DevName, DevName, LenDevName);
}
return TRUE;
}
BOOL
AddHotPlugDevices(
PHOTPLUGDEVICES *HotPlugDevicesList
)
{
CONFIGRET ConfigRet;
SP_DEVINFO_DATA DeviceInfoData;
DWORD dwMemberIndex;
//
// Initialize output list of hotplug devices.
//
*HotPlugDevicesList = NULL;
//
// Enumerate the list of removable devices.
//
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
dwMemberIndex = 0;
while (SetupDiEnumDeviceInfo(g_hRemovableDeviceInfoSet,
dwMemberIndex,
&DeviceInfoData)) {
//
// If any removable device also meets the criteria of a hotplug device,
// add it to the linked list.
//
if (IsHotPlugDevice(DeviceInfoData.DevInst, NULL)) {
AddHotPlugDevice(DeviceInfoData.DevInst, HotPlugDevicesList);
}
dwMemberIndex++;
}
return TRUE;
}
void
FreeHotPlugDevicesList(
PHOTPLUGDEVICES *HotPlugDevicesList
)
{
PHOTPLUGDEVICES HotPlugDevices, HotPlugDevicesFree;
HotPlugDevices = *HotPlugDevicesList;
*HotPlugDevicesList = NULL;
while (HotPlugDevices) {
HotPlugDevicesFree = HotPlugDevices;
HotPlugDevices = HotPlugDevicesFree->Next;
if (HotPlugDevicesFree->DevName) {
LocalFree(HotPlugDevicesFree->DevName);
HotPlugDevicesFree->DevName = NULL;
}
LocalFree(HotPlugDevicesFree);
}
}
/*
* Shows or deletes the shell notify icon and tip
*/
void
HotPlugShowNotifyIcon(
HWND hWnd,
BOOL bShowIcon
)
{
TCHAR HotPlugTip[64];
ShowShellIcon = bShowIcon;
if (bShowIcon) {
LoadString(g_hInstance,
IDS_HOTPLUGTIP,
HotPlugTip,
sizeof(HotPlugTip)/sizeof(TCHAR)
);
HotPlugIcon = LoadImage(g_hInstance,
MAKEINTRESOURCE(IDI_HOTPLUG),
IMAGE_ICON,
16,
16,
0
);
SysTray_NotifyIcon(hWnd, STWM_NOTIFYHOTPLUG, NIM_ADD, HotPlugIcon, HotPlugTip);
}
else {
SysTray_NotifyIcon(hWnd, STWM_NOTIFYHOTPLUG, NIM_DELETE, NULL, NULL);
if (HotPlugIcon) {
DestroyIcon(HotPlugIcon);
}
}
}
//
// first time intialization of Hotplug module.
//
BOOL
HotPlugInit(
HWND hWnd
)
{
HDEVINFO hNewDeviceInfoSet;
BOOL bAnyHotPlugDevices;
LARGE_INTEGER liDelayTime;
//
// Get a new "current" list of all devices present in the system.
//
hNewDeviceInfoSet = SetupDiGetClassDevs(NULL,
NULL,
NULL,
DIGCF_ALLCLASSES | DIGCF_PRESENT);
//
// Update the list of removable devices, don't play any sounds.
//
UpdateRemovableDeviceList(hNewDeviceInfoSet,
NULL,
NULL,
NULL);
//
// Find out whether there are any HotPlug devices in the list of removable
// devices. We're just deciding whether the icon needs to be enabled or
// not, so we don't care if there are any new hotplug devices or not (we
// won't even look at g_hCurrentDeviceInfoSet).
//
bAnyHotPlugDevices = AnyHotPlugDevices(g_hRemovableDeviceInfoSet,
g_hCurrentDeviceInfoSet,
NULL,
NULL);
//
// Delete the old current list of devices and set it
// (g_hCurrentDeviceInfoSet) to the new current list.
//
if (g_hCurrentDeviceInfoSet != INVALID_HANDLE_VALUE) {
SetupDiDestroyDeviceInfoList(g_hCurrentDeviceInfoSet);
}
//
// Update the global list of devices currently in the system.
//
g_hCurrentDeviceInfoSet = hNewDeviceInfoSet;
//
// If hotplug was previously initialized, we don't need to create the events
// and timers below.
//
if (HotPlugInitialized) {
return bAnyHotPlugDevices;
}
hEjectEvent = CreateEvent(NULL, TRUE, TRUE, HPLUG_EJECT_EVENT);
#if BUBBLES
//
// Create a timer that will be used to suppress "insertion warnings" that
// occur when a dock is attached.
//
hDockEventTimer = CreateWaitableTimer(NULL, TRUE, HPLUG_DOCK_TIMER);
if (hDockEventTimer) {
//
// Signal it immediately
//
liDelayTime.QuadPart = 0;
SetWaitableTimer(hDockEventTimer, &liDelayTime, 0, NULL, NULL, FALSE);
}
//
// Create a session-wide timer hotplug.dll will use to suppress surprise
// remove messages during surprise undocks. We create the timer here as
// hotplug comes and goes while we stick around.
//
hSurpriseUndockEventTimer = CreateWaitableTimer(
NULL,
TRUE,
SURPRISE_UNDOCK_TIMER
);
if ((hSurpriseUndockEventTimer != NULL) &&
(GetLastError() == ERROR_SUCCESS)) {
//
// We created it (if not the status would be
// ERROR_ALREADY_EXISTS). Signal it immediately.
//
liDelayTime.QuadPart = 0;
SetWaitableTimer(hSurpriseUndockEventTimer, &liDelayTime, 0, NULL, NULL, FALSE);
}
try {
InitializeCriticalSection(&g_BubbleCrit);
} except(EXCEPTION_EXECUTE_HANDLER) {
NOTHING;
}
#endif // BUBBLES
HotPlugInitialized = TRUE;
return bAnyHotPlugDevices;
}
BOOL
HotPlug_CheckEnable(
HWND hWnd,
BOOL bSvcEnabled
)
/*++
Routine Description:
Called at init time and whenever services are enabled/disabled.
Hotplug is always alive to receive device change notifications.
The shell notify icon is enabled\disabled depending on:
- systray registry setting for services,
AND
- availability of removable devices.
Arguments:
hwnd - Our Window handle
bSvcEnabled - TRUE Service is being enabled.
Return Value:
BOOL Returns TRUE if active.
--*/
{
BOOL EnableShellIcon;
HANDLE hHotplugBalloonEvent = NULL;
//
// If we are being enabled and we are already enabled, or we
// are being disabled and we are already disabled then just
// return since we have nothing to do.
//
if (ServiceEnabled == bSvcEnabled) {
return ServiceEnabled;
}
ServiceEnabled = bSvcEnabled;
//
// There are some special checks we need to make if we are enabling the
// hotplug service.
//
if (bSvcEnabled) {
//
// If this is a remote session and the user does not have the
// SE_LOAD_DRIVER_NAME privileges then we won't enable the service
// since they do not have the privileges to stop any hotplug devices.
//
if (GetSystemMetrics(SM_REMOTESESSION) &&
!pDoesUserHavePrivilege((PCTSTR)SE_LOAD_DRIVER_NAME)) {
ServiceEnabled = FALSE;
} else {
//
// hotplug.dll will disable the hotplug service when it is
// displaying a balloon for a surprise removal or safe removal
// event. When it is displaying it's balloon we don't want to
// enable our service because then there will be two hotplug icons
// in the tray. So if it's named event is set then we will ignore
// any attempts to enable our service. Once hotplug.dll's balloon
// has gone away then it will automatically enable the hotplug
// service.
//
hHotplugBalloonEvent = CreateEvent(NULL,
FALSE,
TRUE,
TEXT("Local\\HotPlug_TaskBarIcon_Event")
);
if (hHotplugBalloonEvent) {
if (WaitForSingleObject(hHotplugBalloonEvent, 0) != WAIT_OBJECT_0) {
ServiceEnabled = FALSE;
}
CloseHandle(hHotplugBalloonEvent);
}
}
}
EnableShellIcon = ServiceEnabled && HotPlugInit(hWnd);
HotPlugShowNotifyIcon(hWnd, EnableShellIcon);
return EnableShellIcon;
}
DWORD
HotPlugEjectDevice_Thread(
LPVOID pThreadParam
)
{
DEVNODE DevNode = (DEVNODE)(ULONG_PTR)pThreadParam;
CONFIGRET ConfigRet;
ConfigRet = CM_Request_Device_Eject_Ex(DevNode,
NULL,
NULL,
0,
0,
NULL);
//
// Set the hEjectEvent so that the right-click popup menu will work again now that we are finished
// ejecting/stopping the device.
//
SetEvent(hEjectEvent);
SetLastError(ConfigRet);
return (ConfigRet == CR_SUCCESS);
}
void
HotPlugEjectDevice(
HWND hwnd,
DEVNODE DevNode
)
{
DWORD ThreadId;
//
// Reset the hEjectEvent so that the user can't bring up the right-click popup menu when
// we are in the process of ejecting/stopping a device.
//
ResetEvent(hEjectEvent);
//
// We need to have stobject.dll eject/stop the device on a separate thread because if
// we remove a device that stobject.dll listens for (battery, sound, ect.) we will cause
// a large delay and the eject/stop could end up getting vetoed because the stobject.dll
// code could not be processed and release it's handles because we were locking up the main
// thread.
//
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)HotPlugEjectDevice_Thread,
(LPVOID)(ULONG_PTR)DevNode,
0,
&ThreadId
);
}
void
HotPlug_Timer(
HWND hwnd
)
/*++
Routine Description:
Hotplug Timer msg handler, used to invoke hmenuEject for single Left click
Arguments:
hDlg - Our Window handle
Return Value:
BOOL Returns TRUE if active.
--*/
{
POINT pt;
UINT MenuIndex;
PHOTPLUGDEVICES HotPlugDevicesList;
PHOTPLUGDEVICES SingleHotPlugDevice;
TCHAR MenuDeviceName[MAX_PATH+64];
TCHAR Format[64];
KillTimer(hwnd, HOTPLUG_TIMER_ID);
if (!HotPlugInitialized) {
PostMessage(hwnd, STWM_ENABLESERVICE, 0, TRUE);
return;
}
//
// We only want to create the popup menu if the hEjectEvent is signaled. If it is not
// signaled then we are in the middle of ejecting/stopping a device on a separate thread
// and don't want to allow the user to bring up the menu until we are finished with that
// device.
//
if (!hEjectEvent ||
WaitForSingleObject(hEjectEvent, 0) == WAIT_OBJECT_0) {
//
// We are not in the middle of ejecting/stopping a device so we should display the popup
// menu.
//
HMENU hmenuEject = CreatePopupMenu();
if (hmenuEject)
{
SetForegroundWindow(hwnd);
GetCursorPos(&pt);
//
// Add each of the removable devices in the list to the menu.
//
if (!AddHotPlugDevices(&HotPlugDevicesList)) {
DestroyMenu(hmenuEject);
return;
}
SingleHotPlugDevice = HotPlugDevicesList;
//
// Add a title and separator at the top of the menu.
//
LoadString(g_hInstance,
IDS_HPLUGMENU_REMOVE,
Format,
sizeof(Format)/sizeof(TCHAR)
);
MenuIndex = 1;
while (SingleHotPlugDevice) {
wsprintf(MenuDeviceName, Format, SingleHotPlugDevice->DevName);
AppendMenu(hmenuEject, MF_STRING, MenuIndex, MenuDeviceName);
SingleHotPlugDevice->EjectMenuIndex = MenuIndex++;
SingleHotPlugDevice = SingleHotPlugDevice->Next;
}
MenuIndex = TrackPopupMenu(hmenuEject,
TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
pt.x,
pt.y,
0,
hwnd,
NULL
);
// now do an eject!
SingleHotPlugDevice = HotPlugDevicesList;
while (SingleHotPlugDevice) {
if (MenuIndex == SingleHotPlugDevice->EjectMenuIndex) {
DEVNODE DevNode;
if (CM_Locate_DevNode(&DevNode,
SingleHotPlugDevice->DevInstanceId,
0) == CR_SUCCESS) {
HotPlugEjectDevice(hwnd, DevNode);
}
break;
}
SingleHotPlugDevice = SingleHotPlugDevice->Next;
}
if (!SingleHotPlugDevice) {
SetIconFocus(hwnd, STWM_NOTIFYHOTPLUG);
}
FreeHotPlugDevicesList(&HotPlugDevicesList);
}
DestroyMenu(hmenuEject);
}
return;
}
void
HotPlugContextMenu(
HWND hwnd
)
{
POINT pt;
HMENU ContextMenu;
UINT MenuIndex;
TCHAR Buffer[MAX_PATH];
ContextMenu = CreatePopupMenu();
if (!ContextMenu) {
return;
}
SetForegroundWindow(hwnd);
GetCursorPos(&pt);
LoadString(g_hInstance, IDS_HPLUGMENU_PROPERTIES, Buffer, sizeof(Buffer)/sizeof(TCHAR));
AppendMenu(ContextMenu, MF_STRING,IDS_HPLUGMENU_PROPERTIES, Buffer);
SetMenuDefaultItem(ContextMenu, IDS_HPLUGMENU_PROPERTIES, FALSE);
MenuIndex = TrackPopupMenu(ContextMenu,
TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
pt.x,
pt.y,
0,
hwnd,
NULL
);
switch (MenuIndex) {
case IDS_HPLUGMENU_PROPERTIES:
SysTray_RunProperties(IDS_RUNHPLUGPROPERTIES);
break;
}
DestroyMenu(ContextMenu);
SetIconFocus(hwnd, STWM_NOTIFYHOTPLUG);
return;
}
void
HotPlug_Notify(
HWND hwnd,
WPARAM wParam,
LPARAM lParam
)
{
switch (lParam) {
case WM_RBUTTONUP:
HotPlugContextMenu(hwnd);
break;
case WM_LBUTTONDOWN:
SetTimer(hwnd, HOTPLUG_TIMER_ID, GetDoubleClickTime()+100, NULL);
break;
case WM_LBUTTONDBLCLK:
KillTimer(hwnd, HOTPLUG_TIMER_ID);
SysTray_RunProperties(IDS_RUNHPLUGPROPERTIES);
break;
}
return;
}
int
HotPlug_DeviceChangeTimer(
HWND hDlg
)
{
BOOL bAnyHotPlugDevices, bNewHotPlugDevice, bWarnUser;
BOOL bRemovableDeviceAdded, bRemovableDeviceRemoved, bRemovableDeviceFailure;
HDEVINFO hNewDeviceInfoSet;
KillTimer(hDlg, HOTPLUG_DEVICECHANGE_TIMERID);
//
// If the service is not enabled then don't bother because the icon will NOT
// be shown, sounds will not be played, etc. (see notes for
// HotplugPlaySoundThisSession).
//
if (!ServiceEnabled) {
goto Clean0;
}
//
// Get a new "current" list of all devices present in the system.
//
hNewDeviceInfoSet = SetupDiGetClassDevs(NULL,
NULL,
NULL,
DIGCF_ALLCLASSES | DIGCF_PRESENT);
//
// Update the list of removable devices, based on the new current list.
//
UpdateRemovableDeviceList(hNewDeviceInfoSet,
&bRemovableDeviceAdded,
&bRemovableDeviceRemoved,
&bRemovableDeviceFailure);
#if SOUNDS
//
// If we should play sounds in this session, check if any removable devices
// were either added or removed.
//
if (HotplugPlaySoundThisSession()) {
//
// We'll only play one sound at a time, so if we discover that multiple
// events have happened simultaneously, let failure override arrival,
// which overrides removal. This way the user receives notification of
// the most important event.
//
if (bRemovableDeviceFailure) {
PlaySound(DEVICE_FAILURE_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
} else if (bRemovableDeviceAdded) {
PlaySound(DEVICE_ARRIVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
} else if (bRemovableDeviceRemoved) {
PlaySound(DEVICE_REMOVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
}
}
#endif // SOUNDS
//
// Let's see if we have any hot plug devices, which means we need to
// show the systray icon. We also want to know about new hotplug
// devices that just arrived, so we compare the set of removable devices
// (which we just updated) against the old current set of devices in the
// system.
//
bAnyHotPlugDevices = AnyHotPlugDevices(g_hRemovableDeviceInfoSet,
g_hCurrentDeviceInfoSet,
&bNewHotPlugDevice,
&bWarnUser);
if (bAnyHotPlugDevices) {
//
// We have some hotplug devices so make sure the icon is shown and show
// the balloon info if necessary.
//
//
// If the hotplug icon is not being shown, then show it.
//
if (!ShowShellIcon) {
HotPlugShowNotifyIcon(hDlg, TRUE);
}
#if BUBBLES
//
// If we have any new hotplug devices then show the balloon info.
//
if (bNewHotPlugDevice && bWarnUser) {
NOTIFYICONDATA nid = { sizeof(nid), hDlg, 0 };
try {
EnterCriticalSection(&g_BubbleCrit);
if ((hDockEventTimer == NULL) ||
(WaitForSingleObject(hDockEventTimer, 0) == WAIT_OBJECT_0)) {
nid.uID = STWM_NOTIFYHOTPLUG;
nid.uFlags = NIF_INFO;
nid.uTimeout = 10000;
nid.dwInfoFlags = NIIF_INFO;
LoadString(g_hInstance,
IDS_HOTPLUG_TITLE,
nid.szInfoTitle,
sizeof(nid.szInfoTitle) / sizeof(TCHAR)
);
LoadString(g_hInstance,
IDS_HOTPLUG_INSERT_INFO,
nid.szInfo,
sizeof(nid.szInfo) / sizeof(TCHAR)
);
Shell_NotifyIcon(NIM_MODIFY, &nid);
}
}
finally
{
LeaveCriticalSection(&g_BubbleCrit);
}
}
#endif // BUBBLES
} else {
//
// There are NOT any hot plug devices so if the icon is still being
// shown, then hide it.
//
if (ShowShellIcon) {
HotPlugShowNotifyIcon(hDlg, FALSE);
}
}
//
// Delete the old current list of devices and set it
// (g_hCurrentDeviceInfoSet) to the new current list.
//
if (g_hCurrentDeviceInfoSet != INVALID_HANDLE_VALUE) {
SetupDiDestroyDeviceInfoList(g_hCurrentDeviceInfoSet);
}
g_hCurrentDeviceInfoSet = hNewDeviceInfoSet;
Clean0:
return 0;
}
void
HotPlug_DeviceChange(
HWND hwnd,
WPARAM wParam,
LPARAM lParam
)
/*++
Routine Description:
Handle WM_DEVICECHANGE messages.
Arguments:
hDlg - Window handle of Dialog
wParam - DBT Event
lParam - DBT event notification type.
Return Value:
--*/
{
LARGE_INTEGER liDelayTime;
NOTIFYICONDATA nid;
BOOL bPresent;
switch(wParam) {
case DBT_DEVNODES_CHANGED:
//
// To avoid deadlock with CM, a timer is started and the timer
// message handler does the real work.
//
SetTimer(hwnd, HOTPLUG_DEVICECHANGE_TIMERID, 100, NULL);
break;
case DBT_CONFIGCHANGED:
//
// A docking event (dock, undock, surprise undock, etc) has
// occured.
//
#if SOUNDS
//
// Play a sound for hardware profile changes if we're supposed to.
//
if (HotplugPlaySoundThisSession()) {
if ((CM_Is_Dock_Station_Present(&bPresent) == CR_SUCCESS) &&
(bPresent)) {
//
// If there is a dock present, we most-likely just docked
// (though we may have just ejected one of many docks), so
// play an arrival.
//
PlaySound(DEVICE_ARRIVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
} else {
//
// If no dock is present we just undocked, so play a
// removal.
//
PlaySound(DEVICE_REMOVAL_SOUND, NULL, SND_ASYNC|SND_NODEFAULT);
}
}
#endif // SOUNDS
#if BUBBLES
//
// Make sure no bubbles appear for devices in a newly
// attached dock.
//
if ((CM_Is_Dock_Station_Present(&bPresent) != CR_SUCCESS) ||
(!bPresent)) {
//
// We don't suppress if something appears after an undock.
//
break;
}
try {
EnterCriticalSection(&g_BubbleCrit);
if (hDockEventTimer) {
liDelayTime.QuadPart = BUBBLE_SUPPRESSION_TIME * -10000000;
SetWaitableTimer(
hDockEventTimer,
&liDelayTime,
0,
NULL,
NULL,
FALSE
);
}
//
// Slap down any bubble that might currently be displayed.
//
nid.cbSize = sizeof(nid);
nid.hWnd = hwnd;
nid.uID = STWM_NOTIFYHOTPLUG;
nid.uFlags = 0;
nid.uTimeout = 0;
nid.dwInfoFlags = 0;
Shell_NotifyIcon(NIM_MODIFY, &nid);
}
finally
{
LeaveCriticalSection(&g_BubbleCrit);
}
#endif // BUBBLES
break;
default:
break;
}
return;
}
void
HotPlug_WmDestroy(
HWND hWnd
)
{
if (hEjectEvent) {
CloseHandle(hEjectEvent);
}
#if BUBBLES
if (hDockEventTimer) {
CloseHandle(hDockEventTimer);
}
if (hSurpriseUndockEventTimer) {
CloseHandle(hSurpriseUndockEventTimer);
}
try {
DeleteCriticalSection(&g_BubbleCrit);
} except(EXCEPTION_EXECUTE_HANDLER) {
NOTHING;
}
#endif // BUBBLES
if (g_hCurrentDeviceInfoSet != INVALID_HANDLE_VALUE) {
SetupDiDestroyDeviceInfoList(g_hCurrentDeviceInfoSet);
g_hCurrentDeviceInfoSet = INVALID_HANDLE_VALUE;
}
if (g_hRemovableDeviceInfoSet != INVALID_HANDLE_VALUE) {
SetupDiDestroyDeviceInfoList(g_hRemovableDeviceInfoSet);
g_hRemovableDeviceInfoSet = INVALID_HANDLE_VALUE;
}
}
void
HotPlug_SessionChange(
HWND hWnd,
WPARAM wParam,
LPARAM lParam
)
{
//
// If our console session is getting disconnected then disable our service
// since we don't need to do any work if no UI is being displayed.
//
// If our console session is getting connected then re-enable our service.
//
if ((wParam == WTS_CONSOLE_CONNECT) ||
(wParam == WTS_REMOTE_CONNECT)) {
HotPlug_CheckEnable(hWnd, TRUE);
} else if ((wParam == WTS_CONSOLE_DISCONNECT) ||
(wParam == WTS_REMOTE_DISCONNECT)) {
HotPlug_CheckEnable(hWnd, FALSE);
}
}
#if SOUNDS
BOOL
IsFastUserSwitchingEnabled(
VOID
)
/*++
Routine Description:
Checks to see if Terminal Services Fast User Switching is enabled. This is
to check if we should use the physical console session for UI dialogs, or
always use session 0.
Fast User Switching exists only on workstation product version, where terminal
services are available, when AllowMultipleTSSessions is set.
On server and above, or when multiple TS users are not allowed, session 0
can only be attached remotely be special request, in which case it should be
considered the "Console" session.
Arguments:
None.
Return Value:
Returns TRUE if Fast User Switching is currently enabled, FALSE otherwise.
--*/
{
static BOOL bVerified = FALSE;
static BOOL bIsTSWorkstation = FALSE;
HKEY hKey;
ULONG ulSize, ulValue;
BOOL bFusEnabled;
//
// Verify the product version if we haven't already.
//
if (!bVerified) {
OSVERSIONINFOEX osvix;
DWORDLONG dwlConditionMask = 0;
ZeroMemory(&osvix, sizeof(OSVERSIONINFOEX));
osvix.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvix.wProductType = VER_NT_WORKSTATION;
VER_SET_CONDITION(dwlConditionMask, VER_PRODUCT_TYPE, VER_LESS_EQUAL);
osvix.wSuiteMask = VER_SUITE_TERMINAL | VER_SUITE_SINGLEUSERTS;
VER_SET_CONDITION(dwlConditionMask, VER_SUITENAME, VER_OR);
if (VerifyVersionInfo(&osvix,
VER_PRODUCT_TYPE | VER_SUITENAME,
dwlConditionMask)) {
bIsTSWorkstation = TRUE;
}
bVerified = TRUE;
}
//
// Fast user switching (FUS) only applies to the Workstation product where
// Terminal Services are enabled (i.e. Personal, Professional).
//
if (!bIsTSWorkstation) {
return FALSE;
}
//
// Check if multiple TS sessions are currently allowed. We can't make this
// info static because it can change dynamically.
//
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"),
0,
KEY_READ,
&hKey) != ERROR_SUCCESS) {
return FALSE;
}
ulValue = 0;
ulSize = sizeof(ulValue);
bFusEnabled = FALSE;
if (RegQueryValueEx(hKey,
TEXT("AllowMultipleTSSessions"),
NULL,
NULL,
(LPBYTE)&ulValue,
&ulSize) == ERROR_SUCCESS) {
bFusEnabled = (ulValue != 0);
}
RegCloseKey(hKey);
return bFusEnabled;
} // IsFastUserSwitchingEnabled
BOOL
HotplugPlaySoundThisSession(
VOID
)
/*++
Routine Description:
This routine determines whether a sound should be played in the current
session.
Arguments:
None.
Return Value:
Returns TRUE if sounds should be played in this session.
Notes:
The user-mode plug and play manager (umpnpmgr.dll) implements the following
behavior for UI dialogs:
* When Fast User Switching is enabled, only the physical Console session
is used for UI dialogs.
* When Fast User Switching is not enabled, only Session 0 is used for UI
dialogs.
Since sound events require no user interaction there is no problem with
multiple sessions responding to these events simultaneously.
We should *always* play a sound on the physical console when possible, and
adopt a behavior similar to umpnpmgr for for the non-Fast User Switching
case, such that session 0 will also play sound events when possible because
it should be treated somewhat special in the non-FUS case...
... BUT, since we disable the service altogether if the session is remote
and the user doesn't have permission to eject hotplug devices (so we don't
show the icon), we won't even respond to DBT_DEVNODES_CHANGED events, and
consequently won't play sound. We could actually turn this on just by
allowing those events to be processed when the services is disabled, but
this function is successful. Since the idea of allowing hardware events on
remote session 0 without FUS is really just for remote management, then it's
probably ok that we don't play sounds for a user that can't manage hardware.
--*/
{
//
// Always play sound events on the physical console.
//
if (IsConsoleSession()) {
return TRUE;
}
//
// If fast user switching is not enabled, play sound events on the
// pseudo-console (Session 0) also.
//
if ((IsPseudoConsoleSession()) &&
(!IsFastUserSwitchingEnabled())) {
return TRUE;
}
//
// Otherwise, no sound.
//
return FALSE;
} // HotplugPlaySoundThisSession
#endif // SOUNDS