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