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