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