Reactos
at master 361 lines 12 kB view raw
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 18CSimpleArray<DEVINST> g_devList; 19CString g_strHotplugTooltip; 20static HICON g_hIconHotplug = NULL; 21static WCHAR g_strMenuSel[DISPLAY_NAME_LEN]; 22static 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*--*/ 35HRESULT 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*--*/ 94HRESULT 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 124HRESULT 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 139HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray) 140{ 141 TRACE("Hotplug_Update\n"); 142 return S_OK; 143} 144 145HRESULT 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 155static void _RunHotplug(CSysTray * pSysTray) 156{ 157 pSysTray->RunDll("hotplug.dll", ""); 158} 159 160static void _ShowContextMenu(CSysTray * pSysTray) 161{ 162 HMENU hPopup = CreatePopupMenu(); 163 ULONG ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR); 164 165 for (INT index = 0; index < g_devList.GetSize(); index++) 166 { 167 WCHAR dispName[DISPLAY_NAME_LEN]; 168 CString menuName; 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 menuName.Format(IDS_HOTPLUG_REMOVE_3, 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_OK | 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_OK | 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 213static 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 238VOID 239HotplugDeviceTimer( 240 _In_ CSysTray *pSysTray) 241{ 242 TRACE("HotplugDeviceTimer()\n"); 243 244 EnumHotpluggedDevices(g_devList); 245 246 if (g_devList.GetSize() > 0) 247 pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip); 248 else 249 pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip, NIS_HIDDEN); 250} 251 252 253HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult) 254{ 255 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam); 256 257 switch (uMsg) 258 { 259 case WM_USER + 220: 260 TRACE("Hotplug_Message: WM_USER+220\n"); 261 if (wParam == HOTPLUG_SERVICE_FLAG) 262 { 263 if (lParam) 264 { 265 pSysTray->EnableService(HOTPLUG_SERVICE_FLAG, TRUE); 266 return Hotplug_Init(pSysTray); 267 } 268 else 269 { 270 pSysTray->EnableService(HOTPLUG_SERVICE_FLAG, FALSE); 271 return Hotplug_Shutdown(pSysTray); 272 } 273 } 274 return S_FALSE; 275 276 case WM_USER + 221: 277 TRACE("Hotplug_Message: WM_USER+221\n"); 278 if (wParam == HOTPLUG_SERVICE_FLAG) 279 { 280 lResult = (LRESULT)pSysTray->IsServiceEnabled(HOTPLUG_SERVICE_FLAG); 281 return S_OK; 282 } 283 return S_FALSE; 284 285 case WM_TIMER: 286 if (wParam == HOTPLUG_TIMER_ID) 287 { 288 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID); 289 _ShowContextMenu(pSysTray); 290 } 291 else if (wParam == HOTPLUG_DEVICE_TIMER_ID) 292 { 293 KillTimer(pSysTray->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID); 294 HotplugDeviceTimer(pSysTray); 295 } 296 break; 297 298 case ID_ICON_HOTPLUG: 299 switch (lParam) 300 { 301 case WM_LBUTTONDOWN: 302 SetTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID, GetDoubleClickTime(), NULL); 303 break; 304 305 case WM_LBUTTONUP: 306 break; 307 308 case WM_LBUTTONDBLCLK: 309 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID); 310 _RunHotplug(pSysTray); 311 break; 312 313 case WM_RBUTTONDOWN: 314 break; 315 316 case WM_RBUTTONUP: 317 _ShowContextMenuR(pSysTray); 318 break; 319 320 case WM_RBUTTONDBLCLK: 321 break; 322 323 case WM_MOUSEMOVE: 324 break; 325 } 326 return S_OK; 327 328 case WM_DEVICECHANGE: 329 switch (wParam) 330 { 331 case DBT_DEVNODES_CHANGED: 332 TRACE("WM_DEVICECHANGE : DBT_DEVNODES_CHANGED\n"); 333 SetTimer(pSysTray->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID, 100, NULL); 334 lResult = true; 335 break; 336 337 case DBT_DEVICEARRIVAL: 338 break; 339 case DBT_DEVICEQUERYREMOVE: 340 break; 341 case DBT_DEVICEQUERYREMOVEFAILED: 342 break; 343 case DBT_DEVICEREMOVECOMPLETE: 344 WCHAR strInfo[128]; 345 swprintf(strInfo, L"The %wS can now be safely removed from the system.", g_strMenuSel); 346 NotifyBalloon(pSysTray, L"Safe to Remove Hardware", strInfo); 347 348 lResult = true; 349 break; 350 case DBT_DEVICEREMOVEPENDING: 351 break; 352 } 353 return S_OK; 354 355 default: 356 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n"); 357 return S_FALSE; 358 } 359 360 return S_FALSE; 361}