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