//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation // // File: hotplug.cpp // //-------------------------------------------------------------------------- #include "HotPlug.h" TCHAR szUnknown[64]; TCHAR szHotPlugFlags[]=TEXT("HotPlugFlags"); TCHAR HOTPLUG_NOTIFY_CLASS_NAME[] = TEXT("HotPlugNotifyClass"); typedef int (*PDEVICEPROPERTIES)( HWND hwndParent, LPTSTR MachineName, LPTSTR DeviceID, BOOL ShowDeviceTree ); // // colors used to highlight removal relationships for selected device // COLORREF RemovalImageBkColor; COLORREF NormalImageBkColor; COLORREF RemovalTextColor; HWND g_hwndNotify = NULL; HMODULE hDevMgr=NULL; PDEVICEPROPERTIES pDeviceProperties = NULL; #define IDH_DISABLEHELP ((DWORD)(-1)) #define IDH_hwwizard_devices_list 15301 // (SysTreeView32) #define idh_hwwizard_stop 15305 // "&Stop" (Button) #define idh_hwwizard_display_components 15307 // "&Display device components" (Button) #define idh_hwwizard_properties 15311 // "&Properties" (Button) #define idh_hwwizard_close 15309 // "&Close" (Button) DWORD UnplugtHelpIDs[] = { IDC_STOPDEVICE, idh_hwwizard_stop, // "&Stop" (Button) IDC_PROPERTIES, idh_hwwizard_properties, // "&Properties" (Button) IDC_VIEWOPTION, idh_hwwizard_display_components, // "&Display device components" (Button) IDC_DEVICETREE, IDH_hwwizard_devices_list, // "" (SysTreeView32) IDCLOSE, idh_hwwizard_close, IDC_HDWDEVICES, NO_HELP, IDC_NOHELP1, NO_HELP, IDC_NOHELP2, NO_HELP, IDC_NOHELP3, NO_HELP, IDC_DEVICEDESC, NO_HELP, 0,0 }; void OnRemoveDevice( HWND hDlg, PDEVICETREE DeviceTree ) { PDEVTREENODE DeviceTreeNode; DeviceTreeNode = DeviceTree->ChildRemovalList; if (!DeviceTreeNode) { return; } // // Confirm with the user that they really want // to remove this device and all of its attached devices. // The dialog returns standard IDOK, IDCANCEL etc. for results. // if anything besides IDOK don't do anything. // DialogBoxParam(hHotPlug, MAKEINTRESOURCE(DLG_CONFIRMREMOVE), hDlg, RemoveConfirmDlgProc, (LPARAM)DeviceTree ); return; } void OnTvnSelChanged( PDEVICETREE DeviceTree, NM_TREEVIEW *nmTreeView ) { PDEVTREENODE DeviceTreeNode = (PDEVTREENODE)(nmTreeView->itemNew.lParam); PTCHAR DeviceName; ULONG DevNodeStatus, Problem; CONFIGRET ConfigRet; TCHAR Buffer[MAX_PATH*2]; if (DeviceTree->RedrawWait) { return; } // // Clear Removal list for previously selected Node // ClearRemovalList(DeviceTree); // // Save the selected treenode. // DeviceTree->SelectedTreeNode = DeviceTreeNode; // // No device is selected // if (!DeviceTreeNode) { EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_STOPDEVICE), FALSE); EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_PROPERTIES), FALSE); SetDlgItemText(DeviceTree->hDlg, IDC_DEVICEDESC, TEXT("")); return; } // // reset the text for the selected item // DeviceName = FetchDeviceName(DeviceTreeNode); if (!DeviceName) { DeviceName = szUnknown; } StringCchPrintf(Buffer, SIZECHARS(Buffer), TEXT("%s %s"), DeviceName, DeviceTreeNode->Location ? DeviceTreeNode->Location : TEXT("") ); SetDlgItemText(DeviceTree->hDlg, IDC_DEVICEDESC, Buffer); // // Turn on the stop\eject button, and set text accordingly. // ConfigRet = CM_Get_DevNode_Status_Ex(&DevNodeStatus, &Problem, DeviceTreeNode->DevInst, 0, NULL ); if (ConfigRet != CR_SUCCESS) { DevNodeStatus = 0; Problem = 0; } // // Any removable (but not surprise removable) device is OK, except // if the user already removed it. // if (Problem != CM_PROB_HELD_FOR_EJECT) { EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_STOPDEVICE), TRUE); } else { EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_STOPDEVICE), FALSE); } EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_PROPERTIES), TRUE); // // reset the overlay icons if device state has changed // if (DeviceTreeNode->Problem != Problem || DeviceTreeNode->DevNodeStatus != DevNodeStatus) { TV_ITEM tv; tv.mask = TVIF_STATE; tv.stateMask = TVIS_OVERLAYMASK; tv.hItem = DeviceTreeNode->hTreeItem; if (DeviceTreeNode->Problem == CM_PROB_DISABLED) { tv.state = INDEXTOOVERLAYMASK(IDI_DISABLED_OVL - IDI_CLASSICON_OVERLAYFIRST + 1); } else if (DeviceTreeNode->Problem) { tv.state = INDEXTOOVERLAYMASK(IDI_PROBLEM_OVL - IDI_CLASSICON_OVERLAYFIRST + 1); } else { tv.state = INDEXTOOVERLAYMASK(0); } TreeView_SetItem(DeviceTree->hwndTree, &tv); } // // Starting from the TopLevel removal node, build up the removal lists // DeviceTreeNode = TopLevelRemovalNode(DeviceTree, DeviceTreeNode); // // Add devices to ChildRemoval list // DeviceTree->ChildRemovalList = DeviceTreeNode; DeviceTreeNode->NextChildRemoval = DeviceTreeNode; InvalidateTreeItemRect(DeviceTree->hwndTree, DeviceTreeNode->hTreeItem); AddChildRemoval(DeviceTree, &DeviceTreeNode->ChildSiblingList); // // Add eject amd removal relations // AddEjectToRemoval(DeviceTree); } int OnCustomDraw( HWND hDlg, PDEVICETREE DeviceTree, LPNMTVCUSTOMDRAW nmtvCustomDraw ) { PDEVTREENODE DeviceTreeNode = (PDEVTREENODE)(nmtvCustomDraw->nmcd.lItemlParam); UNREFERENCED_PARAMETER(hDlg); if (nmtvCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT) { return CDRF_NOTIFYITEMDRAW; } // // If this node is in the Removal list, then do special // highlighting. // if (DeviceTreeNode->NextChildRemoval) { // // set text color if its not the selected item // if (DeviceTree->SelectedTreeNode != DeviceTreeNode) { nmtvCustomDraw->clrText = RemovalTextColor; } // // Highlight the image-icon background // ImageList_SetBkColor(DeviceTree->ClassImageList.ImageList, RemovalImageBkColor ); } else { // // Normal image-icon background // ImageList_SetBkColor(DeviceTree->ClassImageList.ImageList, NormalImageBkColor ); } return CDRF_DODEFAULT; } void OnSysColorChange( HWND hDlg, PDEVICETREE DeviceTree ) { COLORREF ColorWindow, ColorHighlight; BYTE Red, Green, Blue; UNREFERENCED_PARAMETER(hDlg); // // Fetch the colors used for removal highlighting // ColorWindow = GetSysColor(COLOR_WINDOW); ColorHighlight = GetSysColor(COLOR_HIGHLIGHT); Red = (BYTE)(((WORD)GetRValue(ColorWindow) + (WORD)GetRValue(ColorHighlight)) >> 1); Green = (BYTE)(((WORD)GetGValue(ColorWindow) + (WORD)GetGValue(ColorHighlight)) >> 1); Blue = (BYTE)(((WORD)GetBValue(ColorWindow) + (WORD)GetBValue(ColorHighlight)) >> 1); RemovalImageBkColor = RGB(Red, Green, Blue); RemovalTextColor = ColorHighlight; NormalImageBkColor = ColorWindow; // // Update the ImageList Background color // if (DeviceTree->ClassImageList.cbSize) { ImageList_SetBkColor(DeviceTree->ClassImageList.ImageList, ColorWindow ); } } void OnTvnItemExpanding( HWND hDlg, NM_TREEVIEW *nmTreeView ) { PDEVTREENODE DeviceTreeNode = (PDEVTREENODE)(nmTreeView->itemNew.lParam); // // don't allow collapse of root items with children // if (!DeviceTreeNode->ParentNode && (nmTreeView->action == TVE_COLLAPSE || nmTreeView->action == TVE_COLLAPSERESET || (nmTreeView->action == TVE_TOGGLE && (nmTreeView->itemNew.state & TVIS_EXPANDED))) ) { SetDlgMsgResult(hDlg, WM_NOTIFY, TRUE); } else { SetDlgMsgResult(hDlg, WM_NOTIFY, FALSE); } } void OnContextMenu( HWND hDlg, PDEVICETREE DeviceTree ) { int IdCmd; POINT ptPopup; RECT rect; HMENU hMenu; PDEVTREENODE DeviceTreeNode; TCHAR Buffer[MAX_PATH]; DeviceTreeNode = DeviceTree->SelectedTreeNode; if (!DeviceTreeNode) { return; } TreeView_GetItemRect(DeviceTree->hwndTree, DeviceTreeNode->hTreeItem, &rect, TRUE ); ptPopup.x = (rect.left+rect.right)/2; ptPopup.y = (rect.top+rect.bottom)/2; ClientToScreen(DeviceTree->hwndTree, &ptPopup); hMenu = CreatePopupMenu(); if (!hMenu) { return; } // // if device is running add stop item // if (DeviceTreeNode->DevNodeStatus & DN_STARTED) { LoadString(hHotPlug, IDS_STOP, Buffer, SIZECHARS(Buffer) ); AppendMenu(hMenu, MF_STRING, IDC_STOPDEVICE, Buffer); } // // add Properties item (link to device mgr). // LoadString(hHotPlug, IDS_PROPERTIES, Buffer, SIZECHARS(Buffer) ); AppendMenu(hMenu, MF_STRING, IDC_PROPERTIES, Buffer); IdCmd = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN | TPM_NONOTIFY, ptPopup.x, ptPopup.y, 0, hDlg, NULL ); DestroyMenu(hMenu); if (!IdCmd) { return; } switch (IdCmd) { case IDC_STOPDEVICE: OnRemoveDevice(hDlg, DeviceTree); break; case IDC_PROPERTIES: { if (pDeviceProperties) { (*pDeviceProperties)( hDlg, NULL, DeviceTreeNode->InstanceId, FALSE ); } } break; } return; } void OnRightClick( HWND hDlg, PDEVICETREE DeviceTree, NMHDR * nmhdr ) { DWORD dwPos; TV_ITEM tvi; TV_HITTESTINFO tvht; PDEVTREENODE DeviceTreeNode; UNREFERENCED_PARAMETER(hDlg); if (nmhdr->hwndFrom != DeviceTree->hwndTree) { return; } dwPos = GetMessagePos(); tvht.pt.x = LOWORD(dwPos); tvht.pt.y = HIWORD(dwPos); ScreenToClient(DeviceTree->hwndTree, &tvht.pt); tvi.hItem = TreeView_HitTest(DeviceTree->hwndTree, &tvht); if (!tvi.hItem) { return; } tvi.mask = TVIF_PARAM; if (!TreeView_GetItem(DeviceTree->hwndTree, &tvi)) { return; } DeviceTreeNode = (PDEVTREENODE)tvi.lParam; if (!DeviceTreeNode) { return; } // // Make the current right click item, the selected item // if (DeviceTreeNode != DeviceTree->SelectedTreeNode) { TreeView_SelectItem(DeviceTree->hwndTree, DeviceTreeNode->hTreeItem); } } void OnViewOptionClicked( HWND hDlg, PDEVICETREE DeviceTree ) { BOOL bChecked; DWORD HotPlugFlags, NewFlags; HKEY hKey = NULL; // // checked means "show complex view" // bChecked = IsDlgButtonChecked(hDlg, IDC_VIEWOPTION); // // Update HotPlugs registry if needed. // NewFlags = HotPlugFlags = GetHotPlugFlags(&hKey); if (hKey) { if (bChecked) { NewFlags |= HOTPLUG_REGFLAG_VIEWALL; } else { NewFlags &= ~HOTPLUG_REGFLAG_VIEWALL; } if (NewFlags != HotPlugFlags) { RegSetValueEx(hKey, szHotPlugFlags, 0, REG_DWORD, (LPBYTE)&NewFlags, sizeof(NewFlags) ); } if (hKey) { RegCloseKey(hKey); } } if (!DeviceTree->ComplexView && bChecked) { DeviceTree->ComplexView = TRUE; } else if (DeviceTree->ComplexView && !bChecked) { DeviceTree->ComplexView = FALSE; } else { // we are in the correct state, nothing to do. return; } // // redraw the entire tree. // RefreshTree(DeviceTree); return; } LRESULT hotplugNotifyWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { HWND hMainWnd; hMainWnd = (HWND)GetWindowLongPtr(hWnd, GWLP_USERDATA); switch (uMsg) { case WM_CREATE: hMainWnd = (HWND)((CREATESTRUCT*)lParam)->lpCreateParams; SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)hMainWnd); break; case WM_DEVICECHANGE: if (DBT_DEVNODES_CHANGED == wParam) { // // While we are in WM_DEVICECHANGE context, // no CM apis can be called because it would // deadlock. Here, we schedule a timer so that // we can handle the message later on. // SetTimer(hMainWnd, TIMERID_DEVICECHANGE, 1000, NULL); } break; default: break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } BOOL CreateNotifyWindow( HWND hWnd ) { WNDCLASS wndClass; if (!GetClassInfo(hHotPlug, HOTPLUG_NOTIFY_CLASS_NAME, &wndClass)) { ZeroMemory(&wndClass, sizeof(wndClass)); wndClass.lpfnWndProc = hotplugNotifyWndProc; wndClass.hInstance = hHotPlug; wndClass.lpszClassName = HOTPLUG_NOTIFY_CLASS_NAME; if (!RegisterClass(&wndClass)) { return FALSE; } } g_hwndNotify = CreateWindowEx(WS_EX_TOOLWINDOW, HOTPLUG_NOTIFY_CLASS_NAME, TEXT(""), WS_DLGFRAME | WS_BORDER | WS_DISABLED, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, hHotPlug, (void *)hWnd ); return(NULL != g_hwndNotify); } BOOL InitDevTreeDlgProc( HWND hDlg, PDEVICETREE DeviceTree ) { CONFIGRET ConfigRet; HWND hwndTree; DWORD HotPlugFlags; HICON hIcon; HWND hwndParent; DeviceTree->AllowRefresh = TRUE; CreateNotifyWindow(hDlg); hDevMgr = LoadLibrary(TEXT("devmgr.dll")); if (hDevMgr) { pDeviceProperties = (PDEVICEPROPERTIES)GetProcAddress(hDevMgr, "DevicePropertiesW"); } hIcon = LoadIcon(hHotPlug,MAKEINTRESOURCE(IDI_HOTPLUGICON)); if (hIcon) { SendMessage(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon); } hwndParent = GetParent(hDlg); if (hwndParent) { SendMessage(hwndParent, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); SendMessage(hwndParent, WM_SETICON, ICON_BIG, (LPARAM)hIcon); } DeviceTree->hDlg = hDlg; DeviceTree->hwndTree = hwndTree = GetDlgItem(hDlg, IDC_DEVICETREE); LoadString(hHotPlug, IDS_UNKNOWN, (PTCHAR)szUnknown, SIZECHARS(szUnknown) ); // // Disable the Stop button, until an item is selected. // EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_STOPDEVICE), FALSE); EnableWindow(GetDlgItem(DeviceTree->hDlg, IDC_PROPERTIES), FALSE); // // Get the Class Icon Image Lists // DeviceTree->ClassImageList.cbSize = sizeof(SP_CLASSIMAGELIST_DATA); if (SetupDiGetClassImageList(&DeviceTree->ClassImageList)) { TreeView_SetImageList(hwndTree, DeviceTree->ClassImageList.ImageList, TVSIL_NORMAL); } else { DeviceTree->ClassImageList.cbSize = 0; } OnSysColorChange(hDlg, DeviceTree); HotPlugFlags = GetHotPlugFlags(NULL); if (HotPlugFlags & HOTPLUG_REGFLAG_VIEWALL) { DeviceTree->ComplexView = TRUE; CheckDlgButton(hDlg, IDC_VIEWOPTION, BST_CHECKED); } else { DeviceTree->ComplexView = FALSE; CheckDlgButton(hDlg, IDC_VIEWOPTION, BST_UNCHECKED); } // // Get the root devnode. // ConfigRet = CM_Locate_DevNode_Ex(&DeviceTree->DevInst, NULL, CM_LOCATE_DEVNODE_NORMAL, NULL ); if (ConfigRet != CR_SUCCESS) { return FALSE; } RefreshTree(DeviceTree); if (DeviceTree->EjectDeviceInstanceId) { DEVINST EjectDevInst; PDEVTREENODE DeviceTreeNode; // // we are removing a specific device, find it // and post a message to trigger device removal. // ConfigRet = CM_Locate_DevNode_Ex(&EjectDevInst, DeviceTree->EjectDeviceInstanceId, CM_LOCATE_DEVNODE_NORMAL, NULL ); if (ConfigRet != CR_SUCCESS) { return FALSE; } DeviceTreeNode = DevTreeNodeByDevInst(EjectDevInst, &DeviceTree->ChildSiblingList ); if (!DeviceTreeNode) { return FALSE; } TreeView_SelectItem(hwndTree, DeviceTreeNode->hTreeItem); PostMessage(hDlg, WUM_EJECTDEVINST, 0, 0); } else { ShowWindow(hDlg, SW_SHOW); } return TRUE; } void OnContextHelp( LPHELPINFO HelpInfo, PDWORD ContextHelpIDs ) { // // Define an array of dword pairs, // where the first of each pair is the control ID, // and the second is the context ID for a help topic, // which is used in the help file. // if (HelpInfo->iContextType == HELPINFO_WINDOW) { // must be for a control WinHelp((HWND)HelpInfo->hItemHandle, TEXT("hardware.hlp"), HELP_WM_HELP, (DWORD_PTR)(void *)ContextHelpIDs ); } } INT_PTR CALLBACK DevTreeDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { PDEVICETREE DeviceTree = NULL; if (message == WM_INITDIALOG) { DeviceTree = (PDEVICETREE)lParam; SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR)DeviceTree); if (DeviceTree) { InitDevTreeDlgProc(hDlg, DeviceTree); } return TRUE; } DeviceTree = (PDEVICETREE)GetWindowLongPtr(hDlg, DWLP_USER); switch (message) { case WM_DESTROY: // // Destroy the Notification Window // if (g_hwndNotify && IsWindow(g_hwndNotify)) { DestroyWindow(g_hwndNotify); g_hwndNotify = NULL; } // // Clear the DeviceTree // TreeView_DeleteAllItems(DeviceTree->hwndTree); // // Clean up the class image list. // if (DeviceTree->ClassImageList.cbSize) { SetupDiDestroyClassImageList(&DeviceTree->ClassImageList); DeviceTree->ClassImageList.cbSize = 0; } // // Clean up the device tree // ClearRemovalList(DeviceTree); RemoveChildSiblings(DeviceTree, &DeviceTree->ChildSiblingList); if (hDevMgr) { FreeLibrary(hDevMgr); hDevMgr = NULL; pDeviceProperties = NULL; } break; case WM_CLOSE: SendMessage(hDlg, WM_COMMAND, IDCANCEL, 0L); break; case WM_COMMAND: { UINT Control = GET_WM_COMMAND_ID(wParam, lParam); UINT Cmd = GET_WM_COMMAND_CMD(wParam, lParam); switch (Control) { case IDC_VIEWOPTION: if (Cmd == BN_CLICKED) { OnViewOptionClicked(hDlg, DeviceTree); } break; case IDC_STOPDEVICE: OnRemoveDevice(hDlg, DeviceTree); break; case IDOK: // enter -> default to expand\collapse the selected tree node if (DeviceTree->SelectedTreeNode) { TreeView_Expand(DeviceTree->hwndTree, DeviceTree->SelectedTreeNode->hTreeItem, TVE_TOGGLE); } break; case IDC_PROPERTIES: if (DeviceTree->SelectedTreeNode && pDeviceProperties) { (*pDeviceProperties)(hDlg, NULL, DeviceTree->SelectedTreeNode->InstanceId, FALSE); } break; case IDCLOSE: case IDCANCEL: EndDialog(hDlg, IDCANCEL); break; } } break; // Listen for Tree notifications case WM_NOTIFY: switch (((NMHDR *)lParam)->code) { case TVN_SELCHANGED: OnTvnSelChanged(DeviceTree, (NM_TREEVIEW *)lParam); break; case TVN_ITEMEXPANDING: OnTvnItemExpanding(hDlg, (NM_TREEVIEW *)lParam); break; case TVN_KEYDOWN: { TV_KEYDOWN *tvKeyDown = (TV_KEYDOWN *)lParam; if (tvKeyDown->wVKey == VK_DELETE) { OnRemoveDevice(hDlg, DeviceTree); } } break; case NM_CUSTOMDRAW: if (IDC_DEVICETREE == ((NMHDR *)lParam)->idFrom) { SetDlgMsgResult(hDlg, WM_NOTIFY, OnCustomDraw(hDlg, DeviceTree, (NMTVCUSTOMDRAW *)lParam)); } break; case NM_RETURN: // we don't get this in a dialog, see IDOK break; case NM_DBLCLK: OnRemoveDevice(hDlg, DeviceTree); SetDlgMsgResult(hDlg, WM_NOTIFY, TRUE); break; case NM_RCLICK: OnRightClick(hDlg, DeviceTree, (NMHDR *)lParam); break; default: return FALSE; } break; case WUM_EJECTDEVINST: OnRemoveDevice(hDlg, DeviceTree); EndDialog(hDlg, IDCANCEL); break; case WM_SYSCOLORCHANGE: HotPlugPropagateMessage(hDlg, message, wParam, lParam); OnSysColorChange(hDlg,DeviceTree); break; case WM_TIMER: if (TIMERID_DEVICECHANGE == wParam) { KillTimer(hDlg, TIMERID_DEVICECHANGE); DeviceTree->RefreshEvent = TRUE; if (DeviceTree->AllowRefresh) { OnTimerDeviceChange(DeviceTree); } } break; case WM_SETCURSOR: if (DeviceTree->RedrawWait || DeviceTree->RefreshEvent) { SetCursor(LoadCursor(NULL, IDC_WAIT)); SetWindowLongPtr(hDlg, DWLP_MSGRESULT, 1); break; } return FALSE; case WM_CONTEXTMENU: // // handle kbd- shift-F10, mouse rclick is invoked from NM_RCLICK // if ((HWND)wParam == DeviceTree->hwndTree) { OnContextMenu(hDlg, DeviceTree); break; } else { WinHelp((HWND)wParam, TEXT("hardware.hlp"), HELP_CONTEXTMENU, (DWORD_PTR)(void *)(PDWORD)UnplugtHelpIDs); } return FALSE; case WM_HELP: OnContextHelp((LPHELPINFO)lParam, (PDWORD)UnplugtHelpIDs); break; default: return FALSE; } return TRUE; }