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