9550d239603e08f099b8a7af6b66524d06483a1c
[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;
104
105 ZeroMemory(&nim, sizeof(nim));
106 nim.cbSize = sizeof(nim);
107 nim.uID = uId;
108 nim.hWnd = pSysTray->GetHWnd();
109
110 nim.uFlags = NIF_INFO;
111 nim.uTimeout = 10;
112 nim.dwInfoFlags = NIIF_INFO;
113
114 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), szTitle);
115 StringCchCopy(nim.szInfo, _countof(nim.szInfo), szInfo);
116 BOOL ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
117
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.
122 */
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.
128 */
129 return ret ? S_OK : E_FAIL;
130 }
131
132 HRESULT STDMETHODCALLTYPE Hotplug_Init(_In_ CSysTray * pSysTray)
133 {
134 TRACE("Hotplug_Init\n");
135 g_hIconHotplug = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_HOTPLUG_OK));
136 g_IsRunning = TRUE;
137 EnumHotpluggedDevices(g_devList);
138
139 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
140 }
141
142 HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray)
143 {
144 TRACE("Hotplug_Update\n");
145
146 if(g_devList.GetSize() || g_IsRemoving)
147 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip);
148 else
149 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
150 }
151
152 HRESULT STDMETHODCALLTYPE Hotplug_Shutdown(_In_ CSysTray * pSysTray)
153 {
154 TRACE("Hotplug_Shutdown\n");
155
156 g_IsRunning = FALSE;
157
158 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_HOTPLUG, NULL, NULL);
159 }
160
161 static void _RunHotplug(CSysTray * pSysTray)
162 {
163 ShellExecuteW(pSysTray->GetHWnd(), L"open", L"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL, NULL, SW_SHOWNORMAL);
164 }
165
166 static void _ShowContextMenu(CSysTray * pSysTray)
167 {
168 HMENU hPopup = CreatePopupMenu();
169 ULONG ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR);
170
171 for (INT index = 0; index < g_devList.GetSize(); index++)
172 {
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");
177
178 swprintf(menuName, L"Eject %wS", 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_OKCANCEL | 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_OKCANCEL | 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
224 SetForegroundWindow(pSysTray->GetHWnd());
225 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
226 POINT pt;
227 GetCursorPos(&pt);
228
229 DWORD id = TrackPopupMenuEx(hPopup, flags,
230 pt.x, pt.y,
231 pSysTray->GetHWnd(), NULL);
232
233 if (id == IDS_HOTPLUG_REMOVE_2)
234 {
235 _RunHotplug(pSysTray);
236 }
237
238 DestroyMenu(hPopup);
239 }
240
241 HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
242 {
243 HRESULT hr = E_FAIL;
244 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
245
246 switch (uMsg)
247 {
248 /*case WM_CREATE:
249 TRACE("Hotplug_Message: WM_CREATE\n");
250 DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
251
252 ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
253 NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
254 NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
255
256 g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
257 if (g_hDevNotify != NULL)
258 {
259 lResult = true;
260 return S_OK;
261 }
262 return S_FALSE;*/
263
264 case WM_USER + 220:
265 TRACE("Hotplug_Message: WM_USER+220\n");
266 if (wParam == 1)
267 {
268 if (lParam == FALSE)
269 return Hotplug_Init(pSysTray);
270 else
271 return Hotplug_Shutdown(pSysTray);
272 }
273 return S_FALSE;
274
275 case WM_USER + 221:
276 TRACE("Hotplug_Message: WM_USER+221\n");
277 if (wParam == 1)
278 {
279 lResult = (LRESULT)g_IsRunning;
280 return S_OK;
281 }
282 return S_FALSE;
283
284 case ID_ICON_HOTPLUG:
285 Hotplug_Update(pSysTray);
286
287 switch (lParam)
288 {
289 case WM_LBUTTONDOWN:
290 break;
291
292 case WM_LBUTTONUP:
293 _ShowContextMenu(pSysTray);
294 break;
295
296 case WM_LBUTTONDBLCLK:
297 _RunHotplug(pSysTray);
298 break;
299
300 case WM_RBUTTONDOWN:
301 break;
302
303 case WM_RBUTTONUP:
304 _ShowContextMenuR(pSysTray);
305 break;
306
307 case WM_RBUTTONDBLCLK:
308 break;
309
310 case WM_MOUSEMOVE:
311 break;
312 }
313 return S_OK;
314
315 case WM_DEVICECHANGE:
316 switch (wParam)
317 {
318 case DBT_DEVNODES_CHANGED:
319 hr = EnumHotpluggedDevices(g_devList);
320 if (FAILED(hr))
321 return hr;
322
323 lResult = true;
324 break;
325 case DBT_DEVICEARRIVAL:
326 break;
327 case DBT_DEVICEQUERYREMOVE:
328 break;
329 case DBT_DEVICEQUERYREMOVEFAILED:
330 break;
331 case DBT_DEVICEREMOVECOMPLETE:
332 WCHAR strInfo[128];
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);
335
336 lResult = true;
337 break;
338 case DBT_DEVICEREMOVEPENDING:
339 break;
340 }
341 return S_OK;
342
343 /*case WM_CLOSE:
344 if (!UnregisterDeviceNotification(hDeviceNotify))
345 {
346 return S_FALSE;
347 }
348 return S_OK;*/
349
350 default:
351 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");
352 return S_FALSE;
353 }
354
355 return S_FALSE;
356 }