[NTDLL] Allow shimdata to override the process manifest.
[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
105 nim.cbSize = sizeof(nim);
106 nim.uID = uId;
107 nim.hWnd = pSysTray->GetHWnd();
108
109 nim.uFlags = NIF_INFO;
110 nim.uTimeout = 10;
111 nim.dwInfoFlags = NIIF_INFO;
112
113 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), szTitle);
114 StringCchCopy(nim.szInfo, _countof(nim.szInfo), szInfo);
115 BOOL ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
116
117 Sleep(10000); /* As per windows, the balloon notification remains visible for atleast 10 sec.
118 This timer maintains the same condition.
119 Also it is required so that the icon doesn't hide instantly after last device is removed,
120 as that will prevent popping of notification.
121 */
122 StringCchCopy(nim.szInfoTitle, _countof(nim.szInfoTitle), L"");
123 StringCchCopy(nim.szInfo, _countof(nim.szInfo), L"");
124 ret = Shell_NotifyIcon(NIM_MODIFY, &nim);
125 g_IsRemoving = FALSE; /* This flag is used to prevent instant icon hiding after last device is removed.
126 The above timer maintains the required state for the same.
127 */
128 return ret ? S_OK : E_FAIL;
129 }
130
131 HRESULT STDMETHODCALLTYPE Hotplug_Init(_In_ CSysTray * pSysTray)
132 {
133 TRACE("Hotplug_Init\n");
134 g_hIconHotplug = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_HOTPLUG_OK));
135 g_IsRunning = TRUE;
136 EnumHotpluggedDevices(g_devList);
137
138 return pSysTray->NotifyIcon(NIM_ADD, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
139 }
140
141 HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray)
142 {
143 TRACE("Hotplug_Update\n");
144
145 if(g_devList.GetSize() || g_IsRemoving)
146 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip);
147 else
148 return pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strTooltip, NIS_HIDDEN);
149 }
150
151 HRESULT STDMETHODCALLTYPE Hotplug_Shutdown(_In_ CSysTray * pSysTray)
152 {
153 TRACE("Hotplug_Shutdown\n");
154
155 g_IsRunning = FALSE;
156
157 return pSysTray->NotifyIcon(NIM_DELETE, ID_ICON_HOTPLUG, NULL, NULL);
158 }
159
160 static void _RunHotplug(CSysTray * pSysTray)
161 {
162 ShellExecuteW(pSysTray->GetHWnd(), L"open", L"rundll32.exe shell32.dll,Control_RunDLL hotplug.dll", NULL, NULL, SW_SHOWNORMAL);
163 }
164
165 static void _ShowContextMenu(CSysTray * pSysTray)
166 {
167 HMENU hPopup = CreatePopupMenu();
168 ULONG ulLength = DISPLAY_NAME_LEN * sizeof(WCHAR);
169
170 for (INT index = 0; index < g_devList.GetSize(); index++)
171 {
172 WCHAR dispName[DISPLAY_NAME_LEN], menuName[DISPLAY_NAME_LEN + 10];
173 CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[index], CM_DRP_DEVICEDESC, NULL, dispName, &ulLength, 0);
174 if (cr != CR_SUCCESS)
175 StrCpyW(dispName, L"Unknown Device");
176
177 swprintf(menuName, L"Eject %wS", dispName);
178 AppendMenuW(hPopup, MF_STRING, index+1, menuName);
179 }
180
181 SetForegroundWindow(pSysTray->GetHWnd());
182 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
183 POINT pt;
184 GetCursorPos(&pt);
185
186 DWORD id = TrackPopupMenuEx(hPopup, flags,
187 pt.x, pt.y,
188 pSysTray->GetHWnd(), NULL);
189
190 if (id > 0)
191 {
192 id--; // since array indices starts from zero.
193 CONFIGRET cr = CM_Get_DevNode_Registry_Property(g_devList[id], CM_DRP_DEVICEDESC, NULL, g_strMenuSel, &ulLength, 0);
194 if (cr != CR_SUCCESS)
195 StrCpyW(g_strMenuSel, L"Unknown Device");
196
197 cr = CM_Request_Device_Eject_Ex(g_devList[id], 0, 0, 0, 0, 0);
198 if (cr != CR_SUCCESS)
199 {
200 WCHAR strInfo[128];
201 swprintf(strInfo, L"Problem Ejecting %wS", g_strMenuSel);
202 MessageBox(0, L"The device cannot be stopped right now! Try stopping it again later!", strInfo, MB_OKCANCEL | MB_ICONEXCLAMATION);
203 }
204 else
205 {
206 //MessageBox(0, L"Device ejected successfully!! You can safely remove the device now!", L"Safely Remove Hardware", MB_OKCANCEL | MB_ICONINFORMATION);
207 g_IsRemoving = TRUE;
208 g_devList.RemoveAt(id); /* thing is.. even after removing id at this point, the devnode_change occurs after some seconds of sucessful removal
209 and since pendrive is still plugged in it gets enumerated, if problem number is not filtered.
210 */
211 }
212 }
213
214 DestroyMenu(hPopup);
215 }
216
217 static void _ShowContextMenuR(CSysTray * pSysTray)
218 {
219 CString strMenu((LPWSTR)IDS_HOTPLUG_REMOVE_2);
220 HMENU hPopup = CreatePopupMenu();
221 AppendMenuW(hPopup, MF_STRING, IDS_HOTPLUG_REMOVE_2, strMenu);
222
223 SetForegroundWindow(pSysTray->GetHWnd());
224 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
225 POINT pt;
226 GetCursorPos(&pt);
227
228 DWORD id = TrackPopupMenuEx(hPopup, flags,
229 pt.x, pt.y,
230 pSysTray->GetHWnd(), NULL);
231
232 if (id == IDS_HOTPLUG_REMOVE_2)
233 {
234 _RunHotplug(pSysTray);
235 }
236
237 DestroyMenu(hPopup);
238 }
239
240 HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
241 {
242 HRESULT hr = E_FAIL;
243 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
244
245 switch (uMsg)
246 {
247 /*case WM_CREATE:
248 TRACE("Hotplug_Message: WM_CREATE\n");
249 DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
250
251 ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
252 NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
253 NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
254
255 g_hDevNotify = RegisterDeviceNotification(pSysTray->GetHWnd(), &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
256 if (g_hDevNotify != NULL)
257 {
258 lResult = true;
259 return S_OK;
260 }
261 return S_FALSE;*/
262
263 case WM_USER + 220:
264 TRACE("Hotplug_Message: WM_USER+220\n");
265 if (wParam == 1)
266 {
267 if (lParam == FALSE)
268 return Hotplug_Init(pSysTray);
269 else
270 return Hotplug_Shutdown(pSysTray);
271 }
272 return S_FALSE;
273
274 case WM_USER + 221:
275 TRACE("Hotplug_Message: WM_USER+221\n");
276 if (wParam == 1)
277 {
278 lResult = (LRESULT)g_IsRunning;
279 return S_OK;
280 }
281 return S_FALSE;
282
283 case WM_TIMER:
284 if (wParam == HOTPLUG_TIMER_ID)
285 {
286 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID);
287 _ShowContextMenu(pSysTray);
288 }
289 break;
290
291 case ID_ICON_HOTPLUG:
292 Hotplug_Update(pSysTray);
293
294 switch (lParam)
295 {
296 case WM_LBUTTONDOWN:
297 SetTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID, GetDoubleClickTime(), NULL);
298 break;
299
300 case WM_LBUTTONUP:
301 break;
302
303 case WM_LBUTTONDBLCLK:
304 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID);
305 _RunHotplug(pSysTray);
306 break;
307
308 case WM_RBUTTONDOWN:
309 break;
310
311 case WM_RBUTTONUP:
312 _ShowContextMenuR(pSysTray);
313 break;
314
315 case WM_RBUTTONDBLCLK:
316 break;
317
318 case WM_MOUSEMOVE:
319 break;
320 }
321 return S_OK;
322
323 case WM_DEVICECHANGE:
324 switch (wParam)
325 {
326 case DBT_DEVNODES_CHANGED:
327 hr = EnumHotpluggedDevices(g_devList);
328 if (FAILED(hr))
329 return hr;
330
331 lResult = true;
332 break;
333 case DBT_DEVICEARRIVAL:
334 break;
335 case DBT_DEVICEQUERYREMOVE:
336 break;
337 case DBT_DEVICEQUERYREMOVEFAILED:
338 break;
339 case DBT_DEVICEREMOVECOMPLETE:
340 WCHAR strInfo[128];
341 swprintf(strInfo, L"The %wS can now be safely removed from the system.", g_strMenuSel);
342 NotifyBalloon(pSysTray, L"Safe to Remove Hardware", strInfo);
343
344 lResult = true;
345 break;
346 case DBT_DEVICEREMOVEPENDING:
347 break;
348 }
349 return S_OK;
350
351 /*case WM_CLOSE:
352 if (!UnregisterDeviceNotification(hDeviceNotify))
353 {
354 return S_FALSE;
355 }
356 return S_OK;*/
357
358 default:
359 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");
360 return S_FALSE;
361 }
362
363 return S_FALSE;
364 }