[SHELLEXT][MYDOCS][INF] Add mydocs.dll and .mydocs file extension (#2624)
[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 CSimpleArray<DEVINST> g_devList;
19 CString g_strHotplugTooltip;
20 static HICON g_hIconHotplug = NULL;
21 static WCHAR g_strMenuSel[DISPLAY_NAME_LEN];
22 static 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 *--*/
35 HRESULT 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 *--*/
94 HRESULT 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
124 HRESULT 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
139 HRESULT STDMETHODCALLTYPE Hotplug_Update(_In_ CSysTray * pSysTray)
140 {
141 TRACE("Hotplug_Update\n");
142 return S_OK;
143 }
144
145 HRESULT 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
155 static void _RunHotplug(CSysTray * pSysTray)
156 {
157 ShellExecuteW(pSysTray->GetHWnd(),
158 L"open",
159 L"rundll32.exe",
160 L"shell32.dll,Control_RunDLL hotplug.dll",
161 NULL,
162 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];
173 CString menuName;
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 menuName.Format(IDS_HOTPLUG_REMOVE_3, 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_OK | 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_OK | 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 SetMenuDefaultItem(hPopup, IDS_HOTPLUG_REMOVE_2, FALSE);
224
225 SetForegroundWindow(pSysTray->GetHWnd());
226 DWORD flags = TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTALIGN | TPM_BOTTOMALIGN;
227 POINT pt;
228 GetCursorPos(&pt);
229
230 DWORD id = TrackPopupMenuEx(hPopup, flags,
231 pt.x, pt.y,
232 pSysTray->GetHWnd(), NULL);
233
234 if (id == IDS_HOTPLUG_REMOVE_2)
235 {
236 _RunHotplug(pSysTray);
237 }
238
239 DestroyMenu(hPopup);
240 }
241
242
243 VOID
244 HotplugDeviceTimer(
245 _In_ CSysTray *pSysTray)
246 {
247 TRACE("HotplugDeviceTimer()\n");
248
249 EnumHotpluggedDevices(g_devList);
250
251 if (g_devList.GetSize() > 0)
252 pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip);
253 else
254 pSysTray->NotifyIcon(NIM_MODIFY, ID_ICON_HOTPLUG, g_hIconHotplug, g_strHotplugTooltip, NIS_HIDDEN);
255 }
256
257
258 HRESULT STDMETHODCALLTYPE Hotplug_Message(_In_ CSysTray * pSysTray, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT &lResult)
259 {
260 TRACE("Hotplug_Message uMsg=%d, wParam=%x, lParam=%x\n", uMsg, wParam, lParam);
261
262 switch (uMsg)
263 {
264 case WM_USER + 220:
265 TRACE("Hotplug_Message: WM_USER+220\n");
266 if (wParam == HOTPLUG_SERVICE_FLAG)
267 {
268 if (lParam)
269 {
270 pSysTray->EnableService(HOTPLUG_SERVICE_FLAG, TRUE);
271 return Hotplug_Init(pSysTray);
272 }
273 else
274 {
275 pSysTray->EnableService(HOTPLUG_SERVICE_FLAG, FALSE);
276 return Hotplug_Shutdown(pSysTray);
277 }
278 }
279 return S_FALSE;
280
281 case WM_USER + 221:
282 TRACE("Hotplug_Message: WM_USER+221\n");
283 if (wParam == HOTPLUG_SERVICE_FLAG)
284 {
285 lResult = (LRESULT)pSysTray->IsServiceEnabled(HOTPLUG_SERVICE_FLAG);
286 return S_OK;
287 }
288 return S_FALSE;
289
290 case WM_TIMER:
291 if (wParam == HOTPLUG_TIMER_ID)
292 {
293 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID);
294 _ShowContextMenu(pSysTray);
295 }
296 else if (wParam == HOTPLUG_DEVICE_TIMER_ID)
297 {
298 KillTimer(pSysTray->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID);
299 HotplugDeviceTimer(pSysTray);
300 }
301 break;
302
303 case ID_ICON_HOTPLUG:
304 switch (lParam)
305 {
306 case WM_LBUTTONDOWN:
307 SetTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID, GetDoubleClickTime(), NULL);
308 break;
309
310 case WM_LBUTTONUP:
311 break;
312
313 case WM_LBUTTONDBLCLK:
314 KillTimer(pSysTray->GetHWnd(), HOTPLUG_TIMER_ID);
315 _RunHotplug(pSysTray);
316 break;
317
318 case WM_RBUTTONDOWN:
319 break;
320
321 case WM_RBUTTONUP:
322 _ShowContextMenuR(pSysTray);
323 break;
324
325 case WM_RBUTTONDBLCLK:
326 break;
327
328 case WM_MOUSEMOVE:
329 break;
330 }
331 return S_OK;
332
333 case WM_DEVICECHANGE:
334 switch (wParam)
335 {
336 case DBT_DEVNODES_CHANGED:
337 TRACE("WM_DEVICECHANGE : DBT_DEVNODES_CHANGED\n");
338 SetTimer(pSysTray->GetHWnd(), HOTPLUG_DEVICE_TIMER_ID, 100, NULL);
339 lResult = true;
340 break;
341
342 case DBT_DEVICEARRIVAL:
343 break;
344 case DBT_DEVICEQUERYREMOVE:
345 break;
346 case DBT_DEVICEQUERYREMOVEFAILED:
347 break;
348 case DBT_DEVICEREMOVECOMPLETE:
349 WCHAR strInfo[128];
350 swprintf(strInfo, L"The %wS can now be safely removed from the system.", g_strMenuSel);
351 NotifyBalloon(pSysTray, L"Safe to Remove Hardware", strInfo);
352
353 lResult = true;
354 break;
355 case DBT_DEVICEREMOVEPENDING:
356 break;
357 }
358 return S_OK;
359
360 default:
361 TRACE("Hotplug_Message received for unknown ID %d, ignoring.\n");
362 return S_FALSE;
363 }
364
365 return S_FALSE;
366 }