xref: /reactos/dll/shellext/stobject/hotplug.cpp (revision ede7a20a)
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 }
367