1 /* 2 * PROJECT: ReactOS system libraries 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: dll/shellext/stobject/hotplug.cpp 5 * PURPOSE: Removable devices notification icon handler 6 * PROGRAMMERS: Shriraj Sawant a.k.a SR13 <sr.official@hotmail.com> 7 */ 8 9 #include "precomp.h" 10 11 #include <atlsimpcoll.h> 12 #include <dbt.h> 13 #include <cfgmgr32.h> 14 #include <shlwapi.h> 15 16 #define DISPLAY_NAME_LEN 40 17 18 CSimpleArray<DEVINST> g_devList; 19 CString g_strHotplugTooltip; 20 static HICON g_hIconHotplug = NULL; 21 static WCHAR g_strMenuSel[DISPLAY_NAME_LEN]; 22 static BOOL g_IsRemoving = FALSE; 23 24 /*++ 25 * @name EnumHotpluggedDevices 26 * 27 * Enumerates the connected safely removable devices. 28 * 29 * @param devList 30 * List of device instances, representing the currently attached devices. 31 * 32 * @return The error code. 33 * 34 *--*/ 35 HRESULT EnumHotpluggedDevices(CSimpleArray<DEVINST> &devList) 36 { 37 devList.RemoveAll(); // Clear current devList 38 HDEVINFO hdev = SetupDiGetClassDevs(NULL, NULL, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT); 39 if (INVALID_HANDLE_VALUE == hdev) 40 return E_HANDLE; 41 SP_DEVINFO_DATA did = { 0 }; 42 did.cbSize = sizeof(did); 43 44 // Enumerate all the attached devices. 45 for (int idev = 0; SetupDiEnumDeviceInfo(hdev, idev, &did); idev++) 46 { 47 DWORD dwCapabilities = 0, dwSize = sizeof(dwCapabilities); 48 ULONG ulStatus = 0, ulProblem = 0; 49 CONFIGRET cr = CM_Get_DevNode_Status(&ulStatus, &ulProblem, did.DevInst, 0); 50 if (cr != CR_SUCCESS) 51 continue; 52 53 cr = CM_Get_DevNode_Registry_Property(did.DevInst, CM_DRP_CAPABILITIES, NULL, &dwCapabilities, &dwSize, 0); 54 if (cr != CR_SUCCESS) 55 continue; 56 57 // Filter and make list of only the appropriate safely removable devices. 58 if ( (dwCapabilities & CM_DEVCAP_REMOVABLE) && 59 !(dwCapabilities & CM_DEVCAP_DOCKDEVICE) && 60 !(dwCapabilities & CM_DEVCAP_SURPRISEREMOVALOK) && 61 ((dwCapabilities & CM_DEVCAP_EJECTSUPPORTED) || (ulStatus & DN_DISABLEABLE)) && 62 !ulProblem) 63 { 64 devList.Add(did.DevInst); 65 } 66 } 67 SetupDiDestroyDeviceInfoList(hdev); 68 69 if (NO_ERROR != GetLastError() && ERROR_NO_MORE_ITEMS != GetLastError()) 70 { 71 return E_UNEXPECTED; 72 } 73 74 return S_OK; 75 } 76 77 /*++ 78 * @name NotifyBalloon 79 * 80 * Pops the balloon notification of the given notification icon. 81 * 82 * @param pSysTray 83 * Provides interface for acquiring CSysTray information as required. 84 * @param szTitle 85 * Title for the balloon notification. 86 * @param szInfo 87 * Main content for the balloon notification. 88 * @param uId 89 * Represents the particular notification icon. 90 * 91 * @return The error code. 92 * 93 *--*/ 94 HRESULT NotifyBalloon(CSysTray* pSysTray, LPCWSTR szTitle = NULL, LPCWSTR szInfo = NULL, UINT uId = ID_ICON_HOTPLUG) 95 { 96 NOTIFYICONDATA nim = { 0 }; 97 98 nim.cbSize = sizeof(nim); 99 nim.uID = uId; 100 nim.hWnd = pSysTray->GetHWnd(); 101 102 nim.uFlags = NIF_INFO; 103 nim.uTimeout = 10; 104 nim.dwInfoFlags = NIIF_INFO; 105 106 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), szTitle); 107 StringCchCopy(nim.szInfo, _countof(nim.szInfo), szInfo); 108 BOOL ret = Shell_NotifyIcon(NIM_MODIFY, &nim); 109 110 Sleep(10000); /* As per windows, the balloon notification remains visible for atleast 10 sec. 111 This timer maintains the same condition. 112 Also it is required so that the icon doesn't hide instantly after last device is removed, 113 as that will prevent popping of notification. 114 */ 115 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), L""); 116 StringCchCopy(nim.szInfo, _countof(nim.szInfo), L""); 117 ret = Shell_NotifyIcon(NIM_MODIFY, &nim); 118 g_IsRemoving = FALSE; /* This flag is used to prevent instant icon hiding after last device is removed. 119 The above timer maintains the required state for the same. 120 */ 121 return ret ? S_OK : E_FAIL; 122 } 123 124 HRESULT STDMETHODCALLTYPE Hotplug_Init(_In_ CSysTray * pSysTray) 125 { 126 TRACE("Hotplug_Init\n"); 127 128 g_hIconHotplug = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_HOTPLUG_OK)); 129 g_strHotplugTooltip.LoadStringW(IDS_HOTPLUG_REMOVE_1); 130 131 EnumHotpluggedDevices(g_devList); 132 133 if (g_devList.GetSize() > 0) 134 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip); 135 else 136 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip, NIS_HIDDEN); 137 } 138 139 HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray) 140 { 141 TRACE("Hotplug_Update\n"); 142 return S_OK; 143 } 144 145 HRESULT STDMETHODCALLTYPE Hotplug_Shutdown(_In_ CSysTray * pSysTray) 146 { 147 TRACE("Hotplug_Shutdown\n"); 148 149 DestroyIcon(g_hIconHotplug); 150 g_hIconHotplug = NULL; 151 152 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_HOTPLUG, NULL, NULL); 153 } 154 155 static void _RunHotplug(CSysTray * pSysTray) 156 { 157 ShellExecuteW(pSysTray->GetHWnd(), 158 L"open", 159 L"rundll32.exe", 160 L"shell32.dll,Control_RunDLL hotplug.dll", 161 NULL, 162 SW_SHOWNORMAL); 163 } 164 165 static void _ShowContextMenu(CSysTray * pSysTray) 166 { 167 HMENU hPopup = CreatePopupMenu(); 168 ULONG ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR); 169 170 for (INT index = 0; index < g_devList.GetSize(); index++) 171 { 172 WCHAR dispName[DISPLAY_NAME_LEN]; 173 CString menuName; 174 CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[index], CM_DRP_DEVICEDESC, NULL, dispName, &ulLength, 0); 175 if (cr != CR_SUCCESS) 176 StrCpyW(dispName, L"Unknown Device"); 177 178 menuName.Format(IDS_HOTPLUG_REMOVE_3, dispName); 179 AppendMenuW(hPopup, MF_STRING, index+1, menuName); 180 } 181 182 SetForegroundWindow(pSysTray->GetHWnd()); 183 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN; 184 POINT pt; 185 GetCursorPos(&pt); 186 187 DWORD id = TrackPopupMenuEx(hPopup, flags, 188 pt.x, pt.y, 189 pSysTray->GetHWnd(), NULL); 190 191 if (id > 0) 192 { 193 id--; // since array indices starts from zero. 194 CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[id], CM_DRP_DEVICEDESC, NULL, g_strMenuSel, &ulLength, 0); 195 if (cr != CR_SUCCESS) 196 StrCpyW(g_strMenuSel, L"Unknown Device"); 197 198 cr = CM_Request_Device_Eject_Ex(g_devList[id], 0, 0, 0, 0, 0); 199 if (cr != CR_SUCCESS) 200 { 201 WCHAR strInfo[128]; 202 swprintf(strInfo, L"Problem Ejecting %wS", g_strMenuSel); 203 MessageBox(0, L"The device cannot be stopped right now! Try stopping it again later!", strInfo, MB_OK | MB_ICONEXCLAMATION); 204 } 205 else 206 { 207 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OK | MB_ICONINFORMATION); 208 g_IsRemoving = TRUE; 209 g_devList.RemoveAt(id); /* thing is.. even after removing id at this point, the devnode_change occurs after some seconds of sucessful removal 210 and since pendrive is still plugged in it gets enumerated, if problem number is not filtered. 211 */ 212 } 213 } 214 215 DestroyMenu(hPopup); 216 } 217 218 static void _ShowContextMenuR(CSysTray * pSysTray) 219 { 220 CString strMenu((LPWSTR)IDS_HOTPLUG_REMOVE_2); 221 HMENU hPopup = CreatePopupMenu(); 222 AppendMenuW(hPopup, MF_STRING, IDS_HOTPLUG_REMOVE_2, strMenu); 223 SetMenuDefaultItem(hPopup, IDS_HOTPLUG_REMOVE_2, FALSE); 224 225 SetForegroundWindow(pSysTray->GetHWnd()); 226 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN; 227 POINT pt; 228 GetCursorPos(&pt); 229 230 DWORD id = TrackPopupMenuEx(hPopup, flags, 231 pt.x, pt.y, 232 pSysTray->GetHWnd(), NULL); 233 234 if (id == IDS_HOTPLUG_REMOVE_2) 235 { 236 _RunHotplug(pSysTray); 237 } 238 239 DestroyMenu(hPopup); 240 } 241 242 243 VOID 244 HotplugDeviceTimer( 245 _In_ CSysTray *pSysTray) 246 { 247 TRACE("HotplugDeviceTimer()\n"); 248 249 EnumHotpluggedDevices(g_devList); 250 251 if (g_devList.GetSize() > 0) 252 pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip); 253 else 254 pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip, NIS_HIDDEN); 255 } 256 257 258 HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult) 259 { 260 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam); 261 262 switch (uMsg) 263 { 264 case WM_USER + 220: 265 TRACE("Hotplug_Message: WM_USER+220\n"); 266 if (wParam == HOTPLUG_SERVICE_FLAG) 267 { 268 if (lParam) 269 { 270 pSysTray->EnableService(HOTPLUG_SERVICE_FLAG, TRUE); 271 return Hotplug_Init(pSysTray); 272 } 273 else 274 { 275 pSysTray->EnableService(HOTPLUG_SERVICE_FLAG, FALSE); 276 return Hotplug_Shutdown(pSysTray); 277 } 278 } 279 return S_FALSE; 280 281 case WM_USER + 221: 282 TRACE("Hotplug_Message: WM_USER+221\n"); 283 if (wParam == HOTPLUG_SERVICE_FLAG) 284 { 285 lResult = (LRESULT)pSysTray->IsServiceEnabled(HOTPLUG_SERVICE_FLAG); 286 return S_OK; 287 } 288 return S_FALSE; 289 290 case WM_TIMER: 291 if (wParam == HOTPLUG_TIMER_ID) 292 { 293 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID); 294 _ShowContextMenu(pSysTray); 295 } 296 else if (wParam == HOTPLUG_DEVICE_TIMER_ID) 297 { 298 KillTimer(pSysTray->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID); 299 HotplugDeviceTimer(pSysTray); 300 } 301 break; 302 303 case ID_ICON_HOTPLUG: 304 switch (lParam) 305 { 306 case WM_LBUTTONDOWN: 307 SetTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID, GetDoubleClickTime(), NULL); 308 break; 309 310 case WM_LBUTTONUP: 311 break; 312 313 case WM_LBUTTONDBLCLK: 314 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID); 315 _RunHotplug(pSysTray); 316 break; 317 318 case WM_RBUTTONDOWN: 319 break; 320 321 case WM_RBUTTONUP: 322 _ShowContextMenuR(pSysTray); 323 break; 324 325 case WM_RBUTTONDBLCLK: 326 break; 327 328 case WM_MOUSEMOVE: 329 break; 330 } 331 return S_OK; 332 333 case WM_DEVICECHANGE: 334 switch (wParam) 335 { 336 case DBT_DEVNODES_CHANGED: 337 TRACE("WM_DEVICECHANGE : DBT_DEVNODES_CHANGED\n"); 338 SetTimer(pSysTray->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID, 100, NULL); 339 lResult = true; 340 break; 341 342 case DBT_DEVICEARRIVAL: 343 break; 344 case DBT_DEVICEQUERYREMOVE: 345 break; 346 case DBT_DEVICEQUERYREMOVEFAILED: 347 break; 348 case DBT_DEVICEREMOVECOMPLETE: 349 WCHAR strInfo[128]; 350 swprintf(strInfo, L"The %wS can now be safely removed from the system.", g_strMenuSel); 351 NotifyBalloon(pSysTray, L"Safe to Remove Hardware", strInfo); 352 353 lResult = true; 354 break; 355 case DBT_DEVICEREMOVEPENDING: 356 break; 357 } 358 return S_OK; 359 360 default: 361 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n"); 362 return S_FALSE; 363 } 364 365 return S_FALSE; 366 } 367