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>
11 #include <atlsimpcoll.h>
16 #define DISPLAY_NAME_LEN 40
18 //BOOL WINAPI UnregisterDeviceNotification(HDEVNOTIFY Handle);
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_IsRunning
= FALSE
;
26 static BOOL g_IsRemoving
= FALSE
;
29 * @name EnumHotpluggedDevices
31 * Enumerates the connected safely removable devices.
34 * List of device instances, representing the currently attached devices.
36 * @return The error code.
39 HRESULT
EnumHotpluggedDevices(CSimpleArray
<DEVINST
> &devList
)
41 devList
.RemoveAll(); // Clear current devList
42 HDEVINFO hdev
= SetupDiGetClassDevs(NULL
, NULL
, 0, DIGCF_ALLCLASSES
| DIGCF_PRESENT
);
43 if (INVALID_HANDLE_VALUE
== hdev
)
45 SP_DEVINFO_DATA did
= { 0 };
46 did
.cbSize
= sizeof(did
);
48 // Enumerate all the attached devices.
49 for (int idev
= 0; SetupDiEnumDeviceInfo(hdev
, idev
, &did
); idev
++)
51 DWORD dwCapabilities
= 0, dwSize
= sizeof(dwCapabilities
);
52 WCHAR dispName
[DISPLAY_NAME_LEN
];
53 ULONG ulStatus
= 0, ulPnum
= 0, ulLength
= DISPLAY_NAME_LEN
* sizeof(WCHAR
);
54 CONFIGRET cr
= CM_Get_DevNode_Status(&ulStatus
, &ulPnum
, did
.DevInst
, 0);
57 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_DEVICEDESC
, NULL
, dispName
, &ulLength
, 0);
60 cr
= CM_Get_DevNode_Registry_Property(did
.DevInst
, CM_DRP_CAPABILITIES
, NULL
, &dwCapabilities
, &dwSize
, 0);
64 // Filter and make list of only the appropriate safely removable devices.
65 if ( (dwCapabilities
& CM_DEVCAP_REMOVABLE
) &&
66 !(dwCapabilities
& CM_DEVCAP_DOCKDEVICE
) &&
67 !(dwCapabilities
& CM_DEVCAP_SURPRISEREMOVALOK
) &&
68 ((dwCapabilities
& CM_DEVCAP_EJECTSUPPORTED
) || (ulStatus
& DN_DISABLEABLE
)) &&
71 devList
.Add(did
.DevInst
);
74 SetupDiDestroyDeviceInfoList(hdev
);
76 if (NO_ERROR
!= GetLastError() && ERROR_NO_MORE_ITEMS
!= GetLastError())
87 * Pops the balloon notification of the given notification icon.
90 * Provides interface for acquiring CSysTray information as required.
92 * Title for the balloon notification.
94 * Main content for the balloon notification.
96 * Represents the particular notification icon.
98 * @return The error code.
101 HRESULT
NotifyBalloon(CSysTray
* pSysTray
, LPCWSTR szTitle
= NULL
, LPCWSTR szInfo
= NULL
, UINT uId
= ID_ICON_HOTPLUG
)
105 ZeroMemory(&nim
, sizeof(nim
));
106 nim
.cbSize
= sizeof(nim
);
108 nim
.hWnd
= pSysTray
->GetHWnd();
110 nim
.uFlags
= NIF_INFO
;
112 nim
.dwInfoFlags
= NIIF_INFO
;
114 StringCchCopy(nim
.szInfoTitle
, _countof(nim
.szInfoTitle
), szTitle
);
115 StringCchCopy(nim
.szInfo
, _countof(nim
.szInfo
), szInfo
);
116 BOOL ret
= Shell_NotifyIcon(NIM_MODIFY
, &nim
);
118 Sleep(10000); /* As per windows, the balloon notification remains visible for atleast 10 sec.
119 This timer maintains the same condition.
120 Also it is required so that the icon doesn't hide instantly after last device is removed,
121 as that will prevent popping of notification.
123 StringCchCopy(nim
.szInfoTitle
, _countof(nim
.szInfoTitle
), L
"");
124 StringCchCopy(nim
.szInfo
, _countof(nim
.szInfo
), L
"");
125 ret
= Shell_NotifyIcon(NIM_MODIFY
, &nim
);
126 g_IsRemoving
= FALSE
; /* This flag is used to prevent instant icon hiding after last device is removed.
127 The above timer maintains the required state for the same.
129 return ret
? S_OK
: E_FAIL
;
132 HRESULT STDMETHODCALLTYPE
Hotplug_Init(_In_ CSysTray
* pSysTray
)
134 TRACE("Hotplug_Init\n");
135 g_hIconHotplug
= LoadIcon(g_hInstance
, MAKEINTRESOURCE(IDI_HOTPLUG_OK
));
137 EnumHotpluggedDevices(g_devList
);
139 return pSysTray
->NotifyIcon(NIM_ADD
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
, NIS_HIDDEN
);
142 HRESULT STDMETHODCALLTYPE
Hotplug_Update(_In_ CSysTray
* pSysTray
)
144 TRACE("Hotplug_Update\n");
146 if(g_devList
.GetSize() || g_IsRemoving
)
147 return pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
);
149 return pSysTray
->NotifyIcon(NIM_MODIFY
, ID_ICON_HOTPLUG
, g_hIconHotplug
, g_strTooltip
, NIS_HIDDEN
);
152 HRESULT STDMETHODCALLTYPE
Hotplug_Shutdown(_In_ CSysTray
* pSysTray
)
154 TRACE("Hotplug_Shutdown\n");
158 return pSysTray
->NotifyIcon(NIM_DELETE
, ID_ICON_HOTPLUG
, NULL
, NULL
);
161 static void _RunHotplug(CSysTray
* pSysTray
)
163 ShellExecuteW(pSysTray
->GetHWnd(), L
"open", L
"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL
, NULL
, SW_SHOWNORMAL
);
166 static void _ShowContextMenu(CSysTray
* pSysTray
)
168 HMENU hPopup
= CreatePopupMenu();
169 ULONG ulLength
= DISPLAY_NAME_LEN
* sizeof(WCHAR
);
171 for (INT index
= 0; index
< g_devList
.GetSize(); index
++)
173 WCHAR dispName
[DISPLAY_NAME_LEN
], menuName
[DISPLAY_NAME_LEN
+ 10];
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");
178 swprintf(menuName
, L
"Eject %wS", dispName
);
179 AppendMenuW(hPopup
, MF_STRING
, index
+1, menuName
);
182 SetForegroundWindow(pSysTray
->GetHWnd());
183 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
187 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
189 pSysTray
->GetHWnd(), NULL
);
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");
198 cr
= CM_Request_Device_Eject_Ex(g_devList
[id
], 0, 0, 0, 0, 0);
199 if (cr
!= CR_SUCCESS
)
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_OKCANCEL
| MB_ICONEXCLAMATION
);
207 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OKCANCEL | MB_ICONINFORMATION);
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.
218 static void _ShowContextMenuR(CSysTray
* pSysTray
)
220 CString
strMenu((LPWSTR
)IDS_HOTPLUG_REMOVE_2
);
221 HMENU hPopup
= CreatePopupMenu();
222 AppendMenuW(hPopup
, MF_STRING
, IDS_HOTPLUG_REMOVE_2
, strMenu
);
224 SetForegroundWindow(pSysTray
->GetHWnd());
225 DWORD flags
= TPM_RETURNCMD
| TPM_NONOTIFY
| TPM_RIGHTALIGN
| TPM_BOTTOMALIGN
;
229 DWORD id
= TrackPopupMenuEx(hPopup
, flags
,
231 pSysTray
->GetHWnd(), NULL
);
233 if (id
== IDS_HOTPLUG_REMOVE_2
)
235 _RunHotplug(pSysTray
);
241 HRESULT STDMETHODCALLTYPE
Hotplug_Message(_In_ CSysTray
* pSysTray
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
, LRESULT
&lResult
)
244 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg
, wParam
, lParam
);
249 TRACE("Hotplug_Message: WM_CREATE\n");
250 DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
252 ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
253 NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
254 NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
256 g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
257 if (g_hDevNotify != NULL)
265 TRACE("Hotplug_Message: WM_USER+220\n");
269 return Hotplug_Init(pSysTray
);
271 return Hotplug_Shutdown(pSysTray
);
276 TRACE("Hotplug_Message: WM_USER+221\n");
279 lResult
= (LRESULT
)g_IsRunning
;
284 case ID_ICON_HOTPLUG
:
285 Hotplug_Update(pSysTray
);
293 _ShowContextMenu(pSysTray
);
296 case WM_LBUTTONDBLCLK
:
297 _RunHotplug(pSysTray
);
304 _ShowContextMenuR(pSysTray
);
307 case WM_RBUTTONDBLCLK
:
315 case WM_DEVICECHANGE
:
318 case DBT_DEVNODES_CHANGED
:
319 hr
= EnumHotpluggedDevices(g_devList
);
325 case DBT_DEVICEARRIVAL
:
327 case DBT_DEVICEQUERYREMOVE
:
329 case DBT_DEVICEQUERYREMOVEFAILED
:
331 case DBT_DEVICEREMOVECOMPLETE
:
333 swprintf(strInfo
, L
"The %wS can now be safely removed from the system.", g_strMenuSel
);
334 NotifyBalloon(pSysTray
, L
"Safe to Remove Hardware", strInfo
);
338 case DBT_DEVICEREMOVEPENDING
:
344 if (!UnregisterDeviceNotification(hDeviceNotify))
351 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");